gfsnotify_watcher.go 6.0 KB

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