From 0535f6333f2f5d13469fc315a65c53ff8a5e83f3 Mon Sep 17 00:00:00 2001 From: ARaspiK Date: Sun, 5 Jul 2020 14:29:52 +0000 Subject: Add additional flagging functionality More mail flags can now be set, unset, and toggled, not just the read/seen flag. This functionality is implemented with a new `:flag` and `:unflag` command, which are extensions to the matching `:read` and `:unread` commands, adding support for different flags. In fact, the `read`/`unread` commands are now recognized aliases to `flag`/`unflag`. The new commands are also well documented in aerc(1). The change mostly extends the previous read/unread setting functionality by adding a selection for the flag to change. --- commands/msg/read.go | 221 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 135 insertions(+), 86 deletions(-) (limited to 'commands/msg') diff --git a/commands/msg/read.go b/commands/msg/read.go index 1e264c2..0888189 100644 --- a/commands/msg/read.go +++ b/commands/msg/read.go @@ -1,9 +1,9 @@ package msg import ( - "errors" "sync" "time" + "fmt" "git.sr.ht/~sircmpwn/getopt" @@ -13,36 +13,106 @@ import ( "git.sr.ht/~sircmpwn/aerc/worker/types" ) -type Read struct{} +type FlagMsg struct{} func init() { - register(Read{}) + register(FlagMsg{}) } -func (Read) Aliases() []string { - return []string{"read", "unread"} +func (FlagMsg) Aliases() []string { + return []string{"flag", "unflag", "read", "unread"} } -func (Read) Complete(aerc *widgets.Aerc, args []string) []string { +func (FlagMsg) Complete(aerc *widgets.Aerc, args []string) []string { return nil } -func (Read) Execute(aerc *widgets.Aerc, args []string) error { - opts, optind, err := getopt.Getopts(args, "t") +// If this was called as 'flag' or 'unflag', without the toggle (-t) +// option, then it will flag the corresponding messages with the given +// flag. If the toggle option was given, it will individually toggle +// the given flag for the corresponding messages. +// +// If this was called as 'read' or 'unread', it has the same effect as +// 'flag' or 'unflag', respectively, but the 'Seen' flag is affected. +func (FlagMsg) Execute(aerc *widgets.Aerc, args []string) error { + + // The flag to change + var flag models.Flag + // User-readable name of the flag to change + var flagName string + // Whether to toggle the flag (true) or to enable/disable it (false) + var toggle bool + // Whether to enable (true) or disable (false) the flag + enable := (args[0] == "read" || args[0] == "flag") + // User-readable name for the action being performed + var actionName string + // Getopt option string, varies by command name + var getoptString string + // Help message to provide on parsing failure + var helpMessage string + // Used during parsing to prevent choosing a flag muliple times + // A default flag will be used if this is false + flagChosen := false + + if args[0] == "read" || args[0] == "unread" { + flag = models.SeenFlag + flagName = "read" + getoptString = "t" + helpMessage = "Usage: " + args[0] + " [-t]" + } else { // 'flag' / 'unflag' + flag = models.FlaggedFlag + flagName = "flagged" + getoptString = "tax:" + helpMessage = "Usage: " + args[0] + " [-t] [-a | -x ]" + } + + opts, optind, err := getopt.Getopts(args, getoptString) if err != nil { return err } - if optind != len(args) { - return errors.New("Usage: " + args[0] + " [-t]") - } - var toggle bool - for _, opt := range opts { switch opt.Option { case 't': toggle = true + case 'a': + if flagChosen { + return fmt.Errorf("Cannot choose a flag multiple times! " + helpMessage) + } + flag = models.AnsweredFlag + flagName = "answered" + flagChosen = true + case 'x': + if flagChosen { + return fmt.Errorf("Cannot choose a flag multiple times! " + helpMessage) + } + // TODO: Support all flags? + switch opt.Value { + case "Seen": + flag = models.SeenFlag + flagName = "seen" + case "Answered": + flag = models.AnsweredFlag + flagName = "answered" + case "Flagged": + flag = models.FlaggedFlag + flagName = "flagged" + default: + return fmt.Errorf("Unknown / Prohibited flag \"%v\"", opt.Value) + } + flagChosen = true } } + if toggle { + actionName = "Toggling" + } else if enable { + actionName = "Setting" + } else { + actionName = "Unsetting" + } + if optind != len(args) { + // Any non-option arguments: Error + return fmt.Errorf(helpMessage) + } h := newHelper(aerc) store, err := h.store() @@ -50,57 +120,68 @@ func (Read) Execute(aerc *widgets.Aerc, args []string) error { return err } - if toggle { - // ignore command given, simply toggle all the read states - return submitToggle(aerc, store, h) - } - msgUids, err := h.markedOrSelectedUids() - if err != nil { - return err - } - switch args[0] { - case "read": - submitReadChange(aerc, store, msgUids, true) - case "unread": - submitReadChange(aerc, store, msgUids, false) - - } - return nil -} + // UIDs of messages to enable or disable the flag for. + var toEnable []uint32 + var toDisable []uint32 -func splitMessages(msgs []*models.MessageInfo) (read []uint32, unread []uint32) { - for _, m := range msgs { - var seen bool - for _, flag := range m.Flags { - if flag == models.SeenFlag { - seen = true - break + if toggle { + // If toggling, split messages into those that need to + // be enabled / disabled. + msgs, err := h.messages() + if err != nil { + return err + } + for _, m := range msgs { + var enabled bool + for _, mFlag := range m.Flags { + if mFlag == flag { + enabled = true + break + } } + if enabled { + toDisable = append(toDisable, m.Uid) + } else { + toEnable = append(toEnable, m.Uid) + } + } + } else { + msgUids, err := h.markedOrSelectedUids() + if err != nil { + return err } - if seen { - read = append(read, m.Uid) + if enable { + toEnable = msgUids } else { - unread = append(unread, m.Uid) + toDisable = msgUids } } - return read, unread -} -func submitReadChange(aerc *widgets.Aerc, store *lib.MessageStore, - uids []uint32, newState bool) { - store.Read(uids, newState, func(msg types.WorkerMessage) { - switch msg := msg.(type) { - case *types.Done: - aerc.PushStatus(msg_success, 10*time.Second) - case *types.Error: - aerc.PushError(" " + msg.Error.Error()) + var wg sync.WaitGroup + success := true + + if len(toEnable) != 0 { + submitFlagChange(aerc, store, toEnable, flag, true, &wg, &success) + } + if len(toDisable) != 0 { + submitFlagChange(aerc, store, toDisable, flag, false, &wg, &success) + } + + // We need to do flagging in the background, else we block the main thread + go func() { + wg.Wait() + if success { + aerc.PushStatus(actionName + " flag '" + flagName + "' successful", 10*time.Second) } - }) + }() + + return nil } -func submitReadChangeWg(aerc *widgets.Aerc, store *lib.MessageStore, - uids []uint32, newState bool, wg *sync.WaitGroup, success *bool) { - store.Read(uids, newState, func(msg types.WorkerMessage) { +func submitFlagChange(aerc *widgets.Aerc, store *lib.MessageStore, + uids []uint32, flag models.Flag, newState bool, + wg *sync.WaitGroup, success *bool) { + store.Flag(uids, flag, newState, func(msg types.WorkerMessage) { wg.Add(1) switch msg := msg.(type) { case *types.Done: @@ -112,35 +193,3 @@ func submitReadChangeWg(aerc *widgets.Aerc, store *lib.MessageStore, } }) } - -func submitToggle(aerc *widgets.Aerc, store *lib.MessageStore, h *helper) error { - msgs, err := h.messages() - if err != nil { - return err - } - read, unread := splitMessages(msgs) - - var wg sync.WaitGroup - success := true - - if len(read) != 0 { - newState := false - submitReadChangeWg(aerc, store, read, newState, &wg, &success) - } - - if len(unread) != 0 { - newState := true - submitReadChangeWg(aerc, store, unread, newState, &wg, &success) - } - // we need to do that in the background, else we block the main thread - go func() { - wg.Wait() - if success { - aerc.PushStatus(msg_success, 10*time.Second) - } - }() - return nil - -} - -const msg_success = "read state set successfully" -- cgit 1.4.1-2-gfad0