github.com/gogf/gf@v1.16.9/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/gogf/gf. 6 7 package gfsnotify 8 9 import ( 10 "context" 11 "github.com/gogf/gf/errors/gcode" 12 "github.com/gogf/gf/errors/gerror" 13 "github.com/gogf/gf/internal/intlog" 14 15 "github.com/gogf/gf/container/glist" 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 intlog.Error(context.TODO(), err) 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 intlog.Error(context.TODO(), err) 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.Error(context.TODO(), 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 r := w.callbacks.Remove(path); r != nil { 120 list := r.(*glist.List) 121 for { 122 if r := list.PopFront(); r != nil { 123 callbackIdMap.Remove(r.(*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 err := w.watcher.Remove(subPath); err != nil { 134 intlog.Error(context.TODO(), err) 135 } 136 } 137 } 138 } 139 // Lastly remove the monitor of the path from underlying monitor. 140 return w.watcher.Remove(path) 141 } 142 143 // checkPathCanBeRemoved checks whether the given path have no callbacks bound. 144 func (w *Watcher) checkPathCanBeRemoved(path string) bool { 145 // Firstly check the callbacks in the watcher directly. 146 if v := w.callbacks.Get(path); v != nil { 147 return false 148 } 149 // Secondly check its parent whether has callbacks. 150 dirPath := fileDir(path) 151 if v := w.callbacks.Get(dirPath); v != nil { 152 for _, c := range v.(*glist.List).FrontAll() { 153 if c.(*Callback).recursive { 154 return false 155 } 156 } 157 return false 158 } 159 // Recursively check its parent. 160 parentDirPath := "" 161 for { 162 parentDirPath = fileDir(dirPath) 163 if parentDirPath == dirPath { 164 break 165 } 166 if v := w.callbacks.Get(parentDirPath); v != nil { 167 for _, c := range v.(*glist.List).FrontAll() { 168 if c.(*Callback).recursive { 169 return false 170 } 171 } 172 return false 173 } 174 dirPath = parentDirPath 175 } 176 return true 177 } 178 179 // RemoveCallback removes callback with given callback id from watcher. 180 func (w *Watcher) RemoveCallback(callbackId int) { 181 callback := (*Callback)(nil) 182 if r := callbackIdMap.Get(callbackId); r != nil { 183 callback = r.(*Callback) 184 } 185 if callback != nil { 186 if r := w.callbacks.Get(callback.Path); r != nil { 187 r.(*glist.List).Remove(callback.elem) 188 } 189 callbackIdMap.Remove(callbackId) 190 if callback.name != "" { 191 w.nameSet.Remove(callback.name) 192 } 193 } 194 }