aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorARaspiK <araspik@protonmail.com>2020-08-18 20:27:23 +0000
committerReto Brunner <reto@labrat.space>2020-08-19 11:38:57 +0200
commitfe1cabb077cf6c6cb3de122b3f5532acbeba8c85 (patch)
tree6f57a28a507c500df82f991cefd67910eca973f0
parentf4dc7e1f746582d42462ec56347dd354756203b0 (diff)
downloadaerc-fe1cabb077cf6c6cb3de122b3f5532acbeba8c85.tar.gz
Add support for :rmdir
The `:rmdir` command removes the current directory (`-f` is required if the directory is not empty). This is not supported on the notmuch backend. An issue with the maildir backend is that some sync programs (e.g. offlineimap) may recover the directory after it is deleted. They need to specifically be configured to accept deletions, or special commands need to be executed (e.g. `offlineimap --delete-folder`) to properly delete folders. A danger of using this on the IMAP backend is that it is possible for a new message to be added to the directory and for aerc to not show it immediately (due to a slow connection) - using `:rmdir` at this moment (with `-f` if the directory already contains messages) would delete the directory and the new message that just arrived (and all other contents). This is documented in aerc(1) so that users are aware of possible risks.
-rw-r--r--commands/account/rmdir.go96
-rw-r--r--doc/aerc.1.scd22
-rw-r--r--widgets/account.go2
-rw-r--r--worker/imap/remove.go19
-rw-r--r--worker/imap/worker.go2
-rw-r--r--worker/maildir/worker.go12
-rw-r--r--worker/notmuch/worker.go2
-rw-r--r--worker/types/messages.go6
8 files changed, 161 insertions, 0 deletions
diff --git a/commands/account/rmdir.go b/commands/account/rmdir.go
new file mode 100644
index 00000000..ed24ca50
--- /dev/null
+++ b/commands/account/rmdir.go
@@ -0,0 +1,96 @@
+package account
+
+import (
+ "errors"
+ "time"
+
+ "git.sr.ht/~sircmpwn/getopt"
+
+ "git.sr.ht/~sircmpwn/aerc/widgets"
+ "git.sr.ht/~sircmpwn/aerc/worker/types"
+)
+
+type RemoveDir struct{}
+
+func init() {
+ register(RemoveDir{})
+}
+
+func (RemoveDir) Aliases() []string {
+ return []string{"rmdir"}
+}
+
+func (RemoveDir) Complete(aerc *widgets.Aerc, args []string) []string {
+ return nil
+}
+
+func (RemoveDir) Execute(aerc *widgets.Aerc, args []string) error {
+ acct := aerc.SelectedAccount()
+ if acct == nil {
+ return errors.New("No account selected")
+ }
+
+ force := false
+
+ opts, optind, err := getopt.Getopts(args, "f")
+ if err != nil {
+ return err
+ }
+ for _, opt := range opts {
+ switch opt.Option {
+ case 'f':
+ force = true
+ }
+ }
+
+ if len(args) != optind {
+ return errors.New("Usage: rmdir [-f]")
+ }
+
+ // Check for any messages in the directory.
+ if !acct.Messages().Empty() && !force {
+ return errors.New("Refusing to remove non-empty directory; use -f")
+ }
+
+ curDir := acct.SelectedDirectory()
+ var newDir string
+ dirFound := false
+
+ if oldDir, ok := history[acct.Name()]; ok {
+ if oldDir != curDir {
+ newDir = oldDir
+ dirFound = true
+ }
+ }
+
+ if !dirFound {
+ for _, dir := range acct.Directories().List() {
+ if dir != curDir {
+ newDir = dir
+ dirFound = true
+ break
+ }
+ }
+ }
+
+ if !dirFound {
+ return errors.New("No directory to move to afterwards!")
+ }
+
+ acct.Directories().Select(newDir)
+
+ acct.Worker().PostAction(&types.RemoveDirectory{
+ Directory: curDir,
+ }, func(msg types.WorkerMessage) {
+ switch msg := msg.(type) {
+ case *types.Done:
+ aerc.PushStatus("Directory removed.", 10*time.Second)
+ case *types.Error:
+ aerc.PushError(" " + msg.Error.Error())
+ case *types.Unsupported:
+ aerc.PushError(":rmdir is not supported by the backend.")
+ }
+ })
+
+ return nil
+}
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 89189d52..c2f813a4 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -214,6 +214,28 @@ message list, the message in the message viewer, etc).
*mkdir* <name>
Creates a new folder for this account and changes to that folder.
+ This is not supported on the 'notmuch' backend.
+
+*rmdir* [-f]
+ Removes the current folder.
+
+ By default, it will fail if the directory is non-empty (see *-f*).
+
+ *-f*
+ Remove the directory even if it contains messages.
+
+ This is not supported on the 'notmuch' backend.
+
+ Some programs that sync maildirs may recover deleted directories (e.g.
+ offlineimap). These can either be specially configured to properly
+ handle directory deletion, or special commands need to be run to delete
+ directories (e.g. 'offlineimap --delete-folder').
+
+ It is possible, with a slow connection and the 'imap' backend, that new
+ messages arrive in the directory before they show up - using 'rmdir' at
+ this moment would delete the directory and such new messages before the
+ user sees them.
+
*next* <n>[%], *prev* <n>[%]
Selects the next (or previous) message in the message list. If specified as
a percentage, the percentage is applied to the number of messages shown on
diff --git a/widgets/account.go b/widgets/account.go
index bb29ce06..f2795133 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -237,6 +237,8 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
}
case *types.CreateDirectory:
acct.dirlist.UpdateList(nil)
+ case *types.RemoveDirectory:
+ acct.dirlist.UpdateList(nil)
}
case *types.DirectoryInfo:
if store, ok := acct.dirlist.MsgStore(msg.Info.Name); ok {
diff --git a/worker/imap/remove.go b/worker/imap/remove.go
new file mode 100644
index 00000000..47b1f437
--- /dev/null
+++ b/worker/imap/remove.go
@@ -0,0 +1,19 @@
+package imap
+
+import (
+ "git.sr.ht/~sircmpwn/aerc/worker/types"
+)
+
+func (imapw *IMAPWorker) handleRemoveDirectory(msg *types.RemoveDirectory) {
+ if err := imapw.client.Delete(msg.Directory); err != nil {
+ if msg.Quiet {
+ return
+ }
+ imapw.worker.PostMessage(&types.Error{
+ Message: types.RespondTo(msg),
+ Error: err,
+ }, nil)
+ } else {
+ imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
+ }
+}
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index 0be51d71..c016af66 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -165,6 +165,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
w.handleFetchDirectoryContents(msg)
case *types.CreateDirectory:
w.handleCreateDirectory(msg)
+ case *types.RemoveDirectory:
+ w.handleRemoveDirectory(msg)
case *types.FetchMessageHeaders:
w.handleFetchMessageHeaders(msg)
case *types.FetchMessageBodyPart:
diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go
index d1ff3c2f..4a7ae51f 100644
--- a/worker/maildir/worker.go
+++ b/worker/maildir/worker.go
@@ -186,6 +186,8 @@ func (w *Worker) handleMessage(msg types.WorkerMessage) error {
return w.handleFetchDirectoryContents(msg)
case *types.CreateDirectory:
return w.handleCreateDirectory(msg)
+ case *types.RemoveDirectory:
+ return w.handleRemoveDirectory(msg)
case *types.FetchMessageHeaders:
return w.handleFetchMessageHeaders(msg)
case *types.FetchMessageBodyPart:
@@ -362,6 +364,16 @@ func (w *Worker) handleCreateDirectory(msg *types.CreateDirectory) error {
return nil
}
+func (w *Worker) handleRemoveDirectory(msg *types.RemoveDirectory) error {
+ dir := w.c.Dir(msg.Directory)
+ if err := os.RemoveAll(string(dir)); err != nil {
+ w.worker.Logger.Printf("could not remove directory %s: %v",
+ msg.Directory, err)
+ return err
+ }
+ return nil
+}
+
func (w *Worker) handleFetchMessageHeaders(
msg *types.FetchMessageHeaders) error {
for _, uid := range msg.Uids {
diff --git a/worker/notmuch/worker.go b/worker/notmuch/worker.go
index f14b7ff0..66d1cd26 100644
--- a/worker/notmuch/worker.go
+++ b/worker/notmuch/worker.go
@@ -131,6 +131,8 @@ func (w *worker) handleMessage(msg types.WorkerMessage) error {
// return w.handleAppendMessage(msg)
// case *types.CreateDirectory:
// return w.handleCreateDirectory(msg)
+ // case *types.RemoveDirectory:
+ // return w.handleRemoveDirectory(msg)
}
return errUnsupported
}
diff --git a/worker/types/messages.go b/worker/types/messages.go
index 374db81f..ab0e5456 100644
--- a/worker/types/messages.go
+++ b/worker/types/messages.go
@@ -92,6 +92,12 @@ type CreateDirectory struct {
Quiet bool
}
+type RemoveDirectory struct {
+ Message
+ Directory string
+ Quiet bool
+}
+
type FetchMessageHeaders struct {
Message
Uids []uint32