mirror of
https://git.sr.ht/~rjarry/aerc
synced 2025-10-27 13:15:09 +01:00
Go has evolved significantly over the years and has introduced some handy helper functions that make the code easier to read. Use helper functions like slices.Contains, map.Copy, and strings.CutPrefix, when appropriate. Signed-off-by: Moritz Poldrack <git@moritz.sh> Acked-by: Robin Jarry <robin@jarry.cc>
177 lines
4 KiB
Go
177 lines
4 KiB
Go
package msg
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.sr.ht/~rjarry/aerc/app"
|
|
"git.sr.ht/~rjarry/aerc/commands"
|
|
"git.sr.ht/~rjarry/aerc/lib/log"
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
"git.sr.ht/~rjarry/aerc/worker/types"
|
|
)
|
|
|
|
const (
|
|
ARCHIVE_FLAT = "flat"
|
|
ARCHIVE_YEAR = "year"
|
|
ARCHIVE_MONTH = "month"
|
|
)
|
|
|
|
var ARCHIVE_TYPES = []string{ARCHIVE_FLAT, ARCHIVE_YEAR, ARCHIVE_MONTH}
|
|
|
|
type Archive struct {
|
|
MultiFileStrategy *types.MultiFileStrategy `opt:"-m" action:"ParseMFS" complete:"CompleteMFS" desc:"Multi-file strategy."`
|
|
Type string `opt:"type" action:"ParseArchiveType" metavar:"flat|year|month" complete:"CompleteType" desc:"Archiving scheme."`
|
|
}
|
|
|
|
func (a *Archive) ParseMFS(arg string) error {
|
|
if arg != "" {
|
|
mfs, ok := types.StrToStrategy[arg]
|
|
if !ok {
|
|
return fmt.Errorf("invalid multi-file strategy %s", arg)
|
|
}
|
|
a.MultiFileStrategy = &mfs
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *Archive) ParseArchiveType(arg string) error {
|
|
if slices.Contains(ARCHIVE_TYPES, arg) {
|
|
a.Type = arg
|
|
return nil
|
|
}
|
|
return fmt.Errorf("invalid archive type")
|
|
}
|
|
|
|
func init() {
|
|
commands.Register(Archive{})
|
|
}
|
|
|
|
func (Archive) Description() string {
|
|
return "Move the selected message to the archive."
|
|
}
|
|
|
|
func (Archive) Context() commands.CommandContext {
|
|
return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
|
|
}
|
|
|
|
func (Archive) Aliases() []string {
|
|
return []string{"archive"}
|
|
}
|
|
|
|
func (Archive) CompleteMFS(arg string) []string {
|
|
return commands.FilterList(types.StrategyStrs(), arg, nil)
|
|
}
|
|
|
|
func (*Archive) CompleteType(arg string) []string {
|
|
return commands.FilterList(ARCHIVE_TYPES, arg, nil)
|
|
}
|
|
|
|
func (a Archive) Execute(args []string) error {
|
|
h := newHelper()
|
|
msgs, err := h.messages()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = archive(msgs, a.MultiFileStrategy, a.Type)
|
|
return err
|
|
}
|
|
|
|
func archive(msgs []*models.MessageInfo, mfs *types.MultiFileStrategy,
|
|
archiveType string,
|
|
) error {
|
|
h := newHelper()
|
|
acct, err := h.account()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store, err := h.store()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var uids []models.UID
|
|
for _, msg := range msgs {
|
|
uids = append(uids, msg.Uid)
|
|
}
|
|
archiveDir := acct.AccountConfig().Archive
|
|
marker := store.Marker()
|
|
marker.ClearVisualMark()
|
|
next := findNextNonDeleted(uids, store)
|
|
|
|
var uidMap map[string][]models.UID
|
|
switch archiveType {
|
|
case ARCHIVE_MONTH:
|
|
uidMap = groupBy(msgs, func(msg *models.MessageInfo) string {
|
|
dir := strings.Join([]string{
|
|
archiveDir,
|
|
fmt.Sprintf("%d", msg.Envelope.Date.Year()),
|
|
fmt.Sprintf("%02d", msg.Envelope.Date.Month()),
|
|
}, app.SelectedAccount().Worker().PathSeparator(),
|
|
)
|
|
return dir
|
|
})
|
|
case ARCHIVE_YEAR:
|
|
uidMap = groupBy(msgs, func(msg *models.MessageInfo) string {
|
|
dir := strings.Join([]string{
|
|
archiveDir,
|
|
fmt.Sprintf("%v", msg.Envelope.Date.Year()),
|
|
}, app.SelectedAccount().Worker().PathSeparator(),
|
|
)
|
|
return dir
|
|
})
|
|
case ARCHIVE_FLAT:
|
|
uidMap = make(map[string][]models.UID)
|
|
uidMap[archiveDir] = commands.UidsFromMessageInfos(msgs)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(uidMap))
|
|
success := true
|
|
|
|
for dir, uids := range uidMap {
|
|
store.Move(uids, dir, true, mfs, func(
|
|
msg types.WorkerMessage,
|
|
) {
|
|
switch msg := msg.(type) {
|
|
case *types.Done:
|
|
wg.Done()
|
|
case *types.Error:
|
|
app.PushError(msg.Error.Error())
|
|
success = false
|
|
wg.Done()
|
|
marker.Remark()
|
|
}
|
|
})
|
|
}
|
|
// we need to do that in the background, else we block the main thread
|
|
go func() {
|
|
defer log.PanicHandler()
|
|
|
|
wg.Wait()
|
|
if success {
|
|
var s string
|
|
if len(uids) > 1 {
|
|
s = "%d messages archived to %s"
|
|
} else {
|
|
s = "%d message archived to %s"
|
|
}
|
|
app.PushStatus(fmt.Sprintf(s, len(uids), archiveDir), 10*time.Second)
|
|
handleDone(acct, next, store)
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func groupBy(msgs []*models.MessageInfo,
|
|
grouper func(*models.MessageInfo) string,
|
|
) map[string][]models.UID {
|
|
m := make(map[string][]models.UID)
|
|
for _, msg := range msgs {
|
|
group := grouper(msg)
|
|
m[group] = append(m[group], msg.Uid)
|
|
}
|
|
return m
|
|
}
|