github.com/wangyougui/gf/v2@v2.6.5/os/gfsnotify/gfsnotify_watcher.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/wangyougui/gf. 6 7 package gfsnotify 8 9 import ( 10 "context" 11 12 "github.com/wangyougui/gf/v2/container/glist" 13 "github.com/wangyougui/gf/v2/errors/gcode" 14 "github.com/wangyougui/gf/v2/errors/gerror" 15 "github.com/wangyougui/gf/v2/internal/intlog" 16 ) 17 18 // Add monitors `path` with callback function `callbackFunc` to the watcher. 19 // The optional parameter `recursive` specifies whether monitoring the `path` recursively, 20 // which is true in default. 21 func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { 22 return w.AddOnce("", path, callbackFunc, recursive...) 23 } 24 25 // AddOnce monitors `path` with callback function `callbackFunc` only once using unique name 26 // `name` to the watcher. If AddOnce is called multiple times with the same `name` parameter, 27 // `path` is only added to monitor once. 28 // 29 // It returns error if it's called twice with the same `name`. 30 // 31 // The optional parameter `recursive` specifies whether monitoring the `path` recursively, 32 // which is true in default. 33 func (w *Watcher) AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { 34 w.nameSet.AddIfNotExistFuncLock(name, func() bool { 35 // Firstly add the path to watcher. 36 callback, err = w.addWithCallbackFunc(name, path, callbackFunc, recursive...) 37 if err != nil { 38 return false 39 } 40 // If it's recursive adding, it then adds all sub-folders to the monitor. 41 // NOTE: 42 // 1. It only recursively adds **folders** to the monitor, NOT files, 43 // because if the folders are monitored and their sub-files are also monitored. 44 // 2. It bounds no callbacks to the folders, because it will search the callbacks 45 // from its parent recursively if any event produced. 46 if fileIsDir(path) && (len(recursive) == 0 || recursive[0]) { 47 for _, subPath := range fileAllDirs(path) { 48 if fileIsDir(subPath) { 49 if err = w.watcher.Add(subPath); err != nil { 50 err = gerror.Wrapf(err, `add watch failed for path "%s"`, subPath) 51 } else { 52 intlog.Printf(context.TODO(), "watcher adds monitor for: %s", subPath) 53 } 54 } 55 } 56 } 57 if name == "" { 58 return false 59 } 60 return true 61 }) 62 return 63 } 64 65 // addWithCallbackFunc adds the path to underlying monitor, creates and returns a callback object. 66 // Very note that if it calls multiple times with the same `path`, the latest one will overwrite the previous one. 67 func (w *Watcher) addWithCallbackFunc(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { 68 // Check and convert the given path to absolute path. 69 if t := fileRealPath(path); t == "" { 70 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" does not exist`, path) 71 } else { 72 path = t 73 } 74 // Create callback object. 75 callback = &Callback{ 76 Id: callbackIdGenerator.Add(1), 77 Func: callbackFunc, 78 Path: path, 79 name: name, 80 recursive: true, 81 } 82 if len(recursive) > 0 { 83 callback.recursive = recursive[0] 84 } 85 // Register the callback to watcher. 86 w.callbacks.LockFunc(func(m map[string]interface{}) { 87 list := (*glist.List)(nil) 88 if v, ok := m[path]; !ok { 89 list = glist.New(true) 90 m[path] = list 91 } else { 92 list = v.(*glist.List) 93 } 94 callback.elem = list.PushBack(callback) 95 }) 96 // Add the path to underlying monitor. 97 if err = w.watcher.Add(path); err != nil { 98 err = gerror.Wrapf(err, `add watch failed for path "%s"`, path) 99 } else { 100 intlog.Printf(context.TODO(), "watcher adds monitor for: %s", path) 101 } 102 // Add the callback to global callback map. 103 callbackIdMap.Set(callback.Id, callback) 104 return 105 } 106 107 // Close closes the watcher. 108 func (w *Watcher) Close() { 109 w.events.Close() 110 if err := w.watcher.Close(); err != nil { 111 intlog.Errorf(context.TODO(), `%+v`, err) 112 } 113 close(w.closeChan) 114 } 115 116 // Remove removes monitor and all callbacks associated with the `path` recursively. 117 func (w *Watcher) Remove(path string) error { 118 // Firstly remove the callbacks of the path. 119 if value := w.callbacks.Remove(path); value != nil { 120 list := value.(*glist.List) 121 for { 122 if item := list.PopFront(); item != nil { 123 callbackIdMap.Remove(item.(*Callback).Id) 124 } else { 125 break 126 } 127 } 128 } 129 // Secondly remove monitor of all sub-files which have no callbacks. 130 if subPaths, err := fileScanDir(path, "*", true); err == nil && len(subPaths) > 0 { 131 for _, subPath := range subPaths { 132 if w.checkPathCanBeRemoved(subPath) { 133 if internalErr := w.watcher.Remove(subPath); internalErr != nil { 134 intlog.Errorf(context.TODO(), `%+v`, internalErr) 135 } 136 } 137 } 138 } 139 // Lastly remove the monitor of the path from underlying monitor. 140 err := w.watcher.Remove(path) 141 if err != nil { 142 err = gerror.Wrapf(err, `remove watch failed for path "%s"`, path) 143 } 144 return err 145 } 146 147 // checkPathCanBeRemoved checks whether the given path have no callbacks bound. 148 func (w *Watcher) checkPathCanBeRemoved(path string) bool { 149 // Firstly check the callbacks in the watcher directly. 150 if v := w.callbacks.Get(path); v != nil { 151 return false 152 } 153 // Secondly check its parent whether has callbacks. 154 dirPath := fileDir(path) 155 if v := w.callbacks.Get(dirPath); v != nil { 156 for _, c := range v.(*glist.List).FrontAll() { 157 if c.(*Callback).recursive { 158 return false 159 } 160 } 161 return false 162 } 163 // Recursively check its parent. 164 parentDirPath := "" 165 for { 166 parentDirPath = fileDir(dirPath) 167 if parentDirPath == dirPath { 168 break 169 } 170 if v := w.callbacks.Get(parentDirPath); v != nil { 171 for _, c := range v.(*glist.List).FrontAll() { 172 if c.(*Callback).recursive { 173 return false 174 } 175 } 176 return false 177 } 178 dirPath = parentDirPath 179 } 180 return true 181 } 182 183 // RemoveCallback removes callback with given callback id from watcher. 184 func (w *Watcher) RemoveCallback(callbackId int) { 185 callback := (*Callback)(nil) 186 if r := callbackIdMap.Get(callbackId); r != nil { 187 callback = r.(*Callback) 188 } 189 if callback != nil { 190 if r := w.callbacks.Get(callback.Path); r != nil { 191 r.(*glist.List).Remove(callback.elem) 192 } 193 callbackIdMap.Remove(callbackId) 194 if callback.name != "" { 195 w.nameSet.Remove(callback.name) 196 } 197 } 198 }