diff --git a/aerc.go b/aerc.go index a018f9e..6f794c6 100644 --- a/aerc.go +++ b/aerc.go @@ -99,6 +99,9 @@ func main() { defer ui.Close() for !ui.ShouldExit() { + for aerc.Tick() { + // Continue updating our internal state + } if !ui.Tick() { // ~60 FPS time.Sleep(16 * time.Millisecond) diff --git a/lib/msgstore.go b/lib/msgstore.go index fbffa0a..827d7cb 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -2,7 +2,6 @@ package lib import ( "io" - "sync" "time" "github.com/emersion/go-imap" @@ -12,8 +11,6 @@ import ( // Accesses to fields must be guarded by MessageStore.Lock/Unlock type MessageStore struct { - sync.Mutex - Deleted map[uint32]interface{} DirInfo types.DirectoryInfo Messages map[uint32]*types.MessageInfo @@ -49,9 +46,6 @@ func NewMessageStore(worker *types.Worker, func (store *MessageStore) FetchHeaders(uids []uint32, cb func(*types.MessageInfo)) { - store.Lock() - defer store.Unlock() - // 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 @@ -74,9 +68,6 @@ func (store *MessageStore) FetchHeaders(uids []uint32, } func (store *MessageStore) FetchFull(uids []uint32, cb func(io.Reader)) { - store.Lock() - defer store.Unlock() - // 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 @@ -134,8 +125,6 @@ func merge(to *types.MessageInfo, from *types.MessageInfo) { } func (store *MessageStore) Update(msg types.WorkerMessage) { - store.Lock() - update := false switch msg := msg.(type) { case *types.DirectoryInfo: @@ -201,8 +190,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { update = true } - store.Unlock() - if update { store.update() } @@ -220,7 +207,6 @@ func (store *MessageStore) update() { func (store *MessageStore) Delete(uids []uint32, cb func(msg types.WorkerMessage)) { - store.Lock() var set imap.SeqSet for _, uid := range uids { @@ -228,8 +214,6 @@ func (store *MessageStore) Delete(uids []uint32, store.Deleted[uid] = nil } - store.Unlock() - store.worker.PostAction(&types.DeleteMessages{Uids: set}, cb) store.update() } @@ -249,7 +233,6 @@ func (store *MessageStore) Copy(uids []uint32, dest string, func (store *MessageStore) Move(uids []uint32, dest string, cb func(msg types.WorkerMessage)) { - store.Lock() var set imap.SeqSet for _, uid := range uids { @@ -257,8 +240,6 @@ func (store *MessageStore) Move(uids []uint32, dest string, store.Deleted[uid] = nil } - store.Unlock() - store.worker.PostAction(&types.CopyMessages{ Destination: dest, Uids: set, diff --git a/lib/ui/interfaces.go b/lib/ui/interfaces.go index d27afe2..0cdffc1 100644 --- a/lib/ui/interfaces.go +++ b/lib/ui/interfaces.go @@ -4,12 +4,15 @@ import ( "github.com/gdamore/tcell" ) +// Drawable is a UI component that can draw. Unless specified, all methods must +// only be called from a single goroutine, the UI goroutine. type Drawable interface { - // Called when this renderable should draw itself + // Called when this renderable should draw itself. Draw(ctx *Context) - // Specifies a function to call when this cell needs to be redrawn + // Specifies a function to call when this cell needs to be redrawn. The + // callback may be called in any goroutine. OnInvalidate(callback func(d Drawable)) - // Invalidates the drawable + // Invalidates the drawable. This can be called from any goroutine. Invalidate() } diff --git a/lib/ui/ui.go b/lib/ui/ui.go index 9d9a5da..91a26da 100644 --- a/lib/ui/ui.go +++ b/lib/ui/ui.go @@ -10,7 +10,7 @@ import ( type UI struct { Content DrawableInteractive - exit atomic.Value + exit atomic.Value // bool ctx *Context screen tcell.Screen diff --git a/widgets/account.go b/widgets/account.go index afae1d2..431e7b8 100644 --- a/widgets/account.go +++ b/widgets/account.go @@ -65,13 +65,6 @@ func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig, } go worker.Backend.Run() - go func() { - for { - msg := <-worker.Messages - msg = worker.ProcessMessage(msg) - view.onMessage(msg) - } - }() worker.PostAction(&types.Configure{Config: acct}, nil) worker.PostAction(&types.Connect{}, view.connected) @@ -80,6 +73,17 @@ func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig, return view } +func (acct *AccountView) Tick() bool { + select { + case msg := <-acct.worker.Messages: + msg = acct.worker.ProcessMessage(msg) + acct.onMessage(msg) + return true + default: + return false + } +} + func (acct *AccountView) AccountConfig() *config.AccountConfig { return acct.acct } diff --git a/widgets/aerc.go b/widgets/aerc.go index e79e467..187eddb 100644 --- a/widgets/aerc.go +++ b/widgets/aerc.go @@ -65,6 +65,14 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger, return aerc } +func (aerc *Aerc) Tick() bool { + more := false + for _, acct := range aerc.accounts { + more = acct.Tick() || more + } + return more +} + func (aerc *Aerc) Children() []ui.Drawable { return aerc.grid.Children() } diff --git a/widgets/msglist.go b/widgets/msglist.go index 08f7ea4..ddfb92a 100644 --- a/widgets/msglist.go +++ b/widgets/msglist.go @@ -2,7 +2,6 @@ package widgets import ( "log" - "sync/atomic" "github.com/gdamore/tcell" @@ -20,7 +19,7 @@ type MessageList struct { scroll int selected int spinner *Spinner - store atomic.Value // *lib.MessageStore + store *lib.MessageStore } func NewMessageList(conf *config.AercConfig, logger *log.Logger) *MessageList { @@ -30,7 +29,6 @@ func NewMessageList(conf *config.AercConfig, logger *log.Logger) *MessageList { selected: 0, spinner: NewSpinner(), } - ml.store.Store((*lib.MessageStore)(nil)) ml.spinner.OnInvalidate(func(_ ui.Drawable) { ml.Invalidate() }) @@ -53,8 +51,6 @@ func (ml *MessageList) Draw(ctx *ui.Context) { return } - store.Lock() - var ( needsHeaders []uint32 row int = 0 @@ -94,8 +90,6 @@ func (ml *MessageList) Draw(ctx *ui.Context) { tcell.StyleDefault, "%s", msg) } - store.Unlock() - if len(needsHeaders) != 0 { store.FetchHeaders(needsHeaders, nil) ml.spinner.Start() @@ -113,13 +107,11 @@ func (ml *MessageList) storeUpdate(store *lib.MessageStore) { return } - store.Lock() if len(store.Uids) > 0 { for ml.selected >= len(store.Uids) { ml.Prev() } } - store.Unlock() ml.Invalidate() } @@ -129,7 +121,7 @@ func (ml *MessageList) SetStore(store *lib.MessageStore) { ml.scroll = 0 ml.selected = 0 } - ml.store.Store(store) + ml.store = store if store != nil { ml.spinner.Stop() store.OnUpdate(ml.storeUpdate) @@ -140,29 +132,21 @@ func (ml *MessageList) SetStore(store *lib.MessageStore) { } func (ml *MessageList) Store() *lib.MessageStore { - return ml.store.Load().(*lib.MessageStore) + return ml.store } func (ml *MessageList) Empty() bool { store := ml.Store() - store.Lock() - defer store.Unlock() - return store == nil || len(store.Uids) == 0 } func (ml *MessageList) Selected() *types.MessageInfo { store := ml.Store() - store.Lock() - defer store.Unlock() - return store.Messages[store.Uids[len(store.Uids)-ml.selected-1]] } func (ml *MessageList) Select(index int) { store := ml.Store() - store.Lock() - defer store.Unlock() ml.selected = index for ; ml.selected < 0; ml.selected = len(store.Uids) + ml.selected { @@ -181,8 +165,6 @@ func (ml *MessageList) Select(index int) { func (ml *MessageList) nextPrev(delta int) { store := ml.Store() - store.Lock() - defer store.Unlock() if store == nil || len(store.Uids) == 0 { return