blob: b43c026929b5960c63c21a071db626313c922078 (
plain) (
tree)
|
|
package imap
import (
"fmt"
"time"
"git.sr.ht/~rjarry/aerc/lib/log"
"git.sr.ht/~rjarry/aerc/worker/types"
"github.com/emersion/go-imap"
)
var errIdleTimeout = fmt.Errorf("idle timeout")
// 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 {
client *imapClient
debouncer *time.Timer
debounce time.Duration
timeout time.Duration
worker types.WorkerInteractor
stop chan struct{}
start chan struct{}
done chan error
}
func newIdler(cfg imapConfig, w types.WorkerInteractor, startIdler chan struct{}) *idler {
return &idler{
debouncer: nil,
debounce: cfg.idle_debounce,
timeout: cfg.idle_timeout,
worker: w,
stop: make(chan struct{}),
start: startIdler,
done: make(chan error),
}
}
func (i *idler) SetClient(c *imapClient) {
i.client = c
}
func (i *idler) ready() bool {
return (i.client != nil && i.client.State() == imap.SelectedState)
}
func (i *idler) Start() {
if !i.ready() {
return
}
select {
case <-i.stop:
// stop channel is nil (probably after a debounce), we don't
// want to close it
default:
close(i.stop)
}
// create new stop channel
i.stop = make(chan struct{})
// clear done channel
clearing := true
for clearing {
select {
case <-i.done:
continue
default:
clearing = false
}
}
i.worker.Tracef("idler (start): start idle after debounce")
i.debouncer = time.AfterFunc(i.debounce, func() {
i.start <- struct{}{}
i.worker.Tracef("idler (start): started")
})
}
func (i *idler) Execute() {
if !i.ready() {
return
}
// we need to call client.Idle in a goroutine since it is blocking call
// and we still want to receive messages
go func() {
defer log.PanicHandler()
start := time.Now()
err := i.client.Idle(i.stop, nil)
if err != nil {
i.worker.Errorf("idle returned error: %v", err)
}
i.worker.Tracef("idler (execute): idleing for %s", time.Since(start))
i.done <- err
}()
}
func (i *idler) Stop() error {
if !i.ready() {
return nil
}
select {
case <-i.stop:
i.worker.Debugf("idler (stop): idler already stopped?")
return nil
default:
close(i.stop)
}
if i.debouncer != nil {
if i.debouncer.Stop() {
i.worker.Tracef("idler (stop): debounced")
return nil
}
}
select {
case err := <-i.done:
i.worker.Tracef("idler (stop): idle stopped: %v", err)
return err
case <-time.After(i.timeout):
i.worker.Errorf("idler (stop): cannot stop idle (timeout)")
return errIdleTimeout
}
}
|