summaryrefslogtreecommitdiff
path: root/worker/imap/idler.go
diff options
context:
space:
mode:
authorKoni Marti <koni.marti@gmail.com>2022-04-30 01:08:55 +0200
committerRobin Jarry <robin@jarry.cc>2022-05-04 14:07:15 +0200
commit397a6f267f41c501f28d3adb9d641a9283af474f (patch)
tree9e30042b2415ed12a22f6be1eb63a00406630957 /worker/imap/idler.go
parent4d75137b20664cbdf90159c78d1fbf78779f3416 (diff)
downloadaerc-397a6f267f41c501f28d3adb9d641a9283af474f.zip
imap: manage idle mode with an idler
Untangle the idle functionality from the message handling routine. Wait for the idle mode to properly exit every time to ensure a consistent imap state. Timeout when hanging in idle mode and inform the ui. Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
Diffstat (limited to 'worker/imap/idler.go')
-rw-r--r--worker/imap/idler.go149
1 files changed, 149 insertions, 0 deletions
diff --git a/worker/imap/idler.go b/worker/imap/idler.go
new file mode 100644
index 0000000..e9aecfd
--- /dev/null
+++ b/worker/imap/idler.go
@@ -0,0 +1,149 @@
+package imap
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "git.sr.ht/~rjarry/aerc/logging"
+ "git.sr.ht/~rjarry/aerc/worker/types"
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/client"
+)
+
+var (
+ errIdleTimeout = fmt.Errorf("idle timeout")
+ errIdleModeHangs = fmt.Errorf("idle mode hangs; waiting to reconnect")
+)
+
+// idler manages the idle mode of the imap server. Enter idle mode if there's
+// no other task and leave idle mode when a new task arrives. Idle mode is only
+// used when the client is ready and connected. After a connection loss, make
+// sure that idling returns gracefully and the worker remains responsive.
+type idler struct {
+ sync.Mutex
+ config imapConfig
+ client *imapClient
+ worker *types.Worker
+ stop chan struct{}
+ done chan error
+ waiting bool
+ idleing bool
+}
+
+func newIdler(cfg imapConfig, w *types.Worker) *idler {
+ return &idler{config: cfg, worker: w, done: make(chan error)}
+}
+
+func (i *idler) SetClient(c *imapClient) {
+ i.Lock()
+ i.client = c
+ i.Unlock()
+}
+
+func (i *idler) setWaiting(wait bool) {
+ i.Lock()
+ i.waiting = wait
+ i.Unlock()
+}
+
+func (i *idler) isWaiting() bool {
+ i.Lock()
+ defer i.Unlock()
+ return i.waiting
+}
+
+func (i *idler) isReady() bool {
+ i.Lock()
+ defer i.Unlock()
+ return (!i.waiting && i.client != nil &&
+ i.client.State() == imap.SelectedState)
+}
+
+func (i *idler) Start() {
+ if i.isReady() {
+ i.stop = make(chan struct{})
+ go func() {
+ defer logging.PanicHandler()
+ i.idleing = true
+ i.log("=>(idle)")
+ now := time.Now()
+ err := i.client.Idle(i.stop,
+ &client.IdleOptions{
+ LogoutTimeout: 0,
+ PollInterval: 0,
+ })
+ i.idleing = false
+ i.done <- err
+ i.log("elapsed ideling time:", time.Since(now))
+ }()
+ } else if i.isWaiting() {
+ i.log("not started: wait for idle to exit")
+ } else {
+ i.log("not started: client not ready")
+ }
+}
+
+func (i *idler) Stop() error {
+ var reterr error
+ if i.isReady() {
+ close(i.stop)
+ select {
+ case err := <-i.done:
+ if err == nil {
+ i.log("<=(idle)")
+ } else {
+ i.log("<=(idle) with err:", err)
+ }
+ reterr = nil
+ case <-time.After(i.config.idle_timeout):
+ i.log("idle err (timeout); waiting in background")
+
+ i.log("disconnect done->")
+ i.worker.PostMessage(&types.Done{
+ Message: types.RespondTo(&types.Disconnect{}),
+ }, nil)
+
+ i.waitOnIdle()
+
+ reterr = errIdleTimeout
+ }
+ } else if i.isWaiting() {
+ i.log("not stopped: still idleing/hanging")
+ reterr = errIdleModeHangs
+ } else {
+ i.log("not stopped: client not ready")
+ reterr = nil
+ }
+ return reterr
+}
+
+func (i *idler) waitOnIdle() {
+ i.setWaiting(true)
+ i.log("wait for idle in background")
+ go func() {
+ defer logging.PanicHandler()
+ select {
+ case err := <-i.done:
+ if err == nil {
+ i.log("<=(idle) waited")
+ i.log("connect done->")
+ i.worker.PostMessage(&types.Done{
+ Message: types.RespondTo(&types.Connect{}),
+ }, nil)
+ } else {
+ i.log("<=(idle) waited; with err:", err)
+ }
+ i.setWaiting(false)
+ i.stop = make(chan struct{})
+ i.log("restart")
+ i.Start()
+ return
+ }
+ }()
+}
+
+func (i *idler) log(args ...interface{}) {
+ header := fmt.Sprintf("idler (%p) [idle:%t,wait:%t]", i, i.idleing, i.waiting)
+ i.worker.Logger.Println(append([]interface{}{header}, args...)...)
+}