diff options
author | Koni Marti <koni.marti@gmail.com> | 2022-04-30 01:08:55 +0200 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2022-05-04 14:07:15 +0200 |
commit | 397a6f267f41c501f28d3adb9d641a9283af474f (patch) | |
tree | 9e30042b2415ed12a22f6be1eb63a00406630957 /worker/imap/idler.go | |
parent | 4d75137b20664cbdf90159c78d1fbf78779f3416 (diff) | |
download | aerc-397a6f267f41c501f28d3adb9d641a9283af474f.tar.gz |
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.go | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/worker/imap/idler.go b/worker/imap/idler.go new file mode 100644 index 00000000..e9aecfd9 --- /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...)...) +} |