From 845763cb1f1f8b7acdfc8e94e0a2d61ff78f6b9d Mon Sep 17 00:00:00 2001 From: Koni Marti Date: Mon, 11 Jul 2022 20:11:20 +0200 Subject: [PATCH] account: export folder to mbox file Export all message in the current folder to an mbox file. If an error occurs during the export, aerc retries a few times before giving up to prevent a hang. Signed-off-by: Koni Marti Acked-by: Robin Jarry --- commands/account/export-mbox.go | 126 ++++++++++++++++++++++++++++++++ doc/aerc.1.scd | 3 + worker/maildir/worker.go | 3 + 3 files changed, 132 insertions(+) create mode 100644 commands/account/export-mbox.go diff --git a/commands/account/export-mbox.go b/commands/account/export-mbox.go new file mode 100644 index 0000000..528934e --- /dev/null +++ b/commands/account/export-mbox.go @@ -0,0 +1,126 @@ +package account + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sync" + "time" + + "git.sr.ht/~rjarry/aerc/widgets" + mboxer "git.sr.ht/~rjarry/aerc/worker/mbox" + "git.sr.ht/~rjarry/aerc/worker/types" +) + +type ExportMbox struct{} + +func init() { + register(ExportMbox{}) +} + +func (ExportMbox) Aliases() []string { + return []string{"export-mbox"} +} + +func (ExportMbox) Complete(aerc *widgets.Aerc, args []string) []string { + if acct := aerc.SelectedAccount(); acct != nil { + if path := acct.SelectedDirectory(); path != "" { + if f := filepath.Base(path); f != "" { + return []string{f + ".mbox"} + } + } + } + return nil +} + +func (ExportMbox) Execute(aerc *widgets.Aerc, args []string) error { + if len(args) != 2 { + return exportFolderUsage(args[0]) + } + filename := args[1] + + acct := aerc.SelectedAccount() + if acct == nil { + return errors.New("No account selected") + } + store := acct.Store() + if store == nil { + return errors.New("No message store selected") + } + + aerc.PushStatus("Exporting to "+filename, 10*time.Second) + + go func() { + file, err := os.Create(filename) + if err != nil { + acct.Logger().Println(args[0], err.Error()) + aerc.PushError(err.Error()) + return + } + defer file.Close() + + var mu sync.Mutex + var ctr uint32 + var retries int + + done := make(chan bool) + uids := make([]uint32, len(store.Uids())) + copy(uids, store.Uids()) + t := time.Now() + + for len(uids) > 0 { + if retries > 0 { + if retries > 10 { + errorMsg := fmt.Sprintln(args[0], "too many retries:", retries, "; stopping export") + acct.Logger().Println(errorMsg) + aerc.PushError(errorMsg) + break + } + sleeping := time.Duration(retries * 1e9 * 2) + acct.Logger().Println(args[0], "sleeping for", sleeping, "before retrying; retries:", retries) + time.Sleep(sleeping) + } + + acct.Logger().Println(args[0], "fetching", len(uids), "for export") + acct.Worker().PostAction(&types.FetchFullMessages{ + Uids: uids, + }, func(msg types.WorkerMessage) { + switch msg := msg.(type) { + case *types.Done: + acct.Logger().Println(args[0], "done") + done <- true + case *types.Error: + errMsg := fmt.Sprintln(args[0], "error encountered:", msg.Error.Error()) + acct.Logger().Println(errMsg) + aerc.PushError(errMsg) + done <- false + case *types.FullMessage: + mu.Lock() + mboxer.Write(file, msg.Content.Reader, "", t) + for i, uid := range uids { + if uid == msg.Content.Uid { + uids = append(uids[:i], uids[i+1:]...) + break + } + } + ctr++ + mu.Unlock() + } + }) + if ok := <-done; ok { + break + } + retries++ + } + statusInfo := fmt.Sprintf("Exported %d of %d messages to %s.", ctr, len(store.Uids()), filename) + aerc.PushStatus(statusInfo, 10*time.Second) + acct.Logger().Println(args[0], statusInfo) + }() + + return nil +} + +func exportFolderUsage(cmd string) error { + return fmt.Errorf("Usage: %s ", cmd) +} diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd index 5928bf0..97a9eab 100644 --- a/doc/aerc.1.scd +++ b/doc/aerc.1.scd @@ -311,6 +311,9 @@ message list, the message in the message viewer, etc). Expands or collapses the current folder when the directory tree is enabled. +*export-mbox* + Exports all messages in the current folder to an mbox file. + *next-result*, *prev-result* Selects the next or previous search result. diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go index cc03ec8..b217bd2 100644 --- a/worker/maildir/worker.go +++ b/worker/maildir/worker.go @@ -550,6 +550,9 @@ func (w *Worker) handleFetchFullMessages(msg *types.FetchFullMessages) error { }, }, nil) } + w.worker.PostMessage(&types.Done{ + Message: types.RespondTo(msg), + }, nil) return nil }