mirror of https://git.sr.ht/~rjarry/aerc
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
2.7 KiB
Go
133 lines
2.7 KiB
Go
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
|
|
}
|
|
}
|