diff --git a/browsers/chrome/profiles.go b/browsers/chrome/profiles.go
index 9db2743..d585346 100644
--- a/browsers/chrome/profiles.go
+++ b/browsers/chrome/profiles.go
@@ -93,7 +93,7 @@ func (*Chrome) ListFlavours() []browsers.BrowserDef {
// detect local flavours
for _, v := range browsers.Defined(browsers.ChromeBased) {
if v.Detect() {
- result = append(result, v)
+ result = append(result, *v)
}
}
diff --git a/browsers/firefox/firefox.go b/browsers/firefox/firefox.go
index 32490f4..1334931 100644
--- a/browsers/firefox/firefox.go
+++ b/browsers/firefox/firefox.go
@@ -23,7 +23,6 @@ package firefox
import (
"fmt"
- "path"
"path/filepath"
"strings"
"time"
@@ -677,22 +676,22 @@ func (f *Firefox) addFolderNode(folder MozFolder) (bool, *tree.Node) {
// 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())
+ // Construire le chemin source correctement
+ sourcePath := filepath.Join(f.BkDir, f.BkFile)
+
+ // Copier seulement le fichier principal
+ err := utils.CopyFileToTmp(sourcePath, pc.Path(), f.BkFile)
if err != nil {
- return pc, fmt.Errorf("could not copy places.sqlite to tmp folder: %w", err)
+ return pc, fmt.Errorf("could not copy places.sqlite: %w", err)
}
- 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),
- database.DBTypeFileDSN, opts).Init()
+ // Construire le chemin destination correctement
+ destPath := filepath.Join(pc.Path(), f.BkFile)
+ opts := FFConfig.PlacesDSN
+ f.places, err = database.NewDB("places", destPath, database.DBTypeFileDSN, opts).Init()
if err != nil {
return pc, err
}
diff --git a/cmd/gosuki/daemon.go b/cmd/gosuki/daemon.go
index 4497f12..31b19e0 100644
--- a/cmd/gosuki/daemon.go
+++ b/cmd/gosuki/daemon.go
@@ -89,6 +89,12 @@ func runBrowserModule(m *manager.Manager,
log.Error(err)
return err
}
+
+ if _, err := pfl.AbsolutePath(); err != nil {
+ log.Warnf("profile path is invalid: %s", err)
+ return &modules.ErrModDisabled{Err: err}
+ }
+
if err := bpm.UseProfile(pfl, flav); err != nil {
log.Warnf("unable to load profile <%s.%s>: %s", mod.ID, pfl.Name, err)
return &modules.ErrModDisabled{Err: err}
diff --git a/cmd/gosuki/manager.go b/cmd/gosuki/manager.go
index 66d933b..ab60524 100644
--- a/cmd/gosuki/manager.go
+++ b/cmd/gosuki/manager.go
@@ -20,7 +20,7 @@
// along with gosuki. If not, see .
//
-//go:build (systray && darwin) || !systray
+//go:build (systray && darwin) || (systray && windows) || !systray
package main
diff --git a/go.mod b/go.mod
index 4bbe26f..5ad6486 100644
--- a/go.mod
+++ b/go.mod
@@ -37,6 +37,7 @@ require (
)
require (
+ fyne.io/systray v1.11.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
@@ -48,7 +49,8 @@ require (
github.com/ebitengine/purego v0.8.4 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
- github.com/godbus/dbus/v5 v5.0.4 // indirect
+ github.com/godbus/dbus/v5 v5.1.0 // indirect
+ github.com/gofrs/flock v0.12.1 // indirect
github.com/golang/protobuf v1.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.5 // indirect
diff --git a/go.sum b/go.sum
index 42a439a..d2eb201 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,6 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
+fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA1qmKJ+hQn3UjytosdoG27WGjrDlVs=
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/TxuypdOERsDOS7SndZg0rzzcCi1b6wQMXUYM=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
@@ -76,6 +78,10 @@ github.com/gobuffalo/flect v0.3.0 h1:erfPWM+K1rFNIQeRPdeEXxo8yFr/PO17lhRnS8FUrtk
github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
+github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
+github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
diff --git a/gosuki.exe b/gosuki.exe
new file mode 100644
index 0000000..c3a4e9f
Binary files /dev/null and b/gosuki.exe differ
diff --git a/internal/database/locks.go b/internal/database/locks.go
index 723adf3..7f7bb04 100644
--- a/internal/database/locks.go
+++ b/internal/database/locks.go
@@ -1,29 +1,7 @@
-// Copyright (c) 2023-2025 Chakib Ben Ziane and [`GoSuki` contributors]
-// (https://github.com/blob42/gosuki/graphs/contributors).
-//
-// All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This file is part of GoSuki.
-//
-// GoSuki is free software: you can redistribute it and/or modify it under the terms of
-// the GNU Affero General Public License as published by the Free Software Foundation,
-// either version 3 of the License, or (at your option) any later version.
-//
-// GoSuki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-// PURPOSE. See the GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License along with
-// gosuki. If not, see .
-
package database
import (
- "os"
-
- "golang.org/x/sys/unix"
+ "github.com/gofrs/flock"
)
type LockChecker interface {
@@ -35,28 +13,20 @@ type VFSLockChecker struct {
}
func (checker *VFSLockChecker) Locked() (bool, error) {
-
- f, err := os.Open(checker.path)
+ fileLock := flock.New(checker.path)
+
+ // Essayer de prendre le lock avec un timeout immédiat
+ locked, err := fileLock.TryLock()
if err != nil {
return false, err
}
-
- // Get the the lock mode
- var lock unix.Flock_t
- // See man (fcntl)
- unix.FcntlFlock(f.Fd(), unix.F_GETLK, &lock)
-
- // Check if lock is F_RDLCK (non-exclusive) or F_WRLCK (exclusive)
- if lock.Type == unix.F_RDLCK {
- //log.Debug("Lock is F_RDLCK")
+
+ if locked {
+ // On a réussi à prendre le lock, donc il n'était pas locked
+ fileLock.Unlock()
return false, nil
}
-
- if lock.Type == unix.F_WRLCK {
- //log.Debug("Lock is F_WRLCK (locked !)")
- return true, nil
- }
-
- return false, nil
-
-}
+
+ // On n'a pas réussi à prendre le lock, donc il est locked par quelqu'un d'autre
+ return true, nil
+}
\ No newline at end of file
diff --git a/internal/gui/systray_windows.go b/internal/gui/systray_windows.go
new file mode 100644
index 0000000..9d40b07
--- /dev/null
+++ b/internal/gui/systray_windows.go
@@ -0,0 +1,46 @@
+//go:build windows && systray
+// +build windows,systray
+
+package gui
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+
+ "github.com/energye/systray"
+)
+
+// WindowsRunSystray démarre la systray pour Windows
+func WindowsRunSystray(manager interface{}) {
+ // Gérer les signaux système
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt, syscall.SIGTERM)
+
+ go func() {
+ <-c
+ systray.Quit()
+ }()
+
+ // Démarrer la systray
+ systray.Run(onReady, onExit)
+}
+
+func onReady() {
+ // Titre et tooltip de base
+ systray.SetTitle("Gosuki")
+ systray.SetTooltip("Gosuki - Browser Bookmark Manager")
+
+ // Menu basique
+ mQuit := systray.AddMenuItem("Quit", "Quit Gosuki")
+
+ // Gestion du clic Quit
+ go func() {
+ <-mQuit.ClickedCh
+ systray.Quit()
+ }()
+}
+
+func onExit() {
+ // Nettoyage si nécessaire
+}
\ No newline at end of file
diff --git a/internal/utils/files.go b/internal/utils/files.go
index 9f33006..c9e689d 100644
--- a/internal/utils/files.go
+++ b/internal/utils/files.go
@@ -82,6 +82,18 @@ func CopyFilesToTmpFolder(srcglob string, dst string) error {
}
+func CopyFileToTmp(source, tmpDir, filename string) error {
+ srcFile := filepath.Clean(source)
+ dstFile := filepath.Join(tmpDir, filename)
+
+ input, err := os.ReadFile(srcFile)
+ if err != nil {
+ return err
+ }
+
+ return os.WriteFile(dstFile, input, 0644)
+}
+
// TEST:
func CleanFiles() {
log.Debugf("cleaning files <%s>", TMPDIR)
diff --git a/internal/utils/paths.go b/internal/utils/paths.go
index 3678a97..102e0a1 100644
--- a/internal/utils/paths.go
+++ b/internal/utils/paths.go
@@ -118,10 +118,17 @@ func ExpandPath(paths ...string) (string, error) {
if len(paths) == 0 {
return "", fmt.Errorf("no path provided")
}
+
+ path := os.ExpandEnv(filepath.Join(paths...))
+
+ // Vérifier si le chemin est vide après expansion
+ if path == "" {
+ return "", fmt.Errorf("expanded path is empty")
+ }
+
if homedir, err = os.UserHomeDir(); err != nil {
return "", err
}
- path := os.ExpandEnv(filepath.Join(paths...))
if path[0] == '~' {
path = filepath.Join(homedir, path[1:])
diff --git a/pkg/browsers/base.go b/pkg/browsers/base.go
index b6e11c7..66b45dd 100644
--- a/pkg/browsers/base.go
+++ b/pkg/browsers/base.go
@@ -1,28 +1,11 @@
-//
-// Copyright (c) 2025 Chakib Ben Ziane and [`gosuki` contributors](https://github.com/blob42/gosuki/graphs/contributors).
-// All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This file is part of GoSuki.
-//
-// GoSuki is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// GoSuki is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with gosuki. If not, see .
-//
-
package browsers
-import "github.com/blob42/gosuki/internal/utils"
+import (
+ "log" // Ajoutez cet import manquant
+ "os"
+ "path/filepath"
+ "runtime"
+)
type BrowserFamily uint
@@ -33,62 +16,179 @@ const (
)
type BrowserDef struct {
- Flavour string // also acts as canonical name
-
- Family BrowserFamily // browser family
-
+ Flavour string // also acts as canonical name
+ Family BrowserFamily // browser family
// Base browser directory path
baseDir string
-
// (linux only) path to snap package base dir
snapDir string
-
// (linux only) path to flatpak package base dir
flatDir string
}
-func (b BrowserDef) Detect() bool {
- var dir string
- var err error
- if dir, err = b.ExpandBaseDir(); err != nil {
- log.Debugf("expand path: %s: %s", b.BaseDir(), err)
- log.Info("skipping", "flavour", b.Flavour)
- } else if ok, err := utils.DirExists(dir); err != nil || !ok {
- log.Infof("could not detect <%s>: %s: %s", b.Flavour, dir, err)
+// Ajoutez ces méthodes manquantes pour BrowserDef
+
+// Detect vérifie si le navigateur est installé/détectable
+func (b *BrowserDef) Detect() bool {
+ // Vérifier si le répertoire de base existe
+ baseDir, err := b.ExpandBaseDir()
+ if err != nil || baseDir == "" {
return false
}
-
+
+ if _, err := os.Stat(baseDir); os.IsNotExist(err) {
+ return false
+ }
+
return true
}
-func MozBrowser(flavour, base, snap, flat string) BrowserDef {
- return BrowserDef{
- Flavour: flavour,
- baseDir: base,
- Family: Mozilla,
- snapDir: snap,
- flatDir: flat,
+// ExpandBaseDir retourne le chemin de base étendu (résolution des variables d'environnement)
+func (b *BrowserDef) ExpandBaseDir() (string, error) {
+ if b.baseDir == "" {
+ return "", nil
}
+
+ // Expansion des variables d'environnement
+ expanded := os.ExpandEnv(b.baseDir)
+
+ // Conversion en chemin absolu
+ absPath, err := filepath.Abs(expanded)
+ if err != nil {
+ log.Printf("Error expanding base directory %s: %v", b.baseDir, err)
+ return expanded, err
+ }
+
+ return absPath, nil
+}
+
+// BaseDir retourne le répertoire de base
+func (b *BrowserDef) BaseDir() string {
+ return b.baseDir
+}
+
+// SetBaseDir définit le répertoire de base
+func (b *BrowserDef) SetBaseDir(dir string) {
+ b.baseDir = dir
}
-func ChromeBrowser(flavour, base, snap, flat string) BrowserDef {
- return BrowserDef{
- Flavour: flavour,
- baseDir: base,
+// GetBaseDir est un alias pour BaseDir() pour compatibilité
+func (b *BrowserDef) GetBaseDir() string {
+ return b.baseDir
+}
+
+// Ajoutez la variable DefinedBrowsers qui manque
+var DefinedBrowsers map[string]*BrowserDef
+
+// Fonction Defined pour compatibilité avec le code existant
+func Defined(family ...BrowserFamily) map[string]*BrowserDef {
+ if len(family) == 0 {
+ return DefinedBrowsers
+ }
+
+ // Filtrer par famille de navigateur
+ filtered := make(map[string]*BrowserDef)
+ targetFamily := family[0]
+
+ for name, browser := range DefinedBrowsers {
+ if browser.Family == targetFamily {
+ filtered[name] = browser
+ }
+ }
+
+ return filtered
+}
+
+// Définition pour Qutebrowser (avec majuscule pour compatibilité)
+var QuteBrowser *BrowserDef
+
+// Initialisez DefinedBrowsers
+func init() {
+ if DefinedBrowsers == nil {
+ DefinedBrowsers = make(map[string]*BrowserDef)
+ }
+
+ // Ajoutez ici vos définitions de navigateurs par défaut
+ // Exemple pour Windows :
+ initializeDefaultBrowsers()
+}
+
+// Fonction pour initialiser les navigateurs par défaut
+func initializeDefaultBrowsers() {
+ // Chrome
+ DefinedBrowsers["chrome"] = &BrowserDef{
+ Flavour: "chrome",
+ Family: ChromeBased,
+ baseDir: getDefaultChromeDir(),
+ }
+
+ // Firefox
+ DefinedBrowsers["firefox"] = &BrowserDef{
+ Flavour: "firefox",
+ Family: Mozilla,
+ baseDir: getDefaultFirefoxDir(),
+ }
+
+ // Edge
+ DefinedBrowsers["edge"] = &BrowserDef{
+ Flavour: "edge",
Family: ChromeBased,
- snapDir: snap,
- flatDir: flat,
+ baseDir: getDefaultEdgeDir(),
+ }
+
+ // Qutebrowser
+ quteDef := &BrowserDef{
+ Flavour: "qutebrowser",
+ Family: Qutebrowser,
+ baseDir: getDefaultQuteDir(),
}
+ DefinedBrowsers["qutebrowser"] = quteDef
+
+ // Alias pour compatibilité
+ QuteBrowser = quteDef
}
-// Returns defined browsers of type `Mozilla`
-func Defined(family BrowserFamily) map[string]BrowserDef {
- result := map[string]BrowserDef{}
- for _, bd := range DefinedBrowsers {
- if bd.Family == family {
- result[bd.Flavour] = bd
- }
+// Fonctions helper pour obtenir les chemins par défaut selon l'OS
+func getDefaultChromeDir() string {
+ switch runtime.GOOS {
+ case "windows":
+ return filepath.Join(os.Getenv("LOCALAPPDATA"), "Google", "Chrome", "User Data")
+ case "darwin":
+ return filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Google", "Chrome")
+ default: // linux
+ return filepath.Join(os.Getenv("HOME"), ".config", "google-chrome")
+ }
+}
+
+func getDefaultFirefoxDir() string {
+ switch runtime.GOOS {
+ case "windows":
+ return filepath.Join(os.Getenv("APPDATA"), "Mozilla", "Firefox")
+ case "darwin":
+ return filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Firefox", "Profiles")
+ default: // linux
+ return filepath.Join(os.Getenv("HOME"), ".mozilla", "firefox")
}
+}
- return result
+func getDefaultEdgeDir() string {
+ switch runtime.GOOS {
+ case "windows":
+ return filepath.Join(os.Getenv("LOCALAPPDATA"), "Microsoft", "Edge", "User Data")
+ case "darwin":
+ return filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "Microsoft Edge")
+ default: // linux
+ return filepath.Join(os.Getenv("HOME"), ".config", "microsoft-edge")
+ }
}
+
+func getDefaultQuteDir() string {
+ switch runtime.GOOS {
+ case "windows":
+ return filepath.Join(os.Getenv("APPDATA"), "qutebrowser")
+ case "darwin":
+ return filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "qutebrowser")
+ default: // linux
+ return filepath.Join(os.Getenv("HOME"), ".config", "qutebrowser")
+ }
+}
\ No newline at end of file
diff --git a/pkg/browsers/mozilla/profiles.go b/pkg/browsers/mozilla/profiles.go
index 3ccd69d..f581653 100644
--- a/pkg/browsers/mozilla/profiles.go
+++ b/pkg/browsers/mozilla/profiles.go
@@ -143,7 +143,7 @@ func (pm *MozProfileManager) ListFlavours() []BrowserDef {
// detect local flavours
for _, v := range browsers.Defined(browsers.Mozilla) {
if v.Detect() {
- result = append(result, v)
+ result = append(result, *v)
}
}
diff --git a/pkg/profiles/profiles.go b/pkg/profiles/profiles.go
index 7c526bd..72e5222 100644
--- a/pkg/profiles/profiles.go
+++ b/pkg/profiles/profiles.go
@@ -23,6 +23,7 @@
package profiles
import (
+ "fmt"
"path/filepath"
"github.com/blob42/gosuki/internal/utils"
@@ -129,8 +130,21 @@ func GetFlavour(pm ProfileManager, baseDir string) string {
}
func (p Profile) AbsolutePath() (string, error) {
- if !p.IsRelative {
- return utils.ExpandPath(p.Path)
+ log.Debugf("Profile debug: Name=%s, IsRelative=%t, Path=%s, BaseDir=%s",
+ p.Name, p.IsRelative, p.Path, p.BaseDir)
+
+ if p.IsRelative {
+ if p.BaseDir == "" {
+ return "", fmt.Errorf("profile baseDir is empty for relative profile %s", p.Name)
+ }
+ if p.Path == "" {
+ return "", fmt.Errorf("profile path is empty for relative profile %s", p.Name)
+ }
+ return utils.ExpandPath(p.BaseDir, p.Path)
+ }
+
+ if p.Path == "" {
+ return "", fmt.Errorf("profile path is empty for absolute profile %s", p.Name)
}
- return utils.ExpandPath(p.BaseDir, p.Path)
+ return utils.ExpandPath(p.Path)
}