github.com/wangyougui/gf/v2@v2.6.5/os/gfsnotify/gfsnotify_watcher_loop.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  	"github.com/wangyougui/gf/v2/errors/gcode"
    12  	"github.com/wangyougui/gf/v2/errors/gerror"
    13  
    14  	"github.com/wangyougui/gf/v2/container/glist"
    15  	"github.com/wangyougui/gf/v2/internal/intlog"
    16  )
    17  
    18  // watchLoop starts the loop for event listening from underlying inotify monitor.
    19  func (w *Watcher) watchLoop() {
    20  	go func() {
    21  		for {
    22  			select {
    23  			// Close event.
    24  			case <-w.closeChan:
    25  				return
    26  
    27  			// Event listening.
    28  			case ev := <-w.watcher.Events:
    29  				// Filter the repeated event in custom duration.
    30  				_, err := w.cache.SetIfNotExist(
    31  					context.Background(),
    32  					ev.String(),
    33  					func(ctx context.Context) (value interface{}, err error) {
    34  						w.events.Push(&Event{
    35  							event:   ev,
    36  							Path:    ev.Name,
    37  							Op:      Op(ev.Op),
    38  							Watcher: w,
    39  						})
    40  						return struct{}{}, nil
    41  					}, repeatEventFilterDuration,
    42  				)
    43  				if err != nil {
    44  					intlog.Errorf(context.TODO(), `%+v`, err)
    45  				}
    46  
    47  			case err := <-w.watcher.Errors:
    48  				intlog.Errorf(context.TODO(), `%+v`, err)
    49  			}
    50  		}
    51  	}()
    52  }
    53  
    54  // eventLoop is the core event handler.
    55  func (w *Watcher) eventLoop() {
    56  	go func() {
    57  		for {
    58  			if v := w.events.Pop(); v != nil {
    59  				event := v.(*Event)
    60  				// If there's no any callback of this path, it removes it from monitor.
    61  				callbacks := w.getCallbacks(event.Path)
    62  				if len(callbacks) == 0 {
    63  					_ = w.watcher.Remove(event.Path)
    64  					continue
    65  				}
    66  				switch {
    67  				case event.IsRemove():
    68  					// It should check again the existence of the path.
    69  					// It adds it back to the monitor if it still exists.
    70  					if fileExists(event.Path) {
    71  						// It adds the path back to monitor.
    72  						// We need no worry about the repeat adding.
    73  						if err := w.watcher.Add(event.Path); err != nil {
    74  							intlog.Errorf(context.TODO(), `%+v`, err)
    75  						} else {
    76  							intlog.Printf(context.TODO(), "fake remove event, watcher re-adds monitor for: %s", event.Path)
    77  						}
    78  						// Change the event to RENAME, which means it renames itself to its origin name.
    79  						event.Op = RENAME
    80  					}
    81  
    82  				case event.IsRename():
    83  					// It should check again the existence of the path.
    84  					// It adds it back to the monitor if it still exists.
    85  					// Especially Some editors might do RENAME and then CHMOD when it's editing file.
    86  					if fileExists(event.Path) {
    87  						// It might lost the monitoring for the path, so we add the path back to monitor.
    88  						// We need no worry about the repeat adding.
    89  						if err := w.watcher.Add(event.Path); err != nil {
    90  							intlog.Errorf(context.TODO(), `%+v`, err)
    91  						} else {
    92  							intlog.Printf(context.TODO(), "fake rename event, watcher re-adds monitor for: %s", event.Path)
    93  						}
    94  						// Change the event to CHMOD.
    95  						event.Op = CHMOD
    96  					}
    97  
    98  				case event.IsCreate():
    99  					// =========================================
   100  					// Note that it here just adds the path to monitor without any callback registering,
   101  					// because its parent already has the callbacks.
   102  					// =========================================
   103  					if fileIsDir(event.Path) {
   104  						// If it's a folder, it then does adding recursively to monitor.
   105  						for _, subPath := range fileAllDirs(event.Path) {
   106  							if fileIsDir(subPath) {
   107  								if err := w.watcher.Add(subPath); err != nil {
   108  									intlog.Errorf(context.TODO(), `%+v`, err)
   109  								} else {
   110  									intlog.Printf(context.TODO(), "folder creation event, watcher adds monitor for: %s", subPath)
   111  								}
   112  							}
   113  						}
   114  					} else {
   115  						// If it's a file, it directly adds it to monitor.
   116  						if err := w.watcher.Add(event.Path); err != nil {
   117  							intlog.Errorf(context.TODO(), `%+v`, err)
   118  						} else {
   119  							intlog.Printf(context.TODO(), "file creation event, watcher adds monitor for: %s", event.Path)
   120  						}
   121  					}
   122  				}
   123  				// Calling the callbacks in order.
   124  				for _, callback := range callbacks {
   125  					go func(callback *Callback) {
   126  						defer func() {
   127  							if err := recover(); err != nil {
   128  								switch err {
   129  								case callbackExitEventPanicStr:
   130  									w.RemoveCallback(callback.Id)
   131  								default:
   132  									if e, ok := err.(error); ok {
   133  										panic(gerror.WrapCode(gcode.CodeInternalPanic, e))
   134  									}
   135  									panic(err)
   136  								}
   137  							}
   138  						}()
   139  						callback.Func(event)
   140  					}(callback)
   141  				}
   142  			} else {
   143  				break
   144  			}
   145  		}
   146  	}()
   147  }
   148  
   149  // getCallbacks searches and returns all callbacks with given `path`.
   150  // It also searches its parents for callbacks if they're recursive.
   151  func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) {
   152  	// Firstly add the callbacks of itself.
   153  	if v := w.callbacks.Get(path); v != nil {
   154  		for _, v := range v.(*glist.List).FrontAll() {
   155  			callback := v.(*Callback)
   156  			callbacks = append(callbacks, callback)
   157  		}
   158  	}
   159  	// Secondly searches its direct parent for callbacks.
   160  	// It is special handling here, which is the different between `recursive` and `not recursive` logic
   161  	// for direct parent folder of `path` that events are from.
   162  	dirPath := fileDir(path)
   163  	if v := w.callbacks.Get(dirPath); v != nil {
   164  		for _, v := range v.(*glist.List).FrontAll() {
   165  			callback := v.(*Callback)
   166  			callbacks = append(callbacks, callback)
   167  		}
   168  	}
   169  	// Lastly searches all the parents of directory of `path` recursively for callbacks.
   170  	for {
   171  		parentDirPath := fileDir(dirPath)
   172  		if parentDirPath == dirPath {
   173  			break
   174  		}
   175  		if v := w.callbacks.Get(parentDirPath); v != nil {
   176  			for _, v := range v.(*glist.List).FrontAll() {
   177  				callback := v.(*Callback)
   178  				if callback.recursive {
   179  					callbacks = append(callbacks, callback)
   180  				}
   181  			}
   182  		}
   183  		dirPath = parentDirPath
   184  	}
   185  	return
   186  }