From c5daf434604f6b282d95ec6e5220c64988b476d9 Mon Sep 17 00:00:00 2001 From: Adnan Maolood Date: Mon, 4 Jul 2022 15:21:48 -0400 Subject: [PATCH] worker/maildir: implement Maildir++ support See https://www.courier-mta.org/maildir.html#maildircontents Signed-off-by: Adnan Maolood Acked-by: Koni Marti --- doc/aerc-maildir.5.scd | 7 +++++++ worker/maildir/container.go | 33 ++++++++++++++++++++++++++++++--- worker/maildir/worker.go | 13 ++++++++++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/doc/aerc-maildir.5.scd b/doc/aerc-maildir.5.scd index 80bc093..24f2ea2 100644 --- a/doc/aerc-maildir.5.scd +++ b/doc/aerc-maildir.5.scd @@ -44,6 +44,13 @@ The following maildir-specific options are available: source = maildir://~/mail + If your maildir is using the Maildir++ directory layout, you can use the + _maildirpp://_ scheme instead: + + source = maildirpp:///home/me/mail + + source = maildirpp://~/mail + # SEE ALSO *aerc*(1) *aerc-config*(5) *aerc-smtp*(5) *aerc-notmuch*(5) diff --git a/worker/maildir/container.go b/worker/maildir/container.go index 6f7d74a..fb0b190 100644 --- a/worker/maildir/container.go +++ b/worker/maildir/container.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "sort" + "strings" "github.com/emersion/go-maildir" @@ -19,10 +20,11 @@ type Container struct { log *log.Logger uids *uidstore.Store recentUIDS map[uint32]struct{} // used to set the recent flag + maildirpp bool // whether to use Maildir++ directory layout } // NewContainer creates a new container at the specified directory -func NewContainer(dir string, l *log.Logger) (*Container, error) { +func NewContainer(dir string, l *log.Logger, maildirpp bool) (*Container, error) { f, err := os.Open(dir) if err != nil { return nil, err @@ -36,16 +38,19 @@ func NewContainer(dir string, l *log.Logger) (*Container, error) { return nil, fmt.Errorf("Given maildir '%s' not a directory", dir) } return &Container{dir: dir, uids: uidstore.NewStore(), log: l, - recentUIDS: make(map[uint32]struct{})}, nil + recentUIDS: make(map[uint32]struct{}), maildirpp: maildirpp}, nil } // ListFolders returns a list of maildir folders in the container func (c *Container) ListFolders() ([]string, error) { folders := []string{} + if c.maildirpp { + // In Maildir++ layout, INBOX is the root folder + folders = append(folders, "INBOX") + } err := filepath.Walk(c.dir, func(path string, info os.FileInfo, err error) error { if err != nil { return fmt.Errorf("Invalid path '%s': error: %v", path, err) - } if !info.IsDir() { return nil @@ -68,6 +73,21 @@ func (c *Container) ListFolders() ([]string, error) { return nil } + if c.maildirpp { + // In Maildir++ layout, mailboxes are stored in a single directory + // and prefixed with a dot, and subfolders are separated by dots. + if !strings.HasPrefix(dirPath, ".") { + return filepath.SkipDir + } + dirPath = strings.TrimPrefix(dirPath, ".") + dirPath = strings.Replace(dirPath, ".", "/", -1) + folders = append(folders, dirPath) + + // Since all mailboxes are stored in a single directory, don't + // recurse into subdirectories + return filepath.SkipDir + } + folders = append(folders, dirPath) return nil }) @@ -99,6 +119,13 @@ func (c *Container) OpenDirectory(name string) (maildir.Dir, error) { // Dir returns a maildir.Dir with the specified name inside the container func (c *Container) Dir(name string) maildir.Dir { + if c.maildirpp { + // Use Maildir++ layout + if name == "INBOX" { + return maildir.Dir(c.dir) + } + return maildir.Dir(filepath.Join(c.dir, "."+strings.Replace(name, "/", ".", -1))) + } return maildir.Dir(filepath.Join(c.dir, name)) } diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go index 2b55ca6..cf2970e 100644 --- a/worker/maildir/worker.go +++ b/worker/maildir/worker.go @@ -25,6 +25,7 @@ import ( func init() { handlers.RegisterWorkerFactory("maildir", NewWorker) + handlers.RegisterWorkerFactory("maildirpp", NewMaildirppWorker) } var errUnsupported = fmt.Errorf("unsupported command") @@ -37,6 +38,7 @@ type Worker struct { worker *types.Worker watcher *fsnotify.Watcher currentSortCriteria []*types.SortCriterion + maildirpp bool // whether to use Maildir++ directory layout } // NewWorker creates a new maildir worker with the provided worker. @@ -48,6 +50,15 @@ func NewWorker(worker *types.Worker) (types.Backend, error) { return &Worker{worker: worker, watcher: watch}, nil } +// NewMaildirppWorker creates a new Maildir++ worker with the provided worker. +func NewMaildirppWorker(worker *types.Worker) (types.Backend, error) { + watch, err := fsnotify.NewWatcher() + if err != nil { + return nil, fmt.Errorf("could not create file system watcher: %v", err) + } + return &Worker{worker: worker, watcher: watch, maildirpp: true}, nil +} + // Run starts the worker's message handling loop. func (w *Worker) Run() { for { @@ -301,7 +312,7 @@ func (w *Worker) handleConfigure(msg *types.Configure) error { if len(dir) == 0 { return fmt.Errorf("could not resolve maildir from URL '%s'", msg.Config.Source) } - c, err := NewContainer(dir, w.worker.Logger) + c, err := NewContainer(dir, w.worker.Logger, w.maildirpp) if err != nil { w.worker.Logger.Printf("could not configure maildir: %s", dir) return err