package jmap import ( "errors" "fmt" "net/url" "strings" "git.sr.ht/~rjarry/aerc/config" "git.sr.ht/~rjarry/aerc/lib/uidstore" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/handlers" "git.sr.ht/~rjarry/aerc/worker/types" "git.sr.ht/~rockorager/go-jmap" "git.sr.ht/~rockorager/go-jmap/core/push" "git.sr.ht/~rockorager/go-jmap/mail/email" ) func init() { handlers.RegisterWorkerFactory("jmap", NewJMAPWorker) } type JMAPWorker struct { // config account *config.AccountConfig endpoint string oauth bool user *url.Userinfo query url.Values w *types.Worker client *jmap.Client selectedMbox jmap.ID mboxes map[jmap.ID]*MailboxState dir2mbox map[string]jmap.ID uidStore *uidstore.Store emails map[jmap.ID]*email.Email events *push.EventSource mboxState string emailState string changes chan jmap.TypeState stop chan struct{} } func NewJMAPWorker(worker *types.Worker) (types.Backend, error) { return &JMAPWorker{ w: worker, uidStore: uidstore.NewStore(), mboxes: make(map[jmap.ID]*MailboxState), emails: make(map[jmap.ID]*email.Email), dir2mbox: make(map[string]jmap.ID), changes: make(chan jmap.TypeState), }, nil } var capas = models.Capabilities{Sort: true, Thread: false} func (w *JMAPWorker) Capabilities() *models.Capabilities { return &capas } func (w *JMAPWorker) PathSeparator() string { return "/" } func (w *JMAPWorker) handleMessage(msg types.WorkerMessage) error { switch msg := msg.(type) { case *types.Unsupported: // No-op case *types.Configure: return w.handleConfigure(msg) case *types.Connect: if w.stop != nil { return errors.New("already connected") } return w.handleConnect(msg) case *types.Reconnect: if w.stop == nil { return errors.New("not connected") } close(w.stop) return w.handleConnect(&types.Connect{Message: msg.Message}) case *types.Disconnect: if w.stop == nil { return errors.New("not connected") } close(w.stop) return nil case *types.ListDirectories: return w.handleListDirectories(msg) case *types.OpenDirectory: return w.handleOpenDirectory(msg) case *types.FetchDirectoryContents: return w.handleFetchDirectoryContents(msg) case *types.SearchDirectory: return w.handleSearchDirectory(msg) case *types.CreateDirectory: return w.handleCreateDirectory(msg) case *types.RemoveDirectory: return w.handleRemoveDirectory(msg) case *types.FetchMessageHeaders: return w.handleFetchMessageHeaders(msg) case *types.FetchMessageBodyPart: return w.handleFetchMessageBodyPart(msg) case *types.FetchFullMessages: return w.handleFetchFullMessages(msg) case *types.FetchMessageFlags: return nil case *types.DeleteMessages: return w.moveCopy(msg.Uids, "", true) case *types.FlagMessages: return w.updateFlags(msg.Uids, msg.Flags, msg.Enable) case *types.AnsweredMessages: return w.updateFlags(msg.Uids, models.AnsweredFlag, msg.Answered) case *types.CopyMessages: return w.moveCopy(msg.Uids, msg.Destination, false) case *types.MoveMessages: return w.moveCopy(msg.Uids, msg.Destination, true) // case *types.AppendMessage: // return w.handleAppendMessage(msg) } _, cmd, _ := strings.Cut(fmt.Sprintf("%T", msg), ".") return fmt.Errorf("unsupported command for jmap: %s", cmd) } func (w *JMAPWorker) Run() { for { select { case change := <-w.changes: err := w.refresh(change) if err != nil { w.w.Errorf("refresh: %s", err) } case msg := <-w.w.Actions: msg = w.w.ProcessAction(msg) if err := w.handleMessage(msg); err != nil { w.w.PostMessage(&types.Error{ Message: types.RespondTo(msg), Error: err, }, nil) } else { w.w.PostMessage(&types.Done{ Message: types.RespondTo(msg), }, nil) } } } }