Refactoring + doc updates + pass Context to modules

- modules receive on Init() a context with Cli context
- Eval symlinks on profile dir
- WIP handle multiple profiles
This commit is contained in:
Chakib Ben Ziane 2023-02-18 23:08:12 +01:00
parent 55e163127d
commit 280341d4e0
12 changed files with 249 additions and 228 deletions

View File

@ -1,4 +1,4 @@
.PHONY: all run deps docs build test debug .PHONY: all run clean deps docs build test debug
TARGET=gomark TARGET=gomark
# CGO_CFLAGS="-g -O2 -Wno-return-local-addr" # CGO_CFLAGS="-g -O2 -Wno-return-local-addr"

View File

@ -13,15 +13,15 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var startServerCmd = &cli.Command{ var startDaemonCmd = &cli.Command{
Name: "server", Name: "daemon",
Aliases: []string{"s"}, Aliases: []string{"d"},
Usage: "run browser watchers", Usage: "run browser watchers",
// Category: "daemon" // Category: "daemon"
Action: startServer, Action: startDaemon,
} }
func startServer(c *cli.Context) error { func startDaemon(c *cli.Context) error {
defer utils.CleanFiles() defer utils.CleanFiles()
manager := gum.NewManager() manager := gum.NewManager()
manager.ShutdownOn(os.Interrupt) manager.ShutdownOn(os.Interrupt)
@ -41,6 +41,11 @@ func startServer(c *cli.Context) error {
mod := browserMod.ModInfo() mod := browserMod.ModInfo()
// Create context
modContext := &modules.Context{
Cli: c,
}
//Create a browser instance //Create a browser instance
browser, ok := mod.New().(modules.BrowserModule) browser, ok := mod.New().(modules.BrowserModule)
if !ok { if !ok {
@ -57,13 +62,16 @@ func startServer(c *cli.Context) error {
log.Debugf("new browser <%s> instance", browser.Config().Name) log.Debugf("new browser <%s> instance", browser.Config().Name)
h, ok := browser.(modules.HookRunner) h, ok := browser.(modules.HookRunner)
if ok { if ok {
//TODO: document hook running //TODO: document hook running on watch events
h.RegisterHooks(parsing.ParseTags) h.RegisterHooks(parsing.ParseTags)
} }
//TODO: call the setup logic for each browser instance //TODO!: Handle multiple profiles for modules who announce it - here ?
// calls the setup logic for each browser instance which
// includes the browsers.Initializer and browsers.Loader interfaces // includes the browsers.Initializer and browsers.Loader interfaces
err := modules.Setup(browser) err := modules.Setup(browser, modContext)
if err != nil { if err != nil {
log.Errorf("setting up <%s> %v", browser.Config().Name, err) log.Errorf("setting up <%s> %v", browser.Config().Name, err)
if isShutdowner { if isShutdowner {
@ -78,7 +86,7 @@ func startServer(c *cli.Context) error {
continue continue
} }
log.Infof("start watching <%s>", runner.Watcher().ID) log.Infof("start watching <%s>", runner.Watch().ID)
watch.SpawnWatcher(runner) watch.SpawnWatcher(runner)
} }

View File

@ -67,6 +67,7 @@ func init() {
} }
//TODO: #54 define interface for modules to handle and list profiles //TODO: #54 define interface for modules to handle and list profiles
//FIX: Remove since profile listing is implemented at the main module level
func ffListProfiles(_ *cli.Context) error { func ffListProfiles(_ *cli.Context) error {
profs, err := FirefoxProfileManager.GetProfiles() profs, err := FirefoxProfileManager.GetProfiles()
if err != nil { if err != nil {

View File

@ -52,7 +52,6 @@ type FFPlace struct {
AutoIncr AutoIncr
} }
// TODO!: replace by MergedPlaceBookmark and MozBookmark below // TODO!: replace by MergedPlaceBookmark and MozBookmark below
type FFBookmark struct { type FFBookmark struct {
btype sqlid btype sqlid
@ -85,7 +84,6 @@ type Firefox struct {
folderScanMap map[sqlid]*MozFolder folderScanMap map[sqlid]*MozFolder
lastRunTime time.Time lastRunTime time.Time
} }
// func (ff *Firefox) updateModifiedFolders(since timestamp) ([]*MozFolder, error) { // func (ff *Firefox) updateModifiedFolders(since timestamp) ([]*MozFolder, error) {
@ -129,7 +127,6 @@ func (ff *Firefox) scanFolders(since timestamp) ([]*MozFolder, error) {
} }
} }
return folders, err return folders, err
} }
@ -139,7 +136,7 @@ func (ff *Firefox) loadBookmarksToTree(bookmarks []*MozBookmark) {
for _, bkEntry := range bookmarks { for _, bkEntry := range bookmarks {
// Create/Update URL node and apply tag node // Create/Update URL node and apply tag node
ok, urlNode := ff.addUrlNode(bkEntry.Url, bkEntry.Title, bkEntry.PlDesc) ok, urlNode := ff.addURLNode(bkEntry.Url, bkEntry.Title, bkEntry.PlDesc)
if !ok { if !ok {
log.Infof("url <%s> already in url index", bkEntry.Url) log.Infof("url <%s> already in url index", bkEntry.Url)
} }
@ -149,7 +146,9 @@ func (ff *Firefox) loadBookmarksToTree(bookmarks []*MozBookmark) {
* the node tree. * the node tree.
*/ */
for _, tagName := range strings.Split(bkEntry.Tags, ",") { for _, tagName := range strings.Split(bkEntry.Tags, ",") {
if tagName == "" { continue } if tagName == "" {
continue
}
seen, tagNode := ff.addTagNode(tagName) seen, tagNode := ff.addTagNode(tagName)
if !seen { if !seen {
log.Infof("tag <%s> already in tag map", tagNode.Name) log.Infof("tag <%s> already in tag map", tagNode.Name)
@ -192,7 +191,6 @@ func (ff *Firefox) scanBookmarks() ([]*MozBookmark, error) {
} }
err = dotx.Select(ff.places.Handle, &bookmarks, mozilla.MozBookmarkQuery) err = dotx.Select(ff.places.Handle, &bookmarks, mozilla.MozBookmarkQuery)
// load bookmarks and tags into the node tree // load bookmarks and tags into the node tree
// then attach them to their assigned folder hierarchy // then attach them to their assigned folder hierarchy
@ -209,7 +207,6 @@ func (ff *Firefox) scanModifiedBookmarks(since timestamp) ([]*MozBookmark, error
var bookmarks []*MozBookmark var bookmarks []*MozBookmark
dotx, err := database.DotxQueryEmbedFS(mozilla.EmbeddedSqlQueries, dotx, err := database.DotxQueryEmbedFS(mozilla.EmbeddedSqlQueries,
mozilla.MozChangedBookmarkQueryFile) mozilla.MozChangedBookmarkQueryFile)
@ -279,8 +276,11 @@ func (f *Firefox) GetProfilePath(p profiles.Profile) string {
} }
// TEST: // TEST:
// TODO: implement watching of multiple profiles
// NOTE: should be done at core gomark level where multiple instances
// are spawned for each profile
// Implements browser.Initializer interface // Implements browser.Initializer interface
func (f *Firefox) Init() error { func (f *Firefox) Init(ctx *modules.Context) error {
log.Infof("initializing <%s>", f.Name) log.Infof("initializing <%s>", f.Name)
bookmarkPath, err := f.BookmarkPath() bookmarkPath, err := f.BookmarkPath()
if err != nil { if err != nil {
@ -289,17 +289,16 @@ func (f *Firefox) Init() error {
log.Debugf("bookmark path is: %s", bookmarkPath) log.Debugf("bookmark path is: %s", bookmarkPath)
watchedPath, err := filepath.EvalSymlinks(f.BkDir)
// Setup watcher
expandedBaseDir, err := filepath.EvalSymlinks(f.BkDir)
if err != nil { if err != nil {
return err return err
} }
// Setup watcher
w := &watch.Watch{ w := &watch.Watch{
Path: expandedBaseDir, Path: watchedPath,
EventTypes: []fsnotify.Op{fsnotify.Write}, EventTypes: []fsnotify.Op{fsnotify.Write},
EventNames: []string{filepath.Join(expandedBaseDir, "places.sqlite-wal")}, EventNames: []string{filepath.Join(watchedPath, "places.sqlite-wal")},
ResetWatch: false, ResetWatch: false,
} }
@ -316,15 +315,14 @@ func (f *Firefox) Init() error {
return nil return nil
} }
func (f *Firefox) Watcher() *watch.WatchDescriptor { func (f *Firefox) Watch() *watch.WatchDescriptor {
return f.BrowserConfig.Watcher() return f.GetWatcher()
} }
func (f Firefox) Config() *modules.BrowserConfig { func (f Firefox) Config() *modules.BrowserConfig {
return f.BrowserConfig return f.BrowserConfig
} }
// Firefox custom logic for preloading the bookmarks when the browser module // Firefox custom logic for preloading the bookmarks when the browser module
// starts. Implements modules.Loader interface. // starts. Implements modules.Loader interface.
func (f *Firefox) Load() error { func (f *Firefox) Load() error {
@ -393,7 +391,6 @@ func (ff *Firefox) Run() {
} }
defer pc.Clean() defer pc.Clean()
log.Debugf("Checking changes since <%d> %s", log.Debugf("Checking changes since <%d> %s",
ff.lastRunTime.UTC().UnixNano()/1000, ff.lastRunTime.UTC().UnixNano()/1000,
ff.lastRunTime.Local().Format("Mon Jan 2 15:04:05 MST 2006")) ff.lastRunTime.Local().Format("Mon Jan 2 15:04:05 MST 2006"))
@ -435,7 +432,7 @@ func (ff *Firefox) getPathToPlacesCopy() string {
// HACK: addUrl and addTag share a lot of code, find a way to reuse shared code // 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 // 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 // PROBLEM: tag nodes use IDs and URL nodes use URL as hashes
func (f *Firefox) addUrlNode(url, title, desc string) (bool, *tree.Node) { func (f *Firefox) addURLNode(url, title, desc string) (bool, *tree.Node) {
var urlNode *tree.Node var urlNode *tree.Node
iUrlNode, exists := f.URLIndex.Get(url) iUrlNode, exists := f.URLIndex.Get(url)
@ -563,7 +560,6 @@ func (ff *Firefox) addFolderNode(folder MozFolder) (bool, *tree.Node){
return true, folderNode return true, folderNode
} }
// TODO: retire this function after scanBookmarks() is implemented // TODO: retire this function after scanBookmarks() is implemented
// load all bookmarks from `places.sqlite` and store them in BaseBrowser.NodeTree // 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 // this method is used the first time gomark is started or to extract bookmarks
@ -622,7 +618,7 @@ func loadBookmarks(f *Firefox) {
// Add the url to the tag // Add the url to the tag
// NOTE: this call is responsible for updating URLIndexList // NOTE: this call is responsible for updating URLIndexList
ok, urlNode := f.addUrlNode(url, title, desc) ok, urlNode := f.addURLNode(url, title, desc)
if !ok { if !ok {
log.Infof("url <%s> already in url index", url) log.Infof("url <%s> already in url index", url)
} }

View File

@ -97,7 +97,7 @@ func Test_addUrlNode(t *testing.T) {
testNewUrl := "new urlNode: url is not yet in URLIndex" testNewUrl := "new urlNode: url is not yet in URLIndex"
t.Run(testNewUrl, func(t *testing.T) { t.Run(testNewUrl, func(t *testing.T) {
ok, urlNode := ff.addUrlNode(testUrl.url, testUrl.title, testUrl.desc) ok, urlNode := ff.addURLNode(testUrl.url, testUrl.title, testUrl.desc)
if !ok { if !ok {
t.Fatalf("expected %v, got %v", true, false) t.Fatalf("expected %v, got %v", true, false)
} }
@ -118,8 +118,8 @@ func Test_addUrlNode(t *testing.T) {
testUrlExists := "return existing urlNode found in URLIndex" testUrlExists := "return existing urlNode found in URLIndex"
t.Run(testUrlExists, func(t *testing.T) { t.Run(testUrlExists, func(t *testing.T) {
_, origNode := ff.addUrlNode(testUrl.url, testUrl.title, testUrl.desc) _, origNode := ff.addURLNode(testUrl.url, testUrl.title, testUrl.desc)
ok, urlNode := ff.addUrlNode(testUrl.url, testUrl.title, testUrl.desc) ok, urlNode := ff.addURLNode(testUrl.url, testUrl.title, testUrl.desc)
if ok { if ok {
t.Fatalf("expected %v, got %v", false, true) t.Fatalf("expected %v, got %v", false, true)
} }

View File

@ -1,8 +1,4 @@
// ### API Usage: // # Gomark documentation
// - Init Mode (debug, release) and Logging
// - Create a Browser object for each browser using `New[BrowserType]()`
// - Call `Load()` and `Watch()` on every browser
// - Run the gin server
package main package main
import ( import (
@ -15,8 +11,11 @@ import (
"git.sp4ke.xyz/sp4ke/gomark/cmd" "git.sp4ke.xyz/sp4ke/gomark/cmd"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
// Load firefox browser modules // Load firefox browser modules
_ "git.sp4ke.xyz/sp4ke/gomark/firefox" _ "git.sp4ke.xyz/sp4ke/gomark/firefox"
// Load chrome browser module
_ "git.sp4ke.xyz/sp4ke/gomark/chrome" _ "git.sp4ke.xyz/sp4ke/gomark/chrome"
) )
@ -53,7 +52,7 @@ func main() {
modules := modules.GetModules() modules := modules.GetModules()
for _, mod := range modules { for _, mod := range modules {
// Run module's before context hooks // Run module's hooks that should run before context is ready
// for example setup flags management // for example setup flags management
err := cmd.BeforeHook(string(mod.ModInfo().ID))(c) err := cmd.BeforeHook(string(mod.ModInfo().ID))(c)
if err != nil { if err != nil {
@ -72,7 +71,7 @@ func main() {
// Browser modules can register commands through cmd.RegisterModCommand. // Browser modules can register commands through cmd.RegisterModCommand.
// registered commands will be appended here // registered commands will be appended here
app.Commands = []*cli.Command{ app.Commands = []*cli.Command{
startServerCmd, startDaemonCmd,
cmd.ConfigCmds, cmd.ConfigCmds,
cmd.ProfileCmds, cmd.ProfileCmds,
} }
@ -83,6 +82,7 @@ func main() {
log.Debugf("loading %d modules", len(modules)) log.Debugf("loading %d modules", len(modules))
for _, mod := range modules { for _, mod := range modules {
modId := string(mod.ModInfo().ID) modId := string(mod.ModInfo().ID)
log.Debugf("loading module <%s>", modId)
// for each registered module, register own flag management // for each registered module, register own flag management
mod_flags := cmd.GlobalFlags(modId) mod_flags := cmd.GlobalFlags(modId)

View File

@ -72,7 +72,7 @@ type BrowserConfig struct {
parseHooks []parsing.Hook parseHooks []parsing.Hook
} }
func (browserconfig *BrowserConfig) Watcher() *watch.WatchDescriptor { func (browserconfig *BrowserConfig) GetWatcher() *watch.WatchDescriptor {
return browserconfig.watcher return browserconfig.watcher
} }
@ -118,15 +118,15 @@ type Loader interface {
Load() error Load() error
} }
// Initialize the browser before any data loading or run callbacks // Initialize the module before any data loading or callbacks
// If a browser wants to do any preparation and prepare custom state before Loader.Load() // If a module wants to do any preparation and prepare custom state before Loader.Load()
// is called and before any Watchable.Run() or other callbacks are executed. // is called and before any Watchable.Run() or other callbacks are executed.
type Initializer interface { type Initializer interface {
// Init() is the first method called after a browser instance is created // Init() is the first method called after a browser instance is created
// and registered. // and registered.
// Return ok, error // Return ok, error
Init() error Init(*Context) error
} }
// Every browser is setup once, the following methods are called in order of // Every browser is setup once, the following methods are called in order of
@ -135,7 +135,7 @@ type Initializer interface {
// 0- Provision: Sets up and custom configiguration to the browser // 0- Provision: Sets up and custom configiguration to the browser
// 1- Init : any variable and state initialization // 1- Init : any variable and state initialization
// 2- Load: Does the first loading of data (ex first loading of bookmarks ) // 2- Load: Does the first loading of data (ex first loading of bookmarks )
func Setup(browser BrowserModule) error { func Setup(browser BrowserModule, c *Context) error {
//TODO!: default init //TODO!: default init
// Init browsers' BufferDB // Init browsers' BufferDB
@ -155,12 +155,15 @@ func Setup(browser BrowserModule) error {
initializer, ok := browser.(Initializer) initializer, ok := browser.(Initializer)
if ok { if ok {
log.Debugf("<%s> custom init", browserId) log.Debugf("<%s> custom init", browserId)
if err := initializer.Init(); err != nil { if err := initializer.Init(c); err != nil {
return fmt.Errorf("<%s> initialization error: %v", browserId, err) return fmt.Errorf("<%s> initialization error: %v", browserId, err)
} }
} else {
log.Warningf("<%s> does not implement Initializer, not calling Init()", browserId)
} }
// Default browser loading logic // Default browser loading logic
// Make sure that cache is initialized // Make sure that cache is initialized
if !database.Cache.IsInitialized() { if !database.Cache.IsInitialized() {

View File

@ -6,13 +6,21 @@
// Browsers would need to register as gomark Module and as Browser interfaces // Browsers would need to register as gomark Module and as Browser interfaces
package modules package modules
import "errors" import (
"errors"
"github.com/urfave/cli/v2"
)
var ( var (
registeredBrowsers []BrowserModule registeredBrowsers []BrowserModule
registeredModules []Module registeredModules []Module
) )
type Context struct {
Cli *cli.Context
}
// Every new module needs to register as a Module using this interface // Every new module needs to register as a Module using this interface
type Module interface { type Module interface {
ModInfo() ModInfo ModInfo() ModInfo

View File

@ -91,7 +91,12 @@ func (pm *MozProfileManager) GetProfilePath(name string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
return filepath.Join(pm.ConfigDir, p.Path), nil rawPath := filepath.Join(pm.ConfigDir, p.Path)
fullPath , err := filepath.EvalSymlinks(rawPath)
return fullPath, err
// Eval symlinks
} }
func (pm *MozProfileManager) GetProfileByName(name string) (*profiles.Profile, error) { func (pm *MozProfileManager) GetProfileByName(name string) (*profiles.Profile, error) {

View File

@ -11,15 +11,15 @@ const (
// ProfileManager is any module that can detect or list profiles, usually a browser module. // ProfileManager is any module that can detect or list profiles, usually a browser module.
type ProfileManager interface { type ProfileManager interface {
// Get all profile details // Get all profile details
GetProfiles() ([]*Profile, error) GetProfiles() ([]*Profile, error)
// Returns the default profile if no profile is selected // Returns the default profile if no profile is selected
GetDefaultProfile() (*Profile, error) GetDefaultProfile() (*Profile, error)
// Return that absolute path to a profile // Return that absolute path to a profile and follow symlinks
GetProfilePath(Profile) string GetProfilePath(Profile) (string)
} }
type Profile struct { type Profile struct {

View File

@ -8,7 +8,7 @@ func ReduceEvents(interval time.Duration,
w WatchRunner) { w WatchRunner) {
log.Debug("Running reducer") log.Debug("Running reducer")
eventsIn := w.Watcher().eventsChan eventsIn := w.Watch().eventsChan
timer := time.NewTimer(interval) timer := time.NewTimer(interval)
var events []bool var events []bool

View File

@ -21,7 +21,7 @@ type ResetWatcher interface {
// Required interface to be implemented by browsers that want to use the // Required interface to be implemented by browsers that want to use the
// fsnotify event loop and watch changes on bookmark files. // fsnotify event loop and watch changes on bookmark files.
type Watcher interface { type Watcher interface {
Watcher() *WatchDescriptor Watch() *WatchDescriptor
} }
type Runner interface { type Runner interface {
@ -97,7 +97,7 @@ type Watch struct {
} }
func SpawnWatcher(w WatchRunner) { func SpawnWatcher(w WatchRunner) {
watcher := w.Watcher() watcher := w.Watch()
if ! watcher.isWatching { if ! watcher.isWatching {
go WatcherThread(w) go WatcherThread(w)
watcher.isWatching = true watcher.isWatching = true
@ -112,7 +112,7 @@ func SpawnWatcher(w WatchRunner) {
// Main thread for watching file changes // Main thread for watching file changes
func WatcherThread(w WatchRunner) { func WatcherThread(w WatchRunner) {
watcher := w.Watcher() watcher := w.Watch()
log.Infof("<%s> Started watcher", watcher.ID) log.Infof("<%s> Started watcher", watcher.ID)
for { for {
// Keep watcher here as it is reset from within // Keep watcher here as it is reset from within