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) }