aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--commands/account/export-mbox.go126
-rw-r--r--doc/aerc.1.scd3
-rw-r--r--worker/maildir/worker.go3
3 files changed, 132 insertions, 0 deletions
diff --git a/commands/account/export-mbox.go b/commands/account/export-mbox.go
new file mode 100644
index 00000000..528934eb
--- /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 <filename>", cmd)
+}
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 5928bf0d..97a9eab6 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* <file>
+ 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 cc03ec81..b217bd28 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
}