From fc1eb2b9eb0f03d406de214c1bb14c955dcda4db Mon Sep 17 00:00:00 2001 From: "Aeneas Rekkas (arekkas)" Date: Thu, 22 Dec 2016 00:28:08 +0100 Subject: [PATCH] allow fallback to watching by polling --- .travis.yml | 8 +++- realize.go | 3 ++ settings/settings.go | 3 ++ watcher/cmd.go | 6 ++- watcher/watcher.go | 109 ++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 120 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 78a925b..c54278b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,10 @@ go: - tip matrix: allow_failures: - - go: tip \ No newline at end of file + - go: tip + +install: + - go get ./... + +script: + - go install . \ No newline at end of file diff --git a/realize.go b/realize.go index 6dbef9f..c683134 100644 --- a/realize.go +++ b/realize.go @@ -8,6 +8,7 @@ import ( w "github.com/tockins/realize/watcher" "gopkg.in/urfave/cli.v2" "os" + "time" ) const ( @@ -46,6 +47,8 @@ func init() { Settings: c.Settings{ Config: c.Config{ Flimit: 0, + Polling: false, + PollingInterval: time.Millisecond * 200, }, Resources: c.Resources{ Config: config, diff --git a/settings/settings.go b/settings/settings.go index fdd1212..f78c619 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -3,6 +3,7 @@ package settings import ( "gopkg.in/yaml.v2" "os" + "time" ) // Settings defines a group of general settings @@ -16,6 +17,8 @@ type Settings struct { // Config defines structural options type Config struct { Flimit uint64 `yaml:"flimit" json:"flimit"` + Polling bool `yaml:"polling" json:"polling"` + PollingInterval time.Duration `yaml:"polling_interval" json:"polling_interval"` } // Server settings, used for the web panel diff --git a/watcher/cmd.go b/watcher/cmd.go index da54349..86a7883 100644 --- a/watcher/cmd.go +++ b/watcher/cmd.go @@ -16,7 +16,11 @@ func (h *Blueprint) Run() error { wg.Add(len(h.Projects)) for k := range h.Projects { h.Projects[k].parent = h - go h.Projects[k].watching() + if h.Polling { + go h.Projects[k].watchByPolling() + } else { + go h.Projects[k].watching() + } } wg.Wait() return nil diff --git a/watcher/watcher.go b/watcher/watcher.go index 7a6451e..461b5b6 100644 --- a/watcher/watcher.go +++ b/watcher/watcher.go @@ -16,6 +16,97 @@ import ( "time" ) +type pollWatcher struct { + paths map[string]bool +} + +func (w *pollWatcher) isWatching(path string) bool { + a, b := w.paths[path] + return a && b +} + +func (w *pollWatcher) Add(path string) error { + if w.paths == nil { + w.paths = map[string]bool{} + } + w.paths[path] = true + return nil +} + +func (p *Project) watchByPolling() { + var wr sync.WaitGroup + var watcher = new(pollWatcher) + channel, exit := make(chan bool, 1), make(chan bool, 1) + p.path = p.Path + defer func() { + wg.Done() + }() + p.cmd(exit) + if err := p.walks(watcher); err != nil { + log.Fatalln(p.pname(p.Name, 2), ":", p.Red.Bold(err.Error())) + return + } + go p.routines(channel, &wr) + p.LastChangedOn = time.Now().Truncate(time.Second) + // waiting for an event + + var walk = func(changed string, info os.FileInfo, err error) error { + if err != nil { + return err + } else if !watcher.isWatching(changed) { + return nil + } else if !info.ModTime().Truncate(time.Second).After(p.LastChangedOn) { + return nil + } + + var ext string + if index := strings.Index(filepath.Ext(changed), "_"); index == -1 { + ext = filepath.Ext(changed) + } else { + ext = filepath.Ext(changed)[0:index] + } + i := strings.Index(changed, filepath.Ext(changed)) + file := changed[:i] + ext + path := filepath.Dir(changed[:i]) + if changed[:i] != "" && inArray(ext, p.Watcher.Exts) { + p.LastChangedOn = time.Now().Truncate(time.Second) + msg := fmt.Sprintln(p.pname(p.Name, 4), ":", p.Magenta.Bold(strings.ToUpper(ext[1:]) + " changed"), p.Magenta.Bold(file)) + out := BufferOut{Time: time.Now(), Text: strings.ToUpper(ext[1:]) + " changed " + file} + p.print("log", out, msg, "") + // stop and run again + if p.Run { + close(channel) + channel = make(chan bool) + } + // handle multiple errors, need a better way + p.fmt(file) + p.test(path) + p.generate(path) + go p.routines(channel, &wr) + } + + return nil + } + for { + for _, dir := range p.Watcher.Paths { + base := filepath.Join(p.base, dir) + if _, err := os.Stat(base); err == nil { + if err := filepath.Walk(base, walk); err != nil { + log.Println(p.Red.Bold(err.Error())) + } + } else { + log.Println(p.Red.Bold(base + " path doesn't exist")) + } + + select { + case <-exit: + return + case <-time.After(p.parent.Config.PollingInterval / time.Duration(len(p.Watcher.Paths))): + } + } + } +} + // Watching method is the main core. It manages the livereload and the watching func (p *Project) watching() { var wr sync.WaitGroup @@ -42,7 +133,7 @@ func (p *Project) watching() { select { case event := <-watcher.Events: if time.Now().Truncate(time.Second).After(p.LastChangedOn) { - if event.Op&fsnotify.Chmod == fsnotify.Chmod { + if event.Op & fsnotify.Chmod == fsnotify.Chmod { continue } if _, err := os.Stat(event.Name); err == nil { @@ -56,7 +147,7 @@ func (p *Project) watching() { file := event.Name[:i] + ext path := filepath.Dir(event.Name[:i]) if event.Name[:i] != "" && inArray(ext, p.Watcher.Exts) { - msg := fmt.Sprintln(p.pname(p.Name, 4), ":", p.Magenta.Bold(strings.ToUpper(ext[1:])+" changed"), p.Magenta.Bold(file)) + msg := fmt.Sprintln(p.pname(p.Name, 4), ":", p.Magenta.Bold(strings.ToUpper(ext[1:]) + " changed"), p.Magenta.Bold(file)) out := BufferOut{Time: time.Now(), Text: strings.ToUpper(ext[1:]) + " changed " + file} p.print("log", out, msg, "") // stop and run again @@ -92,7 +183,7 @@ func (p *Project) install() error { out := BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Install", Stream: stream} p.print("error", out, msg, stream) } else { - msg := fmt.Sprintln(p.pname(p.Name, 5), ":", p.Green.Regular("Installed")+" after", p.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) + msg := fmt.Sprintln(p.pname(p.Name, 5), ":", p.Green.Regular("Installed") + " after", p.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) out := BufferOut{Time: time.Now(), Text: "Installed after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"} p.print("log", out, msg, stream) } @@ -111,7 +202,7 @@ func (p *Project) run(channel chan bool, wr *sync.WaitGroup) { for { select { case <-runner: - msg := fmt.Sprintln(p.pname(p.Name, 5), ":", p.Green.Regular("Has been run")+" after", p.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) + msg := fmt.Sprintln(p.pname(p.Name, 5), ":", p.Green.Regular("Has been run") + " after", p.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) out := BufferOut{Time: time.Now(), Text: "Has been run after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"} p.print("log", out, msg, "") return @@ -131,7 +222,7 @@ func (p *Project) build() error { out := BufferOut{Time: time.Now(), Text: err.Error(), Type: "Go Build", Stream: stream} p.print("error", out, msg, stream) } else { - msg := fmt.Sprintln(p.pname(p.Name, 5), ":", p.Green.Regular("Builded")+" after", p.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) + msg := fmt.Sprintln(p.pname(p.Name, 5), ":", p.Green.Regular("Builded") + " after", p.Magenta.Regular(big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3), " s")) out := BufferOut{Time: time.Now(), Text: "Builded after " + big.NewFloat(float64(time.Since(start).Seconds())).Text('f', 3) + " s"} p.print("log", out, msg, stream) } @@ -186,7 +277,7 @@ func (p *Project) cmd(exit chan bool) { cast := func(commands []string) { for _, command := range commands { errors, logs := p.afterBefore(command) - msg := fmt.Sprintln(p.pname(p.Name, 5), ":", p.Green.Bold("Command"), p.Green.Bold("\"")+command+p.Green.Bold("\"")) + msg := fmt.Sprintln(p.pname(p.Name, 5), ":", p.Green.Bold("Command"), p.Green.Bold("\"") + command + p.Green.Bold("\"")) out := BufferOut{Time: time.Now(), Text: command, Type: "After/Before"} if logs != "" { p.print("log", out, msg, "") @@ -224,8 +315,12 @@ func (p *Project) cmd(exit chan bool) { }() } +type watcher interface { + Add(path string) error +} + // Walks the file tree of a project -func (p *Project) walks(watcher *fsnotify.Watcher) error { +func (p *Project) walks(watcher watcher) error { var files, folders int64 wd, _ := os.Getwd() walk := func(path string, info os.FileInfo, err error) error {