diff --git a/go.mod b/go.mod index 2fb4ac8..74ad22d 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/emersion/go-message v0.10.4 github.com/emersion/go-sasl v0.0.0-20190704090222-36b50694675c github.com/emersion/go-smtp v0.11.1 + github.com/fsnotify/fsnotify v1.4.7 github.com/gdamore/tcell v1.1.2 github.com/go-ini/ini v1.42.0 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf diff --git a/go.sum b/go.sum index 31f0d5e..79628de 100644 --- a/go.sum +++ b/go.sum @@ -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-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg= 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/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/go-ini/ini v1.42.0 h1:TWr1wGj35+UiWHlBA8er89seFXxzwFn11spilrrj+38= diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go index a5416cc..1e68a2e 100644 --- a/worker/maildir/worker.go +++ b/worker/maildir/worker.go @@ -8,6 +8,7 @@ import ( "path/filepath" "github.com/emersion/go-maildir" + "github.com/fsnotify/fsnotify" "git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/worker/types" @@ -20,31 +21,68 @@ type Worker struct { c *Container selected *maildir.Dir worker *types.Worker + watcher *fsnotify.Watcher } // NewWorker creates a new maildir worker with the provided worker. -func NewWorker(worker *types.Worker) *Worker { - return &Worker{worker: worker} +func NewWorker(worker *types.Worker) (*Worker, error) { + 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. func (w *Worker) Run() { for { - action := <-w.worker.Actions - msg := w.worker.ProcessAction(action) - if err := w.handleMessage(msg); err == errUnsupported { - w.worker.PostMessage(&types.Unsupported{ - Message: types.RespondTo(msg), - }, nil) - } else if err != nil { - w.worker.PostMessage(&types.Error{ - Message: types.RespondTo(msg), - Error: err, - }, nil) + 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) + if err := w.handleMessage(msg); err == errUnsupported { + w.worker.PostMessage(&types.Unsupported{ + Message: types.RespondTo(msg), + }, nil) + } else if err != nil { + w.worker.PostMessage(&types.Error{ + Message: types.RespondTo(msg), + Error: err, + }, 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) { w.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil) } @@ -139,11 +177,28 @@ func (w *Worker) handleListDirectories(msg *types.ListDirectories) error { func (w *Worker) handleOpenDirectory(msg *types.OpenDirectory) error { defer w.done(msg) 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) if err != nil { return err } 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?? info := &types.DirectoryInfo{ Info: &models.DirectoryInfo{ diff --git a/worker/worker.go b/worker/worker.go index dd14a23..a37927e 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -29,7 +29,11 @@ func NewWorker(source string, logger *log.Logger) (*types.Worker, error) { case "imaps": worker.Backend = imap.NewIMAPWorker(worker) 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: return nil, fmt.Errorf("Unknown backend %s", u.Scheme) }