diff options
Diffstat (limited to 'worker/jmap')
-rw-r--r-- | worker/jmap/cache/cache.go | 33 | ||||
-rw-r--r-- | worker/jmap/cache/folder_contents.go | 17 | ||||
-rw-r--r-- | worker/jmap/directories.go | 39 | ||||
-rw-r--r-- | worker/jmap/push.go | 4 | ||||
-rw-r--r-- | worker/jmap/search.go | 137 |
5 files changed, 138 insertions, 92 deletions
diff --git a/worker/jmap/cache/cache.go b/worker/jmap/cache/cache.go index 249ed0e9..6d815177 100644 --- a/worker/jmap/cache/cache.go +++ b/worker/jmap/cache/cache.go @@ -4,10 +4,12 @@ import ( "errors" "os" "path" + "strings" "git.sr.ht/~rjarry/aerc/lib/xdg" "git.sr.ht/~rjarry/aerc/log" "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/util" ) type JMAPCache struct { @@ -74,3 +76,34 @@ func (c *JMAPCache) delete(key string) error { } panic("jmap cache with no backend") } + +func (c *JMAPCache) purge(prefix string) error { + switch { + case c.file != nil: + txn, err := c.file.OpenTransaction() + if err != nil { + return err + } + iter := txn.NewIterator(util.BytesPrefix([]byte(prefix)), nil) + for iter.Next() { + err = txn.Delete(iter.Key(), nil) + if err != nil { + break + } + } + iter.Release() + if err != nil { + txn.Discard() + return err + } + return txn.Commit() + case c.mem != nil: + for key := range c.mem { + if strings.HasPrefix(key, prefix) { + delete(c.mem, key) + } + } + return nil + } + panic("jmap cache with no backend") +} diff --git a/worker/jmap/cache/folder_contents.go b/worker/jmap/cache/folder_contents.go index 6c6a7d80..46a36607 100644 --- a/worker/jmap/cache/folder_contents.go +++ b/worker/jmap/cache/folder_contents.go @@ -3,26 +3,32 @@ package cache import ( "reflect" + "git.sr.ht/~rjarry/aerc/log" + "git.sr.ht/~rjarry/aerc/worker/types" "git.sr.ht/~rockorager/go-jmap" - "git.sr.ht/~rockorager/go-jmap/mail/email" ) type FolderContents struct { MailboxID jmap.ID QueryState string - Filter *email.FilterCondition - Sort []*email.SortComparator + Filter *types.SearchCriteria + Sort []*types.SortCriterion MessageIDs []jmap.ID } func (c *JMAPCache) GetFolderContents(mailboxId jmap.ID) (*FolderContents, error) { - buf, err := c.get(folderContentsKey(mailboxId)) + key := folderContentsKey(mailboxId) + buf, err := c.get(key) if err != nil { return nil, err } m := new(FolderContents) err = unmarshal(buf, m) if err != nil { + log.Debugf("cache format has changed, purging foldercontents") + if e := c.purge("foldercontents/"); e != nil { + log.Errorf("foldercontents cache purge: %s", e) + } return nil, err } return m, nil @@ -45,7 +51,7 @@ func folderContentsKey(mailboxId jmap.ID) string { } func (f *FolderContents) NeedsRefresh( - filter *email.FilterCondition, sort []*email.SortComparator, + filter *types.SearchCriteria, sort []*types.SortCriterion, ) bool { if f.QueryState == "" || f.Filter == nil || len(f.Sort) != len(sort) { return true @@ -56,6 +62,5 @@ func (f *FolderContents) NeedsRefresh( return true } } - return !reflect.DeepEqual(filter, f.Filter) } diff --git a/worker/jmap/directories.go b/worker/jmap/directories.go index 0fa3d898..bc47e691 100644 --- a/worker/jmap/directories.go +++ b/worker/jmap/directories.go @@ -126,25 +126,16 @@ func (w *JMAPWorker) handleFetchDirectoryContents(msg *types.FetchDirectoryConte if err != nil { contents = &cache.FolderContents{ MailboxID: w.selectedMbox, - Filter: &email.FilterCondition{}, } } - filter, err := parseSearch(msg.FilterCriteria) - if err != nil { - return err - } - filter.InMailbox = w.selectedMbox - - sort := translateSort(msg.SortCriteria) - - if contents.NeedsRefresh(filter, sort) { + if contents.NeedsRefresh(msg.Filter, msg.SortCriteria) { var req jmap.Request req.Invoke(&email.Query{ Account: w.accountId, - Filter: filter, - Sort: sort, + Filter: w.translateSearch(w.selectedMbox, msg.Filter), + Sort: translateSort(msg.SortCriteria), }) resp, err := w.Do(&req) if err != nil { @@ -154,8 +145,8 @@ func (w *JMAPWorker) handleFetchDirectoryContents(msg *types.FetchDirectoryConte for _, inv := range resp.Responses { switch r := inv.Args.(type) { case *email.QueryResponse: - contents.Sort = sort - contents.Filter = filter + contents.Sort = msg.SortCriteria + contents.Filter = msg.Filter contents.QueryState = r.QueryState contents.MessageIDs = r.IDs canCalculateChanges = r.CanCalculateChanges @@ -193,27 +184,9 @@ func (w *JMAPWorker) handleFetchDirectoryContents(msg *types.FetchDirectoryConte func (w *JMAPWorker) handleSearchDirectory(msg *types.SearchDirectory) error { var req jmap.Request - filter, err := parseSearch(msg.Argv) - if err != nil { - return err - } - if w.selectedMbox == "" { - // all mail virtual folder: display all but trash and spam - var mboxes []jmap.ID - if id, ok := w.roles[mailbox.RoleJunk]; ok { - mboxes = append(mboxes, id) - } - if id, ok := w.roles[mailbox.RoleTrash]; ok { - mboxes = append(mboxes, id) - } - filter.InMailboxOtherThan = mboxes - } else { - filter.InMailbox = w.selectedMbox - } - req.Invoke(&email.Query{ Account: w.accountId, - Filter: filter, + Filter: w.translateSearch(w.selectedMbox, msg.Criteria), }) resp, err := w.Do(&req) diff --git a/worker/jmap/push.go b/worker/jmap/push.go index 320fee4f..34a90ca4 100644 --- a/worker/jmap/push.go +++ b/worker/jmap/push.go @@ -127,8 +127,8 @@ func (w *JMAPWorker) refresh(newState jmap.TypeState) error { } callID = req.Invoke(&email.QueryChanges{ Account: w.accountId, - Filter: contents.Filter, - Sort: contents.Sort, + Filter: w.translateSearch(id, contents.Filter), + Sort: translateSort(contents.Sort), SinceQueryState: contents.QueryState, }) queryChangesCalls[callID] = id diff --git a/worker/jmap/search.go b/worker/jmap/search.go index 17b5ca11..0101ce9b 100644 --- a/worker/jmap/search.go +++ b/worker/jmap/search.go @@ -1,63 +1,98 @@ package jmap import ( - "strings" - - "git.sr.ht/~rjarry/aerc/lib/parse" - "git.sr.ht/~rjarry/aerc/log" + "git.sr.ht/~rjarry/aerc/worker/types" + "git.sr.ht/~rockorager/go-jmap" "git.sr.ht/~rockorager/go-jmap/mail/email" - "git.sr.ht/~sircmpwn/getopt" + "git.sr.ht/~rockorager/go-jmap/mail/mailbox" ) -func parseSearch(args []string) (*email.FilterCondition, error) { - f := new(email.FilterCondition) - if len(args) == 0 { - return f, nil - } - - opts, optind, err := getopt.Getopts(args, "rubax:X:t:H:f:c:d:") - if err != nil { - return nil, err - } - body := false - text := false - for _, opt := range opts { - switch opt.Option { - case 'r': - f.HasKeyword = "$seen" - case 'u': - f.NotKeyword = "$seen" - case 'f': - f.From = opt.Value - case 't': - f.To = opt.Value - case 'c': - f.Cc = opt.Value - case 'b': - body = true - case 'a': - text = true - case 'd': - start, end, err := parse.DateRange(opt.Value) - if err != nil { - log.Errorf("failed to parse start date: %v", err) - continue - } - if !start.IsZero() { - f.After = &start - } - if !end.IsZero() { - f.Before = &end - } +func (w *JMAPWorker) translateSearch( + mbox jmap.ID, criteria *types.SearchCriteria, +) email.Filter { + cond := new(email.FilterCondition) + + if mbox == "" { + // all mail virtual folder: display all but trash and spam + var mboxes []jmap.ID + if id, ok := w.roles[mailbox.RoleJunk]; ok { + mboxes = append(mboxes, id) + } + if id, ok := w.roles[mailbox.RoleTrash]; ok { + mboxes = append(mboxes, id) } + cond.InMailboxOtherThan = mboxes + } else { + cond.InMailbox = mbox + } + if criteria == nil { + return cond + } + + // dates + if !criteria.StartDate.IsZero() { + cond.After = &criteria.StartDate } + if !criteria.EndDate.IsZero() { + cond.Before = &criteria.EndDate + } + + // general search terms switch { - case text: - f.Text = strings.Join(args[optind:], " ") - case body: - f.Body = strings.Join(args[optind:], " ") + case criteria.SearchAll: + cond.Text = criteria.Terms + case criteria.SearchBody: + cond.Body = criteria.Terms default: - f.Subject = strings.Join(args[optind:], " ") + cond.Subject = criteria.Terms + } + + filter := &email.FilterOperator{Operator: jmap.OperatorAND} + filter.Conditions = append(filter.Conditions, cond) + + // keywords/flags + for kw := range flagsToKeywords(criteria.WithFlags) { + filter.Conditions = append(filter.Conditions, + &email.FilterCondition{HasKeyword: kw}) + } + for kw := range flagsToKeywords(criteria.WithoutFlags) { + filter.Conditions = append(filter.Conditions, + &email.FilterCondition{NotKeyword: kw}) } - return f, nil + + // recipients + addrs := &email.FilterOperator{ + Operator: jmap.OperatorOR, + } + for _, from := range criteria.From { + addrs.Conditions = append(addrs.Conditions, + &email.FilterCondition{From: from}) + } + for _, to := range criteria.To { + addrs.Conditions = append(addrs.Conditions, + &email.FilterCondition{To: to}) + } + for _, cc := range criteria.Cc { + addrs.Conditions = append(addrs.Conditions, + &email.FilterCondition{Cc: cc}) + } + if len(addrs.Conditions) > 0 { + filter.Conditions = append(filter.Conditions, addrs) + } + + // specific headers + headers := &email.FilterOperator{ + Operator: jmap.OperatorAND, + } + for h, values := range criteria.Headers { + for _, v := range values { + headers.Conditions = append(headers.Conditions, + &email.FilterCondition{Header: []string{h, v}}) + } + } + if len(headers.Conditions) > 0 { + filter.Conditions = append(filter.Conditions, headers) + } + + return filter } |