~sircmpwn/aerc

312a53e5ff721e0a29e34aaeceb0eece1203002d — Drew DeVault 2 years ago f3d3e0e
Implement :delete-message
A commands/delete-message.go => commands/delete-message.go +25 -0
@@ 0,0 1,25 @@
package commands

import (
	"errors"

	"git.sr.ht/~sircmpwn/aerc2/widgets"
)

func init() {
	Register("delete-message", DeleteMessage)
}

func DeleteMessage(aerc *widgets.Aerc, args []string) error {
	if len(args) != 1 {
		return errors.New("Usage: :delete-message")
	}
	acct := aerc.SelectedAccount()
	if acct == nil {
		return errors.New("No account selected")
	}
	store := acct.Messages().Store()
	msg := acct.Messages().Selected()
	store.Delete([]uint32{msg.Uid})
	return nil
}

M lib/msgstore.go => lib/msgstore.go +26 -3
@@ 1,6 1,8 @@
package lib

import (
	"fmt"

	"github.com/emersion/go-imap"

	"git.sr.ht/~sircmpwn/aerc2/worker/types"


@@ 53,7 55,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
	case *types.DirectoryInfo:
		store.DirInfo = *msg
		update = true
		break
	case *types.DirectoryContents:
		newMap := make(map[uint32]*types.MessageInfo)
		for _, uid := range msg.Uids {


@@ 66,7 67,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
		store.Messages = newMap
		store.Uids = msg.Uids
		update = true
		break
	case *types.MessageInfo:
		// TODO: merge message info into existing record, if applicable
		store.Messages[msg.Uid] = msg


@@ 74,7 74,22 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
			delete(store.pendingHeaders, msg.Uid)
		}
		update = true
		break
	case *types.MessagesDeleted:
		toDelete := make(map[uint32]interface{})
		for _, uid := range msg.Uids {
			toDelete[uid] = nil
			delete(store.Messages, uid)
		}
		uids := make([]uint32, len(store.Uids)-len(msg.Uids))
		j := 0
		for i, uid := range store.Uids {
			if _, deleted := toDelete[uid]; !deleted {
				uids[j] = store.Uids[i]
				j += 1
			}
		}
		store.Uids = uids
		update = true
	}
	if update && store.onUpdate != nil {
		store.onUpdate(store)


@@ 84,3 99,11 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) {
	store.onUpdate = fn
}

func (store *MessageStore) Delete(uids []uint32) {
	var set imap.SeqSet
	for _, uid := range uids {
		set.AddNum(uid)
	}
	store.worker.PostAction(&types.DeleteMessages{Uids: set}, nil)
}

M widgets/account.go => widgets/account.go +3 -0
@@ 176,6 176,9 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
	case *types.MessageInfo:
		store := acct.msgStores[acct.dirlist.selected]
		store.Update(msg)
	case *types.MessagesDeleted:
		store := acct.msgStores[acct.dirlist.selected]
		store.Update(msg)
	case *types.Error:
		acct.logger.Printf("%v", msg.Error)
		acct.host.SetStatus(fmt.Sprintf("%v", msg.Error)).

M widgets/msglist.go => widgets/msglist.go +20 -0
@@ 8,6 8,7 @@ import (
	"git.sr.ht/~sircmpwn/aerc2/config"
	"git.sr.ht/~sircmpwn/aerc2/lib"
	"git.sr.ht/~sircmpwn/aerc2/lib/ui"
	"git.sr.ht/~sircmpwn/aerc2/worker/types"
)

type MessageList struct {


@@ 98,6 99,16 @@ func (ml *MessageList) Height() int {
	return ml.height
}

func (ml *MessageList) storeUpdate(store *lib.MessageStore) {
	if ml.store != store {
		return
	}
	for ml.selected >= len(ml.store.Uids) {
		ml.Prev()
	}
	ml.Invalidate()
}

func (ml *MessageList) SetStore(store *lib.MessageStore) {
	if ml.store == store {
		ml.scroll = 0


@@ 106,12 117,21 @@ func (ml *MessageList) SetStore(store *lib.MessageStore) {
	ml.store = store
	if store != nil {
		ml.spinner.Stop()
		ml.store.OnUpdate(ml.storeUpdate)
	} else {
		ml.spinner.Start()
	}
	ml.Invalidate()
}

func (ml *MessageList) Store() *lib.MessageStore {
	return ml.store
}

func (ml *MessageList) Selected() *types.MessageInfo {
	return ml.store.Messages[ml.store.Uids[len(ml.store.Uids)-ml.selected-1]]
}

func (ml *MessageList) Select(index int) {
	ml.selected = index
	for ; ml.selected < 0; ml.selected = len(ml.store.Uids) + ml.selected {

M worker/imap/fetch.go => worker/imap/fetch.go +1 -0
@@ 25,6 25,7 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders(
		}()
		go func() {
			for msg := range messages {
				imapw.seqMap[msg.SeqNum-1] = msg.Uid
				imapw.worker.PostMessage(&types.MessageInfo{
					Envelope:     msg.Envelope,
					Flags:        msg.Flags,

A worker/imap/flags.go => worker/imap/flags.go +43 -0
@@ 0,0 1,43 @@
package imap

import (
	"github.com/emersion/go-imap"

	"git.sr.ht/~sircmpwn/aerc2/worker/types"
)

func (imapw *IMAPWorker) handleDeleteMessages(msg *types.DeleteMessages) {
	item := imap.FormatFlagsOp(imap.AddFlags, true)
	flags := []interface{}{imap.DeletedFlag}
	if err := imapw.client.UidStore(&msg.Uids, item, flags, nil); err != nil {
		imapw.worker.PostMessage(&types.Error{
			Message: types.RespondTo(msg),
			Error:   err,
		}, nil)
		return
	}
	var deleted []uint32
	ch := make(chan uint32)
	done := make(chan interface{})
	go func() {
		for seqNum := range ch {
			i := seqNum - 1
			deleted = append(deleted, imapw.seqMap[i])
			imapw.seqMap = append(imapw.seqMap[:i], imapw.seqMap[i+1:]...)
		}
		done <- nil
	}()
	if err := imapw.client.Expunge(ch); err != nil {
		imapw.worker.PostMessage(&types.Error{
			Message: types.RespondTo(msg),
			Error:   err,
		}, nil)
	} else {
		<-done
		imapw.worker.PostMessage(&types.MessagesDeleted{
			Message: types.RespondTo(msg),
			Uids:    deleted,
		}, nil)
		imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
	}
}

M worker/imap/open.go => worker/imap/open.go +1 -0
@@ 39,6 39,7 @@ func (imapw *IMAPWorker) handleFetchDirectoryContents(
			}, nil)
		} else {
			imapw.worker.Logger.Printf("Found %d UIDs", len(uids))
			imapw.seqMap = make([]uint32, len(uids))
			imapw.worker.PostMessage(&types.DirectoryContents{
				Message: types.RespondTo(msg),
				Uids:    uids,

M worker/imap/worker.go => worker/imap/worker.go +5 -1
@@ 33,12 33,14 @@ type IMAPWorker struct {
	selected imap.MailboxStatus
	updates  chan client.Update
	worker   *types.Worker
	// Map of sequence numbers to UIDs, index 0 is seq number 1
	seqMap []uint32
}

func NewIMAPWorker(worker *types.Worker) *IMAPWorker {
	return &IMAPWorker{
		worker:  worker,
		updates: make(chan client.Update, 50),
		worker:  worker,
	}
}



@@ 156,6 158,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
		w.handleFetchDirectoryContents(msg)
	case *types.FetchMessageHeaders:
		w.handleFetchMessageHeaders(msg)
	case *types.DeleteMessages:
		w.handleDeleteMessages(msg)
	default:
		return errUnsupported
	}

M worker/types/messages.go => worker/types/messages.go +10 -0
@@ 86,6 86,11 @@ type FetchMessageBodies struct {
	Uids imap.SeqSet
}

type DeleteMessages struct {
	Message
	Uids imap.SeqSet
}

// Messages

type CertificateApprovalRequest struct {


@@ 122,3 127,8 @@ type MessageInfo struct {
	Size         uint32
	Uid          uint32
}

type MessagesDeleted struct {
	Message
	Uids []uint32
}