maildir: Watch for new messages

When a directory is opened, start watching its "new" subdirectory for
incoming messages using the fsnotify library. When creation events are
detected, run the Unseen routine to move the message from new to cur and
add new UIDs to the store, updating the UI's list of directory contents
as we go.
This commit is contained in:
Ben Burwell 2019-07-16 13:53:33 -04:00 committed by Drew DeVault
parent dfc048fe28
commit 6473848d87
4 changed files with 76 additions and 14 deletions

1
go.mod
View File

@ -14,6 +14,7 @@ require (
github.com/emersion/go-message v0.10.4 github.com/emersion/go-message v0.10.4
github.com/emersion/go-sasl v0.0.0-20190704090222-36b50694675c github.com/emersion/go-sasl v0.0.0-20190704090222-36b50694675c
github.com/emersion/go-smtp v0.11.1 github.com/emersion/go-smtp v0.11.1
github.com/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell v1.1.2 github.com/gdamore/tcell v1.1.2
github.com/go-ini/ini v1.42.0 github.com/go-ini/ini v1.42.0
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf

2
go.sum
View File

@ -39,6 +39,8 @@ github.com/emersion/go-smtp v0.11.1 h1:2IBWhU2zjrfOOmZal3qRxVsfYnf0rN+ccImZrjnMT
github.com/emersion/go-smtp v0.11.1/go.mod h1:CfUbM5NgspbOMHFEgCdoK2PVrKt48HAPtL8hnahwfYg= github.com/emersion/go-smtp v0.11.1/go.mod h1:CfUbM5NgspbOMHFEgCdoK2PVrKt48HAPtL8hnahwfYg=
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg= github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/go-ini/ini v1.42.0 h1:TWr1wGj35+UiWHlBA8er89seFXxzwFn11spilrrj+38= github.com/go-ini/ini v1.42.0 h1:TWr1wGj35+UiWHlBA8er89seFXxzwFn11spilrrj+38=

View File

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"github.com/emersion/go-maildir" "github.com/emersion/go-maildir"
"github.com/fsnotify/fsnotify"
"git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/models"
"git.sr.ht/~sircmpwn/aerc/worker/types" "git.sr.ht/~sircmpwn/aerc/worker/types"
@ -20,17 +21,31 @@ type Worker struct {
c *Container c *Container
selected *maildir.Dir selected *maildir.Dir
worker *types.Worker worker *types.Worker
watcher *fsnotify.Watcher
} }
// NewWorker creates a new maildir worker with the provided worker. // NewWorker creates a new maildir worker with the provided worker.
func NewWorker(worker *types.Worker) *Worker { func NewWorker(worker *types.Worker) (*Worker, error) {
return &Worker{worker: worker} watch, err := fsnotify.NewWatcher()
if err != nil {
return nil, fmt.Errorf("could not create file system watcher: %v", err)
}
return &Worker{worker: worker, watcher: watch}, nil
} }
// Run starts the worker's message handling loop. // Run starts the worker's message handling loop.
func (w *Worker) Run() { func (w *Worker) Run() {
for { for {
action := <-w.worker.Actions select {
case action := <-w.worker.Actions:
w.handleAction(action)
case ev := <-w.watcher.Events:
w.handleFSEvent(ev)
}
}
}
func (w *Worker) handleAction(action types.WorkerMessage) {
msg := w.worker.ProcessAction(action) msg := w.worker.ProcessAction(action)
if err := w.handleMessage(msg); err == errUnsupported { if err := w.handleMessage(msg); err == errUnsupported {
w.worker.PostMessage(&types.Unsupported{ w.worker.PostMessage(&types.Unsupported{
@ -43,6 +58,29 @@ func (w *Worker) Run() {
}, nil) }, nil)
} }
} }
func (w *Worker) handleFSEvent(ev fsnotify.Event) {
// we only care about files being created
if ev.Op != fsnotify.Create {
return
}
// if there's not a selected directory to rescan, ignore
if w.selected == nil {
return
}
_, err := w.selected.Unseen()
if err != nil {
w.worker.Logger.Printf("could not move new to cur : %v", err)
return
}
uids, err := w.c.UIDs(*w.selected)
if err != nil {
w.worker.Logger.Printf("could not scan UIDs: %v", err)
return
}
w.worker.PostMessage(&types.DirectoryContents{
Uids: uids,
}, nil)
} }
func (w *Worker) done(msg types.WorkerMessage) { func (w *Worker) done(msg types.WorkerMessage) {
@ -139,11 +177,28 @@ func (w *Worker) handleListDirectories(msg *types.ListDirectories) error {
func (w *Worker) handleOpenDirectory(msg *types.OpenDirectory) error { func (w *Worker) handleOpenDirectory(msg *types.OpenDirectory) error {
defer w.done(msg) defer w.done(msg)
w.worker.Logger.Printf("opening %s", msg.Directory) w.worker.Logger.Printf("opening %s", msg.Directory)
// remove existing watch path
if w.selected != nil {
prevDir := filepath.Join(string(*w.selected), "new")
if err := w.watcher.Remove(prevDir); err != nil {
return fmt.Errorf("could not unwatch previous directory: %v", err)
}
}
// open the directory
dir, err := w.c.OpenDirectory(msg.Directory) dir, err := w.c.OpenDirectory(msg.Directory)
if err != nil { if err != nil {
return err return err
} }
w.selected = &dir w.selected = &dir
// add watch path
newDir := filepath.Join(string(*w.selected), "new")
if err := w.watcher.Add(newDir); err != nil {
return fmt.Errorf("could not add watch to directory: %v", err)
}
// TODO: why does this need to be sent twice?? // TODO: why does this need to be sent twice??
info := &types.DirectoryInfo{ info := &types.DirectoryInfo{
Info: &models.DirectoryInfo{ Info: &models.DirectoryInfo{

View File

@ -29,7 +29,11 @@ func NewWorker(source string, logger *log.Logger) (*types.Worker, error) {
case "imaps": case "imaps":
worker.Backend = imap.NewIMAPWorker(worker) worker.Backend = imap.NewIMAPWorker(worker)
case "maildir": case "maildir":
worker.Backend = maildir.NewWorker(worker) if w, err := maildir.NewWorker(worker); err != nil {
return nil, fmt.Errorf("could not create maildir worker: %v", err)
} else {
worker.Backend = w
}
default: default:
return nil, fmt.Errorf("Unknown backend %s", u.Scheme) return nil, fmt.Errorf("Unknown backend %s", u.Scheme)
} }