aboutsummaryrefslogtreecommitdiffstats
path: root/worker/jmap
diff options
context:
space:
mode:
Diffstat (limited to 'worker/jmap')
-rw-r--r--worker/jmap/cache/cache.go33
-rw-r--r--worker/jmap/cache/folder_contents.go17
-rw-r--r--worker/jmap/directories.go39
-rw-r--r--worker/jmap/push.go4
-rw-r--r--worker/jmap/search.go137
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
}