1b8b6e218c
Add the initial implementation of a backend for Maildir accounts. Much of the functionality required is implemented in the go-message and go-maildir libraries, so we use them as much as possible. The maildir worker hooks into a new maildir:// URL scheme in the accounts.conf file which points to a container of several maildir directories. From there, the OpenDirectory, FetchDirectoryContents, etc messages work on subdirectories. This is implemented as a Container struct which handles mapping between the symbolic email folder names and UIDs to the concrete directories and file names.
106 lines
2.6 KiB
Go
106 lines
2.6 KiB
Go
package maildir
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"path/filepath"
|
|
"sort"
|
|
|
|
"github.com/emersion/go-maildir"
|
|
|
|
"git.sr.ht/~sircmpwn/aerc/lib/uidstore"
|
|
)
|
|
|
|
// A Container is a directory which contains other directories which adhere to
|
|
// the Maildir spec
|
|
type Container struct {
|
|
dir string
|
|
log *log.Logger
|
|
uids *uidstore.Store
|
|
}
|
|
|
|
// NewContainer creates a new container at the specified directory
|
|
// TODO: return an error if the provided directory is not accessible
|
|
func NewContainer(dir string, l *log.Logger) *Container {
|
|
return &Container{dir: dir, uids: uidstore.NewStore(), log: l}
|
|
}
|
|
|
|
// ListFolders returns a list of maildir folders in the container
|
|
func (c *Container) ListFolders() ([]string, error) {
|
|
files, err := ioutil.ReadDir(c.dir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading folders: %v", err)
|
|
}
|
|
dirnames := []string{}
|
|
for _, f := range files {
|
|
if f.IsDir() {
|
|
dirnames = append(dirnames, f.Name())
|
|
}
|
|
}
|
|
return dirnames, nil
|
|
}
|
|
|
|
// 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)
|
|
keys, err := dir.Unseen()
|
|
if err != nil {
|
|
return dir, err
|
|
}
|
|
for _, key := range keys {
|
|
c.uids.GetOrInsert(key)
|
|
}
|
|
return dir, nil
|
|
}
|
|
|
|
// Dir returns a maildir.Dir with the specified name inside the container
|
|
func (c *Container) Dir(name string) maildir.Dir {
|
|
return maildir.Dir(filepath.Join(c.dir, name))
|
|
}
|
|
|
|
// UIDs fetches the unique message identifiers for the maildir
|
|
func (c *Container) UIDs(d maildir.Dir) ([]uint32, error) {
|
|
keys, err := d.Keys()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get keys for %s: %v", d, err)
|
|
}
|
|
sort.Strings(keys)
|
|
var uids []uint32
|
|
for _, key := range keys {
|
|
uids = append(uids, c.uids.GetOrInsert(key))
|
|
}
|
|
return uids, nil
|
|
}
|
|
|
|
// Message returns a Message struct for the given UID and maildir
|
|
func (c *Container) Message(d maildir.Dir, uid uint32) (*Message, error) {
|
|
if key, ok := c.uids.GetKey(uid); ok {
|
|
return &Message{
|
|
dir: d,
|
|
uid: uid,
|
|
key: key,
|
|
}, nil
|
|
}
|
|
return nil, fmt.Errorf("could not find message with uid %d in maildir %s",
|
|
uid, d)
|
|
}
|
|
|
|
// DeleteAll deletes a set of messages by UID and returns the subset of UIDs
|
|
// which were successfully deleted, stopping upon the first error.
|
|
func (c *Container) DeleteAll(d maildir.Dir, uids []uint32) ([]uint32, error) {
|
|
var success []uint32
|
|
for _, uid := range uids {
|
|
msg, err := c.Message(d, uid)
|
|
if err != nil {
|
|
return success, err
|
|
}
|
|
if err := msg.Remove(); err != nil {
|
|
return success, err
|
|
}
|
|
success = append(success, uid)
|
|
}
|
|
return success, nil
|
|
}
|