diff options
author | Johannes Thyssen Tishman <johannes@thyssentishman.com> | 2024-01-22 20:46:53 +0100 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2024-01-25 23:33:01 +0100 |
commit | e4eab644b0ee1a7bc87fa0581cf0ac28eb64bf58 (patch) | |
tree | 986e64abfe5fb6f9c8750ef82f4f3751e085b6e5 | |
parent | 73d1bb7cbb726bd4e4f567970526707fdaa4fb67 (diff) | |
download | aerc-e4eab644b0ee1a7bc87fa0581cf0ac28eb64bf58.tar.gz |
cp: allow to copy messages across accounts
Add a new -a flag to :cp. When specified, an account name is required
before the folder name. If the destination folder doesn't exist,
it will be created whether or not the -p flag is specified.
Changelog-added: Copy messages across accounts with `:cp -a <account>
<folder>`.
Signed-off-by: Johannes Thyssen Tishman <johannes@thyssentishman.com>
Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r-- | commands/msg/copy.go | 124 | ||||
-rw-r--r-- | doc/aerc.1.scd | 11 |
2 files changed, 112 insertions, 23 deletions
diff --git a/commands/msg/copy.go b/commands/msg/copy.go index 3620238a..ed963707 100644 --- a/commands/msg/copy.go +++ b/commands/msg/copy.go @@ -1,16 +1,21 @@ package msg import ( + "bytes" "fmt" "time" "git.sr.ht/~rjarry/aerc/app" "git.sr.ht/~rjarry/aerc/commands" + "git.sr.ht/~rjarry/aerc/lib" + "git.sr.ht/~rjarry/aerc/log" + "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" ) type Copy struct { CreateFolders bool `opt:"-p"` + Account string `opt:"-a" complete:"CompleteAccount"` Folder string `opt:"folder" complete:"CompleteFolder"` } @@ -26,8 +31,21 @@ func (Copy) Aliases() []string { return []string{"cp", "copy"} } -func (*Copy) CompleteFolder(arg string) []string { - return commands.GetFolders(arg) +func (*Copy) CompleteAccount(arg string) []string { + return commands.FilterList(app.AccountNames(), arg, commands.QuoteSpace) +} + +func (c *Copy) CompleteFolder(arg string) []string { + var acct *app.AccountView + if len(c.Account) > 0 { + acct, _ = app.Account(c.Account) + } else { + acct = app.SelectedAccount() + } + if acct == nil { + return nil + } + return commands.FilterList(acct.Directories().List(), arg, nil) } func (c Copy) Execute(args []string) error { @@ -40,23 +58,91 @@ func (c Copy) Execute(args []string) error { if err != nil { return err } - store.Copy(uids, c.Folder, - c.CreateFolders, func( - msg types.WorkerMessage, - ) { - switch msg := msg.(type) { - case *types.Done: - var s string - if len(uids) > 1 { - s = "%d messages copied to %s" - } else { - s = "%d message copied to %s" - } - app.PushStatus(fmt.Sprintf(s, len(uids), c.Folder), 10*time.Second) - store.Marker().ClearVisualMark() - case *types.Error: - app.PushError(msg.Error.Error()) - } + + if len(c.Account) == 0 { + store.Copy(uids, c.Folder, c.CreateFolders, func(msg types.WorkerMessage) { + c.CallBack(msg, uids, store) }) + return nil + } + + destAcct, err := app.Account(c.Account) + if err != nil { + return err + } + + destStore := destAcct.Store() + if destStore == nil { + app.PushError(fmt.Sprintf("No message store in %s", c.Account)) + return nil + } + + var messages []*types.FullMessage + fetchDone := make(chan bool, 1) + store.FetchFull(uids, func(fm *types.FullMessage) { + messages = append(messages, fm) + if len(messages) == len(uids) { + fetchDone <- true + } + }) + + // Since this operation can take some time with some backends + // (e.g. IMAP), provide some feedback to inform the user that + // something is happening + app.PushStatus("Copying messages...", 10*time.Second) + go func() { + defer log.PanicHandler() + + select { + case <-fetchDone: + break + case <-time.After(30 * time.Second): + // TODO: find a better way to determine if store.FetchFull() + // has finished with some errors. + app.PushError("Failed to fetch all messages") + if len(messages) == 0 { + return + } + } + for _, fm := range messages { + buf := new(bytes.Buffer) + _, err = buf.ReadFrom(fm.Content.Reader) + if err != nil { + log.Warnf("failed to read message: %v", err) + continue + } + destStore.Append( + c.Folder, + models.SeenFlag, + time.Now(), + buf, + buf.Len(), + func(msg types.WorkerMessage) { + c.CallBack(msg, uids, store) + }, + ) + } + }() return nil } + +func (c Copy) CallBack(msg types.WorkerMessage, uids []uint32, store *lib.MessageStore) { + dest := c.Folder + if len(c.Account) != 0 { + dest = fmt.Sprintf("%s in %s", c.Folder, c.Account) + } + + switch msg := msg.(type) { + case *types.Done: + var s string + if len(uids) > 1 { + s = "%d messages copied to %s" + } else { + s = "%d message copied to %s" + } + app.PushStatus(fmt.Sprintf(s, len(uids), dest), 10*time.Second) + store.Marker().ClearVisualMark() + case *types.Error: + app.PushError(msg.Error.Error()) + } +} diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd index 5be01751..c7fc8542 100644 --- a/doc/aerc.1.scd +++ b/doc/aerc.1.scd @@ -272,11 +272,14 @@ message list, the message in the message viewer, etc). *-E*: Forces *[compose].edit-headers* = _false_ for this message only. -*:copy* [*-p*] _<target>_++ -*:cp* [*-p*] _<target>_ - Copies the selected message(s) to the target folder. +*:copy* [*-p*] [*-a* _<account>_] _<folder>_++ +*:cp* [*-p*] [*-a* _<account>_] _<folder>_ + Copies the selected message(s) to _<folder>_. - *-p*: Create the _<target>_ folder if it does not exist. + *-p*: Create _<folder>_ if it does not exist. + + *-a*: Copy to _<folder>_ of _<account>_. If _<folder>_ does + not exist, it will be created whether or not *-p* is used. *:decline* [*-e*|*-E*] Declines an iCalendar meeting invitation. |