mirror of
https://gitee.com/johng/gf.git
synced 2024-12-01 19:57:40 +08:00
187 lines
5.7 KiB
Go
187 lines
5.7 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package gfsnotify
|
|
|
|
import (
|
|
"context"
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
|
|
"github.com/gogf/gf/v2/container/glist"
|
|
"github.com/gogf/gf/v2/internal/intlog"
|
|
)
|
|
|
|
// watchLoop starts the loop for event listening from underlying inotify monitor.
|
|
func (w *Watcher) watchLoop() {
|
|
go func() {
|
|
for {
|
|
select {
|
|
// Close event.
|
|
case <-w.closeChan:
|
|
return
|
|
|
|
// Event listening.
|
|
case ev := <-w.watcher.Events:
|
|
// Filter the repeated event in custom duration.
|
|
_, err := w.cache.SetIfNotExist(
|
|
context.Background(),
|
|
ev.String(),
|
|
func(ctx context.Context) (value interface{}, err error) {
|
|
w.events.Push(&Event{
|
|
event: ev,
|
|
Path: ev.Name,
|
|
Op: Op(ev.Op),
|
|
Watcher: w,
|
|
})
|
|
return struct{}{}, nil
|
|
}, repeatEventFilterDuration,
|
|
)
|
|
if err != nil {
|
|
intlog.Errorf(context.TODO(), `%+v`, err)
|
|
}
|
|
|
|
case err := <-w.watcher.Errors:
|
|
intlog.Errorf(context.TODO(), `%+v`, err)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// eventLoop is the core event handler.
|
|
func (w *Watcher) eventLoop() {
|
|
go func() {
|
|
for {
|
|
if v := w.events.Pop(); v != nil {
|
|
event := v.(*Event)
|
|
// If there's no any callback of this path, it removes it from monitor.
|
|
callbacks := w.getCallbacks(event.Path)
|
|
if len(callbacks) == 0 {
|
|
_ = w.watcher.Remove(event.Path)
|
|
continue
|
|
}
|
|
switch {
|
|
case event.IsRemove():
|
|
// It should check again the existence of the path.
|
|
// It adds it back to the monitor if it still exists.
|
|
if fileExists(event.Path) {
|
|
// It adds the path back to monitor.
|
|
// We need no worry about the repeat adding.
|
|
if err := w.watcher.Add(event.Path); err != nil {
|
|
intlog.Errorf(context.TODO(), `%+v`, err)
|
|
} else {
|
|
intlog.Printf(context.TODO(), "fake remove event, watcher re-adds monitor for: %s", event.Path)
|
|
}
|
|
// Change the event to RENAME, which means it renames itself to its origin name.
|
|
event.Op = RENAME
|
|
}
|
|
|
|
case event.IsRename():
|
|
// It should check again the existence of the path.
|
|
// It adds it back to the monitor if it still exists.
|
|
// Especially Some editors might do RENAME and then CHMOD when it's editing file.
|
|
if fileExists(event.Path) {
|
|
// It might lost the monitoring for the path, so we add the path back to monitor.
|
|
// We need no worry about the repeat adding.
|
|
if err := w.watcher.Add(event.Path); err != nil {
|
|
intlog.Errorf(context.TODO(), `%+v`, err)
|
|
} else {
|
|
intlog.Printf(context.TODO(), "fake rename event, watcher re-adds monitor for: %s", event.Path)
|
|
}
|
|
// Change the event to CHMOD.
|
|
event.Op = CHMOD
|
|
}
|
|
|
|
case event.IsCreate():
|
|
// =========================================
|
|
// Note that it here just adds the path to monitor without any callback registering,
|
|
// because its parent already has the callbacks.
|
|
// =========================================
|
|
if fileIsDir(event.Path) {
|
|
// If it's a folder, it then does adding recursively to monitor.
|
|
for _, subPath := range fileAllDirs(event.Path) {
|
|
if fileIsDir(subPath) {
|
|
if err := w.watcher.Add(subPath); err != nil {
|
|
intlog.Errorf(context.TODO(), `%+v`, err)
|
|
} else {
|
|
intlog.Printf(context.TODO(), "folder creation event, watcher adds monitor for: %s", subPath)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// If it's a file, it directly adds it to monitor.
|
|
if err := w.watcher.Add(event.Path); err != nil {
|
|
intlog.Errorf(context.TODO(), `%+v`, err)
|
|
} else {
|
|
intlog.Printf(context.TODO(), "file creation event, watcher adds monitor for: %s", event.Path)
|
|
}
|
|
}
|
|
}
|
|
// Calling the callbacks in order.
|
|
for _, callback := range callbacks {
|
|
go func(callback *Callback) {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
switch err {
|
|
case callbackExitEventPanicStr:
|
|
w.RemoveCallback(callback.Id)
|
|
default:
|
|
if e, ok := err.(error); ok {
|
|
panic(gerror.WrapCode(gcode.CodeInternalPanic, e))
|
|
}
|
|
panic(err)
|
|
}
|
|
}
|
|
}()
|
|
callback.Func(event)
|
|
}(callback)
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// getCallbacks searches and returns all callbacks with given `path`.
|
|
// It also searches its parents for callbacks if they're recursive.
|
|
func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) {
|
|
// Firstly add the callbacks of itself.
|
|
if v := w.callbacks.Get(path); v != nil {
|
|
for _, v := range v.(*glist.List).FrontAll() {
|
|
callback := v.(*Callback)
|
|
callbacks = append(callbacks, callback)
|
|
}
|
|
}
|
|
// Secondly searches its direct parent for callbacks.
|
|
// It is special handling here, which is the different between `recursive` and `not recursive` logic
|
|
// for direct parent folder of `path` that events are from.
|
|
dirPath := fileDir(path)
|
|
if v := w.callbacks.Get(dirPath); v != nil {
|
|
for _, v := range v.(*glist.List).FrontAll() {
|
|
callback := v.(*Callback)
|
|
callbacks = append(callbacks, callback)
|
|
}
|
|
}
|
|
// Lastly searches all the parents of directory of `path` recursively for callbacks.
|
|
for {
|
|
parentDirPath := fileDir(dirPath)
|
|
if parentDirPath == dirPath {
|
|
break
|
|
}
|
|
if v := w.callbacks.Get(parentDirPath); v != nil {
|
|
for _, v := range v.(*glist.List).FrontAll() {
|
|
callback := v.(*Callback)
|
|
if callback.recursive {
|
|
callbacks = append(callbacks, callback)
|
|
}
|
|
}
|
|
}
|
|
dirPath = parentDirPath
|
|
}
|
|
return
|
|
}
|