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 }