cache: fetch flags from UI

When cached headers are fetched, an action is posted back to the Worker
to immediately fetch the flags for the message from the server (we can't
know the flags state, therefore it's not cached). When scrolling, a lag
occurs when loading cached headers because the n+1 message has to wait
for the flag request to return before the cached headers are retrieved.

Collect the message UIDs in the UI that need flags, and fetch them based
off a debounce timer in a single request. Post the action from the UI to
eliminate an (ugly) go routine in the worker.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
Tim Culverhouse 2022-09-19 19:49:15 -05:00 committed by Robin Jarry
parent a91009edf7
commit 9fdc7acf5b
3 changed files with 29 additions and 11 deletions

View File

@ -48,6 +48,10 @@ type MessageStore struct {
pendingHeaders map[uint32]interface{} pendingHeaders map[uint32]interface{}
worker *types.Worker worker *types.Worker
needsFlags []uint32
fetchFlagsDebounce *time.Timer
fetchFlagsDelay time.Duration
triggerNewEmail func(*models.MessageInfo) triggerNewEmail func(*models.MessageInfo)
triggerDirectoryChange func() triggerDirectoryChange func()
@ -91,6 +95,9 @@ func NewMessageStore(worker *types.Worker,
pendingHeaders: make(map[uint32]interface{}), pendingHeaders: make(map[uint32]interface{}),
worker: worker, worker: worker,
needsFlags: []uint32{},
fetchFlagsDelay: 50 * time.Millisecond,
triggerNewEmail: triggerNewEmail, triggerNewEmail: triggerNewEmail,
triggerDirectoryChange: triggerDirectoryChange, triggerDirectoryChange: triggerDirectoryChange,
@ -251,6 +258,10 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
} else if msg.Info.Envelope != nil { } else if msg.Info.Envelope != nil {
store.Messages[msg.Info.Uid] = msg.Info store.Messages[msg.Info.Uid] = msg.Info
} }
if msg.NeedsFlags {
store.needsFlags = append(store.needsFlags, msg.Info.Uid)
store.fetchFlags()
}
seen := false seen := false
recent := false recent := false
for _, flag := range msg.Info.Flags { for _, flag := range msg.Info.Flags {
@ -752,3 +763,15 @@ func (store *MessageStore) Capabilities() *models.Capabilities {
func (store *MessageStore) SelectedIndex() int { func (store *MessageStore) SelectedIndex() int {
return store.FindIndexByUid(store.selectedUid) return store.FindIndexByUid(store.selectedUid)
} }
func (store *MessageStore) fetchFlags() {
if store.fetchFlagsDebounce != nil {
store.fetchFlagsDebounce.Stop()
}
store.fetchFlagsDebounce = time.AfterFunc(store.fetchFlagsDelay, func() {
store.worker.PostAction(&types.FetchMessageFlags{
Uids: store.needsFlags,
}, nil)
store.needsFlags = []uint32{}
})
}

View File

@ -85,7 +85,7 @@ func (w *IMAPWorker) cacheHeader(mi *models.MessageInfo) {
func (w *IMAPWorker) getCachedHeaders(msg *types.FetchMessageHeaders) []uint32 { func (w *IMAPWorker) getCachedHeaders(msg *types.FetchMessageHeaders) []uint32 {
logging.Debugf("Retrieving headers from cache: %v", msg.Uids) logging.Debugf("Retrieving headers from cache: %v", msg.Uids)
var need, found []uint32 var need []uint32
uv := fmt.Sprintf("%d", w.selected.UidValidity) uv := fmt.Sprintf("%d", w.selected.UidValidity)
for _, uid := range msg.Uids { for _, uid := range msg.Uids {
u := fmt.Sprintf("%d", uid) u := fmt.Sprintf("%d", uid)
@ -118,17 +118,11 @@ func (w *IMAPWorker) getCachedHeaders(msg *types.FetchMessageHeaders) []uint32 {
Uid: ch.Uid, Uid: ch.Uid,
RFC822Headers: hdr, RFC822Headers: hdr,
} }
found = append(found, uid)
logging.Debugf("located cached header %s.%s", uv, u) logging.Debugf("located cached header %s.%s", uv, u)
w.worker.PostMessage(&types.MessageInfo{ w.worker.PostMessage(&types.MessageInfo{
Message: types.RespondTo(msg), Message: types.RespondTo(msg),
Info: mi, Info: mi,
}, nil) NeedsFlags: true,
}
if len(found) > 0 {
// Post in a separate goroutine to prevent deadlocking
go w.worker.PostAction(&types.FetchMessageFlags{
Uids: found,
}, nil) }, nil)
} }
return need return need

View File

@ -212,7 +212,8 @@ type SearchResults struct {
type MessageInfo struct { type MessageInfo struct {
Message Message
Info *models.MessageInfo Info *models.MessageInfo
NeedsFlags bool
} }
type FullMessage struct { type FullMessage struct {