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  }