aboutsummaryrefslogtreecommitdiff
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)
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