aboutsummaryrefslogtreecommitdiffstats
path: root/worker
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
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')
-rw-r--r--worker/lib/maildir.go153
-rw-r--r--worker/maildir/container.go100
-rw-r--r--worker/maildir/message.go40
-rw-r--r--worker/maildir/worker.go22
4 files changed, 172 insertions, 143 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
+}
diff --git a/worker/maildir/container.go b/worker/maildir/container.go
index 4181648b..f9fb507b 100644
--- a/worker/maildir/container.go
+++ b/worker/maildir/container.go
@@ -4,108 +4,34 @@ import (
"fmt"
"os"
"path/filepath"
- "regexp"
"sort"
- "strings"
"github.com/emersion/go-maildir"
"git.sr.ht/~rjarry/aerc/lib/uidstore"
+ "git.sr.ht/~rjarry/aerc/worker/lib"
)
-// uidReg matches filename encoded UIDs in maildirs synched with mbsync or
-// OfflineIMAP
-var uidReg = regexp.MustCompile(`,U=\d+`)
-
// A Container is a directory which contains other directories which adhere to
// the Maildir spec
type Container struct {
- dir string
+ Store *lib.MaildirStore
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, maildirpp bool) (*Container, error) {
- f, err := os.Open(dir)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- s, err := f.Stat()
+ store, err := lib.NewMaildirStore(dir, maildirpp)
if err != nil {
return nil, err
}
- if !s.IsDir() {
- return nil, fmt.Errorf("Given maildir '%s' not a directory", dir)
- }
return &Container{
- dir: dir, uids: uidstore.NewStore(),
- recentUIDS: make(map[uint32]struct{}), maildirpp: maildirpp,
+ Store: store, uids: uidstore.NewStore(),
+ recentUIDS: make(map[uint32]struct{}),
}, 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: %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(c.dir, 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 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.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
-}
-
// SyncNewMail adds emails from new to cur, tracking them
func (c *Container) SyncNewMail(dir maildir.Dir) error {
keys, err := dir.Unseen()
@@ -122,25 +48,13 @@ func (c *Container) SyncNewMail(dir maildir.Dir) error {
// OpenDirectory opens an existing maildir in the container by name, moves new
// messages into cur, and registers the new keys in the UIDStore.
func (c *Container) OpenDirectory(name string) (maildir.Dir, error) {
- dir := c.Dir(name)
+ dir := c.Store.Dir(name)
if err := c.SyncNewMail(dir); err != nil {
return dir, err
}
return dir, nil
}
-// 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.ReplaceAll(name, "/", ".")))
- }
- return maildir.Dir(filepath.Join(c.dir, name))
-}
-
// IsRecent returns if a uid has the Recent flag set
func (c *Container) IsRecent(uid uint32) bool {
_, ok := c.recentUIDS[uid]
@@ -239,7 +153,7 @@ func (c *Container) moveMessage(dest maildir.Dir, src maildir.Dir, uid uint32) e
return fmt.Errorf("could not find path for message id %d", uid)
}
// Remove encoded UID information from the key to prevent sync issues
- name := uidReg.ReplaceAllString(filepath.Base(path), "")
+ name := lib.StripUIDFromMessageFilename(filepath.Base(path))
destPath := filepath.Join(string(dest), "cur", name)
return os.Rename(path, destPath)
}
diff --git a/worker/maildir/message.go b/worker/maildir/message.go
index 9cd13319..3c8ce9ef 100644
--- a/worker/maildir/message.go
+++ b/worker/maildir/message.go
@@ -33,7 +33,7 @@ func (m Message) ModelFlags() ([]models.Flag, error) {
if err != nil {
return nil, err
}
- return translateMaildirFlags(flags), nil
+ return lib.FromMaildirFlags(flags), nil
}
// SetFlags replaces the message's flags with a new set.
@@ -91,44 +91,6 @@ func (m Message) NewBodyPartReader(requestedParts []int) (io.Reader, error) {
return lib.FetchEntityPartReader(msg, requestedParts)
}
-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 translateMaildirFlags(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 translateFlags(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
-}
-
func (m Message) UID() uint32 {
return m.uid
}
diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go
index ddb1bf68..d2bb6464 100644
--- a/worker/maildir/worker.go
+++ b/worker/maildir/worker.go
@@ -182,7 +182,7 @@ func (w *Worker) getDirectoryInfo(name string) *models.DirectoryInfo {
},
}
- dir := w.c.Dir(name)
+ dir := w.c.Store.Dir(name)
var keyFlags map[string][]maildir.Flag
files, err := dirFiles(string(dir))
if err == nil {
@@ -329,7 +329,7 @@ func (w *Worker) handleListDirectories(msg *types.ListDirectories) error {
if w.c == nil {
return errors.New("Incorrect maildir directory")
}
- dirs, err := w.c.ListFolders()
+ dirs, err := w.c.Store.ListFolders()
if err != nil {
logging.Errorf("failed listing directories: %v", err)
return err
@@ -453,7 +453,7 @@ func (w *Worker) sort(uids []uint32, criteria []*types.SortCriterion) ([]uint32,
}
func (w *Worker) handleCreateDirectory(msg *types.CreateDirectory) error {
- dir := w.c.Dir(msg.Directory)
+ dir := w.c.Store.Dir(msg.Directory)
if err := dir.Init(); err != nil {
logging.Errorf("could not create directory %s: %v",
msg.Directory, err)
@@ -463,7 +463,7 @@ func (w *Worker) handleCreateDirectory(msg *types.CreateDirectory) error {
}
func (w *Worker) handleRemoveDirectory(msg *types.RemoveDirectory) error {
- dir := w.c.Dir(msg.Directory)
+ dir := w.c.Store.Dir(msg.Directory)
if err := os.RemoveAll(string(dir)); err != nil {
logging.Errorf("could not remove directory %s: %v",
msg.Directory, err)
@@ -604,7 +604,7 @@ func (w *Worker) handleFlagMessages(msg *types.FlagMessages) error {
w.err(msg, err)
continue
}
- flag := flagToMaildir[msg.Flag]
+ flag := lib.FlagToMaildir[msg.Flag]
if err := m.SetOneFlag(flag, msg.Enable); err != nil {
logging.Errorf("could change flag %v to %v on message: %v", flag, msg.Enable, err)
w.err(msg, err)
@@ -631,7 +631,7 @@ func (w *Worker) handleFlagMessages(msg *types.FlagMessages) error {
}
func (w *Worker) handleCopyMessages(msg *types.CopyMessages) error {
- dest := w.c.Dir(msg.Destination)
+ dest := w.c.Store.Dir(msg.Destination)
err := w.c.CopyAll(dest, *w.selected, msg.Uids)
if err != nil {
return err
@@ -645,7 +645,7 @@ func (w *Worker) handleCopyMessages(msg *types.CopyMessages) error {
}
func (w *Worker) handleMoveMessages(msg *types.MoveMessages) error {
- dest := w.c.Dir(msg.Destination)
+ dest := w.c.Store.Dir(msg.Destination)
moved, err := w.c.MoveAll(dest, *w.selected, msg.Uids)
destInfo := w.getDirectoryInfo(msg.Destination)
w.worker.PostMessage(&types.DirectoryInfo{
@@ -660,8 +660,8 @@ func (w *Worker) handleMoveMessages(msg *types.MoveMessages) error {
func (w *Worker) handleAppendMessage(msg *types.AppendMessage) error {
// since we are the "master" maildir process, we can modify the maildir directly
- dest := w.c.Dir(msg.Destination)
- _, writer, err := dest.Create(translateFlags(msg.Flags))
+ dest := w.c.Store.Dir(msg.Destination)
+ _, writer, err := dest.Create(lib.ToMaildirFlags(msg.Flags))
if err != nil {
logging.Errorf("could not create message at %s: %v", msg.Destination, err)
return err
@@ -733,12 +733,12 @@ func (w *Worker) handleCheckMail(msg *types.CheckMail) {
if err != nil {
w.err(msg, fmt.Errorf("checkmail: error running command: %w", err))
} else {
- dirs, err := w.c.ListFolders()
+ dirs, err := w.c.Store.ListFolders()
if err != nil {
w.err(msg, fmt.Errorf("failed listing directories: %w", err))
}
for _, name := range dirs {
- err := w.c.SyncNewMail(w.c.Dir(name))
+ err := w.c.SyncNewMail(w.c.Store.Dir(name))
if err != nil {
w.err(msg, fmt.Errorf("could not sync new mail: %w", err))
}