From 62cfedea55cc414262724a5c37155b3595b24964 Mon Sep 17 00:00:00 2001
From: Kevin Lyda <kevin@lyda.ie>
Date: Fri, 16 May 2025 21:50:23 +0100
Subject: [PATCH] Implement MARK and UNMARK

---
 NOTES.md                     | 20 ++++++++++----------
 folders/messages.go          | 28 ++++++++++++++++++++++++++++
 repl/command.go              | 19 ++++++++-----------
 repl/messages.go             | 34 ++++++++++++++++++++++++++++++++++
 storage/queries/standard.sql |  2 +-
 storage/standard.sql.go      | 11 ++++++++---
 6 files changed, 89 insertions(+), 25 deletions(-)

diff --git a/NOTES.md b/NOTES.md
index 536d4a0..7651e96 100644
--- a/NOTES.md
+++ b/NOTES.md
@@ -21,23 +21,17 @@ Switch between MAIL and BULLETIN modes? MAIL commands are documented
 ## Things to do
 
   * Run [godoc](http://localhost:6060/) and then review where the help text is lacking.
-  * ~~Move to a storage layer.~~
   * Implement each command.
-    * Next: folder commands - ~~CREATE~~, ~~REMOVE~~, MODIFY, ~~INDEX~~, ~~SELECT~~
-    * Messages: ~~ADD~~, ~~CURRENT~~, ~~DIRECTORY~~, ~~BACK~~, CHANGE,
-                ~~FIRST~~, ~~NEXT~~, ~~READ~~, ~~DELETE~~
-    * Messages edit: CHANGE, REPLY, FORWARD
+    * Next: folder commands - MODIFY
+    * Messages edit: CHANGE, REPLY
     * Moving messages: COPY, MOVE
+    * Mark messages: MARK, UNMARK
     * Compound commands: SET and SHOW - make HELP work for them.
-    * Mail: MAIL, FORWARD, 
+    * Mail: MAIL, FORWARD, RESPOND
   * Run this.Skew.Safe() before... each command?  each write?
   * Handle broadcast messages - have bulletin watch a directory and
     display files from it.  Then have them delete the file if it's older
     than 5 minutes (allow for failure)
-  * Cleanup help output.
-    * Remove the node/cluster/newsgroup/mailing-list related flags.
-    * Remove BBOARD references.
-    * format with `par w72j1`
   * Database
     * trigger to limit values for 'visibility'?
   * Add commands:
@@ -46,6 +40,7 @@ Switch between MAIL and BULLETIN modes? MAIL commands are documented
     * Commands for a local mail system?
     * Commands to connect to Mattermost or mastodon?
     * Commands to manage users.
+  * Handle MARK for SELECT and DIRECTORY.
 
 Done:
 
@@ -66,6 +61,11 @@ Done:
   * ~~this.Folder should be a storage.Folder.~~
   * ~~Add some of the early announcements from the sources - see the
     conversion branch - to the GENERAL folder.~~
+  * ~~Move to a storage layer.~~
+  * ~~Cleanup help output.~~
+    * ~~Remove the node/cluster/newsgroup/mailing-list related flags.~~
+    * ~~Remove BBOARD references.~~
+    * ~~format with `par w72j1`~~
 
 ## Module links
 
diff --git a/folders/messages.go b/folders/messages.go
index d05b8e4..c787c4b 100644
--- a/folders/messages.go
+++ b/folders/messages.go
@@ -158,3 +158,31 @@ func DeleteAllMessages() error {
 	this.Q.DeleteAllMessages(ctx, this.Folder.Name)
 	return nil
 }
+
+// SetMark sets a mark for message(s).
+func SetMark(msgids []int64) error {
+	ctx := storage.Context()
+
+	for _, msgid := range msgids {
+		this.Q.AddMark(ctx, storage.AddMarkParams{
+			Folder: this.Folder.Name,
+			Login:  this.User.Login,
+			Msgid:  msgid,
+		})
+	}
+	return nil
+}
+
+// UnsetMark sets a mark for message(s).
+func UnsetMark(msgids []int64) error {
+	ctx := storage.Context()
+
+	for _, msgid := range msgids {
+		this.Q.DeleteMark(ctx, storage.DeleteMarkParams{
+			Folder: this.Folder.Name,
+			Login:  this.User.Login,
+			Msgid:  msgid,
+		})
+	}
+	return nil
+}
diff --git a/repl/command.go b/repl/command.go
index 1552160..5368143 100644
--- a/repl/command.go
+++ b/repl/command.go
@@ -267,7 +267,7 @@ topic are grouped separately, or to restrict reading of certain messages
 to  specified  users.   Once  created,  that  message  is  automatically
 selected  (see information on SELECT command).  The commands that can be
 used to modify the folder's characteristics  are:  MODIFY,  REMOVE,  SET
-ACCESS, SET BBOARD, SET NODE, and SET SYSTEM.
+ACCESS, and SET SYSTEM.
 
   Format:
     CREATE folder-name
@@ -703,6 +703,7 @@ information.
   Format:
     MARK [message-number or numbers]`,
 		MaxArgs: 1,
+		Action:  ActionMark,
 	},
 	"UNMARK": {
 		Description: `Sets the current  or message-id message as unmarked.
@@ -710,6 +711,7 @@ information.
   Format:
     UNMARK [message-number or numbers]`,
 		MaxArgs: 1,
+		Action:  ActionUnmark,
 	},
 	"MODIFY": {
 		Description: `Modifies the database information for the current folder. Only the owner
@@ -1324,10 +1326,9 @@ privileged qualifier.`,
 				},
 			},
 			"DEFAULT_EXPIRE": {
-				Description: `Specifies  the  number  of days the message created by BBOARD (or direct
-PMDF path) is to be retained.  The default  is  14  days.   The  highest
-limit  that  can  be  specified is 30 days.  This can be overridden by a
-user with privileges.
+				Description: `Specifies the number of days the  message is to be retained. The default
+is 14 days. The highest limit that can be specified is 30 days. This can
+be overridden by a user with privileges.
 
 This  also  specifies the default expiration date when adding a message.
 If no expiration date is  entered  when  prompted  for  a  date,  or  if
@@ -1339,11 +1340,7 @@ used.
 
 If -1 is specified, messages will become permanent.  If 0 is  specified,
 no  default expiration date will be present.  The latter should never be
-specified for a  folder  with  a  BBOARD,  or  else  the  messages  will
-disappear.
-
-NOTE: This value is the same value that SET BBOARD/EXPIRATION specifies.
-If one is changed, the other will change also.`,
+specified for a folder or else the messages will disappear.`,
 				MinArgs: 1,
 				MaxArgs: 1,
 			},
@@ -1597,7 +1594,7 @@ the SELECT command, information about that folder is shown.
 					"/FULL": {
 						Description: `  Control  whether all  information  of the  folder  is displayed.  This
   includes the SYSTEM setting, the access list if the folder is private,
-  and BBOARD information. This information is only those who have access
+  and other information.  This information is only those who have access
   to that folder.`,
 					},
 				},
diff --git a/repl/messages.go b/repl/messages.go
index 13b9763..b0cd69c 100644
--- a/repl/messages.go
+++ b/repl/messages.go
@@ -323,3 +323,37 @@ func ActionDelete(cmd *dclish.Command) error {
 	}
 	return nil
 }
+
+// ActionMark handles the `MARK` command.
+func ActionMark(cmd *dclish.Command) error {
+	var err error
+	msgids := []int64{this.MsgID}
+	if len(cmd.Args) == 1 {
+		msgids, err = ParseNumberList(cmd.Args[0])
+		if err != nil {
+			return err
+		}
+	}
+	err = folders.SetMark(msgids)
+	if err != nil {
+		fmt.Printf("ERROR: %s.\n", err)
+	}
+	return nil
+}
+
+// ActionUnmark handles the `UNMARK` command.
+func ActionUnmark(cmd *dclish.Command) error {
+	var err error
+	msgids := []int64{this.MsgID}
+	if len(cmd.Args) == 1 {
+		msgids, err = ParseNumberList(cmd.Args[0])
+		if err != nil {
+			return err
+		}
+	}
+	err = folders.UnsetMark(msgids)
+	if err != nil {
+		fmt.Printf("ERROR: %s.\n", err)
+	}
+	return nil
+}
diff --git a/storage/queries/standard.sql b/storage/queries/standard.sql
index 8edc1bb..3af1772 100644
--- a/storage/queries/standard.sql
+++ b/storage/queries/standard.sql
@@ -59,7 +59,7 @@ DELETE FROM seen WHERE folder = ? AND login = ? AND msgid = ?;
 INSERT INTO marks (folder, login, msgid) VALUES (?, ?, ?);
 
 -- name: ListMark :many
-SELECT * FROM marks;
+SELECT * FROM marks WHERE folder = ? AND login = ?;
 
 -- name: GetMark :one
 SELECT * FROM marks WHERE folder = ? AND login = ? AND msgid = ?;
diff --git a/storage/standard.sql.go b/storage/standard.sql.go
index 0c05956..8ddd2c8 100644
--- a/storage/standard.sql.go
+++ b/storage/standard.sql.go
@@ -508,11 +508,16 @@ func (q *Queries) ListFolders(ctx context.Context) ([]Folder, error) {
 }
 
 const listMark = `-- name: ListMark :many
-SELECT login, folder, msgid FROM marks
+SELECT login, folder, msgid FROM marks WHERE folder = ? AND login = ?
 `
 
-func (q *Queries) ListMark(ctx context.Context) ([]Mark, error) {
-	rows, err := q.db.QueryContext(ctx, listMark)
+type ListMarkParams struct {
+	Folder string
+	Login  string
+}
+
+func (q *Queries) ListMark(ctx context.Context, arg ListMarkParams) ([]Mark, error) {
+	rows, err := q.db.QueryContext(ctx, listMark, arg.Folder, arg.Login)
 	if err != nil {
 		return nil, err
 	}
-- 
GitLab