about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--aerc.go3
-rw-r--r--lib/msgstore.go19
-rw-r--r--lib/ui/interfaces.go9
-rw-r--r--lib/ui/ui.go2
-rw-r--r--widgets/account.go18
-rw-r--r--widgets/aerc.go8
-rw-r--r--widgets/msglist.go24
7 files changed, 32 insertions, 51 deletions
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