summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2019-06-08 19:41:56 +0200
committerDrew DeVault <sir@cmpwn.com>2019-06-09 11:33:50 -0400
commitacfe7d7625192bc856d5d696f741e35ce38cab25 (patch)
tree64f76ab6927cbb25be95c0ff2e65e063791593a6
parent35f57321f8d53dba2c1a2480aaa5860333e1c269 (diff)
downloadaerc-acfe7d7625192bc856d5d696f741e35ce38cab25.tar.gz
Add archive command
Adds an archive command that moves the current message into the folder
specified in the account config entry.

Supports three layouts at this point:

- flat: puts all messages next to each other
- year: creates a folder per year
- month: same as above, plus folders per month

This also adds a "-p" argument to "cp" and "mv" that works like
"--parents" on mkdir(1). We use this to auto-create the directories
for the archive layout.
-rw-r--r--commands/msg/archive.go62
-rw-r--r--commands/msg/copy.go23
-rw-r--r--commands/msg/move.go23
-rw-r--r--config/binds.conf2
-rw-r--r--config/config.go4
-rw-r--r--doc/aerc-config.5.scd5
-rw-r--r--doc/aerc.1.scd9
-rw-r--r--lib/msgstore.go17
-rw-r--r--widgets/account.go2
-rw-r--r--worker/imap/create.go22
-rw-r--r--worker/imap/worker.go2
-rw-r--r--worker/types/messages.go5
12 files changed, 168 insertions, 8 deletions
diff --git a/commands/msg/archive.go b/commands/msg/archive.go
new file mode 100644
index 0000000..11752f7
--- /dev/null
+++ b/commands/msg/archive.go
@@ -0,0 +1,62 @@
+package msg
+
+import (
+	"errors"
+	"fmt"
+	"path"
+	"time"
+
+	"github.com/gdamore/tcell"
+
+	"git.sr.ht/~sircmpwn/aerc/widgets"
+	"git.sr.ht/~sircmpwn/aerc/worker/types"
+)
+
+const (
+	ARCHIVE_FLAT  = "flat"
+	ARCHIVE_YEAR  = "year"
+	ARCHIVE_MONTH = "month"
+)
+
+func init() {
+	register("archive", Archive)
+}
+
+func Archive(aerc *widgets.Aerc, args []string) error {
+	if len(args) != 2 {
+		return errors.New("Usage: archive <flat|year|month>")
+	}
+	acct := aerc.SelectedAccount()
+	if acct == nil {
+		return errors.New("No account selected")
+	}
+	msg := acct.Messages().Selected()
+	store := acct.Messages().Store()
+	archiveDir := acct.AccountConfig().Archive
+	acct.Messages().Next()
+
+	switch args[1] {
+	case ARCHIVE_MONTH:
+		archiveDir = path.Join(archiveDir,
+			fmt.Sprintf("%d", msg.Envelope.Date.Year()),
+			fmt.Sprintf("%02d", msg.Envelope.Date.Month()))
+	case ARCHIVE_YEAR:
+		archiveDir = path.Join(archiveDir, fmt.Sprintf("%v",
+			msg.Envelope.Date.Year()))
+	case ARCHIVE_FLAT:
+		// deliberately left blank
+	}
+
+	store.Move([]uint32{msg.Uid}, archiveDir, true, func(
+		msg types.WorkerMessage) {
+
+		switch msg := msg.(type) {
+		case *types.Done:
+			aerc.PushStatus("Messages archived.", 10*time.Second)
+		case *types.Error:
+			aerc.PushStatus(" "+msg.Error.Error(), 10*time.Second).
+				Color(tcell.ColorDefault, tcell.ColorRed)
+		}
+	})
+	return nil
+}
diff --git a/commands/msg/copy.go b/commands/msg/copy.go
index 57c93a3..0d9836b 100644
--- a/commands/msg/copy.go
+++ b/commands/msg/copy.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"time"
 
+	"git.sr.ht/~sircmpwn/getopt"
 	"github.com/gdamore/tcell"
 
 	"git.sr.ht/~sircmpwn/aerc/widgets"
@@ -16,9 +17,23 @@ func init() {
 }
 
 func Copy(aerc *widgets.Aerc, args []string) error {
-	if len(args) != 2 {
-		return errors.New("Usage: mv <folder>")
+	opts, optind, err := getopt.Getopts(args[1:], "p")
+	if err != nil {
+		return err
 	}
+	if optind != len(args)-2 {
+		return errors.New("Usage: cp [-p] <folder>")
+	}
+	var (
+		createParents bool
+	)
+	for _, opt := range opts {
+		switch opt.Option {
+		case 'p':
+			createParents = true
+		}
+	}
+
 	widget := aerc.SelectedTab().(widgets.ProvidesMessage)
 	acct := widget.SelectedAccount()
 	if acct == nil {
@@ -26,7 +41,9 @@ func Copy(aerc *widgets.Aerc, args []string) error {
 	}
 	msg := widget.SelectedMessage()
 	store := widget.Store()
-	store.Copy([]uint32{msg.Uid}, args[1], func(msg types.WorkerMessage) {
+	store.Copy([]uint32{msg.Uid}, args[optind+1], createParents, func(
+		msg types.WorkerMessage) {
+
 		switch msg := msg.(type) {
 		case *types.Done:
 			aerc.PushStatus("Messages copied.", 10*time.Second)
diff --git a/commands/msg/move.go b/commands/msg/move.go
index 1224efa..7742ffb 100644
--- a/commands/msg/move.go
+++ b/commands/msg/move.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"time"
 
+	"git.sr.ht/~sircmpwn/getopt"
 	"github.com/gdamore/tcell"
 
 	"git.sr.ht/~sircmpwn/aerc/widgets"
@@ -16,9 +17,23 @@ func init() {
 }
 
 func Move(aerc *widgets.Aerc, args []string) error {
-	if len(args) != 2 {
-		return errors.New("Usage: mv <folder>")
+	opts, optind, err := getopt.Getopts(args[1:], "p")
+	if err != nil {
+		return err
 	}
+	if optind != len(args)-2 {
+		return errors.New("Usage: mv [-p] <folder>")
+	}
+	var (
+		createParents bool
+	)
+	for _, opt := range opts {
+		switch opt.Option {
+		case 'p':
+			createParents = true
+		}
+	}
+
 	widget := aerc.SelectedTab().(widgets.ProvidesMessage)
 	acct := widget.SelectedAccount()
 	if acct == nil {
@@ -31,7 +46,9 @@ func Move(aerc *widgets.Aerc, args []string) error {
 		aerc.RemoveTab(widget)
 	}
 	acct.Messages().Next()
-	store.Move([]uint32{msg.Uid}, args[1], func(msg types.WorkerMessage) {
+	store.Move([]uint32{msg.Uid}, args[optind+1], createParents, func(
+		msg types.WorkerMessage) {
+
 		switch msg := msg.(type) {
 		case *types.Done:
 			aerc.PushStatus("Messages moved.", 10*time.Second)
diff --git a/config/binds.conf b/config/binds.conf
index 2c0476a..3800b7b 100644
--- a/config/binds.conf
+++ b/config/binds.conf
@@ -28,6 +28,7 @@ K = :prev-folder<Enter>
 <Enter> = :view<Enter>
 d = :confirm 'Really delete this message?' ':delete-message<Enter>'<Enter>
 D = :delete<Enter>
+A = :archive flat<Enter>
 
 C = :compose<Enter>
 
@@ -54,6 +55,7 @@ Rq = :reply -aq<Enter>
 <C-k> = :prev-part<Enter>
 <C-j> = :next-part<Enter>
 S = :save<space>
+A = :archive flat<Enter>
 
 [compose]
 # Keybindings used when the embedded terminal is not selected in the compose
diff --git a/config/config.go b/config/config.go
index 3ef587b..8e669f7 100644
--- a/config/config.go
+++ b/config/config.go
@@ -34,6 +34,7 @@ const (
 )
 
 type AccountConfig struct {
+	Archive         string
 	CopyTo          string
 	Default         string
 	From            string
@@ -115,6 +116,7 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
 		}
 		sec := file.Section(_sec)
 		account := AccountConfig{
+			Archive: "Archive",
 			Default: "INBOX",
 			Name:    _sec,
 			Params:  make(map[string]string),
@@ -137,6 +139,8 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
 				account.From = val
 			} else if key == "copy-to" {
 				account.CopyTo = val
+			} else if key == "archive" {
+				account.Archive = val
 			} else if key != "name" {
 				account.Params[key] = val
 			}
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index e002764..caf971d 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -106,6 +106,11 @@ Note that many of these configuration options are written for you, such as
 *source* and *outgoing*, when you run the account configuration wizard
 (*:new-account*).
 
+*archive*
+	Specifies a folder to use as the destination of the *:archive* command.
+
+	Default: Archive
+
 *copy-to*
 	Specifies a folder to copy sent mails to, usually "Sent".
 
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index ca835e5..eab0cb3 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -43,6 +43,15 @@ These commands work in any context.
 
 ## MESSAGE LIST COMMANDS
 
+*archive* <scheme>
+	Moves the selected message to the archive. The available schemes are:
+
+	*flat*: No special structure, all messages in the archive directory
+
+	*year*: Messages are stored in folders per year
+
+	*month*: Messages are stored in folders per year and subfolders per month
+
 *cf* <folder>
 	Change the folder shown in the message list.
 
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 6ab7fc2..900ec16 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -218,20 +218,27 @@ func (store *MessageStore) Delete(uids []uint32,
 	store.update()
 }
 
-func (store *MessageStore) Copy(uids []uint32, dest string,
+func (store *MessageStore) Copy(uids []uint32, dest string, createDest bool,
 	cb func(msg types.WorkerMessage)) {
+
 	var set imap.SeqSet
 	for _, uid := range uids {
 		set.AddNum(uid)
 	}
 
+	if createDest {
+		store.worker.PostAction(&types.CreateDirectory{
+			Directory: dest,
+		}, cb)
+	}
+
 	store.worker.PostAction(&types.CopyMessages{
 		Destination: dest,
 		Uids:        set,
 	}, cb)
 }
 
-func (store *MessageStore) Move(uids []uint32, dest string,
+func (store *MessageStore) Move(uids []uint32, dest string, createDest bool,
 	cb func(msg types.WorkerMessage)) {
 
 	var set imap.SeqSet
@@ -240,6 +247,12 @@ func (store *MessageStore) Move(uids []uint32, dest string,
 		store.Deleted[uid] = nil
 	}
 
+	if createDest {
+		store.worker.PostAction(&types.CreateDirectory{
+			Directory: dest,
+		}, cb)
+	}
+
 	store.worker.PostAction(&types.CopyMessages{
 		Destination: dest,
 		Uids:        set,
diff --git a/widgets/account.go b/widgets/account.go
index 1921dbd..72874b0 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -185,6 +185,8 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
 			} else {
 				acct.msglist.SetStore(nil)
 			}
+		case *types.CreateDirectory:
+			acct.dirlist.UpdateList(nil)
 		}
 	case *types.DirectoryInfo:
 		if store, ok := acct.msgStores[msg.Name]; ok {
diff --git a/worker/imap/create.go b/worker/imap/create.go
new file mode 100644
index 0000000..3cc71c5
--- /dev/null
+++ b/worker/imap/create.go
@@ -0,0 +1,22 @@
+package imap
+
+import (
+	"strings"
+
+	"git.sr.ht/~sircmpwn/aerc/worker/types"
+)
+
+func (imapw *IMAPWorker) handleCreateDirectory(msg *types.CreateDirectory) {
+	if err := imapw.client.Create(msg.Directory); err != nil {
+		if strings.HasPrefix(err.Error(), "Mailbox already exists") {
+			// ignore "already exists" error
+			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 125fba8..f71a950 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -127,6 +127,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
 		w.handleOpenDirectory(msg)
 	case *types.FetchDirectoryContents:
 		w.handleFetchDirectoryContents(msg)
+	case *types.CreateDirectory:
+		w.handleCreateDirectory(msg)
 	case *types.FetchMessageHeaders:
 		w.handleFetchMessageHeaders(msg)
 	case *types.FetchMessageBodyPart:
diff --git a/worker/types/messages.go b/worker/types/messages.go
index 29d3d9f..0d81c4f 100644
--- a/worker/types/messages.go
+++ b/worker/types/messages.go
@@ -82,6 +82,11 @@ type FetchDirectoryContents struct {
 	Message
 }
 
+type CreateDirectory struct {
+	Message
+	Directory string
+}
+
 type FetchMessageHeaders struct {
 	Message
 	Uids imap.SeqSet