aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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 00000000..11752f7d
--- /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 57c93a33..0d9836ba 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 1224efac..7742ffb4 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 2c0476ad..3800b7bb 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 3ef587b0..8e669f78 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 e002764e..caf971de 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 ca835e55..eab0cb38 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 6ab7fc26..900ec166 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 1921dbd6..72874b09 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 00000000..3cc71c59
--- /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 125fba88..f71a950d 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 29d3d9f7..0d81c4f5 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