From 459c8ce84ed659e5ac142706511d466eb9aedfc6 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 30 Sep 2024 11:24:27 +0800 Subject: [PATCH] feat(os/gfsnotify): add recursive watching for created subfolders and sub-files under folders that already watched (#3830) --- os/gfpool/gfpool_pool.go | 5 +- os/gfsnotify/gfsnotify.go | 33 +++-- os/gfsnotify/gfsnotify_filefunc.go | 6 +- os/gfsnotify/gfsnotify_watcher.go | 143 +++++++++---------- os/gfsnotify/gfsnotify_watcher_loop.go | 186 ++++++++++++++++--------- os/gfsnotify/gfsnotify_z_unit_test.go | 35 ++++- os/gspath/gspath_cache.go | 4 +- 7 files changed, 257 insertions(+), 155 deletions(-) diff --git a/os/gfpool/gfpool_pool.go b/os/gfpool/gfpool_pool.go index c8d9942a695..55929b4611c 100644 --- a/os/gfpool/gfpool_pool.go +++ b/os/gfpool/gfpool_pool.go @@ -99,7 +99,7 @@ func (p *Pool) File() (*File, error) { } // It firstly checks using !p.init.Val() for performance purpose. if !p.init.Val() && p.init.Cas(false, true) { - _, _ = gfsnotify.Add(f.path, func(event *gfsnotify.Event) { + var watchCallback = func(event *gfsnotify.Event) { // If the file is removed or renamed, recreates the pool by increasing the pool id. if event.IsRemove() || event.IsRename() { // It drops the old pool. @@ -110,7 +110,8 @@ func (p *Pool) File() (*File, error) { // Whenever the pool id changes, the pool will be recreated. p.id.Add(1) } - }, false) + } + _, _ = gfsnotify.Add(f.path, watchCallback, gfsnotify.WatchOption{NoRecursive: true}) } return f, nil } diff --git a/os/gfsnotify/gfsnotify.go b/os/gfsnotify/gfsnotify.go index 75a76edf56a..51f7c93ea07 100644 --- a/os/gfsnotify/gfsnotify.go +++ b/os/gfsnotify/gfsnotify.go @@ -42,7 +42,7 @@ type Callback struct { Path string // Bound file path (absolute). name string // Registered name for AddOnce. elem *glist.Element // Element in the callbacks of watcher. - recursive bool // Is bound to path recursively or not. + recursive bool // Is bound to sub-path recursively or not. } // Event is the event produced by underlying fsnotify. @@ -53,6 +53,15 @@ type Event struct { Watcher *Watcher // Parent watcher. } +// WatchOption holds the option for watching. +type WatchOption struct { + // NoRecursive explicitly specifies no recursive watching. + // Recursive watching will also watch all its current and following created subfolders and sub-files. + // + // Note that the recursive watching is enabled in default. + NoRecursive bool +} + // Op is the bits union for file operations. type Op uint32 @@ -75,13 +84,15 @@ const ( var ( mu sync.Mutex // Mutex for concurrent safety of defaultWatcher. defaultWatcher *Watcher // Default watcher. - callbackIdMap = gmap.NewIntAnyMap(true) // Id to callback mapping. + callbackIdMap = gmap.NewIntAnyMap(true) // Global callback id to callback function mapping. callbackIdGenerator = gtype.NewInt() // Atomic id generator for callback. ) // New creates and returns a new watcher. // Note that the watcher number is limited by the file handle setting of the system. -// Eg: fs.inotify.max_user_instances system variable in linux systems. +// Example: fs.inotify.max_user_instances system variable in linux systems. +// +// In most case, you can use the default watcher for usage instead of creating one. func New() (*Watcher, error) { w := &Watcher{ cache: gcache.New(), @@ -102,26 +113,30 @@ func New() (*Watcher, error) { } // Add monitors `path` using default watcher with callback function `callbackFunc`. +// +// The parameter `path` can be either a file or a directory path. // The optional parameter `recursive` specifies whether monitoring the `path` recursively, which is true in default. -func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { +func Add(path string, callbackFunc func(event *Event), option ...WatchOption) (callback *Callback, err error) { w, err := getDefaultWatcher() if err != nil { return nil, err } - return w.Add(path, callbackFunc, recursive...) + return w.Add(path, callbackFunc, option...) } // AddOnce monitors `path` using default watcher with callback function `callbackFunc` only once using unique name `name`. -// If AddOnce is called multiple times with the same `name` parameter, `path` is only added to monitor once. It returns error -// if it's called twice with the same `name`. // +// If AddOnce is called multiple times with the same `name` parameter, `path` is only added to monitor once. +// It returns error if it's called twice with the same `name`. +// +// The parameter `path` can be either a file or a directory path. // The optional parameter `recursive` specifies whether monitoring the `path` recursively, which is true in default. -func AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { +func AddOnce(name, path string, callbackFunc func(event *Event), option ...WatchOption) (callback *Callback, err error) { w, err := getDefaultWatcher() if err != nil { return nil, err } - return w.AddOnce(name, path, callbackFunc, recursive...) + return w.AddOnce(name, path, callbackFunc, option...) } // Remove removes all monitoring callbacks of given `path` from watcher recursively. diff --git a/os/gfsnotify/gfsnotify_filefunc.go b/os/gfsnotify/gfsnotify_filefunc.go index c8f4c7b0c3c..754be56cf06 100644 --- a/os/gfsnotify/gfsnotify_filefunc.go +++ b/os/gfsnotify/gfsnotify_filefunc.go @@ -106,14 +106,12 @@ func doFileScanDir(path string, pattern string, recursive ...bool) ([]string, er file, err = os.Open(path) ) if err != nil { - err = gerror.Wrapf(err, `os.Open failed for path "%s"`, path) - return nil, err + return nil, gerror.Wrapf(err, `os.Open failed for path "%s"`, path) } defer file.Close() names, err := file.Readdirnames(-1) if err != nil { - err = gerror.Wrapf(err, `read directory files failed for path "%s"`, path) - return nil, err + return nil, gerror.Wrapf(err, `read directory files failed for path "%s"`, path) } filePath := "" for _, name := range names { diff --git a/os/gfsnotify/gfsnotify_watcher.go b/os/gfsnotify/gfsnotify_watcher.go index d76d64483f9..915e588c239 100644 --- a/os/gfsnotify/gfsnotify_watcher.go +++ b/os/gfsnotify/gfsnotify_watcher.go @@ -16,10 +16,14 @@ import ( ) // Add monitors `path` with callback function `callbackFunc` to the watcher. +// +// The parameter `path` can be either a file or a directory path. // The optional parameter `recursive` specifies whether monitoring the `path` recursively, // which is true in default. -func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { - return w.AddOnce("", path, callbackFunc, recursive...) +func (w *Watcher) Add( + path string, callbackFunc func(event *Event), option ...WatchOption, +) (callback *Callback, err error) { + return w.AddOnce("", path, callbackFunc, option...) } // AddOnce monitors `path` with callback function `callbackFunc` only once using unique name @@ -28,26 +32,40 @@ func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive .. // // It returns error if it's called twice with the same `name`. // +// The parameter `path` can be either a file or a directory path. // The optional parameter `recursive` specifies whether monitoring the `path` recursively, // which is true in default. -func (w *Watcher) AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { +func (w *Watcher) AddOnce( + name, path string, callbackFunc func(event *Event), option ...WatchOption, +) (callback *Callback, err error) { + var watchOption = w.getWatchOption(option...) w.nameSet.AddIfNotExistFuncLock(name, func() bool { // Firstly add the path to watcher. - callback, err = w.addWithCallbackFunc(name, path, callbackFunc, recursive...) + // + // A path can only be watched once; watching it more than once is a no-op and will + // not return an error. + callback, err = w.addWithCallbackFunc( + name, path, callbackFunc, option..., + ) if err != nil { return false } + // If it's recursive adding, it then adds all sub-folders to the monitor. // NOTE: // 1. It only recursively adds **folders** to the monitor, NOT files, // because if the folders are monitored and their sub-files are also monitored. // 2. It bounds no callbacks to the folders, because it will search the callbacks // from its parent recursively if any event produced. - if fileIsDir(path) && (len(recursive) == 0 || recursive[0]) { + if fileIsDir(path) && !watchOption.NoRecursive { for _, subPath := range fileAllDirs(path) { if fileIsDir(subPath) { - if err = w.watcher.Add(subPath); err != nil { - err = gerror.Wrapf(err, `add watch failed for path "%s"`, subPath) + if watchAddErr := w.watcher.Add(subPath); watchAddErr != nil { + err = gerror.Wrapf( + err, + `add watch failed for path "%s", err: %s`, + subPath, watchAddErr.Error(), + ) } else { intlog.Printf(context.TODO(), "watcher adds monitor for: %s", subPath) } @@ -62,14 +80,24 @@ func (w *Watcher) AddOnce(name, path string, callbackFunc func(event *Event), re return } +func (w *Watcher) getWatchOption(option ...WatchOption) WatchOption { + if len(option) > 0 { + return option[0] + } + return WatchOption{} +} + // addWithCallbackFunc adds the path to underlying monitor, creates and returns a callback object. // Very note that if it calls multiple times with the same `path`, the latest one will overwrite the previous one. -func (w *Watcher) addWithCallbackFunc(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { +func (w *Watcher) addWithCallbackFunc( + name, path string, callbackFunc func(event *Event), option ...WatchOption, +) (callback *Callback, err error) { + var watchOption = w.getWatchOption(option...) // Check and convert the given path to absolute path. - if t := fileRealPath(path); t == "" { + if realPath := fileRealPath(path); realPath == "" { return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" does not exist`, path) } else { - path = t + path = realPath } // Create callback object. callback = &Callback{ @@ -77,10 +105,7 @@ func (w *Watcher) addWithCallbackFunc(name, path string, callbackFunc func(event Func: callbackFunc, Path: path, name: name, - recursive: true, - } - if len(recursive) > 0 { - callback.recursive = recursive[0] + recursive: !watchOption.NoRecursive, } // Register the callback to watcher. w.callbacks.LockFunc(func(m map[string]interface{}) { @@ -113,74 +138,50 @@ func (w *Watcher) Close() { w.events.Close() } -// Remove removes monitor and all callbacks associated with the `path` recursively. +// Remove removes watching and all callbacks associated with the `path` recursively. +// Note that, it's recursive in default if given `path` is a directory. func (w *Watcher) Remove(path string) error { - // Firstly remove the callbacks of the path. - if value := w.callbacks.Remove(path); value != nil { - list := value.(*glist.List) - for { - if item := list.PopFront(); item != nil { - callbackIdMap.Remove(item.(*Callback).Id) - } else { - break - } - } - } - // Secondly remove monitor of all sub-files which have no callbacks. - if subPaths, err := fileScanDir(path, "*", true); err == nil && len(subPaths) > 0 { - for _, subPath := range subPaths { - if w.checkPathCanBeRemoved(subPath) { - if internalErr := w.watcher.Remove(subPath); internalErr != nil { - intlog.Errorf(context.TODO(), `%+v`, internalErr) - } - } + var ( + err error + subPaths []string + removedPaths = make([]string, 0) + ) + removedPaths = append(removedPaths, path) + if fileIsDir(path) { + subPaths, err = fileScanDir(path, "*", true) + if err != nil { + return err } + removedPaths = append(removedPaths, subPaths...) } - // Lastly remove the monitor of the path from underlying monitor. - err := w.watcher.Remove(path) - if err != nil { - err = gerror.Wrapf(err, `remove watch failed for path "%s"`, path) - } - return err -} -// checkPathCanBeRemoved checks whether the given path have no callbacks bound. -func (w *Watcher) checkPathCanBeRemoved(path string) bool { - // Firstly check the callbacks in the watcher directly. - if v := w.callbacks.Get(path); v != nil { - return false - } - // Secondly check its parent whether has callbacks. - dirPath := fileDir(path) - if v := w.callbacks.Get(dirPath); v != nil { - for _, c := range v.(*glist.List).FrontAll() { - if c.(*Callback).recursive { - return false - } - } - return false - } - // Recursively check its parent. - parentDirPath := "" - for { - parentDirPath = fileDir(dirPath) - if parentDirPath == dirPath { - break - } - if v := w.callbacks.Get(parentDirPath); v != nil { - for _, c := range v.(*glist.List).FrontAll() { - if c.(*Callback).recursive { - return false + for _, removedPath := range removedPaths { + // remove the callbacks of the path. + if value := w.callbacks.Remove(removedPath); value != nil { + list := value.(*glist.List) + for { + if item := list.PopFront(); item != nil { + callbackIdMap.Remove(item.(*Callback).Id) + } else { + break } } - return false } - dirPath = parentDirPath + // remove the monitor of the path from underlying monitor. + if watcherRemoveErr := w.watcher.Remove(removedPath); watcherRemoveErr != nil { + err = gerror.Wrapf( + err, + `remove watch failed for path "%s", err: %s`, + removedPath, watcherRemoveErr.Error(), + ) + } } - return true + return err } // RemoveCallback removes callback with given callback id from watcher. +// +// Note that, it auto removes the path watching if there's no callback bound on it. func (w *Watcher) RemoveCallback(callbackId int) { callback := (*Callback)(nil) if r := callbackIdMap.Get(callbackId); r != nil { diff --git a/os/gfsnotify/gfsnotify_watcher_loop.go b/os/gfsnotify/gfsnotify_watcher_loop.go index fd1d37f6323..dca551fdf6a 100644 --- a/os/gfsnotify/gfsnotify_watcher_loop.go +++ b/os/gfsnotify/gfsnotify_watcher_loop.go @@ -8,7 +8,6 @@ package gfsnotify import ( "context" - "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" @@ -20,33 +19,36 @@ import ( func (w *Watcher) watchLoop() { for { select { - // Close event. + // close event. case <-w.closeChan: return - // Event listening. + // event listening. case ev, ok := <-w.watcher.Events: if !ok { return } - // Filter the repeated event in custom duration. + // filter the repeated event in custom duration. + var cacheFunc = func(ctx context.Context) (value interface{}, err error) { + w.events.Push(&Event{ + event: ev, + Path: ev.Name, + Op: Op(ev.Op), + Watcher: w, + }) + return struct{}{}, nil + } _, err := w.cache.SetIfNotExist( context.Background(), ev.String(), - func(ctx context.Context) (value interface{}, err error) { - w.events.Push(&Event{ - event: ev, - Path: ev.Name, - Op: Op(ev.Op), - Watcher: w, - }) - return struct{}{}, nil - }, repeatEventFilterDuration, + cacheFunc, + repeatEventFilterDuration, ) if err != nil { intlog.Errorf(context.TODO(), `%+v`, err) } + // error occurs in underlying watcher. case err := <-w.watcher.Errors: intlog.Errorf(context.TODO(), `%+v`, err) } @@ -55,26 +57,39 @@ func (w *Watcher) watchLoop() { // eventLoop is the core event handler. func (w *Watcher) eventLoop() { + var ( + err error + ctx = context.TODO() + ) for { if v := w.events.Pop(); v != nil { event := v.(*Event) - // If there's no any callback of this path, it removes it from monitor. - callbacks := w.getCallbacks(event.Path) + // If there's no any callback of this path, it removes it from monitor, + // as a path watching without callback is meaningless. + callbacks := w.getCallbacksForPath(event.Path) if len(callbacks) == 0 { _ = w.watcher.Remove(event.Path) continue } + switch { case event.IsRemove(): // It should check again the existence of the path. // It adds it back to the monitor if it still exists. if fileExists(event.Path) { - // It adds the path back to monitor. + // A watch will be automatically removed if the watched path is deleted or + // renamed. + // + // It here adds the path back to monitor. // We need no worry about the repeat adding. - if err := w.watcher.Add(event.Path); err != nil { - intlog.Errorf(context.TODO(), `%+v`, err) + if err = w.watcher.Add(event.Path); err != nil { + intlog.Errorf(ctx, `%+v`, err) } else { - intlog.Printf(context.TODO(), "fake remove event, watcher re-adds monitor for: %s", event.Path) + intlog.Printf( + ctx, + "fake remove event, watcher re-adds monitor for: %s", + event.Path, + ) } // Change the event to RENAME, which means it renames itself to its origin name. event.Op = RENAME @@ -85,60 +100,49 @@ func (w *Watcher) eventLoop() { // It adds it back to the monitor if it still exists. // Especially Some editors might do RENAME and then CHMOD when it's editing file. if fileExists(event.Path) { - // It might lost the monitoring for the path, so we add the path back to monitor. + // A watch will be automatically removed if the watched path is deleted or + // renamed. + // + // It might lose the monitoring for the path, so we add the path back to monitor. // We need no worry about the repeat adding. - if err := w.watcher.Add(event.Path); err != nil { - intlog.Errorf(context.TODO(), `%+v`, err) + if err = w.watcher.Add(event.Path); err != nil { + intlog.Errorf(ctx, `%+v`, err) } else { - intlog.Printf(context.TODO(), "fake rename event, watcher re-adds monitor for: %s", event.Path) + intlog.Printf( + ctx, + "fake rename event, watcher re-adds monitor for: %s", + event.Path, + ) } // Change the event to CHMOD. event.Op = CHMOD } case event.IsCreate(): - // ========================================= + // ================================================================================= // Note that it here just adds the path to monitor without any callback registering, // because its parent already has the callbacks. - // ========================================= - if fileIsDir(event.Path) { - // If it's a folder, it then does adding recursively to monitor. + // ================================================================================= + if w.checkRecursiveWatchingInCreatingEvent(event.Path) { + // It handles only folders, watching folders also watching its sub files. for _, subPath := range fileAllDirs(event.Path) { if fileIsDir(subPath) { - if err := w.watcher.Add(subPath); err != nil { - intlog.Errorf(context.TODO(), `%+v`, err) + if err = w.watcher.Add(subPath); err != nil { + intlog.Errorf(ctx, `%+v`, err) } else { - intlog.Printf(context.TODO(), "folder creation event, watcher adds monitor for: %s", subPath) + intlog.Printf( + ctx, + "folder creation event, watcher adds monitor for: %s", + subPath, + ) } } } - } else { - // If it's a file, it directly adds it to monitor. - if err := w.watcher.Add(event.Path); err != nil { - intlog.Errorf(context.TODO(), `%+v`, err) - } else { - intlog.Printf(context.TODO(), "file creation event, watcher adds monitor for: %s", event.Path) - } } } - // Calling the callbacks in order. + // Calling the callbacks in multiple goroutines. for _, callback := range callbacks { - go func(callback *Callback) { - defer func() { - if err := recover(); err != nil { - switch err { - case callbackExitEventPanicStr: - w.RemoveCallback(callback.Id) - default: - if e, ok := err.(error); ok { - panic(gerror.WrapCode(gcode.CodeInternalPanic, e)) - } - panic(err) - } - } - }() - callback.Func(event) - }(callback) + go w.doCallback(event, callback) } } else { break @@ -146,35 +150,85 @@ func (w *Watcher) eventLoop() { } } -// getCallbacks searches and returns all callbacks with given `path`. +// checkRecursiveWatchingInCreatingEvent checks and returns whether recursive adding given `path` to watcher +// in creating event. +func (w *Watcher) checkRecursiveWatchingInCreatingEvent(path string) bool { + if !fileIsDir(path) { + return false + } + var ( + parentDirPath string + dirPath = path + ) + for { + parentDirPath = fileDir(dirPath) + if parentDirPath == dirPath { + break + } + if callbackItem := w.callbacks.Get(parentDirPath); callbackItem != nil { + for _, node := range callbackItem.(*glist.List).FrontAll() { + callback := node.(*Callback) + if callback.recursive { + return true + } + } + } + dirPath = parentDirPath + } + return false +} + +func (w *Watcher) doCallback(event *Event, callback *Callback) { + defer func() { + if exception := recover(); exception != nil { + switch exception { + case callbackExitEventPanicStr: + w.RemoveCallback(callback.Id) + default: + if e, ok := exception.(error); ok { + panic(gerror.WrapCode(gcode.CodeInternalPanic, e)) + } + panic(exception) + } + } + }() + callback.Func(event) +} + +// getCallbacksForPath searches and returns all callbacks with given `path`. +// // It also searches its parents for callbacks if they're recursive. -func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) { +func (w *Watcher) getCallbacksForPath(path string) (callbacks []*Callback) { // Firstly add the callbacks of itself. - if v := w.callbacks.Get(path); v != nil { - for _, v := range v.(*glist.List).FrontAll() { - callback := v.(*Callback) + if item := w.callbacks.Get(path); item != nil { + for _, node := range item.(*glist.List).FrontAll() { + callback := node.(*Callback) callbacks = append(callbacks, callback) } } + // ============================================================================================================ // Secondly searches its direct parent for callbacks. - // It is special handling here, which is the different between `recursive` and `not recursive` logic + // + // Note that it is SPECIAL handling here, which is the different between `recursive` and `not recursive` logic // for direct parent folder of `path` that events are from. + // ============================================================================================================ dirPath := fileDir(path) - if v := w.callbacks.Get(dirPath); v != nil { - for _, v := range v.(*glist.List).FrontAll() { - callback := v.(*Callback) + if item := w.callbacks.Get(dirPath); item != nil { + for _, node := range item.(*glist.List).FrontAll() { + callback := node.(*Callback) callbacks = append(callbacks, callback) } } + // Lastly searches all the parents of directory of `path` recursively for callbacks. for { parentDirPath := fileDir(dirPath) if parentDirPath == dirPath { break } - if v := w.callbacks.Get(parentDirPath); v != nil { - for _, v := range v.(*glist.List).FrontAll() { - callback := v.(*Callback) + if item := w.callbacks.Get(parentDirPath); item != nil { + for _, node := range item.(*glist.List).FrontAll() { + callback := node.(*Callback) if callback.recursive { callbacks = append(callbacks, callback) } diff --git a/os/gfsnotify/gfsnotify_z_unit_test.go b/os/gfsnotify/gfsnotify_z_unit_test.go index 6155768e1b7..1ca79a58fe0 100644 --- a/os/gfsnotify/gfsnotify_z_unit_test.go +++ b/os/gfsnotify/gfsnotify_z_unit_test.go @@ -193,6 +193,39 @@ func TestWatcher_Callback2(t *testing.T) { }) } +func TestWatcher_WatchFolderWithRecursively(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + err error + array = garray.New(true) + dirPath = gfile.Temp(gtime.TimestampNanoStr()) + ) + err = gfile.Mkdir(dirPath) + t.AssertNil(err) + defer gfile.Remove(dirPath) + + _, err = gfsnotify.Add(dirPath, func(event *gfsnotify.Event) { + //fmt.Println(event.String()) + array.Append(1) + }) + t.AssertNil(err) + time.Sleep(time.Millisecond * 100) + t.Assert(array.Len(), 0) + + subDirPath := gfile.Join(dirPath, gtime.TimestampNanoStr()) + err = gfile.Mkdir(subDirPath) + t.AssertNil(err) + time.Sleep(time.Millisecond * 100) + t.Assert(array.Len(), 1) + + f, err := gfile.Create(gfile.Join(subDirPath, gtime.TimestampNanoStr())) + t.AssertNil(err) + t.AssertNil(f.Close()) + time.Sleep(time.Millisecond * 100) + t.Assert(array.Len(), 2) + }) +} + func TestWatcher_WatchFolderWithoutRecursively(t *testing.T) { gtest.C(t, func(t *gtest.T) { var ( @@ -206,7 +239,7 @@ func TestWatcher_WatchFolderWithoutRecursively(t *testing.T) { _, err = gfsnotify.Add(dirPath, func(event *gfsnotify.Event) { // fmt.Println(event.String()) array.Append(1) - }, false) + }, gfsnotify.WatchOption{NoRecursive: true}) t.AssertNil(err) time.Sleep(time.Millisecond * 100) t.Assert(array.Len(), 0) diff --git a/os/gspath/gspath_cache.go b/os/gspath/gspath_cache.go index 1070b0b7960..c442b005e93 100644 --- a/os/gspath/gspath_cache.go +++ b/os/gspath/gspath_cache.go @@ -83,7 +83,7 @@ func (sp *SPath) addToCache(filePath, rootPath string) { // When the files under the directory are updated, the cache will be updated meanwhile. // Note that since the listener is added recursively, if you delete a directory, the files (including the directory) // under the directory will also generate delete events, which means it will generate N+1 events in total -// if a directory deleted and there're N files under it. +// if a directory deleted and there are N files under it. func (sp *SPath) addMonitorByPath(path string) { if sp.cache == nil { return @@ -102,7 +102,7 @@ func (sp *SPath) addMonitorByPath(path string) { case event.IsCreate(): sp.addToCache(event.Path, path) } - }, true) + }) } // removeMonitorByPath removes gfsnotify monitoring of `path` recursively.