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  }