refactor: open track store explicitly and remove global state
This commit is contained in:
+13
-12
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user