aboutsummaryrefslogtreecommitdiff
path: root/commands
diff options
context:
space:
mode:
authorJason Cox <me@jasoncarloscox.com>2024-02-23 11:40:17 -0500
committerRobin Jarry <robin@jarry.cc>2024-04-02 22:22:28 +0200
commit1ce82f50d0981a9ee047e75d94c7ab496070bd4a (patch)
tree72d835ad5da26eea6d5a6acbdd916640b75f1a4e /commands
parent54a72f83035bdf710368846e55a5b003ccab66cd (diff)
downloadaerc-1ce82f50d0981a9ee047e75d94c7ab496070bd4a.tar.gz
aerc-1ce82f50d0981a9ee047e75d94c7ab496070bd4a.zip
notmuch: add strategies for multi-file messages
A single notmuch message can represent multiple files. As a result, file-based operations like move, copy, and delete can be ambiguous. Add a new account config option, multi-file-strategy, to tell aerc how to handle these ambiguous cases. Also add options to relevant commands to set the multi-file strategy on a per-invocation basis. If no multi-file strategy is set, refuse to take file-based actions on multi-file messages. This default behavior is mostly the same as aerc's previous behavior, but a bit stricter in some cases which previously tried to be smart about multi-file operations (e.g., move and delete). Applying multi-file strategies to cross-account copy and move operations is not implemented. These operations will proceed as they have in the past -- aerc will copy/move a single file. However, for cross-account move operations, aerc will refuse to delete multiple files to prevent data loss as not all of the files are added to the destination account. See the changes to aerc-notmuch(5) for details on the currently supported multi-file strategies. Changelog-added: Tell aerc how to handle file-based operations on multi-file notmuch messages with the account config option `multi-file-strategy` and the `-m` flag to `:archive`, `:copy`, `:delete`, and `:move`. Signed-off-by: Jason Cox <me@jasoncarloscox.com> Tested-by: Maarten Aertsen <maarten@nlnetlabs.nl> Acked-by: Robin Jarry <robin@jarry.cc>
Diffstat (limited to 'commands')
-rw-r--r--commands/msg/archive.go26
-rw-r--r--commands/msg/copy.go29
-rw-r--r--commands/msg/delete.go23
-rw-r--r--commands/msg/move.go32
-rw-r--r--commands/msg/recall.go1
-rw-r--r--commands/msg/reply.go2
6 files changed, 92 insertions, 21 deletions
diff --git a/commands/msg/archive.go b/commands/msg/archive.go
index 0714a805..8c5f12b9 100644
--- a/commands/msg/archive.go
+++ b/commands/msg/archive.go
@@ -22,7 +22,19 @@ const (
var ARCHIVE_TYPES = []string{ARCHIVE_FLAT, ARCHIVE_YEAR, ARCHIVE_MONTH}
type Archive struct {
- Type string `opt:"type" action:"ParseArchiveType" metavar:"flat|year|month" complete:"CompleteType"`
+ MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS"`
+ Type string `opt:"type" action:"ParseArchiveType" metavar:"flat|year|month" complete:"CompleteType"`
+}
+
+func (a *Archive) ParseMFS(arg string) error {
+ if arg != "" {
+ mfs, ok := types.StrToStrategy[arg]
+ if !ok {
+ return fmt.Errorf("invalid multi-file strategy %s", arg)
+ }
+ a.MultiFileStrategy = &mfs
+ }
+ return nil
}
func (a *Archive) ParseArchiveType(arg string) error {
@@ -47,6 +59,10 @@ func (Archive) Aliases() []string {
return []string{"archive"}
}
+func (Archive) CompleteMFS(arg string) []string {
+ return commands.FilterList(types.StrategyStrs(), arg, nil)
+}
+
func (*Archive) CompleteType(arg string) []string {
return commands.FilterList(ARCHIVE_TYPES, arg, nil)
}
@@ -57,11 +73,13 @@ func (a Archive) Execute(args []string) error {
if err != nil {
return err
}
- err = archive(msgs, a.Type)
+ err = archive(msgs, a.MultiFileStrategy, a.Type)
return err
}
-func archive(msgs []*models.MessageInfo, archiveType string) error {
+func archive(msgs []*models.MessageInfo, mfs *types.MultiFileStrategy,
+ archiveType string,
+) error {
h := newHelper()
acct, err := h.account()
if err != nil {
@@ -111,7 +129,7 @@ func archive(msgs []*models.MessageInfo, archiveType string) error {
success := true
for dir, uids := range uidMap {
- store.Move(uids, dir, true, func(
+ store.Move(uids, dir, true, mfs, func(
msg types.WorkerMessage,
) {
switch msg := msg.(type) {
diff --git a/commands/msg/copy.go b/commands/msg/copy.go
index af44216f..9ea81055 100644
--- a/commands/msg/copy.go
+++ b/commands/msg/copy.go
@@ -14,9 +14,10 @@ import (
)
type Copy struct {
- CreateFolders bool `opt:"-p"`
- Account string `opt:"-a" complete:"CompleteAccount"`
- Folder string `opt:"folder" complete:"CompleteFolder"`
+ CreateFolders bool `opt:"-p"`
+ Account string `opt:"-a" complete:"CompleteAccount"`
+ MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS"`
+ Folder string `opt:"folder" complete:"CompleteFolder"`
}
func init() {
@@ -31,6 +32,17 @@ func (Copy) Aliases() []string {
return []string{"cp", "copy"}
}
+func (c *Copy) ParseMFS(arg string) error {
+ if arg != "" {
+ mfs, ok := types.StrToStrategy[arg]
+ if !ok {
+ return fmt.Errorf("invalid multi-file strategy %s", arg)
+ }
+ c.MultiFileStrategy = &mfs
+ }
+ return nil
+}
+
func (*Copy) CompleteAccount(arg string) []string {
return commands.FilterList(app.AccountNames(), arg, commands.QuoteSpace)
}
@@ -48,6 +60,10 @@ func (c *Copy) CompleteFolder(arg string) []string {
return commands.FilterList(acct.Directories().List(), arg, nil)
}
+func (Copy) CompleteMFS(arg string) []string {
+ return commands.FilterList(types.StrategyStrs(), arg, nil)
+}
+
func (c Copy) Execute(args []string) error {
h := newHelper()
uids, err := h.markedOrSelectedUids()
@@ -60,9 +76,10 @@ func (c Copy) Execute(args []string) error {
}
if len(c.Account) == 0 {
- store.Copy(uids, c.Folder, c.CreateFolders, func(msg types.WorkerMessage) {
- c.CallBack(msg, uids, store)
- })
+ store.Copy(uids, c.Folder, c.CreateFolders, c.MultiFileStrategy,
+ func(msg types.WorkerMessage) {
+ c.CallBack(msg, uids, store)
+ })
return nil
}
diff --git a/commands/msg/delete.go b/commands/msg/delete.go
index 0abde31c..0d269eab 100644
--- a/commands/msg/delete.go
+++ b/commands/msg/delete.go
@@ -13,7 +13,9 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type Delete struct{}
+type Delete struct {
+ MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS"`
+}
func init() {
commands.Register(Delete{})
@@ -27,7 +29,22 @@ func (Delete) Aliases() []string {
return []string{"delete", "delete-message"}
}
-func (Delete) Execute(args []string) error {
+func (d *Delete) ParseMFS(arg string) error {
+ if arg != "" {
+ mfs, ok := types.StrToStrategy[arg]
+ if !ok {
+ return fmt.Errorf("invalid multi-file strategy %s", arg)
+ }
+ d.MultiFileStrategy = &mfs
+ }
+ return nil
+}
+
+func (Delete) CompleteMFS(arg string) []string {
+ return commands.FilterList(types.StrategyStrs(), arg, nil)
+}
+
+func (d Delete) Execute(args []string) error {
h := newHelper()
store, err := h.store()
if err != nil {
@@ -46,7 +63,7 @@ func (Delete) Execute(args []string) error {
marker.ClearVisualMark()
// caution, can be nil
next := findNextNonDeleted(uids, store)
- store.Delete(uids, func(msg types.WorkerMessage) {
+ store.Delete(uids, d.MultiFileStrategy, func(msg types.WorkerMessage) {
switch msg := msg.(type) {
case *types.Done:
var s string
diff --git a/commands/msg/move.go b/commands/msg/move.go
index d2623268..c073765f 100644
--- a/commands/msg/move.go
+++ b/commands/msg/move.go
@@ -17,9 +17,10 @@ import (
)
type Move struct {
- CreateFolders bool `opt:"-p"`
- Account string `opt:"-a" complete:"CompleteAccount"`
- Folder string `opt:"folder" complete:"CompleteFolder"`
+ CreateFolders bool `opt:"-p"`
+ Account string `opt:"-a" complete:"CompleteAccount"`
+ MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS"`
+ Folder string `opt:"folder" complete:"CompleteFolder"`
}
func init() {
@@ -34,6 +35,17 @@ func (Move) Aliases() []string {
return []string{"mv", "move"}
}
+func (m *Move) ParseMFS(arg string) error {
+ if arg != "" {
+ mfs, ok := types.StrToStrategy[arg]
+ if !ok {
+ return fmt.Errorf("invalid multi-file strategy %s", arg)
+ }
+ m.MultiFileStrategy = &mfs
+ }
+ return nil
+}
+
func (*Move) CompleteAccount(arg string) []string {
return commands.FilterList(app.AccountNames(), arg, commands.QuoteSpace)
}
@@ -51,6 +63,10 @@ func (m *Move) CompleteFolder(arg string) []string {
return commands.FilterList(acct.Directories().List(), arg, nil)
}
+func (Move) CompleteMFS(arg string) []string {
+ return commands.FilterList(types.StrategyStrs(), arg, nil)
+}
+
func (m Move) Execute(args []string) error {
h := newHelper()
acct, err := h.account()
@@ -71,9 +87,10 @@ func (m Move) Execute(args []string) error {
marker.ClearVisualMark()
if len(m.Account) == 0 {
- store.Move(uids, m.Folder, m.CreateFolders, func(msg types.WorkerMessage) {
- m.CallBack(msg, acct, uids, next, marker, false)
- })
+ store.Move(uids, m.Folder, m.CreateFolders, m.MultiFileStrategy,
+ func(msg types.WorkerMessage) {
+ m.CallBack(msg, acct, uids, next, marker, false)
+ })
return nil
}
@@ -158,7 +175,8 @@ func (m Move) Execute(args []string) error {
}
}
if len(appended) > 0 {
- store.Delete(appended, func(msg types.WorkerMessage) {
+ mfs := types.Refuse
+ store.Delete(appended, &mfs, func(msg types.WorkerMessage) {
m.CallBack(msg, acct, appended, next, marker, timeout)
})
}
diff --git a/commands/msg/recall.go b/commands/msg/recall.go
index 280c41b8..7c59ac85 100644
--- a/commands/msg/recall.go
+++ b/commands/msg/recall.go
@@ -75,6 +75,7 @@ func (r Recall) Execute(args []string) error {
deleteMessage := func() {
store.Delete(
uids,
+ nil,
func(msg types.WorkerMessage) {
switch msg := msg.(type) {
case *types.Done:
diff --git a/commands/msg/reply.go b/commands/msg/reply.go
index 02cf42ce..79a0c598 100644
--- a/commands/msg/reply.go
+++ b/commands/msg/reply.go
@@ -184,7 +184,7 @@ func (r reply) Execute(args []string) error {
switch {
case c.Sent() && c.Archive() != "" && !noStore:
store.Answered([]uint32{msg.Uid}, true, nil)
- err := archive([]*models.MessageInfo{msg}, c.Archive())
+ err := archive([]*models.MessageInfo{msg}, nil, c.Archive())
if err != nil {
app.PushStatus("Archive failed", 10*time.Second)
}