~sircmpwn/aerc

b1eb7ad18d2e0bbeecaf61a58825bbc794ceb40c — Srivathsan Murali 2 months ago a31d184
Set AnsweredFlag on successful reply
M commands/compose/send.go => commands/compose/send.go +2 -0
@@ 244,6 244,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
				case *types.Done:
					aerc.PushStatus("Message sent.", 10*time.Second)
					r.Close()
					composer.SetSent()
					composer.Close()
				case *types.Error:
					aerc.PushError(" " + msg.Error.Error())


@@ 256,6 257,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
			w.Close()
		} else {
			aerc.PushStatus("Message sent.", 10*time.Second)
			composer.SetSent()
			composer.Close()
		}
	}()

M commands/msg/reply.go => commands/msg/reply.go +4 -0
@@ 157,6 157,10 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
			tab.Content.Invalidate()
		})

		composer.OnClose(func(c *widgets.Composer) {
			store.Answered([]uint32{msg.Uid}, c.Sent(), nil)
		})

		return nil
	}


M lib/msgstore.go => lib/msgstore.go +9 -0
@@ 342,6 342,15 @@ func (store *MessageStore) Read(uids []uint32, read bool,
	}, cb)
}

func (store *MessageStore) Answered(uids []uint32, answered bool,
	cb func(msg types.WorkerMessage)) {

	store.worker.PostAction(&types.AnsweredMessages{
		Answered: answered,
		Uids:     uids,
	}, cb)
}

func (store *MessageStore) Uids() []uint32 {
	if store.filter {
		return store.results

M widgets/compose.go => widgets/compose.go +9 -0
@@ 52,6 52,7 @@ type Composer struct {
	layout    HeaderLayout
	focusable []ui.MouseableDrawableInteractive
	focused   int
	sent      bool

	onClose []func(ti *Composer)



@@ 163,6 164,14 @@ func buildComposeHeader(conf *config.AercConfig, cmpl *completer.Completer,
	return layout, editors, focusable
}

func (c *Composer) SetSent() {
	c.sent = true
}

func (c *Composer) Sent() bool {
	return c.sent
}

// Note: this does not reload the editor. You must call this before the first
// Draw() call.
func (c *Composer) SetContents(reader io.Reader) *Composer {

M worker/imap/flags.go => worker/imap/flags.go +32 -0
@@ 44,6 44,38 @@ func (imapw *IMAPWorker) handleDeleteMessages(msg *types.DeleteMessages) {
	}
}

func (imapw *IMAPWorker) handleAnsweredMessages(msg *types.AnsweredMessages) {
	item := imap.FormatFlagsOp(imap.AddFlags, true)
	flags := []interface{}{imap.AnsweredFlag}
	if !msg.Answered {
		item = imap.FormatFlagsOp(imap.RemoveFlags, true)
		flags = []interface{}{imap.AnsweredFlag}
	}
	uids := toSeqSet(msg.Uids)
	emitErr := func(err error) {
		imapw.worker.PostMessage(&types.Error{
			Message: types.RespondTo(msg),
			Error:   err,
		}, nil)
	}
	if err := imapw.client.UidStore(uids, item, flags, nil); err != nil {
		emitErr(err)
		return
	}
	imapw.worker.PostAction(&types.FetchMessageHeaders{
		Uids: msg.Uids,
	}, func(_msg types.WorkerMessage) {
		switch m := _msg.(type) {
		case *types.Error:
			err := fmt.Errorf("handleAnsweredMessages: %v", m.Error)
			imapw.worker.Logger.Printf("could not fetch headers: %s", err)
			emitErr(err)
		case *types.Done:
			imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
		}
	})
}

func (imapw *IMAPWorker) handleReadMessages(msg *types.ReadMessages) {
	item := imap.FormatFlagsOp(imap.AddFlags, true)
	flags := []interface{}{imap.SeenFlag}

M worker/imap/worker.go => worker/imap/worker.go +2 -0
@@ 175,6 175,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
		w.handleDeleteMessages(msg)
	case *types.ReadMessages:
		w.handleReadMessages(msg)
	case *types.AnsweredMessages:
		w.handleAnsweredMessages(msg)
	case *types.CopyMessages:
		w.handleCopyMessages(msg)
	case *types.AppendMessage:

M worker/maildir/message.go => worker/maildir/message.go +20 -0
@@ 53,6 53,26 @@ func (m Message) SetFlags(flags []maildir.Flag) error {
	return m.dir.SetFlags(m.key, flags)
}

// MarkReplied either adds or removes the maildir.FlagReplied flag from the
// message.
func (m Message) MarkReplied(answered bool) error {
	flags, err := m.Flags()
	if err != nil {
		return fmt.Errorf("could not read previous flags: %v", err)
	}
	if answered {
		flags = append(flags, maildir.FlagReplied)
		return m.SetFlags(flags)
	}
	var newFlags []maildir.Flag
	for _, flag := range flags {
		if flag != maildir.FlagReplied {
			newFlags = append(newFlags, flag)
		}
	}
	return m.SetFlags(newFlags)
}

// MarkRead either adds or removes the maildir.FlagSeen flag from the message.
func (m Message) MarkRead(seen bool) error {
	flags, err := m.Flags()

M worker/maildir/worker.go => worker/maildir/worker.go +35 -0
@@ 195,6 195,8 @@ func (w *Worker) handleMessage(msg types.WorkerMessage) error {
		return w.handleDeleteMessages(msg)
	case *types.ReadMessages:
		return w.handleReadMessages(msg)
	case *types.AnsweredMessages:
		return w.handleAnsweredMessages(msg)
	case *types.CopyMessages:
		return w.handleCopyMessages(msg)
	case *types.AppendMessage:


@@ 438,6 440,39 @@ func (w *Worker) handleDeleteMessages(msg *types.DeleteMessages) error {
	return nil
}

func (w *Worker) handleAnsweredMessages(msg *types.AnsweredMessages) error {
	for _, uid := range msg.Uids {
		m, err := w.c.Message(*w.selected, uid)
		if err != nil {
			w.worker.Logger.Printf("could not get message: %v", err)
			w.err(msg, err)
			continue
		}
		if err := m.MarkReplied(msg.Answered); err != nil {
			w.worker.Logger.Printf(
				"could not mark message as answered: %v", err)
			w.err(msg, err)
			continue
		}
		info, err := m.MessageInfo()
		if err != nil {
			w.worker.Logger.Printf("could not get message info: %v", err)
			w.err(msg, err)
			continue
		}

		w.worker.PostMessage(&types.MessageInfo{
			Message: types.RespondTo(msg),
			Info:    info,
		}, nil)

		w.worker.PostMessage(&types.DirectoryInfo{
			Info: w.getDirectoryInfo(w.selectedName),
		}, nil)
	}
	return nil
}

func (w *Worker) handleReadMessages(msg *types.ReadMessages) error {
	for _, uid := range msg.Uids {
		m, err := w.c.Message(*w.selected, uid)

M worker/notmuch/message.go => worker/notmuch/message.go +33 -0
@@ 64,6 64,39 @@ func (m *Message) NewBodyPartReader(requestedParts []int) (io.Reader, error) {
	return lib.FetchEntityPartReader(msg, requestedParts)
}

// MarkAnswered either adds or removes the "replied" tag from the message.
func (m *Message) MarkAnswered(answered bool) error {
	haveReplied := false
	tags, err := m.Tags()
	if err != nil {
		return err
	}
	for _, t := range tags {
		if t == "replied" {
			haveReplied = true
			break
		}
	}
	if haveReplied == answered {
		// we already have the desired state
		return nil
	}

	if haveAnswered {
		err := m.RemoveTag("replied")
		if err != nil {
			return err
		}
		return nil
	}

	err = m.AddTag("replied")
	if err != nil {
		return err
	}
	return nil
}

// MarkRead either adds or removes the maildir.FlagSeen flag from the message.
func (m *Message) MarkRead(seen bool) error {
	haveUnread := false

M worker/notmuch/worker.go => worker/notmuch/worker.go +27 -0
@@ 363,6 363,33 @@ func (w *worker) handleFetchFullMessages(msg *types.FetchFullMessages) error {
	return nil
}

func (w *worker) handleAnsweredMessages(msg *types.AnsweredMessages) error {
	for _, uid := range msg.Uids {
		m, err := w.msgFromUid(uid)
		if err != nil {
			w.w.Logger.Printf("could not get message: %v", err)
			w.err(msg, err)
			continue
		}
		if err := m.MarkAnswered(msg.Answered); err != nil {
			w.w.Logger.Printf("could not mark message as answered: %v", err)
			w.err(msg, err)
			continue
		}
		err = w.emitMessageInfo(m, msg)
		if err != nil {
			w.w.Logger.Printf(err.Error())
			w.err(msg, err)
			continue
		}
	}
	if err := w.emitDirectoryInfo(w.currentQueryName); err != nil {
		w.w.Logger.Printf(err.Error())
	}
	w.done(msg)
	return nil
}

func (w *worker) handleReadMessages(msg *types.ReadMessages) error {
	for _, uid := range msg.Uids {
		m, err := w.msgFromUid(uid)

M worker/types/messages.go => worker/types/messages.go +6 -0
@@ 120,6 120,12 @@ type ReadMessages struct {
	Uids []uint32
}

type AnsweredMessages struct {
	Message
	Answered bool
	Uids     []uint32
}

type CopyMessages struct {
	Message
	Destination string