Added batching behavior for page building.
authorEgon Elbre <egonelbre@gmail.com>
Sun, 15 Dec 2013 15:19:22 +0000 (17:19 +0200)
committerspf13 <steve.francia@gmail.com>
Sat, 28 Dec 2013 18:46:52 +0000 (13:46 -0500)
Quite often file watcher gets many changes and each change triggered a
build. One build per second should be sufficient. Also added tracking for
new folders.

commands/hugo.go
watcher/batcher.go [new file with mode: 0644]

index 8cfbc734f50f926277dc7671d43b8fffd2542ab1..ee1d6f27ae75885115b16655c4fee822eeaba3d6 100644 (file)
@@ -15,11 +15,11 @@ package commands
 
 import (
        "fmt"
-       "github.com/howeyc/fsnotify"
        "github.com/mostafah/fsync"
        "github.com/spf13/cobra"
        "github.com/spf13/hugo/hugolib"
        "github.com/spf13/hugo/utils"
+       "github.com/spf13/hugo/watcher"
        "github.com/spf13/nitro"
        "os"
        "path/filepath"
@@ -155,7 +155,7 @@ func buildSite(watching ...bool) (err error) {
 }
 
 func NewWatcher(port int) error {
-       watcher, err := fsnotify.NewWatcher()
+       watcher, err := watcher.New(1 * time.Second)
        var wg sync.WaitGroup
 
        if err != nil {
@@ -166,15 +166,56 @@ func NewWatcher(port int) error {
        defer watcher.Close()
 
        wg.Add(1)
+
+       for _, d := range getDirList() {
+               if d != "" {
+                       _ = watcher.Watch(d)
+               }
+       }
+
        go func() {
                for {
                        select {
-                       case ev := <-watcher.Event:
+                       case evs := <-watcher.Event:
                                if Verbose {
-                                       fmt.Println(ev)
+                                       fmt.Println(evs)
+                               }
+
+                               static_changed := false
+                               dynamic_changed := false
+
+                               for _, ev := range evs {
+                                       ext := filepath.Ext(ev.Name)
+                                       istemp := strings.HasSuffix(ext, "~") || (ext == ".swp") || (ext == ".tmp")
+                                       if istemp {
+                                               continue
+                                       }
+                                       // renames are always followed with Create/Modify
+                                       if ev.IsRename() {
+                                               continue
+                                       }
+
+                                       isstatic := strings.HasPrefix(ev.Name, Config.GetAbsPath(Config.StaticDir))
+                                       static_changed = static_changed || isstatic
+                                       dynamic_changed = dynamic_changed || !isstatic
+
+                                       // add new directory to watch list
+                                       if s, err := os.Stat(ev.Name); err == nil && s.Mode().IsDir() {
+                                               if ev.IsCreate() {
+                                                       watcher.Watch(ev.Name)
+                                               }
+                                       }
+                               }
+
+                               if static_changed {
+                                       fmt.Println("Static file changed, syncing\n")
+                                       utils.CheckErr(copyStatic(), fmt.Sprintf("Error copying static files to %s", Config.GetAbsPath(Config.PublishDir)))
+                               }
+
+                               if dynamic_changed {
+                                       fmt.Println("Change detected, rebuilding site\n")
+                                       utils.StopOnErr(buildSite(true))
                                }
-                               watchChange(ev)
-                               // TODO add newly created directories to the watch list
                        case err := <-watcher.Error:
                                if err != nil {
                                        fmt.Println("error:", err)
@@ -183,12 +224,6 @@ func NewWatcher(port int) error {
                }
        }()
 
-       for _, d := range getDirList() {
-               if d != "" {
-                       _ = watcher.Watch(d)
-               }
-       }
-
        if port > 0 {
                go serve(port)
        }
@@ -196,22 +231,3 @@ func NewWatcher(port int) error {
        wg.Wait()
        return nil
 }
-
-func watchChange(ev *fsnotify.FileEvent) {
-       ext := filepath.Ext(ev.Name)
-       // ignore temp files
-       istemp := strings.HasSuffix(ext, "~") || (ext == ".swp") || (ext == ".tmp")
-       if istemp {
-               return
-       }
-
-       if strings.HasPrefix(ev.Name, Config.GetAbsPath(Config.StaticDir)) {
-               fmt.Println("Static file changed, syncing\n")
-               utils.CheckErr(copyStatic(), fmt.Sprintf("Error copying static files to %s", Config.GetAbsPath(Config.PublishDir)))
-       } else {
-               if !ev.IsRename() { // Rename is always accompanied by a create or modify
-                       fmt.Println("Change detected, rebuilding site\n")
-                       utils.StopOnErr(buildSite(true))
-               }
-       }
-}
diff --git a/watcher/batcher.go b/watcher/batcher.go
new file mode 100644 (file)
index 0000000..a22ad8b
--- /dev/null
@@ -0,0 +1,56 @@
+package watcher\r
+\r
+import (\r
+       "github.com/howeyc/fsnotify"\r
+       "time"\r
+)\r
+\r
+type Batcher struct {\r
+       *fsnotify.Watcher\r
+       interval time.Duration\r
+       done     chan struct{}\r
+\r
+       Event chan []*fsnotify.FileEvent // Events are returned on this channel\r
+}\r
+\r
+func New(interval time.Duration) (*Batcher, error) {\r
+       watcher, err := fsnotify.NewWatcher()\r
+\r
+       batcher := &Batcher{}\r
+       batcher.Watcher = watcher\r
+       batcher.interval = interval\r
+       batcher.done = make(chan struct{}, 1)\r
+       batcher.Event = make(chan []*fsnotify.FileEvent, 1)\r
+\r
+       if err == nil {\r
+               go batcher.run()\r
+       }\r
+\r
+       return batcher, err\r
+}\r
+\r
+func (b *Batcher) run() {\r
+       tick := time.Tick(b.interval)\r
+       evs := make([]*fsnotify.FileEvent, 0)\r
+OuterLoop:\r
+       for {\r
+               select {\r
+               case ev := <-b.Watcher.Event:\r
+                       evs = append(evs, ev)\r
+               case <-tick:\r
+                       if len(evs) == 0 {\r
+                               continue\r
+                       }\r
+                       b.Event <- evs\r
+                       evs = make([]*fsnotify.FileEvent, 0)\r
+               case <-b.done:\r
+                       break OuterLoop\r
+               }\r
+       }\r
+       close(b.done)\r
+}\r
+\r
+func (b *Batcher) Close() {\r
+       b.done <- struct{}{}\r
+       b.Watcher.Close()\r
+}\r