gosuki/firefox.go

507 lines
12 KiB
Go
Raw Normal View History

//TODO: unit test critical error should shutdown the browser
//TODO: shutdown procedure (also close reducer)
2018-06-08 16:27:33 +00:00
package main
import (
2018-11-05 01:40:11 +00:00
"database/sql"
2018-11-09 17:25:50 +00:00
"gomark/database"
"gomark/parsing"
2018-11-13 16:11:16 +00:00
"gomark/tree"
"gomark/utils"
2018-11-09 17:25:50 +00:00
"gomark/watch"
2018-06-14 14:42:54 +00:00
"path"
2018-11-20 17:33:37 +00:00
"path/filepath"
2018-10-25 16:09:03 +00:00
"time"
2018-11-02 17:21:18 +00:00
"github.com/fsnotify/fsnotify"
2018-11-23 03:40:10 +00:00
"github.com/jmoiron/sqlx"
sqlite3 "github.com/mattn/go-sqlite3"
)
2018-11-13 16:11:16 +00:00
const (
QGetBookmarkPlace = `
2018-11-23 03:40:10 +00:00
SELECT *
2018-11-13 17:21:02 +00:00
FROM moz_places
WHERE id = ?
`
2018-11-23 03:40:10 +00:00
QBookmarksChanged = `
2018-11-13 17:21:02 +00:00
SELECT id,type,IFNULL(fk, -1),parent,IFNULL(title, '') from moz_bookmarks
2018-11-23 03:40:10 +00:00
WHERE(lastModified > :last_runtime_utc
2018-11-13 16:11:16 +00:00
AND lastModified < strftime('%s', 'now') * 1000 * 1000
2018-11-23 03:40:10 +00:00
AND NOT id IN (:not_root_tags)
2018-11-13 16:11:16 +00:00
)
`
QGetBookmarks = `WITH bookmarks AS
(SELECT moz_places.url AS url,
moz_places.description as desc,
moz_places.title as urlTitle,
moz_bookmarks.parent AS tagId
FROM moz_places LEFT OUTER JOIN moz_bookmarks
ON moz_places.id = moz_bookmarks.fk
WHERE moz_bookmarks.parent
IN (SELECT id FROM moz_bookmarks WHERE parent = ? ))
SELECT url, IFNULL(urlTitle, ''), IFNULL(desc,''),
tagId, moz_bookmarks.title AS tagTitle
FROM bookmarks LEFT OUTER JOIN moz_bookmarks
ON tagId = moz_bookmarks.id
ORDER BY url`
)
2018-06-08 16:27:33 +00:00
var Firefox = BrowserPaths{
2018-11-02 17:21:18 +00:00
BookmarkFile: "places.sqlite",
BookmarkDir: "/home/spike/.mozilla/firefox/p1rrgord.default/",
2018-06-08 16:27:33 +00:00
}
const (
2018-11-10 12:22:07 +00:00
MozMinJobInterval = 500 * time.Millisecond
)
2018-06-08 16:27:33 +00:00
type FFBrowser struct {
2018-11-13 17:21:02 +00:00
BaseBrowser //embedding
places *database.DB
2018-11-13 16:11:16 +00:00
URLIndexList []string // All elements stored in URLIndex
2018-11-23 03:40:10 +00:00
tagMap map[sqlid]*tree.Node
2018-11-05 01:40:11 +00:00
lastRunTime time.Time
}
2018-11-10 12:22:07 +00:00
const (
_ = iota
BkTypeURL
BkTypeTagFolder
)
2018-11-23 03:40:10 +00:00
type sqlid uint64
2018-11-10 12:22:07 +00:00
const (
_ = iota
ffBkRoot
ffBkMenu
ffBkToolbar
ffBkTags
ffBkOther
ffBkMobile
)
2018-11-23 03:40:10 +00:00
type AutoIncr struct {
ID sqlid
}
2018-11-13 16:11:16 +00:00
type FFPlace struct {
2018-11-23 03:40:10 +00:00
URL string
Description sql.NullString
Title sql.NullString
AutoIncr
2018-06-08 16:27:33 +00:00
}
2018-11-23 03:40:10 +00:00
//type FFBookmark struct {
//BType int `db:type`
//Parent sqlid
//FK sql.NullInt64
//Title sql.NullString
//AutoIncr
//}
2018-11-10 12:22:07 +00:00
type FFBookmark struct {
2018-11-23 03:40:10 +00:00
btype int `db:type`
parent sqlid
fk sqlid
2018-11-13 16:11:16 +00:00
title string
2018-11-23 03:40:10 +00:00
id sqlid
2018-11-10 12:22:07 +00:00
}
func FFPlacesUpdateHook(op int, db string, table string, rowid int64) {
2018-11-09 17:25:50 +00:00
fflog.Debug(op)
}
2018-06-08 16:27:33 +00:00
func NewFFBrowser() IBrowser {
2018-11-09 17:25:50 +00:00
browser := new(FFBrowser)
2018-06-08 16:27:33 +00:00
browser.name = "firefox"
browser.bType = TFirefox
browser.baseDir = Firefox.BookmarkDir
browser.bkFile = Firefox.BookmarkFile
2018-11-02 17:21:18 +00:00
browser.useFileWatcher = true
2018-11-09 17:25:50 +00:00
browser.Stats = &parsing.Stats{}
2018-11-13 16:11:16 +00:00
browser.NodeTree = &tree.Node{Name: "root", Parent: nil, Type: "root"}
2018-11-23 03:40:10 +00:00
browser.tagMap = make(map[sqlid]*tree.Node)
2018-10-28 19:19:12 +00:00
// Initialize `places.sqlite`
bookmarkPath := path.Join(browser.baseDir, browser.bkFile)
2018-11-13 16:11:16 +00:00
browser.places = database.NewRO("Places", bookmarkPath)
// Buffer that lives accross Run() jobs
2018-06-08 16:27:33 +00:00
browser.InitBuffer()
2018-11-02 17:21:18 +00:00
// Setup watcher
2018-11-20 17:33:37 +00:00
expandedBaseDir, err := filepath.EvalSymlinks(browser.baseDir)
if err != nil {
log.Critical(err)
}
2018-11-02 17:21:18 +00:00
w := &Watch{
2018-11-20 17:33:37 +00:00
Path: expandedBaseDir,
2018-11-09 17:25:50 +00:00
EventTypes: []fsnotify.Op{fsnotify.Write},
2018-11-20 17:33:37 +00:00
EventNames: []string{path.Join(expandedBaseDir, "places.sqlite-wal")},
2018-11-09 17:25:50 +00:00
ResetWatch: false,
2018-11-02 17:21:18 +00:00
}
browser.SetupFileWatcher(w)
/*
2018-11-02 17:21:18 +00:00
*Run reducer to avoid duplicate running of jobs
*when a batch of events is received
*/
2018-11-02 17:21:18 +00:00
browser.eventsChan = make(chan fsnotify.Event, EventsChanLen)
go utils.ReduceEvents(MozMinJobInterval, browser.eventsChan, browser)
// Testing
pusers, err := utils.FileProcessUsers(browser.GetBookmarksPath())
if err != nil {
fflog.Error(err)
}
for _, p := range pusers {
pname, err := p.Name()
if err != nil {
fflog.Error(err)
}
fflog.Debugf("%s is using bookmark file", pname)
}
//
//
//
//
//
2018-11-13 16:11:16 +00:00
2018-06-08 16:27:33 +00:00
return browser
}
2018-10-28 19:19:12 +00:00
func (bw *FFBrowser) Shutdown() {
2018-11-09 17:25:50 +00:00
fflog.Debugf("shutting down ... ")
2018-11-05 01:40:11 +00:00
2018-11-13 16:11:16 +00:00
err := bw.BaseBrowser.Close()
2018-10-28 19:19:12 +00:00
if err != nil {
2018-11-09 17:25:50 +00:00
fflog.Critical(err)
2018-10-28 19:19:12 +00:00
}
err = bw.places.Close()
if err != nil {
2018-11-09 17:25:50 +00:00
fflog.Critical(err)
2018-10-28 19:19:12 +00:00
}
}
2018-06-08 16:27:33 +00:00
func (bw *FFBrowser) Watch() bool {
if !bw.isWatching {
2018-11-09 17:25:50 +00:00
go watch.WatcherThread(bw)
bw.isWatching = true
fflog.Infof("Watching %s", bw.GetBookmarksPath())
return true
}
2018-06-08 16:27:33 +00:00
return false
}
func (bw *FFBrowser) Load() {
bw.BaseBrowser.Load()
// Parse bookmarks to a flat tree (for compatibility with tree system)
start := time.Now()
getFFBookmarks(bw)
2018-11-13 16:11:16 +00:00
bw.Stats.LastFullTreeParseTime = time.Since(start)
2018-11-05 01:40:11 +00:00
bw.lastRunTime = time.Now().UTC()
// Finished parsing
//go PrintTree(bw.NodeTree) // debugging
2018-11-09 17:25:50 +00:00
fflog.Debugf("parsed %d bookmarks and %d nodes in %s",
bw.Stats.CurrentUrlCount,
bw.Stats.CurrentNodeCount,
2018-11-13 16:11:16 +00:00
bw.Stats.LastFullTreeParseTime)
bw.ResetStats()
2018-11-05 01:40:11 +00:00
// Sync the URLIndex to the buffer
// We do not use the NodeTree here as firefox tags are represented
// as a flat tree which is not efficient, we use the go hashmap instead
2018-11-09 17:25:50 +00:00
database.SyncURLIndexToBuffer(bw.URLIndexList, bw.URLIndex, bw.BufferDB)
2018-06-08 16:27:33 +00:00
2018-11-13 16:11:16 +00:00
// Handle empty cache
if empty, err := CacheDB.IsEmpty(); empty {
if err != nil {
fflog.Error(err)
}
fflog.Info("cache empty: loading buffer to Cachedb")
2018-11-13 16:11:16 +00:00
bw.BufferDB.CopyTo(CacheDB)
2018-11-13 16:11:16 +00:00
fflog.Debugf("syncing <%s> to disk", CacheDB.Name)
} else {
bw.BufferDB.SyncTo(CacheDB)
}
go CacheDB.SyncToDisk(database.GetDBFullPath())
}
2018-11-13 16:11:16 +00:00
func getFFBookmarks(bw *FFBrowser) {
//QGetTags := "SELECT id,title from moz_bookmarks WHERE parent = %d"
2018-11-20 17:33:37 +00:00
//
2018-11-10 12:22:07 +00:00
rows, err := bw.places.Handle.Query(QGetBookmarks, ffBkTags)
log.Debugf("%#v", err)
// Locked database is critical
if e, ok := err.(sqlite3.Error); ok {
if e.Code == sqlite3.ErrBusy {
fflog.Critical(err)
bw.Shutdown()
return
}
}
2018-10-28 19:19:12 +00:00
if err != nil {
2018-11-20 17:33:37 +00:00
fflog.Errorf("%s: %s", bw.places.Name, err)
return
2018-10-28 19:19:12 +00:00
}
// Rebuild node tree
2018-11-13 18:54:20 +00:00
// Note: the node tree is build only for compatilibity with tree based
// bookmark parsing. For efficiency reading after the initial Load() from
// places.sqlite should be done using a loop instad of tree traversal.
rootNode := bw.NodeTree
2018-10-26 01:04:26 +00:00
/*
*This pass is used only for fetching bookmarks from firefox.
*Checking against the URLIndex should not be done here
*/
for rows.Next() {
var url, title, tagTitle, desc string
2018-11-23 03:40:10 +00:00
var tagId sqlid
err = rows.Scan(&url, &title, &desc, &tagId, &tagTitle)
2018-11-09 17:25:50 +00:00
//fflog.Debugf("%s|%s|%s|%d|%s", url, title, desc, tagId, tagTitle)
2018-10-28 19:19:12 +00:00
if err != nil {
2018-11-09 17:25:50 +00:00
fflog.Error(err)
2018-10-28 19:19:12 +00:00
}
/*
* If this is the first time we see this tag
* add it to the tagMap and create its node
*/
2018-11-13 16:11:16 +00:00
tagNode, tagNodeExists := bw.tagMap[tagId]
if !tagNodeExists {
// Add the tag as a node
2018-11-13 16:11:16 +00:00
tagNode = new(tree.Node)
tagNode.Type = "tag"
tagNode.Name = tagTitle
tagNode.Parent = rootNode
rootNode.Children = append(rootNode.Children, tagNode)
2018-11-13 16:11:16 +00:00
bw.tagMap[tagId] = tagNode
2018-11-09 17:25:50 +00:00
bw.Stats.CurrentNodeCount++
}
// Add the url to the tag
2018-11-13 16:11:16 +00:00
var urlNode *tree.Node
2018-11-05 01:40:11 +00:00
iUrlNode, urlNodeExists := bw.URLIndex.Get(url)
2018-10-26 01:04:26 +00:00
if !urlNodeExists {
2018-11-13 16:11:16 +00:00
urlNode = new(tree.Node)
2018-10-26 01:04:26 +00:00
urlNode.Type = "url"
urlNode.URL = url
urlNode.Name = title
urlNode.Desc = desc
2018-11-05 01:40:11 +00:00
bw.URLIndex.Insert(url, urlNode)
bw.URLIndexList = append(bw.URLIndexList, url)
} else {
2018-11-13 16:11:16 +00:00
urlNode = iUrlNode.(*tree.Node)
2018-10-26 01:04:26 +00:00
}
2018-10-26 01:04:26 +00:00
// Add tag to urlnode tags
urlNode.Tags = append(urlNode.Tags, tagNode.Name)
2018-10-26 01:04:26 +00:00
// Set tag as parent to urlnode
2018-11-13 16:11:16 +00:00
urlNode.Parent = bw.tagMap[tagId]
2018-10-26 01:04:26 +00:00
// Add urlnode as child to tag node
2018-11-13 16:11:16 +00:00
bw.tagMap[tagId].Children = append(bw.tagMap[tagId].Children, urlNode)
2018-11-09 17:25:50 +00:00
bw.Stats.CurrentUrlCount++
}
2018-11-13 16:11:16 +00:00
}
2018-10-26 01:04:26 +00:00
2018-11-13 16:11:16 +00:00
func (bw *FFBrowser) fetchUrlChanges(rows *sql.Rows,
2018-11-23 03:40:10 +00:00
bookmarks map[sqlid]*FFBookmark,
places map[sqlid]*FFPlace) {
2018-11-13 16:11:16 +00:00
bk := new(FFBookmark)
// Get the URL that changed
rows.Scan(&bk.id, &bk.btype, &bk.fk, &bk.parent, &bk.title)
// We found URL change, urls are specified by
// type == 1
// fk -> id of url in moz_places
// parent == tag id
//
// Each tag on a url generates 2 or 3 entries in moz_bookmarks
// 1. If not existing, a (type==2) entry for the tag itself
// 2. A (type==1) entry for the bookmakred url with (fk -> moz_places.url)
// 3. A (type==1) (fk-> moz_places.url) (parent == idOf(tag))
if bk.btype == BkTypeURL {
2018-11-23 03:40:10 +00:00
var place FFPlace
bw.places.Handle.QueryRowx(QGetBookmarkPlace, bk.fk).StructScan(&place)
fflog.Debugf("Changed URL: %s", place.URL)
2018-11-13 16:11:16 +00:00
// put url in the places map
2018-11-23 03:40:10 +00:00
places[place.ID] = &place
2018-11-13 16:11:16 +00:00
}
2018-10-26 01:04:26 +00:00
2018-11-13 16:11:16 +00:00
// This is the tag link
if bk.btype == BkTypeURL &&
bk.parent > ffBkMobile {
bookmarks[bk.id] = bk
}
2018-10-26 01:04:26 +00:00
2018-11-13 16:11:16 +00:00
// Tags are specified by:
// type == 2
// parent == (Id of root )
if bk.btype == BkTypeTagFolder {
bookmarks[bk.id] = bk
}
2018-11-13 16:11:16 +00:00
for rows.Next() {
bw.fetchUrlChanges(rows, bookmarks, places)
}
}
2018-06-08 16:27:33 +00:00
func (bw *FFBrowser) Run() {
2018-11-05 01:40:11 +00:00
//TODO: Watching is broken. Try to open a new connection on each
// watch event
2018-11-13 16:11:16 +00:00
startRun := time.Now()
2018-11-23 03:40:10 +00:00
//fflog.Debugf("Checking changes since %s",
//bw.lastRunTime.Local().Format("Mon Jan 2 15:04:05 MST 2006"))
2018-11-13 16:11:16 +00:00
2018-11-23 03:40:10 +00:00
queryArgs := map[string]interface{}{
"not_root_tags": []int{ffBkRoot, ffBkTags},
"last_runtime_utc": bw.lastRunTime.UTC().UnixNano() / 1000,
}
2018-11-13 16:11:16 +00:00
2018-11-23 03:40:10 +00:00
query, args, err := sqlx.Named(
QBookmarksChanged,
queryArgs,
2018-11-13 16:11:16 +00:00
)
2018-11-05 01:40:11 +00:00
if err != nil {
2018-11-09 17:25:50 +00:00
fflog.Error(err)
2018-11-05 01:40:11 +00:00
}
2018-11-23 03:40:10 +00:00
query, args, err = sqlx.In(query, args...)
if err != nil {
fflog.Error(err)
}
query = bw.places.Handle.Rebind(query)
rows, err := bw.places.Handle.Query(query, args...)
2018-11-05 01:40:11 +00:00
defer rows.Close()
2018-11-23 03:40:10 +00:00
if err != nil {
fflog.Error(err)
}
2018-11-05 01:40:11 +00:00
// Found new results in places db since last time we had changes
//database.DebugPrintRows(rows)
2018-11-05 01:40:11 +00:00
if rows.Next() {
2018-11-13 16:11:16 +00:00
changedURLS := make([]string, 0)
2018-11-05 01:40:11 +00:00
bw.lastRunTime = time.Now().UTC()
2018-11-10 12:22:07 +00:00
2018-11-13 16:11:16 +00:00
//fflog.Debugf("CHANGE ! Time: %s",
//bw.lastRunTime.Local().Format("Mon Jan 2 15:04:05 MST 2006"))
2018-11-23 03:40:10 +00:00
bookmarks := make(map[sqlid]*FFBookmark)
places := make(map[sqlid]*FFPlace)
2018-11-13 16:11:16 +00:00
// Fetch all changes into bookmarks and places maps
bw.fetchUrlChanges(rows, bookmarks, places)
// For each url
for urlId, place := range places {
var urlNode *tree.Node
changedURLS = utils.Extends(changedURLS, place.URL)
2018-11-23 03:40:10 +00:00
iUrlNode, urlNodeExists := bw.URLIndex.Get(place.URL)
2018-11-13 16:11:16 +00:00
if !urlNodeExists {
urlNode = new(tree.Node)
urlNode.Type = "url"
2018-11-23 03:40:10 +00:00
urlNode.URL = place.URL
urlNode.Name = place.Title.String
urlNode.Desc = place.Description.String
bw.URLIndex.Insert(place.URL, urlNode)
2018-11-13 16:11:16 +00:00
} else {
urlNode = iUrlNode.(*tree.Node)
}
// First get any new tags
for bkId, bk := range bookmarks {
if bk.btype == BkTypeTagFolder &&
// Ignore root direcotires
bk.btype != ffBkTags {
tagNode, tagNodeExists := bw.tagMap[bkId]
if !tagNodeExists {
tagNode = new(tree.Node)
tagNode.Type = "tag"
tagNode.Name = bk.title
tagNode.Parent = bw.NodeTree
bw.NodeTree.Children = append(bw.NodeTree.Children,
tagNode)
fflog.Debugf("New tag node %s", tagNode.Name)
bw.tagMap[bkId] = tagNode
}
}
}
// link tags to urls
for _, bk := range bookmarks {
// This effectively applies the tag to the URL
// The tag link should have a parent over 6 and fk->urlId
fflog.Debugf("Bookmark parent %d", bk.parent)
if bk.fk == urlId &&
bk.parent > ffBkMobile {
// The tag node should have already been created
tagNode, tagNodeExists := bw.tagMap[bk.parent]
if tagNodeExists && urlNode != nil {
//fflog.Debugf("URL has tag %s", tagNode.Name)
urlNode.Tags = utils.Extends(urlNode.Tags, tagNode.Name)
2018-11-13 16:11:16 +00:00
urlNode.Parent = bw.tagMap[bk.parent]
tree.Insert(bw.tagMap[bk.parent].Children, urlNode)
bw.Stats.CurrentUrlCount++
}
}
}
}
database.SyncURLIndexToBuffer(changedURLS, bw.URLIndex, bw.BufferDB)
bw.BufferDB.SyncTo(CacheDB)
CacheDB.SyncToDisk(database.GetDBFullPath())
2018-11-05 01:40:11 +00:00
}
//TODO: change logger for more granular debugging
2018-06-08 16:27:33 +00:00
2018-11-13 16:11:16 +00:00
bw.Stats.LastWatchRunTime = time.Since(startRun)
2018-11-13 18:54:51 +00:00
//fflog.Debugf("execution time %s", time.Since(startRun))
2018-06-08 16:27:33 +00:00
}