use interfaces for cleaner code

This commit is contained in:
Chakib Ben Ziane 2017-11-20 16:05:44 +01:00
parent 1a07be6018
commit 22bf5e7e66
7 changed files with 271 additions and 241 deletions

View File

@ -1,5 +1,6 @@
package main package main
// Bookmark type
type Bookmark struct { type Bookmark struct {
url string url string
metadata string metadata string
@ -9,9 +10,7 @@ type Bookmark struct {
} }
func (bk *Bookmark) add(db *DB) { func (bk *Bookmark) add(db *DB) {
// TODO //log.Debugf("Adding bookmark %s", bk.url)
// Single out unique urls
//debugPrint("%v", bk)
_db := db.handle _db := db.handle
tx, err := _db.Begin() tx, err := _db.Begin()

View File

@ -1,23 +1,42 @@
package main package main
import ( import (
"database/sql" "fmt"
"path"
fsnotify "gopkg.in/fsnotify.v1" "github.com/fsnotify/fsnotify"
) )
type BrowserType uint8 type BrowserType uint8
// Browser types
const ( const (
TChromeBrowser BrowserType = iota TChrome BrowserType = iota
FirefoxBrowser TFirefox
) )
type Browser interface { // Chrome details
New(BrowserType) *Browser // Creates and initialize new browser var Chrome = struct {
Watch() *fsnotify.Watcher // Starts watching bookmarks and runs Load on change BookmarkFile string
Load() // Loads bookmarks to db without watching BookmarkDir string
Parse() // Main parsing method }{
"Bookmarks",
"/home/spike/.config/google-chrome/Default/",
}
type IWatchable interface {
Watch() bool
Watcher() *fsnotify.Watcher // returns browser watcher
Parse() // Main parsing method
GetPath() string //returns bookmark path
GetDir() string // returns bookmarks dir
}
type IBrowser interface {
SetupWatcher() // Starts watching bookmarks and runs Load on change
Watch() bool
InitBuffer() // init buffer db, should be defered to close after call
Load() // Loads bookmarks to db without watching
//Parse(...ParseHook) // Main parsing method with different parsing hooks //Parse(...ParseHook) // Main parsing method with different parsing hooks
Close() // Gracefully finish work and stop watching Close() // Gracefully finish work and stop watching
} }
@ -25,16 +44,52 @@ type Browser interface {
// Base browser class serves as reference for implmented browser types // Base browser class serves as reference for implmented browser types
// Browser should contain enough data internally to not rely on any global // Browser should contain enough data internally to not rely on any global
// variable or constant if possible. // variable or constant if possible.
// To create new browsers, you must implement a New<BrowserType>() function
type BaseBrowser struct { type BaseBrowser struct {
watcher *fsnotify.Watcher watcher *fsnotify.Watcher
baseDir string baseDir string
bkFile string bkFile string
parseFunc func(*Browser) bufferDB *DB
bufferDB *sql.DB stats *ParserStats
stats *parserStats bType BrowserType
name string
isWatching bool
} }
type ChromeBrowser struct { func (bw *BaseBrowser) Watcher() *fsnotify.Watcher {
BaseBrowser //embedding return bw.watcher
}
func (bw *BaseBrowser) Load() {
log.Debug("BaseBrowser Load()")
}
func (bw *BaseBrowser) GetPath() string {
return path.Join(bw.baseDir, bw.bkFile)
}
func (bw *BaseBrowser) GetDir() string {
return bw.baseDir
}
func (bw *BaseBrowser) SetupWatcher() {
var err error
bw.watcher, err = fsnotify.NewWatcher()
logPanic(err)
err = bw.watcher.Add(bw.baseDir)
logPanic(err)
}
func (bw *BaseBrowser) Close() {
err := bw.watcher.Close()
bw.bufferDB.Close()
logPanic(err)
}
func (b *BaseBrowser) InitBuffer() {
bufferName := fmt.Sprintf("buffer_%s", b.name)
bufferPath := fmt.Sprintf(DBBufferFmt, bufferName)
b.bufferDB = DB{}.New(bufferName, bufferPath)
b.bufferDB.Init()
} }

181
chrome.go Normal file
View File

@ -0,0 +1,181 @@
package main
import (
"io/ioutil"
"path"
"github.com/buger/jsonparser"
)
var jsonNodeTypes = struct {
Folder, URL string
}{"folder", "url"}
var jsonNodePaths = struct {
Type, Children, URL string
}{"type", "children", "url"}
type ParseChildFunc func([]byte, jsonparser.ValueType, int, error)
type RecursiveParseFunc func([]byte, []byte, jsonparser.ValueType, int) error
type ChromeBrowser struct {
BaseBrowser //embidding
}
func NewChromeBrowser() IBrowser {
browser := &ChromeBrowser{}
browser.name = "chrome"
browser.bType = TChrome
browser.baseDir = Chrome.BookmarkDir
browser.bkFile = Chrome.BookmarkFile
browser.stats = &ParserStats{}
browser.SetupWatcher()
return browser
}
func (bw *ChromeBrowser) Watch() bool {
if !bw.isWatching {
go WatcherThread(bw)
bw.isWatching = true
return true
}
return false
}
func (bw *ChromeBrowser) Load() {
// Check if cache is initialized
if cacheDB == nil || cacheDB.handle == nil {
log.Critical("cache is not yet initialized !")
panic("cache is not yet initialized !")
}
if bw.watcher == nil {
log.Fatal("watcher not initialized SetupWatcher() !")
}
debugPrint("preloading bookmarks")
bw.Parse()
}
func (bw *ChromeBrowser) Parse() {
// Create buffer db
//bufferDB := DB{"buffer", DB_BUFFER_PATH, nil, false}
bw.InitBuffer()
defer bw.bufferDB.Close()
// Load bookmark file
bookmarkPath := path.Join(bw.baseDir, bw.bkFile)
f, err := ioutil.ReadFile(bookmarkPath)
logPanic(err)
var parseChildren ParseChildFunc
var gJsonParseRecursive RecursiveParseFunc
parseChildren = func(childVal []byte, dataType jsonparser.ValueType, offset int, err error) {
if err != nil {
log.Panic(err)
}
gJsonParseRecursive(nil, childVal, dataType, offset)
}
gJsonParseRecursive = func(key []byte, node []byte, dataType jsonparser.ValueType, offset int) error {
// Core of google chrome bookmark parsing
// Any loading to local db is done here
bw.stats.currentNodeCount++
var nodeType, children []byte
var childrenType jsonparser.ValueType
bookmark := &Bookmark{}
// Paths to lookup in node payload
paths := [][]string{
[]string{"type"},
[]string{"name"}, // Title of page
[]string{"url"},
[]string{"children"},
}
jsonparser.EachKey(node, func(idx int, value []byte, vt jsonparser.ValueType, err error) {
switch idx {
case 0:
nodeType = value
case 1: // name or title
bookmark.metadata = _s(value)
case 2:
bookmark.url = _s(value)
case 3:
children, childrenType = value, vt
}
}, paths...)
// If node type is string ignore (needed for sync_transaction_version)
if dataType == jsonparser.String {
return nil
}
// if node is url(leaf), handle the url
if _s(nodeType) == jsonNodeTypes.URL {
// Add bookmark to db here
//debugPrint("%s", url)
//debugPrint("%s", node)
// Find tags in title
//findTagsInTitle(name)
bw.stats.currentUrlCount++
bookmark.add(bw.bufferDB)
}
// if node is a folder with children
if childrenType == jsonparser.Array && len(children) > 2 { // if len(children) > len("[]")
jsonparser.ArrayEach(node, parseChildren, jsonNodePaths.Children)
}
return nil
}
//debugPrint("parsing bookmarks")
// Begin parsing
rootsData, _, _, _ := jsonparser.Get(f, "roots")
debugPrint("loading bookmarks to bufferdb")
// Load bookmarks to currentJobDB
jsonparser.ObjectEach(rootsData, gJsonParseRecursive)
// Finished parsing
log.Infof("parsed %d bookmarks", bw.stats.currentUrlCount)
// Reset parser counter
bw.stats.lastURLCount = bw.stats.currentUrlCount
bw.stats.lastNodeCount = bw.stats.currentNodeCount
bw.stats.currentNodeCount = 0
bw.stats.currentUrlCount = 0
// Compare currentDb with memCacheDb for new bookmarks
// If cacheDB is empty just copy bufferDB to cacheDB
// until local db is already populated and preloaded
//debugPrint("%d", bufferDB.Count())
if empty, err := cacheDB.isEmpty(); empty {
logPanic(err)
debugPrint("cache empty: loading bufferdb to cachedb")
//start := time.Now()
bw.bufferDB.SyncTo(cacheDB)
//debugPrint("<%s> is now (%d)", cacheDB.name, cacheDB.Count())
//elapsed := time.Since(start)
//debugPrint("copy in %s", elapsed)
debugPrint("syncing <%s> to disk", cacheDB.name)
cacheDB.SyncToDisk(getDBFullPath())
}
// TODO: Check if new/modified bookmarks in buffer compared to cache
debugPrint("TODO: check if new/modified bookmarks in %s compared to %s", bw.bufferDB.name, cacheDB.name)
}

4
db.go
View File

@ -20,7 +20,7 @@ var (
const ( const (
DB_FILENAME = "gomarks.db" DB_FILENAME = "gomarks.db"
DB_MEMCACHE_PATH = "file:memcache?mode=memory&cache=shared" DB_MEMCACHE_PATH = "file:memcache?mode=memory&cache=shared"
DB_BUFFER_PATH = "file:buffer?mode=memory&cache=shared" DBBufferFmt = "file:%s?mode=memory&cache=shared"
DB_BACKUP_HOOK = "sqlite_with_backup" DB_BACKUP_HOOK = "sqlite_with_backup"
) )
@ -119,7 +119,7 @@ func (db *DB) Init() {
} }
func (db *DB) Close() { func (db *DB) Close() {
//debugPrint("Closing <%s>", db.name) debugPrint("Closing <%s>", db.name)
db.handle.Close() db.handle.Close()
} }

View File

@ -15,16 +15,15 @@ func main() {
// Initialize sqlite database available in global `cacheDB` variable // Initialize sqlite database available in global `cacheDB` variable
initDB() initDB()
chromeWatcher := &bookmarkWatcher{} cb := NewChromeBrowser()
chromeWatcher.Init(BOOKMARK_DIR, BOOKMARK_FILE, TChromeBrowser) cb.Load()
chromeWatcher.Preload() _ = cb.Watch()
chromeWatcher.Start()
// Flush to disk for testing // Flush to disk for testing
//flushToDisk() //flushToDisk()
//var chrome Browser //var chrome Browser
//chrome = browsers.New() //chrome = browsers.New(browsres.TChrome)
//chrome.Watch() //chrome.Watch()
<-block <-block

140
parse.go
View File

@ -1,35 +1,20 @@
package main package main
import ( import (
"io/ioutil"
"path"
"regexp" "regexp"
"github.com/buger/jsonparser"
) )
const ( const (
RE_TAGS = `\B#\w+` RE_TAGS = `\B#\w+`
) )
type parserStats struct { type ParserStats struct {
lastNodeCount int lastNodeCount int
lastUrlCount int lastURLCount int
currentNodeCount int currentNodeCount int
currentUrlCount int currentUrlCount int
} }
var jsonNodeTypes = struct {
Folder, Url string
}{"folder", "url"}
var jsonNodePaths = struct {
Type, Children, Url string
}{"type", "children", "url"}
type ParseChildFunc func([]byte, jsonparser.ValueType, int, error)
type RecursiveParseFunc func([]byte, []byte, jsonparser.ValueType, int) error
func _s(value interface{}) string { func _s(value interface{}) string {
return string(value.([]byte)) return string(value.([]byte))
} }
@ -39,124 +24,3 @@ func findTagsInTitle(title []byte) {
tags := regex.FindAll(title, -1) tags := regex.FindAll(title, -1)
debugPrint("%s ---> found following tags: %s", title, tags) debugPrint("%s ---> found following tags: %s", title, tags)
} }
func googleParseBookmarks(bw *bookmarkWatcher) {
// Create buffer db
//bufferDB := DB{"buffer", DB_BUFFER_PATH, nil, false}
bufferDB := DB{}.New("buffer", DB_BUFFER_PATH)
defer bufferDB.Close()
bufferDB.Init()
// Load bookmark file
bookmarkPath := path.Join(bw.baseDir, bw.bkFile)
f, err := ioutil.ReadFile(bookmarkPath)
logPanic(err)
var parseChildren ParseChildFunc
var gJsonParseRecursive RecursiveParseFunc
parseChildren = func(childVal []byte, dataType jsonparser.ValueType, offset int, err error) {
if err != nil {
log.Panic(err)
}
gJsonParseRecursive(nil, childVal, dataType, offset)
}
gJsonParseRecursive = func(key []byte, node []byte, dataType jsonparser.ValueType, offset int) error {
// Core of google chrome bookmark parsing
// Any loading to local db is done here
bw.stats.currentNodeCount++
var nodeType, children []byte
var childrenType jsonparser.ValueType
bookmark := &Bookmark{}
// Paths to lookup in node payload
paths := [][]string{
[]string{"type"},
[]string{"name"}, // Title of page
[]string{"url"},
[]string{"children"},
}
jsonparser.EachKey(node, func(idx int, value []byte, vt jsonparser.ValueType, err error) {
switch idx {
case 0:
nodeType = value
case 1: // name or title
bookmark.metadata = _s(value)
case 2:
bookmark.url = _s(value)
case 3:
children, childrenType = value, vt
}
}, paths...)
// If node type is string ignore (needed for sync_transaction_version)
if dataType == jsonparser.String {
return nil
}
// if node is url(leaf), handle the url
if _s(nodeType) == jsonNodeTypes.Url {
// Add bookmark to db here
//debugPrint("%s", url)
//debugPrint("%s", node)
// Find tags in title
//findTagsInTitle(name)
bw.stats.currentUrlCount++
bookmark.add(bufferDB)
}
// if node is a folder with children
if childrenType == jsonparser.Array && len(children) > 2 { // if len(children) > len("[]")
jsonparser.ArrayEach(node, parseChildren, jsonNodePaths.Children)
}
return nil
}
//debugPrint("parsing bookmarks")
// Begin parsing
rootsData, _, _, _ := jsonparser.Get(f, "roots")
debugPrint("loading bookmarks to bufferdb")
// Load bookmarks to currentJobDB
jsonparser.ObjectEach(rootsData, gJsonParseRecursive)
// Finished parsing
log.Infof("parsed %d bookmarks", bw.stats.currentUrlCount)
// Reset parser counter
bw.stats.lastUrlCount = bw.stats.currentUrlCount
bw.stats.lastNodeCount = bw.stats.currentNodeCount
bw.stats.currentNodeCount = 0
bw.stats.currentUrlCount = 0
// Compare currentDb with memCacheDb for new bookmarks
// If cacheDB is empty just copy bufferDB to cacheDB
// until local db is already populated and preloaded
//debugPrint("%d", bufferDB.Count())
if empty, err := cacheDB.isEmpty(); empty {
logPanic(err)
debugPrint("cache empty: loading bufferdb to cachedb")
//start := time.Now()
bufferDB.SyncTo(cacheDB)
//debugPrint("<%s> is now (%d)", cacheDB.name, cacheDB.Count())
//elapsed := time.Since(start)
//debugPrint("copy in %s", elapsed)
debugPrint("syncing <%s> to disk", cacheDB.name)
cacheDB.SyncToDisk(getDBFullPath())
}
// TODO: Check if new/modified bookmarks in buffer compared to cache
debugPrint("TODO: check if new/modified bookmarks in %s compared to %s", bufferDB.name, cacheDB.name)
//_ = cacheDB.Print()
}

View File

@ -1,100 +1,32 @@
package main package main
import ( import (
"database/sql"
"path"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
) )
type bookmarkWatcher struct { func WatcherThread(w IWatchable) {
watcher *fsnotify.Watcher
baseDir string
bkFile string
parseFunc func(*bookmarkWatcher)
bufferDB *sql.DB
stats *parserStats
}
func (bw *bookmarkWatcher) Close() error { bookmarkPath := w.GetPath()
if err := bw.watcher.Close(); err != nil {
return err
}
return nil
}
func (bw *bookmarkWatcher) Init(basedir string, bkfile string, browserType BrowserType) *bookmarkWatcher {
var err error
bw.baseDir = basedir
bw.bkFile = bkfile
bw.stats = &parserStats{}
bw.watcher, err = fsnotify.NewWatcher()
logPanic(err)
switch browserType {
case TChromeBrowser:
bw.parseFunc = googleParseBookmarks
}
return bw
}
func (bw *bookmarkWatcher) Preload() *bookmarkWatcher {
// Check if cache is initialized
if cacheDB == nil || cacheDB.handle == nil {
log.Critical("cache is not yet initialized !")
panic("cache is not yet initialized !")
}
if bw.watcher == nil {
log.Fatal("please run bookmarkWatcher.Init() first !")
}
debugPrint("preloading bookmarks")
bw.parseFunc(bw)
return bw
}
func (bw *bookmarkWatcher) Start() error {
if err := bw.watcher.Add(bw.baseDir); err != nil {
return err
}
go bWatcherThread(bw, bw.parseFunc)
return nil
}
func bWatcherThread(bw *bookmarkWatcher, parseFunc func(bw *bookmarkWatcher)) {
bookmarkPath := path.Join(bw.baseDir, bw.bkFile)
log.Infof("watching %s", bookmarkPath) log.Infof("watching %s", bookmarkPath)
watcher := w.Watcher()
for { for {
select { select {
case event := <-bw.watcher.Events: case event := <-watcher.Events:
if event.Op&fsnotify.Create == fsnotify.Create && if event.Op&fsnotify.Create == fsnotify.Create &&
event.Name == bookmarkPath { event.Name == bookmarkPath {
debugPrint("event: %v | eventName: %v", event.Op, event.Name) debugPrint("event: %v | eventName: %v", event.Op, event.Name)
//debugPrint("modified file: %s", event.Name) //debugPrint("modified file: %s", event.Name)
//start := time.Now() //start := time.Now()
parseFunc(bw) //parseFunc(bw)
w.Parse()
//elapsed := time.Since(start) //elapsed := time.Since(start)
//debugPrint("parsed in %s", elapsed) //debugPrint("parsed in %s", elapsed)
} }
case err := <-bw.watcher.Errors: case err := <-watcher.Errors:
log.Errorf("error: %s", err) log.Errorf("error: %s", err)
} }
} }
debugPrint("Exiting watch thread")
} }