summary refs log blame commit diff stats
path: root/worker/maildir/container.go
blob: f01518103bf9fc3c77502be07f4e9b8d0d85ee75 (plain) (tree)
1
2
3
4
5



               
            




































































































                                                                                  
























                                                                                    
                                                                           



                                                 
                                                                                





                               
package maildir

import (
	"fmt"
	"io"
	"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
}

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)
	}

	f, err := src.Open(key)
	if err != nil {
		return fmt.Errorf("could not open source message: %v", err)
	}

	del, err := dest.NewDelivery()
	if err != nil {
		return fmt.Errorf("could not initialize delivery: %v", err)
	}
	defer del.Close()

	if _, err = io.Copy(del, f); err != nil {
		return fmt.Errorf("could not copy message to delivery: %v", err)
	}

	// TODO: preserve flags

	return nil
}