gosuki/watch/watcher.go

190 lines
4.4 KiB
Go
Raw Normal View History

2018-11-09 17:25:50 +00:00
package watch
2017-10-20 10:51:56 +00:00
import (
2020-11-06 17:50:36 +00:00
"git.sp4ke.xyz/sp4ke/gomark/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")
type WatchRunner interface {
Watcher
Runner
}
// If the browser needs the watcher to be reset for each new event
type ResetWatcher interface {
ResetWatcher() // resets a new watcher
}
// Required interface to be implemented by browsers that want to use the
// fsnotify event loop and watch changes on bookmark files.
type Watcher interface {
Watch() *WatchDescriptor
}
type Runner interface {
Run()
2017-11-20 15:10:11 +00:00
}
2018-11-02 17:21:18 +00:00
// Wrapper around fsnotify watcher
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
// 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
}
// Details about the object being watched
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)
// Reset the watcher at each event occurence (useful for create events)
ResetWatch bool
}
func SpawnWatcher(w WatchRunner) {
watcher := w.Watch()
if ! watcher.isWatching {
go WatcherThread(w)
watcher.isWatching = true
for watched := range watcher.Watched{
log.Infof("Watching %s", watched)
}
}
2018-11-02 17:21:18 +00:00
}
// Main thread for watching file changes
func WatcherThread(w WatchRunner) {
watcher := w.Watch()
log.Infof("<%s> Started watcher", watcher.ID)
for {
// Keep watcher here as it is reset from within
// the select block
2018-11-02 17:21:18 +00:00
resetWatch := false
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)
// On Chrome like browsers the bookmarks file is created
// at every change.
/*
* 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
*/
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-11-02 17:21:18 +00:00
// For watchers who need a reducer
// to avoid spammy events
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()
2018-11-02 17:21:18 +00:00
}
//log.Warningf("event: %v | eventName: %v", event.Op, event.Name)
2018-11-02 17:21:18 +00:00
2018-11-09 17:25:50 +00:00
if watched.ResetWatch {
2018-11-02 17:21:18 +00:00
log.Debugf("resetting watchers")
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
}
}
}
}
}
if resetWatch {
break
2017-10-20 10:51:56 +00:00
}
// Firefox keeps the file open and makes changes on it
// 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
}
}
}