aboutsummaryrefslogtreecommitdiffstats
path: root/worker/jmap/set.go
diff options
context:
space:
mode:
Diffstat (limited to 'worker/jmap/set.go')
-rw-r--r--worker/jmap/set.go241
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
+}