Improve watcher search

This commit is contained in:
Max Claus Nunes 2018-06-22 21:44:26 -03:00
parent 18ea15ac41
commit b45a8e94fe
2 changed files with 77 additions and 22 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
zglob "github.com/mattn/go-zglob" zglob "github.com/mattn/go-zglob"
@ -13,8 +14,8 @@ import (
// Watcher is a interface for the watch process // Watcher is a interface for the watch process
type Watcher struct { type Watcher struct {
PollInterval int PollInterval int
WatchItems []string WatchItems map[string]bool
IgnoreItems []string IgnoreItems map[string]bool
AllowedExtensions map[string]bool AllowedExtensions map[string]bool
Events chan string Events chan string
Errors chan error Errors chan error
@ -35,22 +36,24 @@ func NewWatcher(pollInterval int, watchItems []string, ignoreItems []string, ext
allowedExts["."+ext] = true allowedExts["."+ext] = true
} }
watchMatches, err := resolveGlobMatches(watchItems) watchPaths, err := resolvePaths(watchItems, allowedExts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ignoreMatches, err := resolveGlobMatches(ignoreItems) ignorePaths, err := resolvePaths(ignoreItems, allowedExts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
logger.Debugf("Resolved watch paths: %v", watchPaths)
logger.Debugf("Resolved ignore paths: %v", ignorePaths)
return &Watcher{ return &Watcher{
Events: make(chan string), Events: make(chan string),
Errors: make(chan error), Errors: make(chan error),
PollInterval: pollInterval, PollInterval: pollInterval,
WatchItems: watchMatches, WatchItems: watchPaths,
IgnoreItems: ignoreMatches, IgnoreItems: ignorePaths,
AllowedExtensions: allowedExts, AllowedExtensions: allowedExts,
}, nil }, nil
} }
@ -61,8 +64,8 @@ var errDetectedChange = errors.New("done")
// Watch starts watching for file changes // Watch starts watching for file changes
func (w *Watcher) Watch() { func (w *Watcher) Watch() {
for { for {
for i := range w.WatchItems { for watchPath := range w.WatchItems {
fileChanged, err := w.scanChange(w.WatchItems[i]) fileChanged, err := w.scanChange(watchPath)
if err != nil { if err != nil {
w.Errors <- err w.Errors <- err
return return
@ -84,15 +87,13 @@ func (w *Watcher) scanChange(watchPath string) (string, error) {
var fileChanged string var fileChanged string
err := filepath.Walk(watchPath, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(watchPath, func(path string, info os.FileInfo, err error) error {
// ignore hidden files and directories // always ignore hidden files and directories
if filepath.Base(path)[0] == '.' { if filepath.Base(path)[0] == '.' {
return nil return nil
} }
for _, x := range w.IgnoreItems { if _, ignored := w.IgnoreItems[path]; ignored {
if x == path { return filepath.SkipDir
return filepath.SkipDir
}
} }
ext := filepath.Ext(path) ext := filepath.Ext(path)
@ -111,18 +112,61 @@ func (w *Watcher) scanChange(watchPath string) (string, error) {
return fileChanged, nil return fileChanged, nil
} }
func resolveGlobMatches(paths []string) ([]string, error) { func resolvePaths(paths []string, extensions map[string]bool) (map[string]bool, error) {
var result []string result := map[string]bool{}
for _, path := range paths { for _, path := range paths {
matches, err := zglob.Glob(path) matches := []string{path}
if err != nil {
return nil, fmt.Errorf("couldn't resolve glob path \"%s\": %v", path, err) isGlob := strings.Contains(path, "*")
if isGlob {
var err error
matches, err = zglob.Glob(path)
if err != nil {
return nil, fmt.Errorf("couldn't resolve glob path \"%s\": %v", path, err)
}
} }
logger.Debugf("Resolved glob path %s: %v", path, matches) for _, match := range matches {
result = append(result, matches...) // don't care for extension filter right now for non glob paths
// since they could be a directory
if isGlob {
if _, ok := extensions[filepath.Ext(path)]; !ok {
continue
}
}
if _, ok := result[match]; !ok {
result[match] = true
}
}
} }
removeOverlappedPaths(result)
return result, nil return result, nil
} }
// remove overlapped paths so it makes the scan for changes later faster and simpler
func removeOverlappedPaths(mapPaths map[string]bool) {
for p1 := range mapPaths {
for p2 := range mapPaths {
if p1 == p2 {
continue
}
if strings.HasPrefix(p2, p1) {
mapPaths[p2] = false
} else if strings.HasPrefix(p1, p2) {
mapPaths[p1] = false
}
}
}
// cleanup path list
for p := range mapPaths {
if !mapPaths[p] {
delete(mapPaths, p)
}
}
}

View File

@ -19,7 +19,7 @@ func TestWatcherDefaultValues(t *testing.T) {
assert.Nil(t, err, "wacher error") assert.Nil(t, err, "wacher error")
assert.Equal(t, 500, w.PollInterval) assert.Equal(t, 500, w.PollInterval)
assert.Equal(t, watchItems, w.WatchItems) assert.Equal(t, map[string]bool{"testdata/server": true}, w.WatchItems)
assert.Len(t, w.IgnoreItems, 0) assert.Len(t, w.IgnoreItems, 0)
assert.Equal(t, map[string]bool{".go": true}, w.AllowedExtensions) assert.Equal(t, map[string]bool{".go": true}, w.AllowedExtensions)
} }
@ -32,7 +32,18 @@ func TestWatcherGlobPath(t *testing.T) {
w, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions) w, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions)
assert.Nil(t, err, "wacher error") assert.Nil(t, err, "wacher error")
assert.Equal(t, []string{"testdata/server/main_test.go"}, w.IgnoreItems) assert.Equal(t, map[string]bool{"testdata/server/main_test.go": true}, w.IgnoreItems)
}
func TestWatcherRemoveOverlapdPaths(t *testing.T) {
pollInterval := 0
watchItems := []string{filepath.Join("testdata", "server")}
ignoreItems := []string{"./testdata/**/*", "./testdata/server"}
var extensions []string
w, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions)
assert.Nil(t, err, "wacher error")
assert.Equal(t, map[string]bool{"./testdata/server": true}, w.IgnoreItems)
} }
func TestWatcherWatchChange(t *testing.T) { func TestWatcherWatchChange(t *testing.T) {