From 8464b373851142b0becaaa10db34df3559b2b62e Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Tue, 17 Oct 2023 15:31:09 +0200 Subject: search: use a common api for all workers Define a SearchCriteria structure. Update the FetchDirectoryContents, FetchDirectoryThreaded and SearchDirectory worker messages to include this SearchCriteria structure instead of a []string slice. Parse the search arguments in a single place into a SearchCriteria structure and use it to search/filter via the message store. Update all workers to use that new API. Clarify the man page indicating that notmuch supports searching with aerc's syntax and also with notmuch specific syntax. getopt is no longer needed, remove it from go.mod. NB: to support more complex search filters in JMAP, we need to use an email.Filter interface. Since GOB does not support encoding/decoding interfaces, store the raw SearchCriteria and []SortCriterion values in the cached FolderContents. Translate them to JMAP API objects when sending an email.Query request to the server. Signed-off-by: Robin Jarry Reviewed-by: Koni Marti Tested-by: Moritz Poldrack Tested-by: Inwit --- commands/account/search.go | 114 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 4 deletions(-) (limited to 'commands/account') diff --git a/commands/account/search.go b/commands/account/search.go index 7b98d98b..bb5617c0 100644 --- a/commands/account/search.go +++ b/commands/account/search.go @@ -2,18 +2,35 @@ package account import ( "errors" + "fmt" + "net/textproto" "strings" + "time" "git.sr.ht/~rjarry/aerc/app" "git.sr.ht/~rjarry/aerc/commands" + "git.sr.ht/~rjarry/aerc/lib/parse" "git.sr.ht/~rjarry/aerc/lib/state" "git.sr.ht/~rjarry/aerc/lib/ui" "git.sr.ht/~rjarry/aerc/log" + "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" ) type SearchFilter struct { - Unused struct{} `opt:"-"` + Read bool `opt:"-r" action:"ParseRead"` + Unread bool `opt:"-u" action:"ParseUnread"` + Body bool `opt:"-b"` + All bool `opt:"-a"` + Headers textproto.MIMEHeader `opt:"-H" action:"ParseHeader" metavar:"
:"` + WithFlags models.Flags `opt:"-x" action:"ParseFlag"` + WithoutFlags models.Flags `opt:"-X" action:"ParseNotFlag"` + To []string `opt:"-t" action:"ParseTo"` + From []string `opt:"-f" action:"ParseFrom"` + Cc []string `opt:"-c" action:"ParseCc"` + StartDate time.Time `opt:"-d" action:"ParseDate"` + EndDate time.Time + Terms string `opt:"..." required:"false"` } func init() { @@ -49,7 +66,82 @@ func (SearchFilter) Complete(args []string) []string { return nil } -func (SearchFilter) Execute(args []string) error { +func (s *SearchFilter) ParseRead(arg string) error { + s.WithFlags |= models.SeenFlag + s.WithoutFlags &^= models.SeenFlag + return nil +} + +func (s *SearchFilter) ParseUnread(arg string) error { + s.WithFlags &^= models.SeenFlag + s.WithoutFlags |= models.SeenFlag + return nil +} + +var flagValues = map[string]models.Flags{ + "seen": models.SeenFlag, + "answered": models.AnsweredFlag, + "flagged": models.FlaggedFlag, +} + +func (s *SearchFilter) ParseFlag(arg string) error { + f, ok := flagValues[strings.ToLower(arg)] + if !ok { + return fmt.Errorf("%q unknown flag", arg) + } + s.WithFlags |= f + s.WithoutFlags &^= f + return nil +} + +func (s *SearchFilter) ParseNotFlag(arg string) error { + f, ok := flagValues[strings.ToLower(arg)] + if !ok { + return fmt.Errorf("%q unknown flag", arg) + } + s.WithFlags &^= f + s.WithoutFlags |= f + return nil +} + +func (s *SearchFilter) ParseHeader(arg string) error { + name, value, hasColon := strings.Cut(arg, ":") + if !hasColon { + return fmt.Errorf("%q invalid syntax", arg) + } + if s.Headers == nil { + s.Headers = make(textproto.MIMEHeader) + } + s.Headers.Add(name, strings.TrimSpace(value)) + return nil +} + +func (s *SearchFilter) ParseTo(arg string) error { + s.To = append(s.To, arg) + return nil +} + +func (s *SearchFilter) ParseFrom(arg string) error { + s.From = append(s.From, arg) + return nil +} + +func (s *SearchFilter) ParseCc(arg string) error { + s.Cc = append(s.Cc, arg) + return nil +} + +func (s *SearchFilter) ParseDate(arg string) error { + start, end, err := parse.DateRange(arg) + if err != nil { + return err + } + s.StartDate = start + s.EndDate = end + return nil +} + +func (s SearchFilter) Execute(args []string) error { acct := app.SelectedAccount() if acct == nil { return errors.New("No account selected") @@ -59,12 +151,26 @@ func (SearchFilter) Execute(args []string) error { return errors.New("Cannot perform action. Messages still loading") } + criteria := types.SearchCriteria{ + WithFlags: s.WithFlags, + WithoutFlags: s.WithoutFlags, + From: s.From, + To: s.To, + Cc: s.Cc, + Headers: s.Headers, + StartDate: s.StartDate, + EndDate: s.EndDate, + SearchBody: s.Body, + SearchAll: s.All, + Terms: s.Terms, + } + if args[0] == "filter" { if len(args[1:]) == 0 { return Clear{}.Execute([]string{"clear"}) } acct.SetStatus(state.FilterActivity("Filtering..."), state.Search("")) - store.SetFilter(args[1:]) + store.SetFilter(&criteria) cb := func(msg types.WorkerMessage) { if _, ok := msg.(*types.Done); ok { acct.SetStatus(state.FilterResult(strings.Join(args, " "))) @@ -81,7 +187,7 @@ func (SearchFilter) Execute(args []string) error { // TODO: Remove when stores have multiple OnUpdate handlers ui.Invalidate() } - store.Search(args, cb) + store.Search(&criteria, cb) } return nil } -- cgit