feat: chrome multi profiles, sync mutex for sqlite db access

This commit is contained in:
blob42 2023-09-25 20:56:05 +02:00
parent 2ca7c6f72b
commit 7532d59016
8 changed files with 91 additions and 47 deletions

View File

@ -127,10 +127,38 @@ type Chrome struct {
*ChromeConfig
}
func (ch *Chrome) Init(ctx *modules.Context, p *profiles.Profile) error {
// if called without profile setup default profile
if p == nil {
prof, err := ProfileManager.GetProfileByID(BrowserName, ch.Profile)
if err != nil {
return err
}
bookmarkDir, err := prof.AbsolutePath()
if err != nil {
return err
}
ch.BkDir = bookmarkDir
return ch.init(ctx)
}
ch.ChromeConfig = NewChromeConfig()
ch.Profile = p.Id
if bookmarkDir, err := p.AbsolutePath(); err != nil {
return err
} else {
ch.BkDir = bookmarkDir
}
return ch.init(ctx)
}
// Init() is the first method called after a browser instance is created
// and registered.
// Return ok, error
func (ch *Chrome) Init(_ *modules.Context) error {
func (ch *Chrome) init(_ *modules.Context) error {
log.Infof("initializing <%s>", ch.Name)
return ch.setupWatchers()
}
@ -433,7 +461,8 @@ func (ch *Chrome) Run() {
// If the cache is empty just copy buffer to cache
// until local db is already populated and preloaded
//debugPrint("%d", BufferDB.Count())
// debugPrint("%d", BufferDB.Count())
log.Debugf("checking if db is empty")
if empty, err := database.Cache.DB.IsEmpty(); empty {
if err != nil {
log.Error(err)
@ -444,12 +473,16 @@ func (ch *Chrome) Run() {
log.Debugf("syncing <%s> to disk", database.Cache.DB.Name)
} else {
log.Debug("syncing to db")
ch.BufferDB.SyncTo(database.Cache.DB)
}
go database.Cache.DB.SyncToDisk(database.GetDBFullPath())
go func(){
if err = database.Cache.DB.SyncToDisk(database.GetDBFullPath()); err != nil {
log.Critical(err)
}
}()
ch.LastWatchRunTime = time.Since(startRun)
}
@ -474,7 +507,7 @@ func init() {
// interface guards
var _ modules.BrowserModule = (*Chrome)(nil)
var _ modules.Initializer = (*Chrome)(nil)
var _ modules.ProfileInitializer = (*Chrome)(nil)
var _ modules.Loader = (*Chrome)(nil)
var _ watch.WatchRunner = (*Chrome)(nil)
var _ hooks.HookRunner = (*Chrome)(nil)

View File

@ -110,6 +110,7 @@ func (chrome *Chrome) WatchAllProfiles() bool {
}
// chrome uses ID to identify the profile path
func (cpm *ChromeProfileManager) GetProfileByID (flavour string, id string) (*profiles.Profile, error) {
profiles, err := cpm.GetProfiles(flavour)
if err != nil {

View File

@ -23,8 +23,6 @@
package firefox
import (
"github.com/urfave/cli/v2"
"git.blob42.xyz/gosuki/gosuki/internal/config"
"git.blob42.xyz/gosuki/gosuki/internal/database"
"git.blob42.xyz/gosuki/gosuki/pkg/browsers/mozilla"
@ -156,15 +154,15 @@ func init() {
// log.Debugf("%p", FFConfig)
// An example of running custom code when config is ready
config.RegisterConfReadyHooks(func(c *cli.Context) error{
// log.Debugf("%#v", config.GetAll().Dump())
if userConf := config.GetModule(BrowserName); userConf != nil {
watchAll, _ := userConf.Get("WatchAllProfiles")
log.Debugf("WATCH_ALL: %v", watchAll)
}
return nil
})
// config.RegisterConfReadyHooks(func(c *cli.Context) error{
// // log.Debugf("%#v", config.GetAll().Dump())
//
//
// if userConf := config.GetModule(BrowserName); userConf != nil {
// watchAll, _ := userConf.Get("WatchAllProfiles")
// log.Debugf("WATCH_ALL: %v", watchAll)
// }
//
// return nil
// })
}

View File

@ -308,8 +308,9 @@ func (f *Firefox) Init(ctx *modules.Context, p *profiles.Profile) error {
return f.init(ctx)
}
//TEST: try multiple profiles at same time
// use a new config for this profile
// f.FirefoxConfig = NewFirefoxConfig()
f.FirefoxConfig = NewFirefoxConfig()
f.Profile = p.Name
@ -419,7 +420,8 @@ func (f *Firefox) Load() error {
f.BufferDB.SyncTo(database.Cache.DB)
}
database.Cache.DB.SyncToDisk(database.GetDBFullPath())
//TODO: send as goroutine ?
go database.Cache.DB.SyncToDisk(database.GetDBFullPath())
//DEBUG:
// tree.PrintTree(f.NodeTree)
@ -463,7 +465,12 @@ func (ff *Firefox) Run() {
database.SyncURLIndexToBuffer(ff.URLIndexList, ff.URLIndex, ff.BufferDB)
ff.BufferDB.SyncTo(database.Cache.DB)
database.Cache.DB.SyncToDisk(database.GetDBFullPath())
go func(){
err = database.Cache.DB.SyncToDisk(database.GetDBFullPath()); if err != nil {
log.Critical(err)
}
}()
//TODO!: is LastWatchRunTime alone enough ?
ff.LastWatchRunTime = time.Since(startRun)

View File

@ -151,14 +151,10 @@ func startDaemon(c *cli.Context) error {
log.Criticalf("TODO: module <%s> is not a BrowserModule", mod.ID)
}
if c.Bool("watch-all") {
panic("TODO: watch all profiles")
}
// call runModule for each profile
bpm, ok := browser.(profiles.ProfileManager)
if ok {
if bpm.WatchAllProfiles() {
if c.Bool("watch-all") || bpm.WatchAllProfiles() {
falvours := bpm.ListFlavours()
for _, f := range falvours {
profs, err := bpm.GetProfiles(f.Name)

View File

@ -95,6 +95,7 @@ var detectInstalledCmd = &cli.Command{
Usage: "detect installed browsers",
Action: func(_ *cli.Context) error {
mods := modules.GetModules()
fmt.Printf("installed browsers:\n\n")
for _, mod := range mods {
browser, isBrowser := mod.ModInfo().New().(modules.BrowserModule)
if !isBrowser {
@ -109,9 +110,6 @@ var detectInstalledCmd = &cli.Command{
}
flavours := pm.ListFlavours()
if len(flavours) > 0 {
fmt.Printf("Installed browsers:\n\n")
}
for _, f := range flavours {
log.Debugf("found flavour <%s> for <%s>", f.Name, mod.ModInfo().ID)
if dir, err := utils.ExpandPath(f.BaseDir); err != nil {

View File

@ -39,8 +39,8 @@ import (
)
var (
//TODO!: document this
_sql3conns []*sqlite3.SQLiteConn // Only used for backup hook
backupHookRegistered bool // set to true once the backup hook is registered
DefaultDBPath = "./"
)
@ -236,7 +236,6 @@ func NewDB(name string, dbPath string, dbFormat string, opts ...DsnOptions) *DB
}
// TODO: Should check if DB is locked
// We should export Open() in its own method and wrap
// with interface so we can mock it and test the lock status in Init()
// Initialize a sqlite database with Gosuki Schema if not already done
@ -252,16 +251,11 @@ func (db *DB) Init() (*DB, error) {
// Detect if database file is locked
if db.Type == DBTypeRegularFile {
locked, err := db.Locked()
if err != nil {
if locked, err := db.Locked(); err != nil {
return nil, DBError{DBName: db.Name, Err: err}
}
if locked {
} else if locked {
return nil, ErrVfsLocked
}
}
// Open database
@ -408,14 +402,16 @@ func flushSqliteCon(con *sqlx.DB) {
func registerSqliteHooks() {
// sqlite backup hook
log.Debugf("backup_hook: registering driver %s", DriverBackupMode)
// log.Debugf("backup_hook: registering driver %s", DriverBackupMode)
// Register the hook
sql.Register(DriverBackupMode,
&sqlite3.SQLiteDriver{
//TODO!: document why ?
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
//log.Debugf("[ConnectHook] registering new connection")
_sql3conns = append(_sql3conns, conn)
//log.Debugf("%v", _sql3conns)
// log.Debugf("[ConnectHook] registered new connection")
log.Debugf("%v", _sql3conns)
return nil
},
})

View File

@ -23,13 +23,17 @@ package database
import (
"fmt"
"sync"
"github.com/jmoiron/sqlx"
sqlite3 "github.com/mattn/go-sqlite3"
)
// For ever row in `src` try to insert it into `dst`.
// If if fails then try to update it. It means `src` is synced to `dst`
var mu sync.Mutex
// Manual UPSERT:
// For every row in `src` try to insert it into `dst`. if if fails then try to
// update it. It means `src` is synced to `dst`
func (src *DB) SyncTo(dst *DB) {
var sqlite3Err sqlite3.Error
var existingUrls []*SBookmark
@ -69,7 +73,7 @@ func (src *DB) SyncTo(dst *DB) {
VALUES (?, ?, ?, ?, ?)`,
)
defer func() {
err := tryInsertDstRow.Close()
err = tryInsertDstRow.Close()
if err != nil {
log.Critical(err)
}
@ -87,7 +91,7 @@ func (src *DB) SyncTo(dst *DB) {
)
defer func(){
err := updateDstRow.Close()
err = updateDstRow.Close()
if err != nil {
log.Critical()
}
@ -102,13 +106,13 @@ func (src *DB) SyncTo(dst *DB) {
log.Error(err)
}
// Lock destination db
log.Debugf("starting transaction")
dstTx, err := dst.Handle.Begin()
if err != nil {
log.Error(err)
}
// Start syncing all entries from source table
log.Debugf("scanning entries in source table")
for srcTable.Next() {
@ -149,7 +153,7 @@ func (src *DB) SyncTo(dst *DB) {
}
// Start a new transaction to update the existing urls
dstTx, err = dst.Handle.Begin() // Lock dst db
dstTx, err = dst.Handle.Begin()
if err != nil {
log.Error(err)
}
@ -196,6 +200,7 @@ func (src *DB) SyncTo(dst *DB) {
if err != nil {
log.Errorf("%s: %s", err, scan.URL)
}
log.Debugf("synced %s to %s", scan.URL, dst.Name)
}
@ -213,8 +218,13 @@ func (src *DB) SyncTo(dst *DB) {
}
}
// TODO!: add concurrency
// Multiple threads(goroutines) are trying to sync together when running
// with watch all. Use sync.Mutex !!
func (src *DB) SyncToDisk(dbpath string) error {
log.Debugf("Syncing <%s> to <%s>", src.Name, dbpath)
mu.Lock()
defer mu.Unlock()
defer func() {
if r := recover(); r != nil {
@ -247,7 +257,12 @@ func (src *DB) SyncToDisk(dbpath string) error {
}
if len(_sql3conns) < 2 {
return fmt.Errorf("not enough sql connections for backup")
return fmt.Errorf("not enough sql connections for backup call")
}
if _sql3conns[0] == nil {
log.Critical("nil sql connection")
return fmt.Errorf("nil sql connection")
}
bkp, err := _sql3conns[1].Backup("main", _sql3conns[0], "main")