summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorBen Burwell <ben@benburwell.com>2019-07-16 13:53:33 -0400
committerDrew DeVault <sir@cmpwn.com>2019-07-17 17:29:11 -0400
commit6473848d87de099b31812f20c7eb181d5c0c3c51 (patch)
treeda24e6f265cb1b01463e7538c7bdb20ff9e9e389
parentdfc048fe285939d9de3b5753f723c935d042cc2b (diff)
downloadaerc-6473848d87de099b31812f20c7eb181d5c0c3c51.tar.gz
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.
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--worker/maildir/worker.go81
-rw-r--r--worker/worker.go6
4 files changed, 76 insertions, 14 deletions
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)
 	}