2018-11-09 17:25:50 +00:00
|
|
|
package watch
|
2017-10-20 10:51:56 +00:00
|
|
|
|
|
|
|
import (
|
2023-02-18 23:13:23 +00:00
|
|
|
"git.blob42.xyz/gomark/gosuki/logging"
|
2018-11-09 17:25:50 +00:00
|
|
|
|
2017-10-20 10:51:56 +00:00
|
|
|
"github.com/fsnotify/fsnotify"
|
|
|
|
)
|
|
|
|
|
2018-11-09 17:25:50 +00:00
|
|
|
var log = logging.GetLogger("WATCH")
|
|
|
|
|
2022-10-27 22:33:20 +00:00
|
|
|
type WatchRunner interface {
|
|
|
|
Watcher
|
|
|
|
Runner
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the browser needs the watcher to be reset for each new event
|
|
|
|
type ResetWatcher interface {
|
2023-09-08 10:00:06 +00:00
|
|
|
ResetWatcher() error // resets a new watcher
|
2022-10-27 22:33:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Required interface to be implemented by browsers that want to use the
|
|
|
|
// fsnotify event loop and watch changes on bookmark files.
|
|
|
|
type Watcher interface {
|
2023-02-18 22:08:12 +00:00
|
|
|
Watch() *WatchDescriptor
|
2022-10-27 22:33:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Runner interface {
|
|
|
|
Run()
|
2017-11-20 15:10:11 +00:00
|
|
|
}
|
|
|
|
|
2023-09-08 10:00:06 +00:00
|
|
|
// interface for modules that keep stats
|
|
|
|
type Stats interface {
|
|
|
|
ResetStats()
|
|
|
|
}
|
|
|
|
|
2018-11-02 17:21:18 +00:00
|
|
|
// Wrapper around fsnotify watcher
|
2022-10-27 22:33:20 +00:00
|
|
|
type WatchDescriptor struct {
|
|
|
|
ID string
|
2018-11-09 17:25:50 +00:00
|
|
|
W *fsnotify.Watcher // underlying fsnotify watcher
|
|
|
|
Watched map[string]*Watch // watched paths
|
|
|
|
Watches []*Watch // helper var
|
2022-10-27 22:33:20 +00:00
|
|
|
|
|
|
|
// channel used to communicate watched events
|
|
|
|
eventsChan chan fsnotify.Event
|
|
|
|
isWatching bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w WatchDescriptor) hasReducer() bool {
|
|
|
|
//TODO: test the type of eventsChan
|
|
|
|
return w.eventsChan != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewWatcherWithReducer(name string, reducerLen int, watches ...*Watch) (*WatchDescriptor, error) {
|
|
|
|
w, err := NewWatcher(name, watches...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
w.eventsChan = make(chan fsnotify.Event, reducerLen)
|
|
|
|
|
|
|
|
return w, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewWatcher(name string, watches ...*Watch) (*WatchDescriptor, error) {
|
|
|
|
|
|
|
|
fswatcher, err := fsnotify.NewWatcher()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
watchedMap := make(map[string]*Watch)
|
|
|
|
for _, v := range watches {
|
|
|
|
watchedMap[v.Path] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
watcher := &WatchDescriptor{
|
|
|
|
ID: name,
|
|
|
|
W: fswatcher,
|
|
|
|
Watched: watchedMap,
|
|
|
|
Watches: watches,
|
|
|
|
eventsChan: nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add all watched paths
|
|
|
|
for _, v := range watches {
|
|
|
|
|
|
|
|
err = watcher.W.Add(v.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return watcher, nil
|
2018-11-02 17:21:18 +00:00
|
|
|
}
|
|
|
|
|
2023-02-23 11:07:55 +00:00
|
|
|
// Watch is a a filesystem object that can be watched for changes.
|
2018-11-02 17:21:18 +00:00
|
|
|
type Watch struct {
|
2018-11-09 17:25:50 +00:00
|
|
|
Path string // Path to watch for events
|
|
|
|
EventTypes []fsnotify.Op // events to watch for
|
|
|
|
EventNames []string // event names to watch for (file/dir names)
|
2022-10-27 22:33:20 +00:00
|
|
|
|
2023-02-23 11:07:55 +00:00
|
|
|
// Reset the watcher at each event occurence (useful for `create` events)
|
2022-10-27 22:33:20 +00:00
|
|
|
ResetWatch bool
|
|
|
|
}
|
|
|
|
|
2023-09-05 17:45:21 +00:00
|
|
|
func SpawnWatcher(wr WatchRunner) {
|
|
|
|
watcher := wr.Watch()
|
2022-10-27 22:33:20 +00:00
|
|
|
if ! watcher.isWatching {
|
2023-09-05 17:45:21 +00:00
|
|
|
go WatcherThread(wr)
|
2022-10-27 22:33:20 +00:00
|
|
|
watcher.isWatching = true
|
|
|
|
|
|
|
|
for watched := range watcher.Watched{
|
|
|
|
log.Infof("Watching %s", watched)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-02 17:21:18 +00:00
|
|
|
}
|
|
|
|
|
2018-06-08 15:38:57 +00:00
|
|
|
// Main thread for watching file changes
|
2022-10-27 22:33:20 +00:00
|
|
|
func WatcherThread(w WatchRunner) {
|
2017-11-16 13:27:50 +00:00
|
|
|
|
2023-02-18 22:08:12 +00:00
|
|
|
watcher := w.Watch()
|
2022-10-27 22:33:20 +00:00
|
|
|
log.Infof("<%s> Started watcher", watcher.ID)
|
2017-11-09 20:27:03 +00:00
|
|
|
for {
|
2018-10-26 16:25:07 +00:00
|
|
|
// Keep watcher here as it is reset from within
|
|
|
|
// the select block
|
2018-11-02 17:21:18 +00:00
|
|
|
resetWatch := false
|
2018-10-26 16:25:07 +00:00
|
|
|
|
2017-11-09 20:27:03 +00:00
|
|
|
select {
|
2018-11-09 17:25:50 +00:00
|
|
|
case event := <-watcher.W.Events:
|
2018-11-02 17:21:18 +00:00
|
|
|
// Very verbose
|
2018-11-05 01:40:11 +00:00
|
|
|
//log.Debugf("event: %v | eventName: %v", event.Op, event.Name)
|
2018-06-08 15:38:57 +00:00
|
|
|
|
|
|
|
// On Chrome like browsers the bookmarks file is created
|
|
|
|
// at every change.
|
2018-10-28 14:18:45 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* When a file inside a watched directory is renamed/created,
|
|
|
|
* fsnotify does not seem to resume watching the newly created file, we
|
|
|
|
* need to destroy and create a new watcher. The ResetWatcher() and
|
|
|
|
* `break` statement ensure we get out of the `select` block and catch
|
|
|
|
* the newly created watcher to catch events even after rename/create
|
2023-09-08 13:44:04 +00:00
|
|
|
*
|
|
|
|
* NOTE: this does not seem to be an issue anymore. More testing
|
|
|
|
* and user feedback is needed. Leaving this comment here for now.
|
2018-10-28 14:18:45 +00:00
|
|
|
*/
|
|
|
|
|
2018-11-09 17:25:50 +00:00
|
|
|
for _, watched := range watcher.Watches {
|
|
|
|
for _, watchedEv := range watched.EventTypes {
|
|
|
|
for _, watchedName := range watched.EventNames {
|
2018-11-02 17:21:18 +00:00
|
|
|
if event.Op&watchedEv == watchedEv &&
|
|
|
|
event.Name == watchedName {
|
2018-10-26 16:25:07 +00:00
|
|
|
|
2018-11-02 17:21:18 +00:00
|
|
|
// For watchers who need a reducer
|
|
|
|
// to avoid spammy events
|
2022-10-27 22:33:20 +00:00
|
|
|
if watcher.hasReducer() {
|
|
|
|
ch := watcher.eventsChan
|
2018-11-02 17:21:18 +00:00
|
|
|
ch <- event
|
|
|
|
} else {
|
2018-11-13 18:54:20 +00:00
|
|
|
w.Run()
|
2023-09-08 10:00:06 +00:00
|
|
|
if stats, ok := w.(Stats); ok {
|
|
|
|
stats.ResetStats()
|
|
|
|
}
|
2018-11-02 17:21:18 +00:00
|
|
|
}
|
2018-10-26 16:25:07 +00:00
|
|
|
|
2020-09-09 13:07:29 +00:00
|
|
|
//log.Warningf("event: %v | eventName: %v", event.Op, event.Name)
|
2018-11-02 17:21:18 +00:00
|
|
|
|
2023-09-08 10:00:06 +00:00
|
|
|
//TODO!: remove condition and use interface instead
|
2018-11-09 17:25:50 +00:00
|
|
|
if watched.ResetWatch {
|
2018-11-02 17:21:18 +00:00
|
|
|
log.Debugf("resetting watchers")
|
2022-10-27 22:33:20 +00:00
|
|
|
if r, ok := w.(ResetWatcher); ok {
|
|
|
|
r.ResetWatcher()
|
|
|
|
resetWatch = true // needed to break out of big loop
|
|
|
|
} else {
|
|
|
|
log.Fatalf("<%s> does not implement ResetWatcher", watcher.ID)
|
|
|
|
}
|
2018-11-02 17:21:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-08 15:38:57 +00:00
|
|
|
|
|
|
|
// Firefox keeps the file open and makes changes on it
|
2018-06-14 00:30:18 +00:00
|
|
|
// It needs a debouncer
|
2018-11-02 17:21:18 +00:00
|
|
|
//if event.Name == bookmarkPath {
|
|
|
|
//log.Debugf("event: %v | eventName: %v", event.Op, event.Name)
|
|
|
|
////go debounce(1000*time.Millisecond, spammyEventsChannel, w)
|
|
|
|
//ch := w.EventsChan()
|
|
|
|
//ch <- event
|
|
|
|
////w.Run()
|
|
|
|
//}
|
2018-11-09 17:25:50 +00:00
|
|
|
case err := <-watcher.W.Errors:
|
2019-02-18 18:44:27 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
}
|
2017-10-20 10:51:56 +00:00
|
|
|
}
|
2023-02-23 11:07:55 +00:00
|
|
|
|
|
|
|
if resetWatch {
|
2023-09-08 13:44:04 +00:00
|
|
|
log.Debug("breaking out of watch loop")
|
2023-02-23 11:07:55 +00:00
|
|
|
break
|
|
|
|
}
|
2017-10-20 10:51:56 +00:00
|
|
|
}
|
|
|
|
}
|