~sircmpwn/aerc

11f0a7267fd1a9d1c6dd55e1dc044b8ed639bbc0 — Drew DeVault 2 years ago 0f8b7a1
Implement message store side of message fetching
4 files changed, 58 insertions(+), 21 deletions(-)

M widgets/account.go
M widgets/msglist.go
M worker/imap/open.go
M worker/types/messages.go
M widgets/account.go => widgets/account.go +2 -2
@@ 58,7 58,7 @@ func NewAccountView(conf *config.AccountConfig,
	dirlist := NewDirectoryList(conf, logger, worker)
	grid.AddChild(ui.NewBordered(dirlist, ui.BORDER_RIGHT)).Span(2, 1)

	msglist := NewMessageList(logger, worker)
	msglist := NewMessageList(logger)
	grid.AddChild(msglist).At(0, 1)

	acct := &AccountView{


@@ 183,7 183,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
		if store, ok := acct.msgStores[msg.Name]; ok {
			store.Update(msg)
		} else {
			acct.msgStores[msg.Name] = NewMessageStore(msg)
			acct.msgStores[msg.Name] = NewMessageStore(acct.worker, msg)
		}
	case *types.DirectoryContents:
		store := acct.msgStores[acct.dirlist.selected]

M widgets/msglist.go => widgets/msglist.go +52 -10
@@ 3,6 3,7 @@ package widgets
import (
	"log"

	"github.com/emersion/go-imap"
	"github.com/gdamore/tcell"

	"git.sr.ht/~sircmpwn/aerc2/config"


@@ 12,20 13,52 @@ import (

type MessageStore struct {
	DirInfo  types.DirectoryInfo
	Messages map[uint64]*types.MessageInfo
	Messages map[uint32]*types.MessageInfo
	// Map of uids we've asked the worker to fetch
	onUpdate       func(store *MessageStore)
	pendingBodies  map[uint32]interface{}
	pendingHeaders map[uint32]interface{}
	worker         *types.Worker
}

func NewMessageStore(dirInfo *types.DirectoryInfo) *MessageStore {
	return &MessageStore{DirInfo: *dirInfo}
func NewMessageStore(worker *types.Worker,
	dirInfo *types.DirectoryInfo) *MessageStore {

	return &MessageStore{
		DirInfo: *dirInfo,

		pendingBodies:  make(map[uint32]interface{}),
		pendingHeaders: make(map[uint32]interface{}),
		worker:         worker,
	}
}

func (store *MessageStore) FetchHeaders(uids []uint32) {
	// TODO: this could be optimized by pre-allocating toFetch and trimming it
	// at the end. In practice we expect to get most messages back in one frame.
	var toFetch imap.SeqSet
	for _, uid := range uids {
		if _, ok := store.pendingHeaders[uid]; !ok {
			toFetch.AddNum(uint32(uid))
			store.pendingHeaders[uid] = nil
		}
	}
	if !toFetch.Empty() {
		store.worker.PostAction(&types.FetchMessageHeaders{
			Uids: toFetch,
		}, nil)
	}
}

func (store *MessageStore) Update(msg types.WorkerMessage) {
	update := false
	switch msg := msg.(type) {
	case *types.DirectoryInfo:
		store.DirInfo = *msg
		update = true
		break
	case *types.DirectoryContents:
		newMap := make(map[uint64]*types.MessageInfo)
		newMap := make(map[uint32]*types.MessageInfo)
		for _, uid := range msg.Uids {
			if msg, ok := store.Messages[uid]; ok {
				newMap[uid] = msg


@@ 34,11 67,23 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
			}
		}
		store.Messages = newMap
		update = true
		break
	case *types.MessageInfo:
		store.Messages[msg.Uid] = msg
		if _, ok := store.pendingHeaders[msg.Uid]; msg.Envelope != nil && ok {
			delete(store.pendingHeaders, msg.Uid)
		}
		update = true
		break
	}
	if update && store.onUpdate != nil {
		store.onUpdate(store)
	}
}

func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) {
	store.onUpdate = fn
}

type MessageList struct {


@@ 47,15 92,13 @@ type MessageList struct {
	onInvalidate func(d ui.Drawable)
	spinner      *Spinner
	store        *MessageStore
	worker       *types.Worker
}

// TODO: fish in config
func NewMessageList(logger *log.Logger, worker *types.Worker) *MessageList {
func NewMessageList(logger *log.Logger) *MessageList {
	ml := &MessageList{
		logger:  logger,
		spinner: NewSpinner(),
		worker:  worker,
	}
	ml.spinner.OnInvalidate(func(_ ui.Drawable) {
		ml.Invalidate()


@@ 84,7 127,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
	}

	var (
		needsHeaders []uint64
		needsHeaders []uint32
		row          int = 0
	)



@@ 102,12 145,11 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
	}

	if len(needsHeaders) != 0 {
		ml.store.FetchHeaders(needsHeaders)
		ml.spinner.Start()
	} else {
		ml.spinner.Stop()
	}

	// TODO: Fetch these messages
}

func (ml *MessageList) SetStore(store *MessageStore) {

M worker/imap/open.go => worker/imap/open.go +2 -7
@@ 29,7 29,7 @@ func (imapw *IMAPWorker) handleFetchDirectoryContents(
	go func() {
		seqSet := &imap.SeqSet{}
		seqSet.AddRange(1, imapw.selected.Messages)
		uid32, err := imapw.client.UidSearch(&imap.SearchCriteria{
		uids, err := imapw.client.UidSearch(&imap.SearchCriteria{
			SeqNum: seqSet,
		})
		if err != nil {


@@ 38,12 38,7 @@ func (imapw *IMAPWorker) handleFetchDirectoryContents(
				Error:   err,
			}, nil)
		} else {
			imapw.worker.Logger.Printf("Found %d UIDs", len(uid32))
			var uids []uint64
			for _, uid := range uid32 {
				uids = append(uids,
					(uint64(imapw.selected.UidValidity)<<32)|uint64(uid))
			}
			imapw.worker.Logger.Printf("Found %d UIDs", len(uids))
			imapw.worker.PostMessage(&types.DirectoryContents{
				Message: types.RespondTo(msg),
				Uids:    uids,

M worker/types/messages.go => worker/types/messages.go +2 -2
@@ 110,7 110,7 @@ type DirectoryInfo struct {

type DirectoryContents struct {
	Message
	Uids []uint64
	Uids []uint32
}

type MessageInfo struct {


@@ 120,5 120,5 @@ type MessageInfo struct {
	InternalDate time.Time
	Mail         *mail.Message
	Size         uint32
	Uid          uint64
	Uid          uint32
}