From 46e10831d7170d3895a0278d15a9e1aff342496b Mon Sep 17 00:00:00 2001
From: Kevin Lyda <kevin@lyda.ie>
Date: Fri, 16 May 2025 23:30:36 +0100
Subject: [PATCH] Implement a number of SET commands

---
 folders/messages.go          |  17 ++++--
 repl/command.go              |  98 +++++++-----------------------
 repl/messages.go             |  46 ++++++++++++--
 repl/msg-text.go             |   8 +++
 repl/repl.go                 |   5 +-
 repl/set.go                  |  59 +++++++++++++++---
 repl/xfer.go                 |  53 +++++++++++++++++
 storage/folders.sql.go       | 112 +++++++++++++++++++++++++++++++++++
 storage/messages.sql.go      |  11 ++++
 storage/queries/folders.sql  |  24 ++++++++
 storage/queries/messages.sql |   3 +
 this/this.go                 |   1 -
 12 files changed, 342 insertions(+), 95 deletions(-)
 create mode 100644 repl/msg-text.go
 create mode 100644 repl/xfer.go

diff --git a/folders/messages.go b/folders/messages.go
index c787c4b..92f859b 100644
--- a/folders/messages.go
+++ b/folders/messages.go
@@ -2,6 +2,7 @@ package folders
 
 import (
 	"errors"
+	"fmt"
 	"time"
 
 	"git.lyda.ie/kevin/bulletin/storage"
@@ -40,6 +41,7 @@ func CreateMessage(author, subject, message, folder string, permanent, shutdown
 // ReadMessage reads a message for a user.
 func ReadMessage(login, folder string, msgid int64) (*storage.Message, error) {
 	ctx := storage.Context()
+	fmt.Printf("TODO: Make sure %s can read this message.\n", login)
 	msg, err := this.Q.ReadMessage(ctx, storage.ReadMessageParams{
 		Folder: folder,
 		ID:     msgid,
@@ -51,11 +53,6 @@ func ReadMessage(login, folder string, msgid int64) (*storage.Message, error) {
 	if msg.ID != int64(msgid) || msgid == 0 {
 		return nil, errors.New("Specified message was not found")
 	}
-	err = this.Q.SetMessageSeen(ctx, storage.SetMessageSeenParams{
-		Login:  login,
-		Folder: folder,
-		Msgid:  int64(msgid),
-	})
 
 	return &msg, nil
 }
@@ -130,6 +127,16 @@ func FirstMessage(folder string) int64 {
 	return first
 }
 
+// LastMessage gets the last message in a folder.
+func LastMessage(folder string) int64 {
+	ctx := storage.Context()
+	last, err := this.Q.LastMsgidIgnoringSeen(ctx, folder)
+	if err != nil {
+		return 0
+	}
+	return last
+}
+
 // ListMessages lists messages.
 func ListMessages(folder string) ([]storage.Message, error) {
 	ctx := storage.Context()
diff --git a/repl/command.go b/repl/command.go
index 5368143..e35f61b 100644
--- a/repl/command.go
+++ b/repl/command.go
@@ -127,13 +127,6 @@ topic of the message.
   Specifies that the editor is to be used to read the message.  This is
   useful for scanning a long message.`,
 			},
-			"/HEADER": {
-				Description: `/[NO]HEADER
-
-  Specifies that if a message header exists, the header will be shown.
-  If /HEADER or /NOHEADER is specified, the setting will apply for all
-  further reads in the selected folder.  The default is /HEADER.`,
-			},
 		},
 	},
 	"CHANGE": {
@@ -176,10 +169,6 @@ This can be suppressed by the qualifier /NEW.
 			"/GENERAL": {
 				Description: `  Specifies that the message is to be converted from a SYSTEM message to
   a GENERAL message.  This only applies to the GENERAL folder.`,
-			},
-			"/HEADER": {
-				Description: `  Specifies  that the  message header  is to  be replaced.  You will  be
-  prompted for the new message description.`,
 			},
 			"/NEW": {
 				Description: `  If the editor is to be used for replacing the text of the message, NEW
@@ -236,17 +225,11 @@ The key words  CURRENT and LAST can  also be specified in  the range in,
 place of an actual number, i.e. CURRENT-LAST, 1-CURRENT, etc.`,
 		MinArgs: 1,
 		MaxArgs: 2,
+		Action:  ActionCopy,
 		Flags: dclish.Flags{
 			"/ALL": {
 				Description: `  Specifies to copy all the messages in the old folder.`,
 			},
-			"/HEADER": {
-				Description: `/[NO]HEADER
-
-  Valid  only if  destination folder  is  a news  group. Specifies  that
-  header of  message is to  be included with the  text when the  text is
-  copied. The default is /NOHEADER.`,
-			},
 			"/MERGE": {
 				Description: `  Specifies that the  original date and time of the  copied messages are
   saved and that the messages  are placed in correct chronological order
@@ -382,13 +365,6 @@ of the message again, you can enter the CURRENT command.
 				Description: `  Specifies that the editor  is to be used to read  the message. This is
   useful for scanning a long message.`,
 			},
-			"/HEADER": {
-				Description: `/[NO]HEADER
-
-  Specifies that if a message header exists, the header will be shown.
-  If /HEADER or /NOHEADER is specified, the setting will apply for all
-  further reads in the selected folder.  The default is /HEADER.`,
-			},
 		},
 	},
 	"DELETE": {
@@ -560,13 +536,6 @@ place of an actual number, i.e. CURRENT-LAST, 1-CURRENT, etc.`,
 			"/FF": {
 				Description: `  Specifies that a form feed is placed between messages in the file.`,
 			},
-			"/HEADER": {
-				Description: `/[NO]HEADER
-
-  Controls whether a  header containing the owner, subject,  and date of
-  the  message is  written in  the  file. The  default is  to write  the
-  header.`,
-			},
 			"/NEW": {
 				Description: `  Specifies  that  a new  file  is  to  be  created. Otherwise,  if  the
   specified file exists, the file would be appended to that file.`,
@@ -631,10 +600,6 @@ where one left off after one has read a message.
 				Description: `  If specified,  causes the listing  to be reinitialized and  start from
   the first folder.`,
 			},
-			"/SUBSCRIBE": {
-				Description: `  If  specified,  lists  only  those   news  folders  which  have  been
-  subscribed to.`,
-			},
 		},
 	},
 	"LAST": {
@@ -642,18 +607,12 @@ where one left off after one has read a message.
 
   Format:
     LAST`,
+		Action: ActionLast,
 		Flags: dclish.Flags{
 			"/EDIT": {
 				Description: `  Specifies that the editor  is to be used to read  the message. This is
   useful for scanning a long message.`,
 			},
-			"/HEADER": {
-				Description: `/[NO]HEADER
-
-  Specifies that if  a message header exists, the header  will be shown.
-  If /HEADER or  /NOHEADER is specified, the setting will  apply for all
-  further reads in the selected folder. The default is /HEADER.`,
-			},
 		},
 	},
 	"MAIL": {
@@ -674,13 +633,6 @@ specified as xxx%"""address""".`,
 			"/EDIT": {
 				Description: `  Specifies that  the editor is  to be used  to edit the  message before
   mailing it.`,
-			},
-			"/HEADER": {
-				Description: `/[NO]HEADER
-
-  Controls whether a  header containing the owner, subject,  and date of
-  the  message is  written in  the  mail. The  default is  to write  the
-  header.`,
 			},
 			"/SUBJECT": {
 				Description: `/SUBJECT=text
@@ -768,6 +720,7 @@ The key words  CURRENT and LAST can  also be specified in  the range in,
 place of an actual number, i.e. CURRENT-LAST, 1-CURRENT, etc.`,
 		MinArgs: 1,
 		MaxArgs: 2,
+		Action:  ActionMove,
 		Flags: dclish.Flags{
 			"/ALL": {
 				Description: `  Specifies to move  all the messages from the old  folder. Note: If the
@@ -797,13 +750,6 @@ you would like to skip over.`,
 				Description: `  Specifies that the editor  is to be used to read  the message. This is
   useful for scanning a long message.`,
 			},
-			"/HEADER": {
-				Description: `/[NO]HEADER
-
-  Specifies that if  a message header exists, the header  will be shown.
-  If /HEADER or  /NOHEADER is specified, the setting will  apply for all
-  further reads in the selected folder. The default is /HEADER.`,
-			},
 		},
 	},
 	"PRINT": {
@@ -844,12 +790,6 @@ it's subject line, DIRECTORY/PRINT/SUBJ would allow you do it.`,
   to have  your job  print, the  system manager  should stop  the queue,
   physically change  the paper stock  on the output device,  and restart
   the queue specifying the new form type as the mounted form.)`,
-			},
-			"/HEADER": {
-				Description: `/[NO]HEADER
-
-  Controls whether a header containing the owner, subject, and date of the
-  message is printed at the beginning. The default is to write the header.`,
 			},
 			"/NOTIFY": {
 				Description: `/[NO]NOTIFY
@@ -904,13 +844,6 @@ the help on the SEEN command.`,
 			"/EDIT": {
 				Description: `  Specifies that the editor  is to be used to read  the message. This is
   useful for scanning a long message.`,
-			},
-			"/HEADER": {
-				Description: `/[NO]HEADER
-
-  Specifies that if  a message header exists, the header  will be shown.
-  If /HEADER or  /NOHEADER is specified, the setting will  apply for all
-  further reads in the selected folder. The default is /HEADER.`,
 			},
 			"/MARKED": {
 				Description: `  Specifies to read only messages that have been marked (marked messages
@@ -1263,12 +1196,14 @@ sure they are read.
 
   Format:
     SET ALWAYS`,
+				Action: ActionSetAlways,
 			},
 			"NOALWAYS": {
 				Description: `Removes ALWAYS attribute from the selected folder.
 
   Format:
     SET NOALWAYS`,
+				Action: ActionSetAlways,
 			},
 			"BRIEF": {
 				Description: `Controls whether you will be alerted upon logging  that  there  are  new
@@ -1281,6 +1216,7 @@ READNEW setting (and visa versa).
 
   Format:
     SET BRIEF`,
+				Action: ActionSetBrief,
 				Flags: dclish.Flags{
 					"/ALL": {
 						Description: `  Specifies that the  SET BRIEF option is the default  for all users for
@@ -1306,6 +1242,7 @@ privileged qualifier.`,
 
   Format:
     SET NOBRIEF`,
+				Action: ActionSetBrief,
 				Flags: dclish.Flags{
 					"/ALL": {
 						Description: `  Specifies that the SET NOBRIEF option is the default for all users for
@@ -1385,6 +1322,7 @@ In a cluster, if the logical name MAIL$SYSTEM_FLAGS is defined so that
 bit 1 is set, users will be notified no matter which node they are logged
 in to.  If you wish to disable this, you should define BULL_SYSTEM_FLAGS
 so that bit 1 is cleared.`,
+				Action: ActionSetNotify,
 				Flags: dclish.Flags{
 					"/ALL": {
 						Description: `  Specifies that the SET NOTIFY option  is the default for all users for
@@ -1410,6 +1348,7 @@ so that bit 1 is cleared.`,
 
   Format:
     SET NONOTIFY [folder-name]`,
+				Action:  ActionSetNonotify,
 				MaxArgs: 1,
 			},
 			"PRIVILEGES": {
@@ -1560,14 +1499,21 @@ is allowed to have SYSTEM and SHUTDOWN messages added to it.  This is a
 privileged command.
 
   Format:
-    SET [NO]SYSTEM
+    SET SYSTEM
 
 By default, the GENERAL folder is a SYSTEM folder, and the setting for
-that folder cannot be removed.
+that folder cannot be removed.`,
+				Action: ActionSetSystem,
+			},
+			"NOSYSTEM": {
+				Description: `Specifies that the selected folder is not a SYSTEM folder.
 
-If the selected folder is remote, /SYSTEM cannot be specified unless the
-folder at the other node is also a SYSTEM folder.
-`,
+  Format:
+    SET NOSYSTEM
+
+By default, the GENERAL folder is a SYSTEM folder, and the setting for
+that folder cannot be removed.`,
+				Action: ActionSetNosystem,
 			},
 		},
 	},
@@ -1654,7 +1600,7 @@ have done this.`,
   Specifies to display  only those users whose latest  read message date
   is the  same date  or later  than the  specified date.  If no  date is
   specified, the  date of the  current message  is used. Only  valid for
-  folders or with /LOGIN. Use /START for newsgroups.`,
+  folders or with /LOGIN.`,
 					},
 				},
 			},
diff --git a/repl/messages.go b/repl/messages.go
index b0cd69c..6388862 100644
--- a/repl/messages.go
+++ b/repl/messages.go
@@ -200,6 +200,17 @@ func ActionFirst(_ *dclish.Command) error {
 	return nil
 }
 
+// ActionLast handles the `LAST` command.
+func ActionLast(_ *dclish.Command) error {
+	msgid := folders.LastMessage(this.Folder.Name)
+	if msgid == 0 {
+		fmt.Println("No messages in folder")
+		return nil
+	}
+	this.MsgID = msgid
+	return nil
+}
+
 // ActionNext handles the `NEXT` command.
 func ActionNext(_ *dclish.Command) error {
 	// TODO: handle flags.
@@ -219,6 +230,31 @@ func ActionNext(_ *dclish.Command) error {
 	return nil
 }
 
+// ActionPrint handles the `PRINT` command.
+func ActionPrint(cmd *dclish.Command) error {
+	// TODO: handle flags.
+	msgids := []int64{this.MsgID}
+	if len(cmd.Args) == 1 {
+		var err error
+		msgids, err = ParseNumberList(cmd.Args[0])
+		if err != nil {
+			return err
+		}
+	}
+	print("\033[5i")
+	for _, msgid := range msgids {
+		msg, err := folders.ReadMessage(this.User.Login, this.Folder.Name, msgid)
+		if err != nil {
+			fmt.Printf("Message %d not found.\n")
+		} else {
+			fmt.Print(msg.String())
+		}
+		print("\n\v")
+	}
+	print("\033[4i")
+	return nil
+}
+
 // ActionRead handles the `READ` command.
 func ActionRead(cmd *dclish.Command) error {
 	// TODO: handle flags.
@@ -234,11 +270,11 @@ func ActionRead(cmd *dclish.Command) error {
 	if err != nil {
 		return err
 	}
-	// TODO: pager needs to report if the whole message was read
-	// and only increment if not.
-	pager.Pager(msg.String())
-	msgid = folders.NextMsgid(this.User.Login, this.Folder.Name, msgid)
-	this.MsgID = max(this.MsgID, msgid)
+	if pager.Pager(msg.String()) {
+		folders.MarkSeen([]int64{msgid})
+		msgid = folders.NextMsgid(this.User.Login, this.Folder.Name, msgid)
+		this.MsgID = max(this.MsgID, msgid)
+	}
 	return nil
 }
 
diff --git a/repl/msg-text.go b/repl/msg-text.go
new file mode 100644
index 0000000..029ef27
--- /dev/null
+++ b/repl/msg-text.go
@@ -0,0 +1,8 @@
+package repl
+
+import "strings"
+
+func quote(content string) string {
+	lines := strings.Split(content, "\n")
+	return "> " + strings.Join(lines, "\n> ")
+}
diff --git a/repl/repl.go b/repl/repl.go
index 88173b2..6f3e236 100644
--- a/repl/repl.go
+++ b/repl/repl.go
@@ -34,8 +34,10 @@ func Loop() error {
 	// TODO: Remove once commands are implemented.
 	unimplemented := 0
 	total := len(commands)
+	fmt.Print("Missing")
 	for c := range commands {
 		if commands[c].Action == nil {
+			fmt.Printf(" [%s]", c)
 			unimplemented++
 		}
 		if len(commands[c].Commands) > 0 {
@@ -43,13 +45,14 @@ func Loop() error {
 			unimplemented--
 			for subc := range commands[c].Commands {
 				if commands[c].Commands[subc].Action == nil {
+					fmt.Printf(" [%s %s]", c, subc)
 					unimplemented++
 				}
 			}
 			total += len(commands[c].Commands)
 		}
 	}
-	fmt.Printf("TODO: %d out of %d commands still to be implemented.\n",
+	fmt.Printf("\nTODO: %d out of %d commands still to be implemented.\n",
 		unimplemented, total)
 	// TODO: END
 
diff --git a/repl/set.go b/repl/set.go
index b72e0b3..f8e078c 100644
--- a/repl/set.go
+++ b/repl/set.go
@@ -8,6 +8,7 @@ import (
 
 	"git.lyda.ie/kevin/bulletin/dclish"
 	"git.lyda.ie/kevin/bulletin/folders"
+	"git.lyda.ie/kevin/bulletin/storage"
 	"git.lyda.ie/kevin/bulletin/this"
 )
 
@@ -19,25 +20,43 @@ func ActionSetAccess(_ *dclish.Command) error {
 
 // ActionSetAlways handles the `SET ALWAYS` command.
 func ActionSetAlways(_ *dclish.Command) error {
-	fmt.Println("TODO: implement ActionSetAlways.")
+	ctx := storage.Context()
+	this.Q.UpdateFolderAlways(ctx, storage.UpdateFolderAlwaysParams{
+		Always: 1,
+		Name:   this.Folder.Name,
+	})
 	return nil
 }
 
 // ActionSetNoalways handles the `SET NOALWAYS` command.
 func ActionSetNoalways(_ *dclish.Command) error {
-	fmt.Println("TODO: implement ActionSetNoalways.")
+	ctx := storage.Context()
+	this.Q.UpdateFolderAlways(ctx, storage.UpdateFolderAlwaysParams{
+		Always: 0,
+		Name:   this.Folder.Name,
+	})
 	return nil
 }
 
 // ActionSetBrief handles the `SET BRIEF` command.
 func ActionSetBrief(_ *dclish.Command) error {
-	fmt.Println("TODO: implement ActionSetBrief.")
+	// TODO: parse flags.
+	ctx := storage.Context()
+	this.Q.UpdateFolderBrief(ctx, storage.UpdateFolderBriefParams{
+		Brief: 1,
+		Name:  this.Folder.Name,
+	})
 	return nil
 }
 
 // ActionSetNobrief handles the `SET NOBRIEF` command.
 func ActionSetNobrief(_ *dclish.Command) error {
-	fmt.Println("TODO: implement ActionSetNobrief.")
+	// TODO: parse flags.
+	ctx := storage.Context()
+	this.Q.UpdateFolderBrief(ctx, storage.UpdateFolderBriefParams{
+		Brief: 1,
+		Name:  this.Folder.Name,
+	})
 	return nil
 }
 
@@ -79,13 +98,23 @@ func ActionSetFolder(cmd *dclish.Command) error {
 
 // ActionSetNotify handles the `SET NOTIFY` command.
 func ActionSetNotify(_ *dclish.Command) error {
-	fmt.Println("TODO: implement ActionSetNotify.")
+	// TODO: parse flags and args.
+	ctx := storage.Context()
+	this.Q.UpdateFolderNotify(ctx, storage.UpdateFolderNotifyParams{
+		Notify: 1,
+		Name:   this.Folder.Name,
+	})
 	return nil
 }
 
 // ActionSetNonotify handles the `SET NONOTIFY` command.
 func ActionSetNonotify(_ *dclish.Command) error {
-	fmt.Println("TODO: implement ActionSetNonotify.")
+	// TODO: parse flags and args.
+	ctx := storage.Context()
+	this.Q.UpdateFolderNotify(ctx, storage.UpdateFolderNotifyParams{
+		Notify: 0,
+		Name:   this.Folder.Name,
+	})
 	return nil
 }
 
@@ -163,6 +192,22 @@ func ActionSetNoShowNew(_ *dclish.Command) error {
 
 // ActionSetSystem handles the `SET SYSTEM` command.
 func ActionSetSystem(_ *dclish.Command) error {
-	fmt.Println("TODO: implement ActionSetSystem.")
+	// TODO: parse flags and args.
+	ctx := storage.Context()
+	this.Q.UpdateFolderSystem(ctx, storage.UpdateFolderSystemParams{
+		System: 1,
+		Name:   this.Folder.Name,
+	})
+	return nil
+}
+
+// ActionSetNosystem handles the `SET SYSTEM` command.
+func ActionSetNosystem(_ *dclish.Command) error {
+	// TODO: parse flags and args.
+	ctx := storage.Context()
+	this.Q.UpdateFolderSystem(ctx, storage.UpdateFolderSystemParams{
+		System: 1,
+		Name:   this.Folder.Name,
+	})
 	return nil
 }
diff --git a/repl/xfer.go b/repl/xfer.go
new file mode 100644
index 0000000..c4c2254
--- /dev/null
+++ b/repl/xfer.go
@@ -0,0 +1,53 @@
+// Package repl implements the main event loop.
+package repl
+
+import (
+	"fmt"
+
+	"git.lyda.ie/kevin/bulletin/dclish"
+	"git.lyda.ie/kevin/bulletin/this"
+)
+
+// ActionCopy handles the `COPY` command.
+func ActionCopy(cmd *dclish.Command) error {
+	// TODO: handle flags.
+	folder := cmd.Args[0]
+	msgids := []int64{this.MsgID}
+	if len(cmd.Args) == 2 {
+		var err error
+		msgids, err = ParseNumberList(cmd.Args[1])
+		if err != nil {
+			return err
+		}
+	}
+	fmt.Printf("TODO: copy %+v to %s\n", msgids, folder)
+	/*
+		msg, err := folders.ReadMessage(this.User.Login, this.Folder.Name, msgid)
+		if err != nil {
+			return err
+		}
+	*/
+	return nil
+}
+
+// ActionMove handles the `MOVE` command.
+func ActionMove(cmd *dclish.Command) error {
+	// TODO: handle flags.
+	folder := cmd.Args[0]
+	msgids := []int64{this.MsgID}
+	if len(cmd.Args) == 2 {
+		var err error
+		msgids, err = ParseNumberList(cmd.Args[1])
+		if err != nil {
+			return err
+		}
+	}
+	fmt.Printf("TODO: move %+v to %s\n", msgids, folder)
+	/*
+		msg, err := folders.ReadMessage(this.User.Login, this.Folder.Name, msgid)
+		if err != nil {
+			return err
+		}
+	*/
+	return nil
+}
diff --git a/storage/folders.sql.go b/storage/folders.sql.go
index 4666356..7476fc8 100644
--- a/storage/folders.sql.go
+++ b/storage/folders.sql.go
@@ -226,3 +226,115 @@ func (q *Queries) ListFolderForAdmin(ctx context.Context) ([]ListFolderForAdminR
 	}
 	return items, nil
 }
+
+const updateFolderAlways = `-- name: UpdateFolderAlways :exec
+UPDATE folders SET always = ? WHERE name = ?
+`
+
+type UpdateFolderAlwaysParams struct {
+	Always int64
+	Name   string
+}
+
+func (q *Queries) UpdateFolderAlways(ctx context.Context, arg UpdateFolderAlwaysParams) error {
+	_, err := q.db.ExecContext(ctx, updateFolderAlways, arg.Always, arg.Name)
+	return err
+}
+
+const updateFolderBrief = `-- name: UpdateFolderBrief :exec
+UPDATE folders SET brief = ? WHERE name = ?
+`
+
+type UpdateFolderBriefParams struct {
+	Brief int64
+	Name  string
+}
+
+func (q *Queries) UpdateFolderBrief(ctx context.Context, arg UpdateFolderBriefParams) error {
+	_, err := q.db.ExecContext(ctx, updateFolderBrief, arg.Brief, arg.Name)
+	return err
+}
+
+const updateFolderExpire = `-- name: UpdateFolderExpire :exec
+UPDATE folders SET expire = ? WHERE name = ?
+`
+
+type UpdateFolderExpireParams struct {
+	Expire int64
+	Name   string
+}
+
+func (q *Queries) UpdateFolderExpire(ctx context.Context, arg UpdateFolderExpireParams) error {
+	_, err := q.db.ExecContext(ctx, updateFolderExpire, arg.Expire, arg.Name)
+	return err
+}
+
+const updateFolderNotify = `-- name: UpdateFolderNotify :exec
+UPDATE folders SET notify = ? WHERE name = ?
+`
+
+type UpdateFolderNotifyParams struct {
+	Notify int64
+	Name   string
+}
+
+func (q *Queries) UpdateFolderNotify(ctx context.Context, arg UpdateFolderNotifyParams) error {
+	_, err := q.db.ExecContext(ctx, updateFolderNotify, arg.Notify, arg.Name)
+	return err
+}
+
+const updateFolderReadnew = `-- name: UpdateFolderReadnew :exec
+UPDATE folders SET readnew = ? WHERE name = ?
+`
+
+type UpdateFolderReadnewParams struct {
+	Readnew int64
+	Name    string
+}
+
+func (q *Queries) UpdateFolderReadnew(ctx context.Context, arg UpdateFolderReadnewParams) error {
+	_, err := q.db.ExecContext(ctx, updateFolderReadnew, arg.Readnew, arg.Name)
+	return err
+}
+
+const updateFolderShownew = `-- name: UpdateFolderShownew :exec
+UPDATE folders SET shownew = ? WHERE name = ?
+`
+
+type UpdateFolderShownewParams struct {
+	Shownew int64
+	Name    string
+}
+
+func (q *Queries) UpdateFolderShownew(ctx context.Context, arg UpdateFolderShownewParams) error {
+	_, err := q.db.ExecContext(ctx, updateFolderShownew, arg.Shownew, arg.Name)
+	return err
+}
+
+const updateFolderSystem = `-- name: UpdateFolderSystem :exec
+UPDATE folders SET system = ? WHERE name = ?
+`
+
+type UpdateFolderSystemParams struct {
+	System int64
+	Name   string
+}
+
+func (q *Queries) UpdateFolderSystem(ctx context.Context, arg UpdateFolderSystemParams) error {
+	_, err := q.db.ExecContext(ctx, updateFolderSystem, arg.System, arg.Name)
+	return err
+}
+
+const updateFolderVisibility = `-- name: UpdateFolderVisibility :exec
+UPDATE folders SET visibility = ? WHERE name = ?
+`
+
+type UpdateFolderVisibilityParams struct {
+	Visibility int64
+	Name       string
+}
+
+func (q *Queries) UpdateFolderVisibility(ctx context.Context, arg UpdateFolderVisibilityParams) error {
+	_, err := q.db.ExecContext(ctx, updateFolderVisibility, arg.Visibility, arg.Name)
+	return err
+}
diff --git a/storage/messages.sql.go b/storage/messages.sql.go
index 9f51c30..bdec2bb 100644
--- a/storage/messages.sql.go
+++ b/storage/messages.sql.go
@@ -50,6 +50,17 @@ func (q *Queries) DeleteAllMessages(ctx context.Context, folder string) error {
 	return err
 }
 
+const lastMsgidIgnoringSeen = `-- name: LastMsgidIgnoringSeen :one
+SELECT CAST(MAX(id) AS INT) FROM messages AS m WHERE m.folder = ?1
+`
+
+func (q *Queries) LastMsgidIgnoringSeen(ctx context.Context, folder string) (int64, error) {
+	row := q.db.QueryRowContext(ctx, lastMsgidIgnoringSeen, folder)
+	var column_1 int64
+	err := row.Scan(&column_1)
+	return column_1, err
+}
+
 const nextMsgid = `-- name: NextMsgid :one
 SELECT CAST(COALESCE(MIN(id), 0) AS INT) FROM messages AS m
   WHERE m.folder = ?1 AND m.id > ?2
diff --git a/storage/queries/folders.sql b/storage/queries/folders.sql
index da30c3f..3c5f8a2 100644
--- a/storage/queries/folders.sql
+++ b/storage/queries/folders.sql
@@ -38,3 +38,27 @@ SELECT 1 FROM owners WHERE folder = ? AND login = ?;
 
 -- name: GetFolderExpire :one
 SELECT expire FROM folders WHERE name = ?;
+
+-- name: UpdateFolderAlways :exec
+UPDATE folders SET always = ? WHERE name = ?;
+
+-- name: UpdateFolderSystem :exec
+UPDATE folders SET system = ? WHERE name = ?;
+
+-- name: UpdateFolderBrief :exec
+UPDATE folders SET brief = ? WHERE name = ?;
+
+-- name: UpdateFolderNotify :exec
+UPDATE folders SET notify = ? WHERE name = ?;
+
+-- name: UpdateFolderReadnew :exec
+UPDATE folders SET readnew = ? WHERE name = ?;
+
+-- name: UpdateFolderShownew :exec
+UPDATE folders SET shownew = ? WHERE name = ?;
+
+-- name: UpdateFolderVisibility :exec
+UPDATE folders SET visibility = ? WHERE name = ?;
+
+-- name: UpdateFolderExpire :exec
+UPDATE folders SET expire = ? WHERE name = ?;
diff --git a/storage/queries/messages.sql b/storage/queries/messages.sql
index 7750b8f..e9bd74d 100644
--- a/storage/queries/messages.sql
+++ b/storage/queries/messages.sql
@@ -34,3 +34,6 @@ SELECT CAST(MAX(id) AS INT) FROM messages AS m
 
 -- name: DeleteAllMessages :exec
 DELETE FROM messages WHERE folder = ?;
+
+-- name: LastMsgidIgnoringSeen :one
+SELECT CAST(MAX(id) AS INT) FROM messages AS m WHERE m.folder = ?1;
diff --git a/this/this.go b/this/this.go
index 22a6426..e7588d9 100644
--- a/this/this.go
+++ b/this/this.go
@@ -89,6 +89,5 @@ func StartThis(login, name string) error {
 		})
 	}
 
-	fmt.Printf("User: %s\nFolder: %s\nMsgID: %d\n", User, Folder.Name, MsgID)
 	return nil
 }
-- 
GitLab