package maildir import ( "fmt" "io/ioutil" "log" "path/filepath" "sort" "github.com/emersion/go-maildir" "git.sr.ht/~sircmpwn/aerc/lib/uidstore" "git.sr.ht/~sircmpwn/aerc/worker/types" ) // 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 } func (c *Container) CopyAll( dest maildir.Dir, src maildir.Dir, uids []uint32) error { for _, uid := range uids { if err := c.copyMessage(dest, src, uid); err != nil { return fmt.Errorf("could not copy message %d: %v", uid, err) } } return nil } func (c *Container) copyMessage( dest maildir.Dir, src maildir.Dir, uid uint32) error { key, ok := c.uids.GetKey(uid) if !ok { return fmt.Errorf("could not find key for message id %d", uid) } _, err := src.Copy(dest, key) return err } func (c *Container) ScanThreads(d maildir.Dir) ([]*types.Thread, error) { uids, err := c.UIDs(d) if err != nil { return nil, err } // a map from message-id to the Thread that represents it threads := make(map[string]*types.Thread) for _, uid := range uids { m, err := c.Message(d, uid) if err != nil { return nil, err } hs, err := m.Headers() if err != nil { return nil, err } msgID, err := hs.Text("message-id") if err != nil { return nil, err } irt, err := hs.Text("in-reply-to") if err != nil { return nil, err } t := &types.Thread{Uid: uid} threads[msgID] = t if p, ok := threads[irt]; ok { p.Children = append(p.Children, t) t.Parent = p } } var threadSlice []*types.Thread for _, t := range threads { if t.Parent == nil { threadSlice = append(threadSlice, t) } } return threadSlice, nil }