Browse Source

Add set verb and message subcommand

Blallo 3 years ago
parent
commit
31287f03e8
3 changed files with 450 additions and 0 deletions
  1. 2 0
      cli/papero/main.go
  2. 215 0
      cli/papero/set.go
  3. 233 0
      imaputils/modify.go

+ 2 - 0
cli/papero/main.go

@@ -17,8 +17,10 @@ var Log *log.Logger
 
 func init() {
 	info := InfoCmd{}
+	set := SetCmd{}
 	Session.Info.Commands = cli.CommandMap{
 		"info": info,
+		"set":  set,
 	}
 	Session.Info.Name = "papero"
 	flag.Usage = func() { cli.Usage(os.Stdout, Session.Info) }

+ 215 - 0
cli/papero/set.go

@@ -0,0 +1,215 @@
+package main
+
+import (
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+
+	"git.lattuga.net/blallo/papero/cli"
+	"git.lattuga.net/blallo/papero/imaputils"
+	"github.com/emersion/go-imap"
+)
+
+var setVerbs cli.CommandMap
+
+func init() {
+	setVerbs = cli.CommandMap{
+		"message": SetMessageCmd{},
+		// "subscribe": SetSubscribedCmd{},
+		// "unsubscribe": SetUnsubscribedCmd{},
+	}
+}
+
+type SetCmd struct{}
+
+func (s SetCmd) Func(args []string) error {
+	flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
+	flagset.Usage = func() { s.Help(os.Stdout, flagset) }
+	flagset.Parse(args[1:])
+	subArgs := flagset.Args()
+	if len(subArgs) == 0 {
+		s.Help(os.Stderr, flagset)
+		os.Exit(1)
+	}
+	cmd := subArgs[0]
+	if Session.Info.Opts.Debug {
+		Log.Debug("set verb:", cmd)
+	}
+
+	return setVerbs[cmd].Func(subArgs)
+}
+
+func (s SetCmd) Help(w io.Writer, set *flag.FlagSet) {
+	fmt.Fprintf(w, "USAGE: %s set VERB [verb opts]\n", Session.Info.Name)
+	fmt.Fprintf(w, "\nVERBS:\n")
+	for verb := range setVerbs {
+		fmt.Fprintf(w, "\t%s\n", verb)
+	}
+}
+
+// FLAG UTILS
+
+type pair struct {
+	set, unset bool
+}
+
+type pairFlag struct {
+	*pair
+}
+
+func newPairFlag() pairFlag {
+	return pairFlag{
+		&pair{false, false},
+	}
+}
+
+func (p pair) String() string {
+	switch p {
+	case pair{true, false}:
+		return "set"
+	case pair{false, true}:
+		return "unset"
+	default:
+		return "undef"
+	}
+}
+
+func (pf pairFlag) String() string {
+	return pf.pair.String()
+}
+
+func (pf pairFlag) Set(s string) error {
+	Log.Debugf("into Set: %s\n", s)
+	if s == "set" {
+		Log.Debug("setting")
+		v := pair{true, false}
+		*pf.pair = v
+		return nil
+	}
+
+	if s == "unset" {
+		Log.Debug("unsetting")
+		v := pair{false, true}
+		*pf.pair = v
+		return nil
+	}
+
+	Log.Debug(fmt.Sprintf("unacceptable value %s\n", s))
+	return errors.New("only `set` or `unset` are allowed")
+}
+
+// VERBS
+
+type SetMessageCmd struct{}
+
+func (sm SetMessageCmd) Func(args []string) error {
+	if Session.Info.Opts.Debug {
+		Log.Debug("enter set message")
+	}
+	flags := imaputils.NewFlagsStatus()
+	var opts imaputils.SetFlagsOpts
+	flagSeen := newPairFlag()
+	flagAnswered := newPairFlag()
+	flagFlagged := newPairFlag()
+	flagDeleted := newPairFlag()
+	flagDraft := newPairFlag()
+
+	flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
+
+	flagset.Var(&flagSeen, "seen", "Set or unset `seen` flag on message")
+	flagset.Var(&flagAnswered, "answered", "Set or unset `answered` flag on message")
+	flagset.Var(&flagFlagged, "flagged", "Set or unset `flagged` flag on message")
+	flagset.Var(&flagDeleted, "deleted", "Set or unset `deleted` flag on message")
+	flagset.Var(&flagDraft, "draft", "Set or unset `draft` flag on message")
+
+	flagset.Usage = func() {
+		sm.Help(os.Stdout, flagset)
+	}
+	flagset.Parse(args[1:])
+
+	subArgs := flagset.Args()
+	if len(subArgs) < 2 {
+		Log.Debugf("Too few arguments: %s\n", subArgs)
+		sm.Help(os.Stderr, flagset)
+		os.Exit(1)
+	}
+
+	opts.Mailbox = subArgs[0]
+	seq := new(imap.SeqSet)
+	for _, msgId := range subArgs[1:] {
+		if err := parseMessageSeq(seq, msgId); err != nil {
+			return err
+		}
+	}
+	opts.MessageSeq = seq
+	opts.Debug = Session.Info.Opts.Debug
+
+	if err := assignFlag(*flagSeen.pair, flags.SetSeen, flags.UnsetSeen); err != nil {
+		return err
+	}
+	if err := assignFlag(*flagAnswered.pair, flags.SetAnswered, flags.UnsetAnswered); err != nil {
+		return err
+	}
+	if err := assignFlag(*flagFlagged.pair, flags.SetFlagged, flags.UnsetFlagged); err != nil {
+		return err
+	}
+	if err := assignFlag(*flagDeleted.pair, flags.SetDeleted, flags.UnsetDeleted); err != nil {
+		return err
+	}
+	if err := assignFlag(*flagDraft.pair, flags.SetDraft, flags.UnsetDraft); err != nil {
+		return err
+	}
+
+	Log.Debugf("Setting flags: %s\n", flags)
+	opts.Flags = flags
+
+	return imaputils.SetFlags(Session.Config, &opts)
+}
+
+func (sm SetMessageCmd) Help(w io.Writer, set *flag.FlagSet) {
+	fmt.Fprintf(w, "USAGE: %s set message [opts] MAILBOX MESSAGE_ID_OR_RANGE [MESSAGE_ID_OR_RANGE [MESSAGE_ID_OR_RANGE ...]]\n", Session.Info.Name)
+	fmt.Fprintln(w, "\nMESSAGE_ID_OR_RANGE being a single message id or a git-like range")
+	fmt.Fprintln(w, "Example:")
+	fmt.Fprintln(w, "\t12345\t- A single message")
+	fmt.Fprintln(w, "\t123...258\t- A range from id 123 to id 258")
+	fmt.Fprintf(w, "\nOPTS:\n")
+	set.PrintDefaults()
+}
+
+func parseMessageSeq(seq *imap.SeqSet, data string) error {
+	if strings.Contains(data, "..") {
+		split := strings.Split(data, "..")
+		start, err := strconv.ParseUint(split[0], 10, 0)
+		if err != nil {
+			return err
+		}
+		stop, err := strconv.ParseUint(split[2], 10, 0)
+		if err != nil {
+			return err
+		}
+		seq.AddRange(uint32(start), uint32(stop))
+	} else {
+		msg, err := strconv.ParseUint(data, 10, 0)
+		if err != nil {
+			return err
+		}
+		seq.AddNum(uint32(msg))
+	}
+	return nil
+}
+
+func assignFlag(flagOp pair, setCallback, unsetCallback func()) error {
+	switch flagOp {
+	case pair{true, false}:
+		setCallback()
+	case pair{false, true}:
+		unsetCallback()
+	default:
+		return nil
+	}
+	return nil
+}

+ 233 - 0
imaputils/modify.go

@@ -0,0 +1,233 @@
+package imaputils
+
+import (
+	"errors"
+	"fmt"
+
+	"git.lattuga.net/blallo/papero/config"
+	"github.com/emersion/go-imap"
+)
+
+type tristate int
+
+const (
+	undef   tristate = 0
+	doSet   tristate = 1
+	doUnset tristate = 2
+)
+
+func (t tristate) IsSet() bool {
+	switch t {
+	case undef:
+		return false
+	case doSet:
+		return true
+	case doUnset:
+		return false
+	default:
+		panic(errors.New(fmt.Sprintf("unexpected tristate value %d", t)))
+	}
+}
+
+func (t tristate) IsUnset() bool {
+	switch t {
+	case undef:
+		return false
+	case doSet:
+		return false
+	case doUnset:
+		return true
+	default:
+		panic(errors.New(fmt.Sprintf("unexpected tristate value %d", t)))
+	}
+}
+
+func (t tristate) String() string {
+	switch t {
+	case undef:
+		return "undef"
+	case doSet:
+		return "set"
+	case doUnset:
+		return "unset"
+	}
+	panic("unexpected value for tristate")
+}
+
+type FlagsStatus struct {
+	FlagSeen     tristate
+	FlagAnswered tristate
+	FlagFlagged  tristate
+	FlagDeleted  tristate
+	FlagDraft    tristate
+	//FlagUnknown  bool
+}
+
+func NewFlagsStatus() *FlagsStatus {
+	return &FlagsStatus{
+		undef,
+		undef,
+		undef,
+		undef,
+		undef,
+	}
+}
+
+func (f *FlagsStatus) IntoSetFlagList() []interface{} {
+	var result []interface{}
+	switch {
+	case f.FlagSeen.IsSet():
+		result = append(result, imap.SeenFlag)
+	case f.FlagAnswered.IsSet():
+		result = append(result, imap.AnsweredFlag)
+	case f.FlagFlagged.IsSet():
+		result = append(result, imap.FlaggedFlag)
+	case f.FlagDeleted.IsSet():
+		result = append(result, imap.DeletedFlag)
+	case f.FlagDraft.IsSet():
+		result = append(result, imap.DraftFlag)
+	}
+	return result
+}
+
+func (f *FlagsStatus) IntoUnsetFlagList() []interface{} {
+	var result []interface{}
+	switch {
+	case f.FlagSeen.IsUnset():
+		result = append(result, imap.SeenFlag)
+	case f.FlagAnswered.IsUnset():
+		result = append(result, imap.AnsweredFlag)
+	case f.FlagFlagged.IsUnset():
+		result = append(result, imap.FlaggedFlag)
+	case f.FlagDeleted.IsUnset():
+		result = append(result, imap.DeletedFlag)
+	case f.FlagDraft.IsUnset():
+		result = append(result, imap.DraftFlag)
+	}
+	return result
+}
+
+func (f *FlagsStatus) WillSet() bool {
+	switch {
+	case f.FlagSeen.IsSet():
+		return true
+	case f.FlagAnswered.IsSet():
+		return true
+	case f.FlagFlagged.IsSet():
+		return true
+	case f.FlagDeleted.IsSet():
+		return true
+	case f.FlagDraft.IsSet():
+		return true
+	}
+	return false
+}
+
+func (f *FlagsStatus) WillUnset() bool {
+	switch {
+	case f.FlagSeen.IsUnset():
+		return true
+	case f.FlagAnswered.IsUnset():
+		return true
+	case f.FlagFlagged.IsUnset():
+		return true
+	case f.FlagDeleted.IsUnset():
+		return true
+	case f.FlagDraft.IsUnset():
+		return true
+	}
+	return false
+}
+
+func (f *FlagsStatus) SetSeen() {
+	f.FlagSeen = doSet
+}
+
+func (f *FlagsStatus) UnsetSeen() {
+	f.FlagSeen = doUnset
+}
+
+func (f *FlagsStatus) SetAnswered() {
+	f.FlagAnswered = doSet
+}
+
+func (f *FlagsStatus) UnsetAnswered() {
+	f.FlagAnswered = doUnset
+}
+
+func (f *FlagsStatus) SetFlagged() {
+	f.FlagFlagged = doSet
+}
+
+func (f *FlagsStatus) UnsetFlagged() {
+	f.FlagFlagged = doUnset
+}
+
+func (f *FlagsStatus) SetDeleted() {
+	f.FlagDeleted = doSet
+}
+
+func (f *FlagsStatus) UnsetDeleted() {
+	f.FlagDeleted = doUnset
+}
+
+func (f *FlagsStatus) SetDraft() {
+	f.FlagDraft = doSet
+}
+
+func (f *FlagsStatus) UnsetDraft() {
+	f.FlagDraft = doUnset
+}
+
+//func (f *FlagsStatus) SetUnknown() {
+//	f.FlagUnknown = doSet
+//}
+
+//func (f *FlagsStatus) UnsetUnknown() {
+//	f.FlagUnknown = doUnset
+//}
+
+type SetFlagsOpts struct {
+	Mailbox    string
+	MessageSeq *imap.SeqSet
+	Flags      *FlagsStatus
+	Debug      bool
+}
+
+// SetFlags changes the flags on the specified message in the specified mailbox.
+// First it adds the new flags set, then in removes the flags explicitly unset.
+// Unfortunately, this operation is not atomic.
+func SetFlags(conf *config.AccountData, opts *SetFlagsOpts) error {
+	conn := NewConnection(conf)
+
+	err := conn.Start(opts.Debug)
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+
+	_, err = conn.client.Select(opts.Mailbox, false)
+	if err != nil {
+		return err
+	}
+
+	if opts.Flags.WillSet() {
+		item := imap.FormatFlagsOp(imap.AddFlags, true)
+		flags := opts.Flags.IntoSetFlagList()
+		err = conn.client.Store(opts.MessageSeq, item, flags, nil)
+		if err != nil {
+			return err
+		}
+	}
+
+	if opts.Flags.WillUnset() {
+		item := imap.FormatFlagsOp(imap.RemoveFlags, true)
+		flags := opts.Flags.IntoUnsetFlagList()
+		err = conn.client.Store(opts.MessageSeq, item, flags, nil)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}