refactor: open track store explicitly and remove global state

This commit is contained in:
Mathis Maquenne
2026-08-05 11:34:27 +02:00
parent 714d567603
commit 0ab25addd5
7 changed files with 61 additions and 47 deletions
+13 -12
View File
@@ -10,13 +10,10 @@ import (
"github.com/mathismqn/godeez/internal/config" "github.com/mathismqn/godeez/internal/config"
"github.com/mathismqn/godeez/internal/deezer" "github.com/mathismqn/godeez/internal/deezer"
"github.com/mathismqn/godeez/internal/downloader" "github.com/mathismqn/godeez/internal/downloader"
"github.com/mathismqn/godeez/internal/store"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
type contextKey string
const appConfigKey contextKey = "appConfig"
var opts downloader.Options var opts downloader.Options
var downloadCmd = &cobra.Command{ var downloadCmd = &cobra.Command{
@@ -48,19 +45,23 @@ func newDownloadCmd(kind deezer.Kind) *cobra.Command {
Short: downloadShort(kind), Short: downloadShort(kind),
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error { 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) opts.Quality = strings.ToLower(opts.Quality)
return opts.Validate() return opts.Validate()
}, },
RunE: func(cmd *cobra.Command, args []string) error { 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) { if errors.Is(err, context.Canceled) {
return nil return nil
} }
+1 -20
View File
@@ -6,7 +6,6 @@ import (
"path/filepath" "path/filepath"
"github.com/mathismqn/godeez/internal/fileutil" "github.com/mathismqn/godeez/internal/fileutil"
"github.com/mathismqn/godeez/internal/store"
) )
type Config struct { type Config struct {
@@ -14,7 +13,7 @@ type Config struct {
OutputDir string OutputDir string
} }
func New() (*Config, error) { func Load() (*Config, error) {
arl := os.Getenv("DEEZER_ARL") arl := os.Getenv("DEEZER_ARL")
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
@@ -27,24 +26,6 @@ func New() (*Config, error) {
return nil, fmt.Errorf("failed to create output directory: %w", err) 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{ return &Config{
ARLCookie: arl, ARLCookie: arl,
OutputDir: outputDir, OutputDir: outputDir,
+25
View File
@@ -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)
}
+4 -2
View File
@@ -23,6 +23,7 @@ const chunkSize = 2048
type Client struct { type Client struct {
appConfig *config.Config appConfig *config.Config
store *store.Store
kind deezer.Kind kind deezer.Kind
deezerClient *deezer.Client deezerClient *deezer.Client
@@ -31,9 +32,10 @@ type Client struct {
hashIndexErr error 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{ return &Client{
appConfig: appConfig, appConfig: appConfig,
store: st,
kind: kind, kind: kind,
} }
} }
@@ -249,7 +251,7 @@ func (c *Client) finalizeDownload(resource deezer.Resource, track *deezer.Track,
Downloaded: time.Now(), 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)) warnings = append(warnings, fmt.Sprintf("failed to save download info: %v", err))
} }
+2 -3
View File
@@ -4,11 +4,10 @@ import (
"context" "context"
"github.com/mathismqn/godeez/internal/fileutil" "github.com/mathismqn/godeez/internal/fileutil"
"github.com/mathismqn/godeez/internal/store"
) )
func (c *Client) shouldSkipDownload(ctx context.Context, trackID, mediaFormat string) (string, bool) { 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 { if err != nil || existing.Quality != mediaFormat {
return "", false return "", false
} }
@@ -31,7 +30,7 @@ func (c *Client) shouldSkipDownload(ctx context.Context, trackID, mediaFormat st
} }
existing.Path = foundPath existing.Path = foundPath
_ = existing.Save() _ = c.store.PutDownloadInfo(existing)
return foundPath, true return foundPath, true
} }
+4 -4
View File
@@ -18,10 +18,10 @@ type DownloadInfo struct {
var trackBucket = []byte("tracks") var trackBucket = []byte("tracks")
func GetDownloadInfo(trackID string) (*DownloadInfo, error) { func (s *Store) DownloadInfo(trackID string) (*DownloadInfo, error) {
var info DownloadInfo 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) b := tx.Bucket(trackBucket)
if b == nil { if b == nil {
return fmt.Errorf("bucket not found") return fmt.Errorf("bucket not found")
@@ -39,8 +39,8 @@ func GetDownloadInfo(trackID string) (*DownloadInfo, error) {
return &info, nil return &info, nil
} }
func (d *DownloadInfo) Save() error { func (s *Store) PutDownloadInfo(d *DownloadInfo) error {
return db.Update(func(tx *bbolt.Tx) error { return s.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists(trackBucket) b, err := tx.CreateBucketIfNotExists(trackBucket)
if err != nil { if err != nil {
return fmt.Errorf("failed to create bucket: %w", err) return fmt.Errorf("failed to create bucket: %w", err)
+12 -6
View File
@@ -7,13 +7,19 @@ import (
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
var db *bolt.DB type Store struct {
db *bolt.DB
}
func OpenDB(cfgDir string) error { func Open(dir string) (*Store, error) {
var err error db, err := bolt.Open(path.Join(dir, ".tracks.db"), 0600, nil)
db, err = bolt.Open(path.Join(cfgDir, ".tracks.db"), 0600, nil)
if err != 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()
} }