aboutsummaryrefslogtreecommitdiffstats
path: root/worker
diff options
context:
space:
mode:
Diffstat (limited to 'worker')
-rw-r--r--worker/lib/search.go254
-rw-r--r--worker/mbox/create.go60
-rw-r--r--worker/mbox/io.go50
-rw-r--r--worker/mbox/models.go203
-rw-r--r--worker/mbox/worker.go379
-rw-r--r--worker/worker_enabled.go8
6 files changed, 952 insertions, 2 deletions
diff --git a/worker/lib/search.go b/worker/lib/search.go
new file mode 100644
index 00000000..c7d3bee7
--- /dev/null
+++ b/worker/lib/search.go
@@ -0,0 +1,254 @@
+package lib
+
+import (
+ "io/ioutil"
+ "net/textproto"
+ "strings"
+ "unicode"
+
+ "git.sr.ht/~sircmpwn/getopt"
+
+ "git.sr.ht/~rjarry/aerc/models"
+)
+
+type searchCriteria struct {
+ Header textproto.MIMEHeader
+ Body []string
+ Text []string
+
+ WithFlags []models.Flag
+ WithoutFlags []models.Flag
+}
+
+func GetSearchCriteria(args []string) (*searchCriteria, error) {
+ criteria := &searchCriteria{Header: make(textproto.MIMEHeader)}
+
+ opts, optind, err := getopt.Getopts(args, "rux:X:bat:H:f:c:")
+ if err != nil {
+ return nil, err
+ }
+ body := false
+ text := false
+ for _, opt := range opts {
+ switch opt.Option {
+ case 'r':
+ criteria.WithFlags = append(criteria.WithFlags, models.SeenFlag)
+ case 'u':
+ criteria.WithoutFlags = append(criteria.WithoutFlags, models.SeenFlag)
+ case 'x':
+ criteria.WithFlags = append(criteria.WithFlags, getParsedFlag(opt.Value))
+ case 'X':
+ criteria.WithoutFlags = append(criteria.WithoutFlags, getParsedFlag(opt.Value))
+ case 'H':
+ // TODO
+ case 'f':
+ criteria.Header.Add("From", opt.Value)
+ case 't':
+ criteria.Header.Add("To", opt.Value)
+ case 'c':
+ criteria.Header.Add("Cc", opt.Value)
+ case 'b':
+ body = true
+ case 'a':
+ text = true
+ }
+ }
+ if text {
+ criteria.Text = args[optind:]
+ } else if body {
+ criteria.Body = args[optind:]
+ } else {
+ for _, arg := range args[optind:] {
+ criteria.Header.Add("Subject", arg)
+ }
+ }
+ return criteria, nil
+}
+
+func getParsedFlag(name string) models.Flag {
+ var f models.Flag
+ switch strings.ToLower(name) {
+ case "seen":
+ f = models.SeenFlag
+ case "answered":
+ f = models.AnsweredFlag
+ case "flagged":
+ f = models.FlaggedFlag
+ }
+ return f
+}
+
+func Search(messages []RawMessage, criteria *searchCriteria) ([]uint32, error) {
+ requiredParts := getRequiredParts(criteria)
+
+ matchedUids := []uint32{}
+ for _, m := range messages {
+ success, err := searchMessage(m, criteria, requiredParts)
+ if err != nil {
+ return nil, err
+ } else if success {
+ matchedUids = append(matchedUids, m.UID())
+ }
+ }
+
+ return matchedUids, nil
+}
+
+// searchMessage executes the search criteria for the given RawMessage,
+// returns true if search succeeded
+func searchMessage(message RawMessage, criteria *searchCriteria,
+ parts MsgParts) (bool, error) {
+
+ // setup parts of the message to use in the search
+ // this is so that we try to minimise reading unnecessary parts
+ var (
+ flags []models.Flag
+ header *models.MessageInfo
+ body string
+ all string
+ err error
+ )
+
+ if parts&FLAGS > 0 {
+ flags, err = message.ModelFlags()
+ if err != nil {
+ return false, err
+ }
+ }
+ if parts&HEADER > 0 {
+ header, err = MessageInfo(message)
+ if err != nil {
+ return false, err
+ }
+ }
+ if parts&BODY > 0 {
+ // TODO: select body properly; this is just an 'all' clone
+ reader, err := message.NewReader()
+ if err != nil {
+ return false, err
+ }
+ defer reader.Close()
+ bytes, err := ioutil.ReadAll(reader)
+ if err != nil {
+ return false, err
+ }
+ body = string(bytes)
+ }
+ if parts&ALL > 0 {
+ reader, err := message.NewReader()
+ if err != nil {
+ return false, err
+ }
+ defer reader.Close()
+ bytes, err := ioutil.ReadAll(reader)
+ if err != nil {
+ return false, err
+ }
+ all = string(bytes)
+ }
+
+ // now search through the criteria
+ // implicit AND at the moment so fail fast
+ if criteria.Header != nil {
+ for k, v := range criteria.Header {
+ headerValue := header.RFC822Headers.Get(k)
+ for _, text := range v {
+ if !containsSmartCase(headerValue, text) {
+ return false, nil
+ }
+ }
+ }
+ }
+ if criteria.Body != nil {
+ for _, searchTerm := range criteria.Body {
+ if !containsSmartCase(body, searchTerm) {
+ return false, nil
+ }
+ }
+ }
+ if criteria.Text != nil {
+ for _, searchTerm := range criteria.Text {
+ if !containsSmartCase(all, searchTerm) {
+ return false, nil
+ }
+ }
+ }
+ if criteria.WithFlags != nil {
+ for _, searchFlag := range criteria.WithFlags {
+ if !containsFlag(flags, searchFlag) {
+ return false, nil
+ }
+ }
+ }
+ if criteria.WithoutFlags != nil {
+ for _, searchFlag := range criteria.WithoutFlags {
+ if containsFlag(flags, searchFlag) {
+ return false, nil
+ }
+ }
+ }
+ return true, nil
+}
+
+// containsFlag returns true if searchFlag appears in flags
+func containsFlag(flags []models.Flag, searchFlag models.Flag) bool {
+ match := false
+ for _, flag := range flags {
+ if searchFlag == flag {
+ match = true
+ }
+ }
+ return match
+}
+
+// containsSmartCase is a smarter version of strings.Contains for searching.
+// Is case-insensitive unless substr contains an upper case character
+func containsSmartCase(s string, substr string) bool {
+ if hasUpper(substr) {
+ return strings.Contains(s, substr)
+ }
+ return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
+}
+
+func hasUpper(s string) bool {
+ for _, r := range s {
+ if unicode.IsUpper(r) {
+ return true
+ }
+ }
+ return false
+}
+
+// The parts of a message, kind of
+type MsgParts int
+
+const NONE MsgParts = 0
+const (
+ FLAGS MsgParts = 1 << iota
+ HEADER
+ BODY
+ ALL
+)
+
+// Returns a bitmask of the parts of the message required to be loaded for the
+// given criteria
+func getRequiredParts(criteria *searchCriteria) MsgParts {
+ required := NONE
+ if len(criteria.Header) > 0 {
+ required |= HEADER
+ }
+ if criteria.Body != nil && len(criteria.Body) > 0 {
+ required |= BODY
+ }
+ if criteria.Text != nil && len(criteria.Text) > 0 {
+ required |= ALL
+ }
+ if criteria.WithFlags != nil && len(criteria.WithFlags) > 0 {
+ required |= FLAGS
+ }
+ if criteria.WithoutFlags != nil && len(criteria.WithoutFlags) > 0 {
+ required |= FLAGS
+ }
+
+ return required
+}
diff --git a/worker/mbox/create.go b/worker/mbox/create.go
new file mode 100644
index 00000000..7c4d9f7b
--- /dev/null
+++ b/worker/mbox/create.go
@@ -0,0 +1,60 @@
+package mboxer
+
+import (
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+func createMailboxContainer(path string) (*mailboxContainer, error) {
+
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+
+ defer file.Close()
+
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return nil, err
+ }
+
+ mbdata := &mailboxContainer{mailboxes: make(map[string]*container)}
+
+ openMboxFile := func(path string, r io.Reader) error {
+ // read mbox file
+ messages, err := Read(r)
+ if err != nil {
+ return err
+ }
+ _, name := filepath.Split(path)
+ name = strings.TrimSuffix(name, ".mbox")
+ mbdata.mailboxes[name] = &container{filename: path, messages: messages}
+ return nil
+ }
+
+ if fileInfo.IsDir() {
+ files, err := filepath.Glob(filepath.Join(path, "*.mbox"))
+ if err != nil {
+ return nil, err
+ }
+ for _, file := range files {
+ f, err := os.Open(file)
+ if err != nil {
+ continue
+ }
+ if err := openMboxFile(file, f); err != nil {
+ return nil, err
+ }
+ f.Close()
+ }
+ } else {
+ if err := openMboxFile(path, file); err != nil {
+ return nil, err
+ }
+ }
+
+ return mbdata, nil
+}
diff --git a/worker/mbox/io.go b/worker/mbox/io.go
new file mode 100644
index 00000000..38469160
--- /dev/null
+++ b/worker/mbox/io.go
@@ -0,0 +1,50 @@
+package mboxer
+
+import (
+ "io"
+ "io/ioutil"
+ "time"
+
+ "git.sr.ht/~rjarry/aerc/models"
+ "git.sr.ht/~rjarry/aerc/worker/lib"
+ "github.com/emersion/go-mbox"
+)
+
+func Read(r io.Reader) ([]lib.RawMessage, error) {
+ mbr := mbox.NewReader(r)
+ uid := uint32(0)
+ messages := make([]lib.RawMessage, 0)
+ for {
+ msg, err := mbr.NextMessage()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return nil, err
+ }
+
+ content, err := ioutil.ReadAll(msg)
+ if err != nil {
+ return nil, err
+ }
+
+ messages = append(messages, &message{
+ uid: uid, flags: []models.Flag{models.SeenFlag}, content: content,
+ })
+
+ uid++
+ }
+ return messages, nil
+}
+
+func Write(w io.Writer, reader io.Reader, from string, date time.Time) error {
+ wc := mbox.NewWriter(w)
+ mw, err := wc.CreateMessage(from, time.Now())
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(mw, reader)
+ if err != nil {
+ return err
+ }
+ return wc.Close()
+}
diff --git a/worker/mbox/models.go b/worker/mbox/models.go
new file mode 100644
index 00000000..f97530ed
--- /dev/null
+++ b/worker/mbox/models.go
@@ -0,0 +1,203 @@
+package mboxer
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+
+ "git.sr.ht/~rjarry/aerc/models"
+ "git.sr.ht/~rjarry/aerc/worker/lib"
+)
+
+type mailboxContainer struct {
+ mailboxes map[string]*container
+}
+
+func (md *mailboxContainer) Names() []string {
+ files := make([]string, 0)
+ for file := range md.mailboxes {
+ files = append(files, file)
+ }
+ return files
+}
+
+func (md *mailboxContainer) Mailbox(f string) (*container, bool) {
+ mb, ok := md.mailboxes[f]
+ return mb, ok
+}
+
+func (md *mailboxContainer) Create(file string) *container {
+ md.mailboxes[file] = &container{filename: file}
+ return md.mailboxes[file]
+}
+
+func (md *mailboxContainer) Remove(file string) error {
+ delete(md.mailboxes, file)
+ return nil
+}
+
+func (md *mailboxContainer) DirectoryInfo(file string) *models.DirectoryInfo {
+ var exists int
+ if md, ok := md.Mailbox(file); ok {
+ exists = len(md.Uids())
+ }
+ return &models.DirectoryInfo{
+ Name: file,
+ Flags: []string{},
+ ReadOnly: false,
+ Exists: exists,
+ Recent: 0,
+ Unseen: 0,
+ AccurateCounts: false,
+ Caps: &models.Capabilities{
+ Sort: true,
+ Thread: false,
+ },
+ }
+}
+
+func (md *mailboxContainer) Copy(dest, src string, uids []uint32) error {
+ srcmbox, ok := md.Mailbox(src)
+ if !ok {
+ return fmt.Errorf("source %s not found", src)
+ }
+ destmbox, ok := md.Mailbox(dest)
+ if !ok {
+ return fmt.Errorf("destination %s not found", dest)
+ }
+ for _, uidSrc := range srcmbox.Uids() {
+ found := false
+ for _, uid := range uids {
+ if uid == uidSrc {
+ found = true
+ break
+ }
+ }
+ if found {
+ msg, err := srcmbox.Message(uidSrc)
+ if err != nil {
+ return fmt.Errorf("could not get message with uid %d from folder %s", uidSrc, src)
+ }
+ r, err := msg.NewReader()
+ if err != nil {
+ return fmt.Errorf("could not get reader for message with uid %d", uidSrc)
+ }
+ flags, err := msg.ModelFlags()
+ if err != nil {
+ return fmt.Errorf("could not get flags for message with uid %d", uidSrc)
+ }
+ destmbox.Append(r, flags)
+ }
+ }
+ md.mailboxes[dest] = destmbox
+ return nil
+}
+
+type container struct {
+ filename string
+ messages []lib.RawMessage
+}
+
+func (f *container) Uids() []uint32 {
+ uids := make([]uint32, len(f.messages))
+ for i, m := range f.messages {
+ uids[i] = m.UID()
+ }
+ return uids
+}
+
+func (f *container) Message(uid uint32) (lib.RawMessage, error) {
+ for _, m := range f.messages {
+ if uid == m.UID() {
+ return m, nil
+ }
+ }
+ return &message{}, fmt.Errorf("uid [%d] not found", uid)
+}
+
+func (f *container) Delete(uids []uint32) (deleted []uint32) {
+ newMessages := make([]lib.RawMessage, 0)
+ for _, m := range f.messages {
+ del := false
+ for _, uid := range uids {
+ if m.UID() == uid {
+ del = true
+ break
+ }
+ }
+ if del {
+ deleted = append(deleted, m.UID())
+ } else {
+ newMessages = append(newMessages, m)
+ }
+ }
+ f.messages = newMessages
+ return
+}
+
+func (f *container) newUid() (next uint32) {
+ for _, m := range f.messages {
+ if uid := m.UID(); uid > next {
+ next = uid
+ }
+ }
+ next++
+ return
+}
+
+func (f *container) Append(r io.Reader, flags []models.Flag) error {
+ data, err := ioutil.ReadAll(r)
+ if err != nil {
+ return err
+ }
+ f.messages = append(f.messages, &message{
+ uid: f.newUid(),
+ flags: flags,
+ content: data,
+ })
+ return nil
+}
+
+// message implements the lib.RawMessage interface
+type message struct {
+ uid uint32
+ flags []models.Flag
+ content []byte
+}
+
+func (m *message) NewReader() (io.ReadCloser, error) {
+ return ioutil.NopCloser(bytes.NewReader(m.content)), nil
+}
+
+func (m *message) ModelFlags() ([]models.Flag, error) {
+ return m.flags, nil
+}
+
+func (m *message) Labels() ([]string, error) {
+ return nil, nil
+}
+
+func (m *message) UID() uint32 {
+ return m.uid
+}
+
+func (m *message) SetFlag(flag models.Flag, state bool) error {
+ flagSet := make(map[models.Flag]bool)
+ flags, err := m.ModelFlags()
+ if err != nil {
+ return err
+ }
+ for _, f := range flags {
+ flagSet[f] = true
+ }
+ flagSet[flag] = state
+ newFlags := make([]models.Flag, 0)
+ for flag, isSet := range flagSet {
+ if isSet {
+ newFlags = append(newFlags, flag)
+ }
+ }
+ m.flags = newFlags
+ return nil
+}
diff --git a/worker/mbox/worker.go b/worker/mbox/worker.go
new file mode 100644
index 00000000..c7f105b5
--- /dev/null
+++ b/worker/mbox/worker.go
@@ -0,0 +1,379 @@
+package mboxer
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "net/url"
+ "os"
+ "path/filepath"
+ "sort"
+
+ "git.sr.ht/~rjarry/aerc/models"
+ "git.sr.ht/~rjarry/aerc/worker/handlers"
+ "git.sr.ht/~rjarry/aerc/worker/lib"
+ "git.sr.ht/~rjarry/aerc/worker/types"
+ gomessage "github.com/emersion/go-message"
+)
+
+func init() {
+ handlers.RegisterWorkerFactory("mbox", NewWorker)
+}
+
+var errUnsupported = fmt.Errorf("unsupported command")
+
+type mboxWorker struct {
+ data *mailboxContainer
+ name string
+ folder *container
+ worker *types.Worker
+}
+
+func NewWorker(worker *types.Worker) (types.Backend, error) {
+ return &mboxWorker{
+ worker: worker,
+ }, nil
+}
+
+func (w *mboxWorker) handleMessage(msg types.WorkerMessage) error {
+ var reterr error // will be returned at the end, needed to support idle
+
+ switch msg := msg.(type) {
+
+ case *types.Unsupported:
+ // No-op
+
+ case *types.Configure:
+ u, err := url.Parse(msg.Config.Source)
+ if err != nil {
+ reterr = err
+ break
+ }
+ dir := u.Path
+ if u.Host == "~" {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ reterr = err
+ break
+ }
+ dir = filepath.Join(home, u.Path)
+ } else {
+ dir = filepath.Join(u.Host, u.Path)
+ }
+ w.data, err = createMailboxContainer(dir)
+ if err != nil || w.data == nil {
+ w.data = &mailboxContainer{
+ mailboxes: make(map[string]*container),
+ }
+ reterr = err
+ break
+ } else {
+ w.worker.Logger.Printf("mbox: configured with mbox file %s", dir)
+ }
+
+ case *types.Connect, *types.Reconnect, *types.Disconnect:
+ w.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
+
+ case *types.ListDirectories:
+ dirs := w.data.Names()
+ sort.Strings(dirs)
+ for _, name := range dirs {
+ w.worker.PostMessage(&types.Directory{
+ Message: types.RespondTo(msg),
+ Dir: &models.Directory{
+ Name: name,
+ Attributes: nil,
+ },
+ }, nil)
+ w.worker.PostMessage(&types.DirectoryInfo{
+ Info: w.data.DirectoryInfo(name),
+ }, nil)
+ }
+ w.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
+
+ case *types.OpenDirectory:
+ w.name = msg.Directory
+ var ok bool
+ w.folder, ok = w.data.Mailbox(w.name)
+ if !ok {
+ w.folder = w.data.Create(w.name)
+ w.worker.PostMessage(&types.Done{
+ Message: types.RespondTo(&types.CreateDirectory{})}, nil)
+ }
+ w.worker.PostMessage(&types.DirectoryInfo{
+ Info: w.data.DirectoryInfo(msg.Directory),
+ }, nil)
+ w.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
+ w.worker.Logger.Printf("mbox: %s opened\n", msg.Directory)
+
+ case *types.FetchDirectoryContents:
+ var infos []*models.MessageInfo
+ for _, uid := range w.folder.Uids() {
+ m, err := w.folder.Message(uid)
+ if err != nil {
+ w.worker.Logger.Println("mbox: could not get message", err)
+ continue
+ }
+ info, err := lib.MessageInfo(m)
+ if err != nil {
+ w.worker.Logger.Println("mbox: could not get message info", err)
+ continue
+ }
+ infos = append(infos, info)
+ }
+ uids, err := lib.Sort(infos, msg.SortCriteria)
+ if err != nil {
+ reterr = err
+ break
+ }
+ if len(uids) == 0 {
+ reterr = fmt.Errorf("mbox: no uids in directory")
+ break
+ }
+ w.worker.PostMessage(&types.DirectoryContents{
+ Message: types.RespondTo(msg),
+ Uids: uids,
+ }, nil)
+ w.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
+
+ case *types.FetchDirectoryThreaded:
+ reterr = errUnsupported
+
+ case *types.CreateDirectory:
+ w.data.Create(msg.Directory)
+ w.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
+
+ case *types.RemoveDirectory:
+ if err := w.data.Remove(msg.Directory); err != nil {
+ reterr = err
+ break
+ }
+ w.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
+
+ case *types.FetchMessageHeaders:
+ for _, uid := range msg.Uids {
+ m, err := w.folder.Message(uid)
+ if err != nil {
+ reterr = err
+ break
+ }
+ msgInfo, err := lib.MessageInfo(m)
+ if err != nil {
+ reterr = err
+ break
+ } else {
+ w.worker.PostMessage(&types.MessageInfo{
+ Message: types.RespondTo(msg),
+ Info: msgInfo,
+ }, nil)
+ }
+ }
+ w.worker.PostMessage(
+ &types.Done{Message: types.RespondTo(msg)}, nil)
+
+ case *types.FetchMessageBodyPart:
+ m, err := w.folder.Message(msg.Uid)
+ if err != nil {
+ w.worker.Logger.Printf("could not get message %d: %v", msg.Uid, err)
+ reterr = err
+ break
+ }
+
+ contentReader, err := m.NewReader()
+ if err != nil {
+ reterr = fmt.Errorf("could not get message reader: %v", err)
+ break
+ }
+
+ fullMsg, err := gomessage.Read(contentReader)
+ if err != nil {
+ reterr = fmt.Errorf("could not read message: %v", err)
+ break
+ }
+
+ r, err := lib.FetchEntityPartReader(fullMsg, msg.Part)
+ if err != nil {
+ w.worker.Logger.Printf(
+ "could not get body part reader for message=%d, parts=%#v: %v",
+ msg.Uid, msg.Part, err)
+ reterr = err
+ break
+ }
+
+ w.worker.PostMessage(&types.MessageBodyPart{
+ Message: types.RespondTo(msg),
+ Part: &models.MessageBodyPart{
+ Reader: r,
+ Uid: msg.Uid,
+ },
+ }, nil)
+
+ case *types.FetchFullMessages:
+ for _, uid := range msg.Uids {
+ m, err := w.folder.Message(uid)
+ if err != nil {
+ w.worker.Logger.Printf("could not get message for uid %d: %v", uid, err)
+ continue
+ }
+ r, err := m.NewReader()
+ if err != nil {
+ w.worker.Logger.Printf("could not get message reader: %v", err)
+ continue
+ }
+ defer r.Close()
+ b, err := ioutil.ReadAll(r)
+ if err != nil {
+ w.worker.Logger.Printf("could not get message reader: %v", err)
+ continue
+ }
+ w.worker.PostMessage(&types.FullMessage{
+ Message: types.RespondTo(msg),
+ Content: &models.FullMessage{
+ Uid: uid,
+ Reader: bytes.NewReader(b),
+ },
+ }, nil)
+ }
+ w.worker.PostMessage(&types.Done{
+ Message: types.RespondTo(msg),
+ }, nil)
+
+ case *types.DeleteMessages:
+ deleted := w.folder.Delete(msg.Uids)
+ if len(deleted) > 0 {
+ w.worker.PostMessage(&types.MessagesDeleted{
+ Message: types.RespondTo(msg),
+ Uids: deleted,
+ }, nil)
+ }
+
+ w.worker.PostMessage(&types.DirectoryInfo{
+ Info: w.data.DirectoryInfo(w.name),
+ }, nil)
+
+ w.worker.PostMessage(
+ &types.Done{Message: types.RespondTo(msg)}, nil)
+
+ case *types.FlagMessages:
+ for _, uid := range msg.Uids {
+ m, err := w.folder.Message(uid)
+ if err != nil {
+ w.worker.Logger.Printf("could not get message: %v", err)
+ continue
+ }
+ if err := m.(*message).SetFlag(msg.Flag, msg.Enable); err != nil {
+ w.worker.Logger.Printf("could change flag %v to %v on message: %v", msg.Flag, msg.Enable, err)
+ continue
+ }
+ info, err := lib.MessageInfo(m)
+ if err != nil {
+ w.worker.Logger.Printf("could not get message info: %v", err)
+ continue
+ }
+
+ w.worker.PostMessage(&types.MessageInfo{
+ Message: types.RespondTo(msg),
+ Info: info,
+ }, nil)
+ }
+
+ w.worker.PostMessage(&types.DirectoryInfo{
+ Info: w.data.DirectoryInfo(w.name),
+ }, nil)
+
+ w.worker.PostMessage(
+ &types.Done{Message: types.RespondTo(msg)}, nil)
+
+ case *types.CopyMessages:
+ err := w.data.Copy(msg.Destination, w.name, msg.Uids)
+ if err != nil {
+ reterr = err
+ break
+ }
+
+ w.worker.PostMessage(&types.DirectoryInfo{
+ Info: w.data.DirectoryInfo(w.name),
+ }, nil)
+
+ w.worker.PostMessage(&types.DirectoryInfo{
+ Info: w.data.DirectoryInfo(msg.Destination),
+ }, nil)
+
+ w.worker.PostMessage(
+ &types.Done{Message: types.RespondTo(msg)}, nil)
+
+ case *types.SearchDirectory:
+ criteria, err := lib.GetSearchCriteria(msg.Argv)
+ if err != nil {
+ reterr = err
+ break
+ }
+ w.worker.Logger.Printf("Searching with parsed criteria: %#v", criteria)
+ m := make([]lib.RawMessage, 0, len(w.folder.Uids()))
+ for _, uid := range w.folder.Uids() {
+ msg, err := w.folder.Message(uid)
+ if err != nil {
+ w.worker.Logger.Println("faild to get message for uid:", uid)
+ continue
+ }
+ m = append(m, msg)
+ }
+ uids, err := lib.Search(m, criteria)
+ if err != nil {
+ reterr = err
+ break
+ }
+ w.worker.PostMessage(&types.SearchResults{
+ Message: types.RespondTo(msg),
+ Uids: uids,
+ }, nil)
+
+ case *types.AppendMessage:
+ if msg.Destination == "" {
+ reterr = fmt.Errorf("AppendMessage with empty destination directory")
+ break
+ }
+ folder, ok := w.data.Mailbox(msg.Destination)
+ if !ok {
+ folder = w.data.Create(msg.Destination)
+ w.worker.PostMessage(&types.Done{
+ Message: types.RespondTo(&types.CreateDirectory{})}, nil)
+ }
+
+ if err := folder.Append(msg.Reader, msg.Flags); err != nil {
+ reterr = err
+ break
+ } else {
+ w.worker.PostMessage(&types.DirectoryInfo{
+ Info: w.data.DirectoryInfo(msg.Destination),
+ }, nil)
+ w.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
+ }
+
+ case *types.AnsweredMessages:
+ reterr = errUnsupported
+ default:
+ reterr = errUnsupported
+ }
+
+ return reterr
+}
+
+func (w *mboxWorker) Run() {
+ for {
+ select {
+ case msg := <-w.worker.Actions:
+ msg = w.worker.ProcessAction(msg)
+ if err := w.handleMessage(msg); err == errUnsupported {
+ w.worker.PostMessage(&types.Unsupported{
+ Message: types.RespondTo(msg),
+ }, nil)
+ } else if err != nil {
+ w.worker.PostMessage(&types.Error{
+ Message: types.RespondTo(msg),
+ Error: err,
+ }, nil)
+ }
+ }
+ }
+}
diff --git a/worker/worker_enabled.go b/worker/worker_enabled.go
index f0b9dbcb..a644525f 100644
--- a/worker/worker_enabled.go
+++ b/worker/worker_enabled.go
@@ -1,5 +1,9 @@
package worker
// the following workers are always enabled
-import _ "git.sr.ht/~rjarry/aerc/worker/imap"
-import _ "git.sr.ht/~rjarry/aerc/worker/maildir"
+import (
+ _ "git.sr.ht/~rjarry/aerc/worker/imap"
+ _ "git.sr.ht/~rjarry/aerc/worker/maildir"
+
+ _ "git.sr.ht/~rjarry/aerc/worker/mbox"
+)