From 0ab25addd5d8bd2efb53a9d34b60ab47ba611685 Mon Sep 17 00:00:00 2001 From: Mathis Maquenne <124215603+mathismqn@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:34:27 +0200 Subject: [PATCH] refactor: open track store explicitly and remove global state --- cmd/download.go | 25 +++++++++++++------------ internal/config/config.go | 21 +-------------------- internal/config/migrate.go | 25 +++++++++++++++++++++++++ internal/downloader/client.go | 6 ++++-- internal/downloader/skip.go | 5 ++--- internal/store/download_info.go | 8 ++++---- internal/store/store.go | 18 ++++++++++++------ 7 files changed, 61 insertions(+), 47 deletions(-) create mode 100644 internal/config/migrate.go diff --git a/cmd/download.go b/cmd/download.go index 24873f6..76a7299 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -10,13 +10,10 @@ import ( "github.com/mathismqn/godeez/internal/config" "github.com/mathismqn/godeez/internal/deezer" "github.com/mathismqn/godeez/internal/downloader" + "github.com/mathismqn/godeez/internal/store" "github.com/spf13/cobra" ) -type contextKey string - -const appConfigKey contextKey = "appConfig" - var opts downloader.Options var downloadCmd = &cobra.Command{ @@ -48,19 +45,23 @@ func newDownloadCmd(kind deezer.Kind) *cobra.Command { Short: downloadShort(kind), Args: cobra.ExactArgs(1), PreRunE: func(cmd *cobra.Command, args []string) error { - appConfig, err := config.New() - if err != nil { - return err - } - cmd.SetContext(context.WithValue(cmd.Context(), appConfigKey, appConfig)) - opts.Quality = strings.ToLower(opts.Quality) return opts.Validate() }, RunE: func(cmd *cobra.Command, args []string) error { - appConfig := cmd.Context().Value(appConfigKey).(*config.Config) + cfg, err := config.Load() + if err != nil { + return err + } + config.MigrateLegacy(cfg.OutputDir) - err := downloader.New(appConfig, kind).Run(cmd.Context(), opts, args[0]) + st, err := store.Open(cfg.OutputDir) + if err != nil { + return err + } + defer st.Close() + + err = downloader.New(cfg, st, kind).Run(cmd.Context(), opts, args[0]) if errors.Is(err, context.Canceled) { return nil } diff --git a/internal/config/config.go b/internal/config/config.go index 8f06dd3..23d5898 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,7 +6,6 @@ import ( "path/filepath" "github.com/mathismqn/godeez/internal/fileutil" - "github.com/mathismqn/godeez/internal/store" ) type Config struct { @@ -14,7 +13,7 @@ type Config struct { OutputDir string } -func New() (*Config, error) { +func Load() (*Config, error) { arl := os.Getenv("DEEZER_ARL") homeDir, err := os.UserHomeDir() @@ -27,24 +26,6 @@ func New() (*Config, error) { return nil, fmt.Errorf("failed to create output directory: %w", err) } - // Migrate tracks.db from ~/.godeez/ to output dir - oldDB := filepath.Join(homeDir, ".godeez", "tracks.db") - newDB := filepath.Join(outputDir, ".tracks.db") - if _, err := os.Stat(oldDB); err == nil { - if _, err := os.Stat(newDB); os.IsNotExist(err) { - os.Rename(oldDB, newDB) - } - } - - // Clean up old config directory - oldDir := filepath.Join(homeDir, ".godeez") - os.Remove(filepath.Join(oldDir, "config.toml")) - os.Remove(oldDir) // fails silently if not empty - - if err := store.OpenDB(outputDir); err != nil { - return nil, err - } - return &Config{ ARLCookie: arl, OutputDir: outputDir, diff --git a/internal/config/migrate.go b/internal/config/migrate.go new file mode 100644 index 0000000..9bcfbb8 --- /dev/null +++ b/internal/config/migrate.go @@ -0,0 +1,25 @@ +package config + +import ( + "os" + "path/filepath" +) + +func MigrateLegacy(outputDir string) { + homeDir, err := os.UserHomeDir() + if err != nil { + return + } + + oldDB := filepath.Join(homeDir, ".godeez", "tracks.db") + newDB := filepath.Join(outputDir, ".tracks.db") + if _, err := os.Stat(oldDB); err == nil { + if _, err := os.Stat(newDB); os.IsNotExist(err) { + os.Rename(oldDB, newDB) + } + } + + oldDir := filepath.Join(homeDir, ".godeez") + os.Remove(filepath.Join(oldDir, "config.toml")) + os.Remove(oldDir) +} diff --git a/internal/downloader/client.go b/internal/downloader/client.go index 31df887..e5bb651 100644 --- a/internal/downloader/client.go +++ b/internal/downloader/client.go @@ -23,6 +23,7 @@ const chunkSize = 2048 type Client struct { appConfig *config.Config + store *store.Store kind deezer.Kind deezerClient *deezer.Client @@ -31,9 +32,10 @@ type Client struct { hashIndexErr error } -func New(appConfig *config.Config, kind deezer.Kind) *Client { +func New(appConfig *config.Config, st *store.Store, kind deezer.Kind) *Client { return &Client{ appConfig: appConfig, + store: st, kind: kind, } } @@ -249,7 +251,7 @@ func (c *Client) finalizeDownload(resource deezer.Resource, track *deezer.Track, Downloaded: time.Now(), } - if err := info.Save(); err != nil { + if err := c.store.PutDownloadInfo(info); err != nil { warnings = append(warnings, fmt.Sprintf("failed to save download info: %v", err)) } diff --git a/internal/downloader/skip.go b/internal/downloader/skip.go index 802cc04..1fbc19a 100644 --- a/internal/downloader/skip.go +++ b/internal/downloader/skip.go @@ -4,11 +4,10 @@ import ( "context" "github.com/mathismqn/godeez/internal/fileutil" - "github.com/mathismqn/godeez/internal/store" ) func (c *Client) shouldSkipDownload(ctx context.Context, trackID, mediaFormat string) (string, bool) { - existing, err := store.GetDownloadInfo(trackID) + existing, err := c.store.DownloadInfo(trackID) if err != nil || existing.Quality != mediaFormat { return "", false } @@ -31,7 +30,7 @@ func (c *Client) shouldSkipDownload(ctx context.Context, trackID, mediaFormat st } existing.Path = foundPath - _ = existing.Save() + _ = c.store.PutDownloadInfo(existing) return foundPath, true } diff --git a/internal/store/download_info.go b/internal/store/download_info.go index 5758323..a512969 100644 --- a/internal/store/download_info.go +++ b/internal/store/download_info.go @@ -18,10 +18,10 @@ type DownloadInfo struct { var trackBucket = []byte("tracks") -func GetDownloadInfo(trackID string) (*DownloadInfo, error) { +func (s *Store) DownloadInfo(trackID string) (*DownloadInfo, error) { var info DownloadInfo - if err := db.View(func(tx *bbolt.Tx) error { + if err := s.db.View(func(tx *bbolt.Tx) error { b := tx.Bucket(trackBucket) if b == nil { return fmt.Errorf("bucket not found") @@ -39,8 +39,8 @@ func GetDownloadInfo(trackID string) (*DownloadInfo, error) { return &info, nil } -func (d *DownloadInfo) Save() error { - return db.Update(func(tx *bbolt.Tx) error { +func (s *Store) PutDownloadInfo(d *DownloadInfo) error { + return s.db.Update(func(tx *bbolt.Tx) error { b, err := tx.CreateBucketIfNotExists(trackBucket) if err != nil { return fmt.Errorf("failed to create bucket: %w", err) diff --git a/internal/store/store.go b/internal/store/store.go index b0f7fd3..3f13208 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -7,13 +7,19 @@ import ( bolt "go.etcd.io/bbolt" ) -var db *bolt.DB +type Store struct { + db *bolt.DB +} -func OpenDB(cfgDir string) error { - var err error - db, err = bolt.Open(path.Join(cfgDir, ".tracks.db"), 0600, nil) +func Open(dir string) (*Store, error) { + db, err := bolt.Open(path.Join(dir, ".tracks.db"), 0600, nil) if err != nil { - return fmt.Errorf("failed to open database: %w", err) + return nil, fmt.Errorf("failed to open database: %w", err) } - return nil + + return &Store{db: db}, nil +} + +func (s *Store) Close() error { + return s.db.Close() }