aboutsummaryrefslogtreecommitdiffstats
path: root/worker/lib
diff options
context:
space:
mode:
authorJulian Pidancet <julian.pidancet@oracle.com>2022-10-26 22:29:02 +0200
committerRobin Jarry <robin@jarry.cc>2022-10-27 21:39:41 +0200
commitf021bfd1c7b7885919cb71884727fd8dfdf8eba7 (patch)
tree0fe6ce6d038630d0d244b878d2eaf276ee90d0b4 /worker/lib
parent19d16420de3748cdcae2dc9d1a123a1f6fb2425b (diff)
downloadaerc-f021bfd1c7b7885919cb71884727fd8dfdf8eba7.tar.gz
maildir: move common maildir code out of worker
This change moves code that could be common to both notmuch and maildir workers in worker/lib. Signed-off-by: Julian Pidancet <julian.pidancet@oracle.com> Acked-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
Diffstat (limited to 'worker/lib')
-rw-r--r--worker/lib/maildir.go153
1 files changed, 153 insertions, 0 deletions
diff --git a/worker/lib/maildir.go b/worker/lib/maildir.go
new file mode 100644
index 00000000..f6199f9c
--- /dev/null
+++ b/worker/lib/maildir.go
@@ -0,0 +1,153 @@
+package lib
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "git.sr.ht/~rjarry/aerc/models"
+ "github.com/emersion/go-maildir"
+)
+
+type MaildirStore struct {
+ root string
+ maildirpp bool // whether to use Maildir++ directory layout
+}
+
+func NewMaildirStore(root string, maildirpp bool) (*MaildirStore, error) {
+ f, err := os.Open(root)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ s, err := f.Stat()
+ if err != nil {
+ return nil, err
+ }
+ if !s.IsDir() {
+ return nil, fmt.Errorf("Given maildir '%s' not a directory", root)
+ }
+ return &MaildirStore{
+ root: root, maildirpp: maildirpp,
+ }, nil
+}
+
+// ListFolders returns a list of maildir folders in the container
+func (s *MaildirStore) ListFolders() ([]string, error) {
+ folders := []string{}
+ if s.maildirpp {
+ // In Maildir++ layout, INBOX is the root folder
+ folders = append(folders, "INBOX")
+ }
+ err := filepath.Walk(s.root, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return fmt.Errorf("Invalid path '%s': error: %w", path, err)
+ }
+ if !info.IsDir() {
+ return nil
+ }
+
+ // Skip maildir's default directories
+ n := info.Name()
+ if n == "new" || n == "tmp" || n == "cur" {
+ return filepath.SkipDir
+ }
+
+ // Get the relative path from the parent directory
+ dirPath, err := filepath.Rel(s.root, path)
+ if err != nil {
+ return err
+ }
+
+ // Skip the parent directory
+ if dirPath == "." {
+ return nil
+ }
+
+ // Drop dirs that lack {new,tmp,cur} subdirs
+ for _, sub := range []string{"new", "tmp", "cur"} {
+ if _, err := os.Stat(filepath.Join(path, sub)); os.IsNotExist(err) {
+ return nil
+ }
+ }
+
+ if s.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.ReplaceAll(dirPath, ".", "/")
+ 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
+ })
+ return folders, err
+}
+
+// Folder returns a maildir.Dir with the specified name inside the Store
+func (s *MaildirStore) Dir(name string) maildir.Dir {
+ if s.maildirpp {
+ // Use Maildir++ layout
+ if name == "INBOX" {
+ return maildir.Dir(s.root)
+ }
+ return maildir.Dir(filepath.Join(s.root, "."+strings.ReplaceAll(name, "/", ".")))
+ }
+ return maildir.Dir(filepath.Join(s.root, name))
+}
+
+// uidReg matches filename encoded UIDs in maildirs synched with mbsync or
+// OfflineIMAP
+var uidReg = regexp.MustCompile(`,U=\d+`)
+
+func StripUIDFromMessageFilename(basename string) string {
+ return uidReg.ReplaceAllString(basename, "")
+}
+
+var MaildirToFlag = map[maildir.Flag]models.Flag{
+ maildir.FlagReplied: models.AnsweredFlag,
+ maildir.FlagSeen: models.SeenFlag,
+ maildir.FlagTrashed: models.DeletedFlag,
+ maildir.FlagFlagged: models.FlaggedFlag,
+ // maildir.FlagDraft Flag = 'D'
+ // maildir.FlagPassed Flag = 'P'
+}
+
+var FlagToMaildir = map[models.Flag]maildir.Flag{
+ models.AnsweredFlag: maildir.FlagReplied,
+ models.SeenFlag: maildir.FlagSeen,
+ models.DeletedFlag: maildir.FlagTrashed,
+ models.FlaggedFlag: maildir.FlagFlagged,
+ // maildir.FlagDraft Flag = 'D'
+ // maildir.FlagPassed Flag = 'P'
+}
+
+func FromMaildirFlags(maildirFlags []maildir.Flag) []models.Flag {
+ var flags []models.Flag
+ for _, maildirFlag := range maildirFlags {
+ if flag, ok := MaildirToFlag[maildirFlag]; ok {
+ flags = append(flags, flag)
+ }
+ }
+ return flags
+}
+
+func ToMaildirFlags(flags []models.Flag) []maildir.Flag {
+ var maildirFlags []maildir.Flag
+ for _, flag := range flags {
+ if maildirFlag, ok := FlagToMaildir[flag]; ok {
+ maildirFlags = append(maildirFlags, maildirFlag)
+ }
+ }
+ return maildirFlags
+}