diff --git a/lib/msgstore.go b/lib/msgstore.go new file mode 100644 index 0000000..79e1977 --- /dev/null +++ b/lib/msgstore.go @@ -0,0 +1,86 @@ +package lib + +import ( + "github.com/emersion/go-imap" + + "git.sr.ht/~sircmpwn/aerc2/worker/types" +) + +type MessageStore struct { + DirInfo types.DirectoryInfo + Messages map[uint32]*types.MessageInfo + // Ordered list of known UIDs + Uids []uint32 + // 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(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[uint32]*types.MessageInfo) + for _, uid := range msg.Uids { + if msg, ok := store.Messages[uid]; ok { + newMap[uid] = msg + } else { + newMap[uid] = nil + } + } + 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 + 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 +} diff --git a/widgets/account.go b/widgets/account.go index 8716b11..6919c0e 100644 --- a/widgets/account.go +++ b/widgets/account.go @@ -8,6 +8,7 @@ import ( "github.com/gdamore/tcell" "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" "git.sr.ht/~sircmpwn/aerc2/worker/types" @@ -23,7 +24,7 @@ type AccountView struct { onInvalidate func(d ui.Drawable) runCmd func(cmd string) error msglist *MessageList - msgStores map[string]*MessageStore + msgStores map[string]*lib.MessageStore pendingKeys []config.KeyStroke statusline *StatusLine statusbar *ui.Stack @@ -70,7 +71,7 @@ func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig, grid: grid, logger: logger, msglist: msglist, - msgStores: make(map[string]*MessageStore), + msgStores: make(map[string]*lib.MessageStore), runCmd: runCmd, statusbar: statusbar, statusline: statusline, @@ -226,7 +227,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(acct.worker, msg) + acct.msgStores[msg.Name] = lib.NewMessageStore(acct.worker, msg) } case *types.DirectoryContents: store := acct.msgStores[acct.dirlist.selected] diff --git a/widgets/msglist.go b/widgets/msglist.go index 5ba75a8..0fb919d 100644 --- a/widgets/msglist.go +++ b/widgets/msglist.go @@ -3,100 +3,20 @@ package widgets import ( "log" - "github.com/emersion/go-imap" "github.com/gdamore/tcell" "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 MessageStore struct { - DirInfo types.DirectoryInfo - Messages map[uint32]*types.MessageInfo - // Ordered list of known UIDs - Uids []uint32 - // 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(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[uint32]*types.MessageInfo) - for _, uid := range msg.Uids { - if msg, ok := store.Messages[uid]; ok { - newMap[uid] = msg - } else { - newMap[uid] = nil - } - } - 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 - 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 { conf *config.AercConfig logger *log.Logger onInvalidate func(d ui.Drawable) selected int spinner *Spinner - store *MessageStore + store *lib.MessageStore } // TODO: fish in config @@ -171,7 +91,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) { } } -func (ml *MessageList) SetStore(store *MessageStore) { +func (ml *MessageList) SetStore(store *lib.MessageStore) { ml.store = store if store != nil { ml.spinner.Stop()