diff options
Diffstat (limited to 'worker/jmap/set.go')
-rw-r--r-- | worker/jmap/set.go | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/worker/jmap/set.go b/worker/jmap/set.go new file mode 100644 index 00000000..4495e428 --- /dev/null +++ b/worker/jmap/set.go @@ -0,0 +1,241 @@ +package jmap + +import ( + "fmt" + + "git.sr.ht/~rjarry/aerc/models" + "git.sr.ht/~rjarry/aerc/worker/types" + "git.sr.ht/~rockorager/go-jmap" + "git.sr.ht/~rockorager/go-jmap/mail/email" + "git.sr.ht/~rockorager/go-jmap/mail/mailbox" +) + +func (w *JMAPWorker) updateFlags(uids []uint32, flags models.Flags, enable bool) error { + var req jmap.Request + patches := make(map[jmap.ID]jmap.Patch) + + for _, uid := range uids { + id, ok := w.uidStore.GetKey(uid) + if !ok { + return fmt.Errorf("bug: unknown uid %d", uid) + } + patch := jmap.Patch{} + for kw := range flagsToKeywords(flags) { + path := fmt.Sprintf("keywords/%s", kw) + if enable { + patch[path] = true + } else { + patch[path] = nil + } + } + patches[jmap.ID(id)] = patch + } + + req.Invoke(&email.Set{ + Account: w.accountId, + Update: patches, + }) + + resp, err := w.Do(&req) + if err != nil { + return err + } + + return checkNotUpdated(resp) +} + +func (w *JMAPWorker) moveCopy(uids []uint32, destDir string, deleteSrc bool) error { + var req jmap.Request + var destMbox jmap.ID + var destroy []jmap.ID + var ok bool + + patches := make(map[jmap.ID]jmap.Patch) + + destMbox, ok = w.dir2mbox[destDir] + if !ok && destDir != "" { + return fmt.Errorf("unknown destination mailbox") + } + if destMbox != "" && destMbox == w.selectedMbox { + return fmt.Errorf("cannot move to current mailbox") + } + + for _, uid := range uids { + dest := destMbox + id, ok := w.uidStore.GetKey(uid) + if !ok { + return fmt.Errorf("bug: unknown uid %d", uid) + } + mail, err := w.cache.GetEmail(jmap.ID(id)) + if err != nil { + return fmt.Errorf("bug: unknown message id %s: %w", id, err) + } + + patch := w.moveCopyPatch(mail, dest, deleteSrc) + if len(patch) == 0 { + destroy = append(destroy, mail.ID) + w.w.Debugf("destroying <%s>", mail.MessageID[0]) + } else { + patches[jmap.ID(id)] = patch + } + } + + req.Invoke(&email.Set{ + Account: w.accountId, + Update: patches, + Destroy: destroy, + }) + + resp, err := w.Do(&req) + if err != nil { + return err + } + + return checkNotUpdated(resp) +} + +func (w *JMAPWorker) moveCopyPatch( + mail *email.Email, dest jmap.ID, deleteSrc bool, +) jmap.Patch { + patch := jmap.Patch{} + + if dest == "" && deleteSrc && len(mail.MailboxIDs) == 1 { + dest = w.roles[mailbox.RoleTrash] + } + if dest != "" && dest != w.selectedMbox { + d := w.mbox2dir[dest] + if deleteSrc { + w.w.Debugf("moving <%s> to %q", mail.MessageID[0], d) + } else { + w.w.Debugf("copying <%s> to %q", mail.MessageID[0], d) + } + patch[w.mboxPatch(dest)] = true + } + if deleteSrc && len(patch) > 0 { + switch { + case w.selectedMbox != "": + patch[w.mboxPatch(w.selectedMbox)] = nil + case len(mail.MailboxIDs) == 1: + // In "all mail" virtual mailbox and email is in + // a single mailbox, "Move" it to the specified + // destination + patch = jmap.Patch{"mailboxIds": []jmap.ID{dest}} + default: + // In "all mail" virtual mailbox and email is in + // multiple mailboxes. Since we cannot know what mailbox + // to remove, try at least to remove role=inbox. + patch[w.rolePatch(mailbox.RoleInbox)] = nil + } + } + + return patch +} + +func (w *JMAPWorker) mboxPatch(mbox jmap.ID) string { + return fmt.Sprintf("mailboxIds/%s", mbox) +} + +func (w *JMAPWorker) rolePatch(role mailbox.Role) string { + return fmt.Sprintf("mailboxIds/%s", w.roles[role]) +} + +func (w *JMAPWorker) handleModifyLabels(msg *types.ModifyLabels) error { + var req jmap.Request + patch := jmap.Patch{} + + for _, a := range msg.Add { + mboxId, ok := w.dir2mbox[a] + if !ok { + return fmt.Errorf("unkown label: %q", a) + } + patch[w.mboxPatch(mboxId)] = true + } + for _, r := range msg.Remove { + mboxId, ok := w.dir2mbox[r] + if !ok { + return fmt.Errorf("unkown label: %q", r) + } + patch[w.mboxPatch(mboxId)] = nil + } + + patches := make(map[jmap.ID]jmap.Patch) + + for _, uid := range msg.Uids { + id, ok := w.uidStore.GetKey(uid) + if !ok { + return fmt.Errorf("bug: unknown uid %d", uid) + } + patches[jmap.ID(id)] = patch + } + + req.Invoke(&email.Set{ + Account: w.accountId, + Update: patches, + }) + + resp, err := w.Do(&req) + if err != nil { + return err + } + + return checkNotUpdated(resp) +} + +func checkNotUpdated(resp *jmap.Response) error { + for _, inv := range resp.Responses { + switch r := inv.Args.(type) { + case *email.SetResponse: + for _, err := range r.NotUpdated { + return wrapSetError(err) + } + case *jmap.MethodError: + return wrapMethodError(r) + } + } + return nil +} + +func (w *JMAPWorker) handleAppendMessage(msg *types.AppendMessage) error { + dest, ok := w.dir2mbox[msg.Destination] + if !ok { + return fmt.Errorf("unknown destination mailbox") + } + + // Upload the message + blob, err := w.Upload(msg.Reader) + if err != nil { + return err + } + + var req jmap.Request + + // Import the blob into specified directory + req.Invoke(&email.Import{ + Account: w.accountId, + Emails: map[string]*email.EmailImport{ + "aerc": { + BlobID: blob.ID, + MailboxIDs: map[jmap.ID]bool{dest: true}, + Keywords: flagsToKeywords(msg.Flags), + }, + }, + }) + + resp, err := w.Do(&req) + if err != nil { + return err + } + + for _, inv := range resp.Responses { + switch r := inv.Args.(type) { + case *email.ImportResponse: + if err, ok := r.NotCreated["aerc"]; ok { + return wrapSetError(err) + } + case *jmap.MethodError: + return wrapMethodError(r) + } + } + + return nil +} |