Wip firefox watch process

This commit is contained in:
Chakib Ben Ziane 2018-11-02 18:21:18 +01:00
parent 95791ade34
commit 17e370be39
5 changed files with 171 additions and 99 deletions

View File

@ -58,7 +58,7 @@ type IBrowser interface {
// //
// `BufferDB`: sqlite buffer used across jobs // `BufferDB`: sqlite buffer used across jobs
type BaseBrowser struct { type BaseBrowser struct {
watcher *fsnotify.Watcher watcher *Watcher
eventsChan chan fsnotify.Event eventsChan chan fsnotify.Event
baseDir string baseDir string
bkFile string bkFile string
@ -84,9 +84,10 @@ type BaseBrowser struct {
parseHooks []ParseHook parseHooks []ParseHook
} }
func (bw *BaseBrowser) GetFileWatcher() *fsnotify.Watcher { func (bw *BaseBrowser) GetWatcher() *Watcher {
fsnotifyWatcherType := reflect.TypeOf((*fsnotify.Watcher)(nil)).Elem() watcherType := reflect.TypeOf((*Watcher)(nil)).Elem()
if reflect.TypeOf(bw.watcher) == reflect.PtrTo(fsnotifyWatcherType) { // In case we use other types of watchers/events
if reflect.TypeOf(bw.watcher) == reflect.PtrTo(watcherType) {
return bw.watcher return bw.watcher
} }
return nil return nil
@ -101,8 +102,9 @@ func (bw *BaseBrowser) Load() {
log.Criticalf("<%s> Loading bookmarks while cache not yet initialized !", bw.name) log.Criticalf("<%s> Loading bookmarks while cache not yet initialized !", bw.name)
} }
// In case we use other types of watchers/events
if bw.useFileWatcher && bw.watcher == nil { if bw.useFileWatcher && bw.watcher == nil {
log.Warningf("<%s> watcher not initialized, use SetupWatcher() when creating the browser !", bw.name) log.Warningf("<%s> watcher not initialized, use SetupFileWatcher() when creating the browser !", bw.name)
} }
log.Debugf("<%s> preloading bookmarks", bw.name) log.Debugf("<%s> preloading bookmarks", bw.name)
@ -120,32 +122,51 @@ func (bw *BaseBrowser) GetDir() string {
return bw.baseDir return bw.baseDir
} }
func (bw *BaseBrowser) SetupFileWatcher() { // Setup file watcher using the provided []Watch elements
func (bw *BaseBrowser) SetupFileWatcher(watches ...*Watch) {
var err error
if !bw.useFileWatcher { if !bw.useFileWatcher {
return return
} }
var err error
bw.watcher, err = fsnotify.NewWatcher() fswatcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
log.Critical(err) log.Critical(err)
} }
err = bw.watcher.Add(bw.GetDir())
if err != nil { watchedMap := make(map[string]*Watch)
log.Critical(err) for _, v := range watches {
watchedMap[v.path] = v
}
bw.watcher = &Watcher{
w: fswatcher,
watched: watchedMap,
watches: watches,
}
// Add all watched paths
for _, v := range watches {
err = bw.watcher.w.Add(v.path)
if err != nil {
log.Critical(err)
}
} }
} }
func (bw *BaseBrowser) ResetWatcher() { func (bw *BaseBrowser) ResetWatcher() {
err := bw.watcher.Close() err := bw.watcher.w.Close()
if err != nil { if err != nil {
log.Critical(err) log.Critical(err)
} }
bw.SetupFileWatcher() bw.SetupFileWatcher(bw.watcher.watches...)
} }
func (bw *BaseBrowser) Close() error { func (bw *BaseBrowser) Close() error {
err := bw.watcher.Close() err := bw.watcher.w.Close()
if err != nil { if err != nil {
return err return err
} }
@ -197,6 +218,14 @@ func (b *BaseBrowser) ResetStats() {
b.Stats.currentUrlCount = 0 b.Stats.currentUrlCount = 0
} }
func (b *BaseBrowser) HasReducer() bool {
return b.eventsChan != nil
}
func (b *BaseBrowser) Name() string {
return b.name
}
// Runs browsed defined hooks on bookmark // Runs browsed defined hooks on bookmark
func (b *BaseBrowser) RunParseHooks(node *Node) { func (b *BaseBrowser) RunParseHooks(node *Node) {
for _, hook := range b.parseHooks { for _, hook := range b.parseHooks {

View File

@ -7,11 +7,12 @@ import (
"github.com/OneOfOne/xxhash" "github.com/OneOfOne/xxhash"
"github.com/buger/jsonparser" "github.com/buger/jsonparser"
"github.com/fsnotify/fsnotify"
) )
var ChromeData = BrowserPaths{ var ChromeData = BrowserPaths{
"Bookmarks", BookmarkFile: "Bookmarks",
"/home/spike/.config/google-chrome-unstable/Default/", BookmarkDir: "/home/spike/.config/google-chrome-unstable/Default/",
} }
type ChromeBrowser struct { type ChromeBrowser struct {
@ -75,19 +76,27 @@ func (rawNode *RawNode) getNode() *Node {
} }
func NewChromeBrowser() IBrowser { func NewChromeBrowser() IBrowser {
browser := &ChromeBrowser{} browser := new(ChromeBrowser)
browser.name = "chrome" browser.name = "chrome"
browser.bType = TChrome browser.bType = TChrome
browser.baseDir = ChromeData.BookmarkDir browser.baseDir = ChromeData.BookmarkDir
browser.bkFile = ChromeData.BookmarkFile browser.bkFile = ChromeData.BookmarkFile
browser.Stats = &ParserStats{} browser.Stats = new(ParserStats)
browser.NodeTree = &Node{Name: "root", Parent: nil, Type: "root"} browser.NodeTree = &Node{Name: "root", Parent: nil, Type: "root"}
browser.useFileWatcher = true browser.useFileWatcher = true
// Across jobs buffer // Across jobs buffer
browser.InitBuffer() browser.InitBuffer()
browser.SetupFileWatcher() // Create watch objects, we will watch the basedir for create events
watchedEvents := []fsnotify.Op{fsnotify.Create}
w := &Watch{
path: browser.baseDir,
eventTypes: watchedEvents,
eventNames: []string{path.Join(browser.baseDir, browser.bkFile)},
resetWatch: true,
}
browser.SetupFileWatcher(w)
return browser return browser
} }

View File

@ -1,22 +1,22 @@
package main package main
import ( import (
"database/sql"
"path" "path"
"time" "time"
sqlite3 "github.com/mattn/go-sqlite3" "github.com/fsnotify/fsnotify"
) )
var Firefox = BrowserPaths{ var Firefox = BrowserPaths{
"places.sqlite", BookmarkFile: "places.sqlite",
"/home/spike/.mozilla/firefox/p1rrgord.default/", BookmarkDir: "/home/spike/.mozilla/firefox/p1rrgord.default/",
} }
const ( const (
MozPlacesRootID = 1 MozPlacesRootID = 1
MozPlacesTagsRootID = 4 MozPlacesTagsRootID = 4
MozPlacesMobileRootID = 6 MozPlacesMobileRootID = 6
MozMinJobInterval = 2 * time.Second
) )
type FFBrowser struct { type FFBrowser struct {
@ -40,47 +40,38 @@ func NewFFBrowser() IBrowser {
browser.bType = TFirefox browser.bType = TFirefox
browser.baseDir = Firefox.BookmarkDir browser.baseDir = Firefox.BookmarkDir
browser.bkFile = Firefox.BookmarkFile browser.bkFile = Firefox.BookmarkFile
browser.useFileWatcher = false browser.useFileWatcher = true
browser.Stats = &ParserStats{} browser.Stats = &ParserStats{}
browser.NodeTree = &Node{Name: "root", Parent: nil, Type: "root"} browser.NodeTree = &Node{Name: "root", Parent: nil, Type: "root"}
browser.urlMap = make(map[string]*Node) browser.urlMap = make(map[string]*Node)
// sqlite update hook
sql.Register(DBUpdateMode,
&sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
log.Warningf("registered connect hook <%s>", DBUpdateMode)
conn.RegisterUpdateHook(
func(op int, db string, table string, rowid int64) {
switch op {
case sqlite3.SQLITE_UPDATE:
log.Warning("Notified of insert on db", db, "table", table, "rowid", rowid)
}
// Update hook here
log.Warningf("notified op %s", op)
})
return nil
},
})
// Initialize `places.sqlite` // Initialize `places.sqlite`
bookmarkPath := path.Join(browser.baseDir, browser.bkFile) bookmarkPath := path.Join(browser.baseDir, browser.bkFile)
browser.places = DB{}.New("Places", bookmarkPath) browser.places = DB{}.New("Places", bookmarkPath)
browser.places.engineMode = DBUpdateMode
browser.places.InitRO() browser.places.InitRO()
// Buffer that lives accross Run() jobs // Buffer that lives accross Run() jobs
browser.InitBuffer() browser.InitBuffer()
// Setup watcher
w := &Watch{
path: path.Join(browser.baseDir),
eventTypes: []fsnotify.Op{fsnotify.Write},
eventNames: []string{path.Join(browser.baseDir, "places.sqlite-wal")},
resetWatch: false,
}
browser.SetupFileWatcher(w)
/* /*
*Run debouncer to avoid duplicate running of jobs *Run reducer to avoid duplicate running of jobs
*when a batch of events is received *when a batch of events is received
*/ */
//browser.eventsChan = make(chan fsnotify.Event, EventsChanLen) browser.eventsChan = make(chan fsnotify.Event, EventsChanLen)
//go debouncer(3000*time.Millisecond, browser.eventsChan, browser)
go reducer(MozMinJobInterval, browser.eventsChan, browser)
return browser return browser
} }
@ -105,11 +96,12 @@ func (bw *FFBrowser) Watch() bool {
log.Debugf("<%s> TODO ... ", bw.name) log.Debugf("<%s> TODO ... ", bw.name)
if !bw.isWatching { if !bw.isWatching {
go WatcherThread(bw)
bw.isWatching = true bw.isWatching = true
log.Infof("<%s> Watching %s", bw.name, bw.GetPath())
return true return true
} }
//return false
return false return false
} }
@ -248,5 +240,6 @@ func getFFBookmarks(bw *FFBrowser) {
} }
func (bw *FFBrowser) Run() { func (bw *FFBrowser) Run() {
log.Debugf("<%s>", bw.name)
} }

View File

@ -7,37 +7,38 @@ import (
) )
// TODO // TODO
// Run debounce in it's own thread when the watcher is started // Run reducer in it's own thread when the watcher is started
// It receives a struct{event, func} and runs the func only once in the interval // It receives a struct{event, func} and runs the func only once in the interval
func debouncer(interval time.Duration, input chan fsnotify.Event, w IWatchable) { func reducer(interval time.Duration, input chan fsnotify.Event, w IWatchable) {
log.Debug("Running debouncer") var waiting bool
var event fsnotify.Event
var isResting bool log.Debug("Running reducer")
timer := time.NewTimer(interval)
ticker := time.NewTicker(interval)
for { for {
select { select {
case event = <-input: case <-input:
log.Debugf("received an event %v on the events channel", event.Op) log.Debugf("received event, len(chan): %d ", len(input))
if !isResting { if !waiting {
waiting = true
// Run the job // Run the job
//log.Debug("Not resting, running job") log.Debug("Not resting")
time.AfterFunc(1*time.Second, func() { w.Run()
w.Run()
})
//log.Debug("Restting timer")
timer.Reset(interval)
//log.Debug("Is resting now")
isResting = true
}
//else {
//log.Debug("Resting, will not run job")
//}
case <-timer.C: //ticker = time.NewTicker(interval)
//log.Debugf("timer done, not resting") } else { // Ignore this event
isResting = false log.Debug("resting")
break
}
case <-ticker.C:
//log.Debug("tick")
ticker = time.NewTicker(interval)
if waiting {
waiting = false
}
} }
} }
} }

View File

@ -7,29 +7,48 @@ import (
// Used as input to WatcherThread // Used as input to WatcherThread
// It does not have to be a browser as long is the interface is implemented // It does not have to be a browser as long is the interface is implemented
type IWatchable interface { type IWatchable interface {
SetupFileWatcher() // Starts watching bookmarks and runs Load on change Name() string // Name of the watchable
Watch() bool // starts watching linked watcher HasReducer() bool // Does the watchable has a reducer
Run() // Callback fired on event SetupFileWatcher(...*Watch) // Starts watching bookmarks and runs Load on change
GetFileWatcher() *fsnotify.Watcher // returns linked watcher Watch() bool // starts watching linked watcher
ResetWatcher() // resets a new watcher Run() // Callback fired on event
GetPath() string // returns watched path GetWatcher() *Watcher // returns linked watcher
GetDir() string // returns watched dir ResetWatcher() // resets a new watcher
GetPath() string // returns watched path
GetDir() string // returns watched dir
EventsChan() chan fsnotify.Event EventsChan() chan fsnotify.Event
} }
// Wrapper around fsnotify watcher
type Watcher struct {
w *fsnotify.Watcher // underlying fsnotify watcher
watched map[string]*Watch // watched paths
watches []*Watch // helper var
}
// Details about the object being watched
type Watch struct {
path string // Path to watch for events
eventTypes []fsnotify.Op // events to watch for
eventNames []string // event names to watch for (file/dir names)
resetWatch bool // Reset the watcher when the event happens (useful for create events)
}
// Main thread for watching file changes // Main thread for watching file changes
func WatcherThread(w IWatchable) { func WatcherThread(w IWatchable) {
bookmarkPath := w.GetPath() log.Infof("<%s> Started watcher", w.Name())
log.Infof("watching %s", bookmarkPath)
for { for {
// Keep watcher here as it is reset from within // Keep watcher here as it is reset from within
// the select block // the select block
watcher := w.GetFileWatcher() watcher := w.GetWatcher()
resetWatch := false
select { select {
case event := <-watcher.Events: case event := <-watcher.w.Events:
// Very verbose
log.Debugf("event: %v | eventName: %v", event.Op, event.Name)
// On Chrome like browsers the bookmarks file is created // On Chrome like browsers the bookmarks file is created
// at every change. // at every change.
@ -42,28 +61,49 @@ func WatcherThread(w IWatchable) {
* the newly created watcher to catch events even after rename/create * the newly created watcher to catch events even after rename/create
*/ */
if event.Op&fsnotify.Create == fsnotify.Create && for _, watched := range watcher.watches {
event.Name == bookmarkPath { for _, watchedEv := range watched.eventTypes {
for _, watchedName := range watched.eventNames {
w.Run() if event.Op&watchedEv == watchedEv &&
log.Debugf("event: %v | eventName: %v", event.Op, event.Name) event.Name == watchedName {
log.Debugf("resetting watchers") // For watchers who need a reducer
w.ResetWatcher() // to avoid spammy events
if w.HasReducer() {
ch := w.EventsChan()
ch <- event
} else {
//w.Run()
}
//log.Warning("event: %v | eventName: %v", event.Op, event.Name)
if watched.resetWatch {
log.Debugf("resetting watchers")
w.ResetWatcher()
resetWatch = true // needed to break out of big loop
}
}
}
}
}
if resetWatch {
break break
} }
// Firefox keeps the file open and makes changes on it // Firefox keeps the file open and makes changes on it
// It needs a debouncer // It needs a debouncer
if event.Name == bookmarkPath { //if event.Name == bookmarkPath {
log.Debugf("event: %v | eventName: %v", event.Op, event.Name) //log.Debugf("event: %v | eventName: %v", event.Op, event.Name)
//go debounce(1000*time.Millisecond, spammyEventsChannel, w) ////go debounce(1000*time.Millisecond, spammyEventsChannel, w)
ch := w.EventsChan() //ch := w.EventsChan()
ch <- event //ch <- event
//w.Run() ////w.Run()
} //}
case err := <-watcher.Errors: case err := <-watcher.w.Errors:
log.Error(err) log.Error(err)
} }
} }