mirror of https://github.com/maxcnunes/gaper.git
Improve watcher search
This commit is contained in:
parent
18ea15ac41
commit
b45a8e94fe
80
watcher.go
80
watcher.go
|
@ -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,16 +87,14 @@ 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)
|
||||||
if _, ok := w.AllowedExtensions[ext]; ok && info.ModTime().After(startTime) {
|
if _, ok := w.AllowedExtensions[ext]; ok && info.ModTime().After(startTime) {
|
||||||
|
@ -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}
|
||||||
|
|
||||||
|
isGlob := strings.Contains(path, "*")
|
||||||
|
if isGlob {
|
||||||
|
var err error
|
||||||
|
matches, err = zglob.Glob(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't resolve glob path \"%s\": %v", path, err)
|
return nil, fmt.Errorf("couldn't resolve glob path \"%s\": %v", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("Resolved glob path %s: %v", path, matches)
|
|
||||||
result = append(result, matches...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, match := range 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue