gosuki/firefox/firefox.go

894 lines
23 KiB
Go
Raw Normal View History

2022-09-20 10:11:16 +00:00
// TODO: unit test critical error should shutdown the browser
// TODO: shutdown procedure (also close reducer)
// TODO: handle flag management from this package
package firefox
2018-06-08 16:27:33 +00:00
import (
2018-11-05 01:40:11 +00:00
"database/sql"
2019-02-22 18:50:26 +00:00
"errors"
2019-03-01 17:30:50 +00:00
"fmt"
2018-06-14 14:42:54 +00:00
"path"
2018-11-20 17:33:37 +00:00
"path/filepath"
2022-12-11 22:06:07 +00:00
"strings"
2018-10-25 16:09:03 +00:00
"time"
2020-11-06 17:50:36 +00:00
"git.sp4ke.xyz/sp4ke/gomark/database"
"git.sp4ke.xyz/sp4ke/gomark/logging"
"git.sp4ke.xyz/sp4ke/gomark/modules"
"git.sp4ke.xyz/sp4ke/gomark/mozilla"
"git.sp4ke.xyz/sp4ke/gomark/profiles"
// "git.sp4ke.xyz/sp4ke/gomark/profiles"
2020-11-06 17:50:36 +00:00
"git.sp4ke.xyz/sp4ke/gomark/tree"
"git.sp4ke.xyz/sp4ke/gomark/utils"
"git.sp4ke.xyz/sp4ke/gomark/watch"
2020-08-12 18:13:01 +00:00
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"
)
var (
ErrInitFirefox = errors.New("could not start Firefox watcher")
log = logging.GetLogger("FF")
)
const (
WatchMinJobInterval = 1500 * time.Millisecond
TagsBranchName = mozilla.TagsBranchName // name of the `tags` branch in the node tree
)
//TODO!: delete
// moz_bookmarks.type
2018-11-10 12:22:07 +00:00
const (
_ = iota
BkTypeURL
BkTypeTagFolder
)
type sqlid = mozilla.Sqlid
type timestamp = int64
2018-11-10 12:22:07 +00:00
2018-11-23 03:40:10 +00:00
type AutoIncr struct {
ID sqlid
}
// TODO!: remove
2018-11-13 16:11:16 +00:00
type FFPlace struct {
URL string `db:"url"`
Description sql.NullString `db:"description"`
Title sql.NullString `db:"title"`
2018-11-23 03:40:10 +00:00
AutoIncr
2018-06-08 16:27:33 +00:00
}
2022-12-10 01:49:30 +00:00
2023-01-03 13:33:02 +00:00
// TODO!: replace by MergedPlaceBookmark and MozBookmark below
2018-11-10 12:22:07 +00:00
type FFBookmark struct {
btype sqlid
2018-11-23 03:40:10 +00:00
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
}
type MozBookmark = mozilla.MozBookmark
type MozFolder = mozilla.MozFolder
2022-12-10 01:49:30 +00:00
type Firefox struct {
*FirefoxConfig
// sqlite con to places.sqlite
places *database.DB
// All elements stored in URLIndex
URLIndexList []string
// Map from moz_bookmarks tag ids to a tree node
// tagMap is used as a quick lookup table into the node tree
tagMap map[string]*tree.Node
// map from moz_bookmarks folder id to a folder node in the tree
folderMap map[sqlid]*tree.Node
// internal folder map used for scanning
folderScanMap map[sqlid]*MozFolder
lastRunTime time.Time
}
2023-01-11 11:45:56 +00:00
// func (ff *Firefox) updateModifiedFolders(since timestamp) ([]*MozFolder, error) {
// // Get list of modified folders
// var folders = []*MozFolders
// folderChangeQuery := map[string]interface
//
// return nil, nil
// }
2022-12-05 22:21:23 +00:00
// scan all folders from moz_bookmarks and load them into the node tree
// takes a timestamp(int64) parameter to select folders based on last modified date
func (ff *Firefox) scanFolders(since timestamp) ([]*MozFolder, error) {
2022-12-05 22:21:23 +00:00
var folders []*MozFolder
2022-12-10 01:49:30 +00:00
ff.folderScanMap = make(map[sqlid]*MozFolder)
folderQueryArgs := map[string]interface{} {
"change_since": since,
}
boundQuery, args, err := ff.places.Handle.BindNamed(mozilla.QFolders, folderQueryArgs)
if err != nil {
return nil, err
}
err = ff.places.Handle.Select(&folders, boundQuery, args...)
if err != nil {
return nil, err
}
2022-12-05 22:21:23 +00:00
2022-12-10 01:49:30 +00:00
// store all folders in a hashmap for easier tree construction
for _, folder := range folders {
ff.folderScanMap[folder.Id] = folder
}
for _, folder := range folders {
// Ignore the `tags` virtual folder
if folder.Id != 4 {
ff.addFolderNode(*folder)
}
}
2022-12-05 22:21:23 +00:00
return folders, err
}
// load bookmarks and tags into the node tree then attach them to
// their assigned folder hierarchy
func (ff *Firefox) loadBookmarksToTree(bookmarks []*MozBookmark) {
2022-12-11 22:06:07 +00:00
for _, bkEntry := range bookmarks {
2022-12-15 02:22:54 +00:00
// Create/Update URL node and apply tag node
ok, urlNode := ff.addUrlNode(bkEntry.Url, bkEntry.Title, bkEntry.PlDesc)
if !ok {
log.Infof("url <%s> already in url index", bkEntry.Url)
}
/*
* Iterate through bookmark tags and synchronize new tags with
* the node tree.
*/
for _, tagName := range strings.Split(bkEntry.Tags, ",") {
2022-12-26 18:15:00 +00:00
if tagName == "" { continue }
2022-12-11 22:06:07 +00:00
seen, tagNode := ff.addTagNode(tagName)
if !seen {
log.Infof("tag <%s> already in tag map", tagNode.Name)
}
// Add tag name to urlnode tags
2022-12-26 19:55:48 +00:00
urlNode.Tags = utils.Extends(urlNode.Tags, tagNode.Name)
2022-12-15 02:22:54 +00:00
// Add URL node as child of Tag node
// Parent will be a folder or nothing?
2022-12-11 22:06:07 +00:00
tree.AddChild(ff.tagMap[tagNode.Name], urlNode)
2022-12-26 18:15:00 +00:00
ff.CurrentUrlCount++
}
2022-12-11 22:06:07 +00:00
// Link this URL node to its corresponding folder node if it exists.
//TODO: add all parent folders in the tags list of this url node
2022-12-15 02:22:54 +00:00
folderNode, fOk := ff.folderMap[bkEntry.ParentId]
2023-01-11 11:14:10 +00:00
// If we found the parent folder
2022-12-15 02:22:54 +00:00
if fOk {
tree.AddChild(folderNode, urlNode)
2023-01-11 11:14:10 +00:00
}
}
}
2022-12-15 02:22:54 +00:00
// scans bookmarks from places.sqlite and loads them into the node tree
func (ff *Firefox) scanBookmarks() ([]*MozBookmark, error) {
2022-12-15 02:22:54 +00:00
// scan folders and load them into node tree
_, err := ff.scanFolders(0)
if err != nil {
return nil, err
2022-12-11 22:06:07 +00:00
}
var bookmarks []*MozBookmark
dotx, err := database.DotxQueryEmbedFS(mozilla.EmbeddedSqlQueries, mozilla.MozBookmarkQueryFile)
if err != nil {
return nil, err
}
err = dotx.Select(ff.places.Handle, &bookmarks, mozilla.MozBookmarkQuery)
// load bookmarks and tags into the node tree
// then attach them to their assigned folder hierarchy
2022-12-05 22:21:23 +00:00
return bookmarks, err
}
func (ff *Firefox) scanModifiedBookmarks(since timestamp) ([]*MozBookmark, error) {
// scan new/modifed folders and load them into node tree
_, err := ff.scanFolders(since)
2023-01-11 12:29:35 +00:00
// tree.PrintTree(ff.NodeTree)
if err != nil {
return nil, err
}
var bookmarks []*MozBookmark
dotx, err := database.DotxQueryEmbedFS(mozilla.EmbeddedSqlQueries,
mozilla.MozChangedBookmarkQueryFile)
if err != nil {
return nil, err
}
2022-12-10 01:49:30 +00:00
queryArgs := map[string]interface{}{
"change_since": since,
}
2022-12-10 01:49:30 +00:00
// query, args, err := dotx.NamedQuery(ff.places.Handle, mozilla.MozChangedBookmarkQuery, queryArgs)
boundQuery, args, err := dotx.BindNamed(ff.places.Handle, mozilla.MozChangedBookmarkQuery, queryArgs)
if err != nil {
return nil, err
}
err = ff.places.Handle.Select(&bookmarks, boundQuery, args...)
if err != nil {
return nil, err
}
return bookmarks, err
}
func init() {
modules.RegisterBrowser(Firefox{FirefoxConfig: FFConfig})
//TIP: cmd.RegisterModCommand(BrowserName, &cli.Command{
2022-12-04 20:01:53 +00:00
// Name: "test",
// })
// cmd.RegisterModCommand(BrowserName, &cli.Command{
// Name: "test2",
// })
}
func NewFirefox() *Firefox {
return &Firefox{
FirefoxConfig: FFConfig,
places: &database.DB{},
URLIndexList: []string{},
2022-12-11 22:06:07 +00:00
tagMap: map[string]*tree.Node{},
2022-12-10 01:49:30 +00:00
folderMap: map[sqlid]*tree.Node{},
}
}
func (f Firefox) ModInfo() modules.ModInfo {
return modules.ModInfo{
ID: modules.ModID(f.Name),
2022-11-07 19:07:13 +00:00
//HACK: duplicate instance with init().RegisterBrowser ??
New: func() modules.Module {
return NewFirefox()
2022-11-07 19:07:13 +00:00
},
}
}
// Implement the profiles.ProfileManager interface
func (f *Firefox) GetProfiles() ([]*profiles.Profile, error) {
return FirefoxProfileManager.GetProfiles()
}
func (f *Firefox) GetDefaultProfile() (*profiles.Profile, error) {
return FirefoxProfileManager.GetDefaultProfile()
}
func (f *Firefox) GetProfilePath(p profiles.Profile) string {
return filepath.Join(FirefoxProfileManager.ConfigDir, p.Path)
}
// TEST:
// Implements browser.Initializer interface
func (f *Firefox) Init() error {
log.Infof("initializing <%s>", f.Name)
bookmarkPath, err := f.BookmarkPath()
if err != nil {
return err
}
log.Debugf("bookmark path is: %s", bookmarkPath)
// Setup watcher
expandedBaseDir, err := filepath.EvalSymlinks(f.BkDir)
if err != nil {
return err
}
w := &watch.Watch{
Path: expandedBaseDir,
EventTypes: []fsnotify.Op{fsnotify.Write},
EventNames: []string{filepath.Join(expandedBaseDir, "places.sqlite-wal")},
ResetWatch: false,
}
modules.SetupWatchersWithReducer(f.BrowserConfig, modules.ReducerChanLen, w)
/*
*Run reducer to avoid duplicate jobs when a batch of events is received
*/
// TODO!: make a new copy of places for every new event change
// Add a reducer to the watcher
go watch.ReduceEvents(WatchMinJobInterval, f)
return nil
2019-03-01 17:30:50 +00:00
}
func (f *Firefox) Watcher() *watch.WatchDescriptor {
2022-11-07 19:07:13 +00:00
return f.BrowserConfig.Watcher()
}
func (f Firefox) Config() *modules.BrowserConfig {
2022-11-07 19:07:13 +00:00
return f.BrowserConfig
2022-11-01 13:27:19 +00:00
}
// Firefox custom logic for preloading the bookmarks when the browser module
// starts. Implements modules.Loader interface.
func (f *Firefox) Load() error {
pc, err := f.initPlacesCopy()
if err != nil {
return err
}
defer pc.Clean()
2022-12-26 19:55:48 +00:00
// load all bookmarks
start := time.Now()
bookmarks, err := f.scanBookmarks()
if err != nil {
return err
}
f.loadBookmarksToTree(bookmarks)
2022-12-10 01:49:30 +00:00
f.LastFullTreeParseTime = time.Since(start)
f.lastRunTime = time.Now().UTC()
log.Debugf("parsed %d bookmarks and %d nodes in %s",
2022-12-10 01:49:30 +00:00
f.CurrentUrlCount,
f.CurrentNodeCount,
f.LastFullTreeParseTime)
2022-12-26 18:15:00 +00:00
f.Reset()
2019-03-01 18:03:48 +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
2019-03-01 18:03:48 +00:00
database.SyncURLIndexToBuffer(f.URLIndexList, f.URLIndex, f.BufferDB)
// Handle empty cache
if empty, err := database.Cache.DB.IsEmpty(); empty {
2019-03-01 18:03:48 +00:00
if err != nil {
return err
2019-03-01 18:03:48 +00:00
}
log.Info("cache empty: loading buffer to Cachedb")
2019-03-01 17:30:50 +00:00
f.BufferDB.CopyTo(database.Cache.DB)
log.Debugf("syncing <%s> to disk", database.Cache.DB.Name)
} else {
f.BufferDB.SyncTo(database.Cache.DB)
2019-03-01 17:30:50 +00:00
}
database.Cache.DB.SyncToDisk(database.GetDBFullPath())
//DEBUG:
2022-12-10 01:49:30 +00:00
// tree.PrintTree(f.NodeTree)
// Close the copy places.sqlite
err = f.places.Close()
return err
2019-03-01 17:30:50 +00:00
}
// Implements modules.Runner interface
func (ff *Firefox) Run() {
startRun := time.Now()
pc, err := ff.initPlacesCopy()
if err != nil {
log.Error(err)
}
defer pc.Clean()
log.Debugf("Checking changes since <%d> %s",
ff.lastRunTime.UTC().UnixNano()/1000,
ff.lastRunTime.Local().Format("Mon Jan 2 15:04:05 MST 2006"))
scanSince := ff.lastRunTime.UTC().UnixNano() / 1000
bookmarks, err := ff.scanModifiedBookmarks(scanSince)
if err != nil {
log.Error(err)
}
ff.loadBookmarksToTree(bookmarks)
// tree.PrintTree(ff.NodeTree)
database.SyncURLIndexToBuffer(ff.URLIndexList, ff.URLIndex, ff.BufferDB)
ff.BufferDB.SyncTo(database.Cache.DB)
database.Cache.DB.SyncToDisk(database.GetDBFullPath())
ff.LastWatchRunTime = time.Since(startRun)
ff.lastRunTime = time.Now().UTC()
}
// Implement modules.Runner interface
// TODO: lock the copied places until the RUN operation is over
//HACK: remove
func (f *Firefox) Runn() {
startRun := time.Now()
pc, err := f.initPlacesCopy()
if err != nil {
log.Error(err)
}
defer pc.Clean()
log.Debugf("Checking changes since <%d> %s",
2022-11-01 13:27:19 +00:00
f.lastRunTime.UTC().UnixNano()/1000,
f.lastRunTime.Local().Format("Mon Jan 2 15:04:05 MST 2006"))
queryArgs := map[string]interface{}{
"not_root_tags": []int{mozilla.RootID, mozilla.TagsID},
2022-11-01 13:27:19 +00:00
"last_runtime_utc": f.lastRunTime.UTC().UnixNano() / 1000,
}
query, args, err := sqlx.Named(
mozilla.QBookmarksChanged,
queryArgs,
)
if err != nil {
log.Error(err)
}
query, args, err = sqlx.In(query, args...)
if err != nil {
log.Error(err)
}
2022-11-01 13:27:19 +00:00
query = f.places.Handle.Rebind(query)
utils.PrettyPrint(query)
2022-11-01 13:27:19 +00:00
rows, err := f.places.Handle.Query(query, args...)
if err != nil {
log.Error(err)
}
// Found new results in places db since last time we had changes
// database.DebugPrintRows(rows) // WARN: This will disable reading rows
// TEST: implement this in a func and unit test it
// NOTE: this looks like a lot of code reuse in fetchUrlChanges()
for rows.Next() {
// next := rows.Next()
// log.Debug("next rows is: ", next)
// if !next {
// break
// }
changedURLS := make([]string, 0)
log.Debugf("Found changes since: %s",
2022-11-01 13:27:19 +00:00
f.lastRunTime.Local().Format("Mon Jan 2 15:04:05 MST 2006"))
// extract bookmarks to this map
bookmarks := make(map[sqlid]*FFBookmark)
// record new places to this map
places := make(map[sqlid]*FFPlace)
// Fetch all changes into bookmarks and places maps
2022-11-01 13:27:19 +00:00
f.fetchUrlChanges(rows, bookmarks, places)
// utils.PrettyPrint(places)
// For each url
for urlId, place := range places {
var urlNode *tree.Node
changedURLS = utils.Extends(changedURLS, place.URL)
2022-11-01 13:27:19 +00:00
ok, urlNode := f.addUrlNode(place.URL, place.Title.String, place.Description.String)
if !ok {
log.Infof("url <%s> already in url index", place.URL)
}
// First get any new bookmarks
for bkId, bk := range bookmarks {
// if bookmark type is folder or tag
if bk.btype == BkTypeTagFolder &&
// Ignore root directories
// NOTE: TagsID change time shows last time bookmark tags
// whre changed ?
bkId != mozilla.TagsID {
log.Debugf("adding tag node %s", bk.title)
2022-12-11 22:06:07 +00:00
ok, tagNode := f.addTagNode(bk.title)
if !ok {
log.Infof("tag <%s> already in tag map", tagNode.Name)
}
}
}
// link tags(moz_bookmark) to urls (moz_places)
for _, bk := range bookmarks {
// This effectively applies the tag to the URL
// The tag link should have a parent over 6 and fk->urlId
log.Debugf("Bookmark parent %d", bk.parent)
if bk.fk == urlId &&
bk.parent > mozilla.MobileID {
// The tag node should have already been created
2022-12-11 22:06:07 +00:00
// tagNode, tagNodeExists := f.tagMap[bk.parent]
tagNode, tagNodeExists := f.tagMap["bk.parent"]
if tagNodeExists && urlNode != nil {
log.Debugf("URL has tag %s", tagNode.Name)
urlNode.Tags = utils.Extends(urlNode.Tags, tagNode.Name)
2022-12-11 22:06:07 +00:00
tree.AddChild(f.tagMap["bk.parent"], urlNode)
//TEST: remove after testing this code section
2022-11-01 13:27:19 +00:00
// urlNode.Parent = f.tagMap[bk.parent]
// tree.Insert(f.tagMap[bk.parent].Children, urlNode)
2022-12-26 18:15:00 +00:00
f.CurrentUrlCount++
}
}
}
}
2022-11-01 13:27:19 +00:00
database.SyncURLIndexToBuffer(changedURLS, f.URLIndex, f.BufferDB)
f.BufferDB.SyncTo(database.Cache.DB)
database.Cache.DB.SyncToDisk(database.GetDBFullPath())
2019-03-01 17:30:50 +00:00
}
err = rows.Close()
if err != nil {
log.Error(err)
}
2022-12-26 18:15:00 +00:00
f.LastWatchRunTime = time.Since(startRun)
// log.Debugf("execution time %s", time.Since(startRun))
2019-03-01 17:30:50 +00:00
2022-11-01 13:27:19 +00:00
// tree.PrintTree(f.NodeTree) // debugging
2022-11-01 13:27:19 +00:00
err = f.places.Close()
if err != nil {
log.Error(err)
}
2022-11-01 13:27:19 +00:00
f.lastRunTime = time.Now().UTC()
}
// Implement modules.Shutdowner
func (f *Firefox) Shutdown() {
log.Debugf("shutting down ... ")
if f.places != nil {
err := f.places.Close()
if err != nil {
log.Critical(err)
}
}
2019-03-01 17:30:50 +00:00
}
2022-12-10 01:49:30 +00:00
func (ff *Firefox) getPathToPlacesCopy() string {
return path.Join(utils.TMPDIR, ff.BkFile)
}
// HACK: addUrl and addTag share a lot of code, find a way to reuse shared code
// and only pass extra details about tag/url along in some data structure
// PROBLEM: tag nodes use IDs and URL nodes use URL as hashes
2022-11-01 13:27:19 +00:00
func (f *Firefox) addUrlNode(url, title, desc string) (bool, *tree.Node) {
2022-12-05 22:21:23 +00:00
var urlNode *tree.Node
2022-11-01 13:27:19 +00:00
iUrlNode, exists := f.URLIndex.Get(url)
if !exists {
urlNode := &tree.Node{
Name: title,
2022-11-01 13:27:19 +00:00
Type: tree.URLNode,
URL: url,
Desc: desc,
}
log.Debugf("inserting url %s in url index", url)
2022-11-01 13:27:19 +00:00
f.URLIndex.Insert(url, urlNode)
f.URLIndexList = append(f.URLIndexList, url)
2022-12-26 18:15:00 +00:00
f.CurrentNodeCount++
return true, urlNode
} else {
urlNode = iUrlNode.(*tree.Node)
}
return false, urlNode
}
2022-12-05 22:21:23 +00:00
// adds a new tagNode if it is not yet in the tagMap
// returns true if tag added or false if already existing
// returns the created tagNode
2022-12-11 22:06:07 +00:00
func (ff *Firefox) addTagNode(tagName string) (bool, *tree.Node) {
// Check if "tags" branch exists or create it
var branchOk bool
var tagsBranch *tree.Node
for _, c := range ff.NodeTree.Children {
if c.Name == TagsBranchName {
branchOk = true
tagsBranch = c
}
}
if !branchOk {
tagsBranch = &tree.Node{
Name: TagsBranchName,
}
tree.AddChild(ff.NodeTree, tagsBranch)
}
// node, exists :=
2022-12-11 22:06:07 +00:00
node, exists := ff.tagMap[tagName]
if exists {
return false, node
}
tagNode := &tree.Node{
Name: tagName,
2022-11-01 13:27:19 +00:00
Type: tree.TagNode,
2022-12-10 01:49:30 +00:00
Parent: ff.NodeTree, // root node
}
2022-12-11 22:06:07 +00:00
tree.AddChild(tagsBranch, tagNode)
ff.tagMap[tagName] = tagNode
2022-12-26 18:15:00 +00:00
ff.CurrentNodeCount++
return true, tagNode
}
2022-12-05 22:21:23 +00:00
// add a folder node to the parsed node tree under the specified folder parent
2022-12-10 01:49:30 +00:00
// returns true if a new folder is created and false if folder already exists
func (ff *Firefox) addFolderNode(folder MozFolder) (bool, *tree.Node){
2022-12-05 22:21:23 +00:00
2022-12-10 01:49:30 +00:00
// use hashmap.RBTree to keep an index of scanned folders pointing
// to their corresponding nodes in the tree
2022-12-05 22:21:23 +00:00
2022-12-10 01:49:30 +00:00
folderNode, seen := ff.folderMap[folder.Id]
2022-12-05 22:21:23 +00:00
2022-12-10 01:49:30 +00:00
if seen {
2023-01-11 11:45:56 +00:00
// Update folder name if changed
2023-01-11 12:29:35 +00:00
//TODO!: trigger bookmark tag change in gomarks.db
2023-01-11 12:29:35 +00:00
if folderNode.Name != folder.Title &&
// Ignore root folders since we use our custom names
!utils.InList([]int{2,3,5,6}, int(folder.Id)){
2023-01-11 11:45:56 +00:00
log.Debugf("folder node <%s> updated to <%s>", folderNode.Name, folder.Title)
folderNode.Name = folder.Title
}
2022-12-10 01:49:30 +00:00
return false, folderNode
}
2022-12-05 22:21:23 +00:00
2022-12-10 01:49:30 +00:00
folderNode = &tree.Node{
// Name: folder.Title,
Type: tree.FolderNode,
}
2022-12-05 22:21:23 +00:00
2022-12-10 01:49:30 +00:00
// keeping the same folder structure as Firefox
2022-12-10 01:49:30 +00:00
// If this folders' is a Firefox root folder use the appropriate title
// then add it to the root node
2023-01-11 11:14:10 +00:00
if utils.InList([]int{2,3,5,6}, int(folder.Id)) {
2023-01-11 12:29:35 +00:00
folderNode.Name = mozilla.RootFolderNames[folder.Id]
2022-12-10 01:49:30 +00:00
tree.AddChild(ff.NodeTree, folderNode)
} else {
folderNode.Name = folder.Title
}
2022-12-10 01:49:30 +00:00
// check if folder's parent is already in the tree
fParent, ok := ff.folderMap[folder.Parent]
// if we already saw folder's parent add it underneath
if ok {
tree.AddChild(fParent, folderNode)
2022-12-10 01:49:30 +00:00
// if we never saw this folders' parent
} else if folder.Parent != 1 { // recursively build the parent of this folder
_, ok := ff.folderScanMap[folder.Parent]
if ok {
_, newParentNode := ff.addFolderNode(*ff.folderScanMap[folder.Parent])
tree.AddChild(newParentNode, folderNode)
}
2022-12-10 01:49:30 +00:00
}
2022-12-10 01:49:30 +00:00
// Store a pointer to this folder
ff.folderMap[folder.Id] = folderNode
ff.CurrentNodeCount++
return true, folderNode
}
2022-12-10 01:49:30 +00:00
2022-12-11 22:06:07 +00:00
//TODO: retire this function after scanBookmarks() is implemented
// load all bookmarks from `places.sqlite` and store them in BaseBrowser.NodeTree
// this method is used the first time gomark is started or to extract bookmarks
// using a command
func loadBookmarks(f *Firefox) {
2022-11-01 13:27:19 +00:00
log.Debugf("root tree children len is %d", len(f.NodeTree.Children))
//QGetTags := "SELECT id,title from moz_bookmarks WHERE parent = %d"
2018-11-20 17:33:37 +00:00
//
rows, err := f.places.Handle.Query(mozilla.QgetBookmarks, mozilla.TagsID)
2020-09-01 12:43:01 +00:00
if err != nil {
log.Fatal(err)
}
// Locked database is critical
if e, ok := err.(sqlite3.Error); ok {
if e.Code == sqlite3.ErrBusy {
log.Critical(err)
2022-11-01 13:27:19 +00:00
f.Shutdown()
return
}
}
2018-10-28 19:19:12 +00:00
if err != nil {
2022-11-01 13:27:19 +00:00
log.Errorf("%s: %s", f.places.Name, err)
2018-11-20 17:33:37 +00:00
return
2018-10-28 19:19:12 +00:00
}
// Rebuilding node tree
// Note: the node tree is built only for compatilibity with tree based
// bookmark parsing and might later be useful for debug/UI features.
// For efficiency reading after the initial Load() from
2018-11-13 18:54:20 +00:00
// places.sqlite should be done using a loop instad of tree traversal.
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)
// log.Debugf("%s|%s|%s|%d|%s", url, title, desc, tagId, tagTitle)
2018-10-28 19:19:12 +00:00
if err != nil {
log.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
*/
2022-12-11 22:06:07 +00:00
ok, tagNode := f.addTagNode(tagTitle)
if !ok {
log.Infof("tag <%s> already in tag map", tagNode.Name)
}
// Add the url to the tag
// NOTE: this call is responsible for updating URLIndexList
2022-11-01 13:27:19 +00:00
ok, urlNode := f.addUrlNode(url, title, desc)
if !ok {
log.Infof("url <%s> already in url index", url)
2018-10-26 01:04:26 +00:00
}
// Add tag name to urlnode tags
2018-10-26 01:04:26 +00:00
urlNode.Tags = append(urlNode.Tags, tagNode.Name)
2018-10-26 01:04:26 +00:00
// Set tag as parent to urlnode
2022-12-11 22:06:07 +00:00
tree.AddChild(f.tagMap[tagTitle], urlNode)
2022-12-26 18:15:00 +00:00
f.CurrentUrlCount++
}
2022-11-01 13:27:19 +00:00
log.Debugf("root tree children len is %d", len(f.NodeTree.Children))
2018-11-13 16:11:16 +00:00
}
2018-10-26 01:04:26 +00:00
// fetchUrlChanges method 
// scan rows from a firefox `places.sqlite` db and extract all bookmarks and
// places (moz_bookmarks, moz_places tables) that changed/are new since the browser.lastRunTime
// using the QBookmarksChanged query
2022-11-01 13:27:19 +00:00
func (f *Firefox) fetchUrlChanges(rows *sql.Rows,
2018-11-23 03:40:10 +00:00
bookmarks map[sqlid]*FFBookmark,
places map[sqlid]*FFPlace,
) {
bk := &FFBookmark{}
2018-11-13 16:11:16 +00:00
// Get the URL that changed
err := rows.Scan(&bk.id, &bk.btype, &bk.fk, &bk.parent, &bk.title)
if err != nil {
log.Fatal(err)
}
// database.DebugPrintRow(rows)
2018-11-13 16:11:16 +00:00
// 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.id)
// 3. A (type==1) (fk-> moz_places.id) (parent == idOf(tag))
2018-11-13 16:11:16 +00:00
if bk.btype == BkTypeURL {
2018-11-23 03:40:10 +00:00
var place FFPlace
// Use unsafe db to ignore non existant columns in
// dest field
2022-11-01 13:27:19 +00:00
udb := f.places.Handle.Unsafe()
err := udb.QueryRowx(mozilla.QGetBookmarkPlace, bk.fk).StructScan(&place)
if err != nil {
log.Fatal(err)
}
log.Debugf("Changed URL: %s", place.URL)
log.Debugf("%v", place)
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 &&
// ignore original tags/folder from mozilla
bk.parent > mozilla.MobileID {
2018-11-13 16:11:16 +00:00
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() {
2022-11-01 13:27:19 +00:00
f.fetchUrlChanges(rows, bookmarks, places)
2018-11-13 16:11:16 +00:00
}
log.Debugf("fetching changes done !")
}
2022-12-10 01:49:30 +00:00
// Copies places.sqlite to a tmp dir to read a VFS lock sqlite db
func (f *Firefox) initPlacesCopy() (mozilla.PlaceCopyJob, error) {
// create a new copy job
pc := mozilla.NewPlaceCopyJob()
err := utils.CopyFilesToTmpFolder(path.Join(f.BkDir, f.BkFile+"*"), pc.Path())
2022-12-10 01:49:30 +00:00
if err != nil {
return pc, fmt.Errorf("could not copy places.sqlite to tmp folder: %s", err)
2022-12-10 01:49:30 +00:00
}
opts := FFConfig.PlacesDSN
f.places, err = database.NewDB("places",
// using the copied places file instead of the original to avoid
// sqlite vfs lock errors
path.Join(pc.Path(), f.BkFile),
2022-12-10 01:49:30 +00:00
database.DBTypeFileDSN, opts).Init()
if err != nil {
return pc, err
2022-12-10 01:49:30 +00:00
}
return pc, nil
2022-12-10 01:49:30 +00:00
}