.isrc db
This commit is contained in:
@@ -46,10 +46,14 @@ func (a *App) startup(ctx context.Context) {
|
||||
if err := backend.InitHistoryDB("SpotiFLAC"); err != nil {
|
||||
fmt.Printf("Failed to init history DB: %v\n", err)
|
||||
}
|
||||
if err := backend.InitISRCCacheDB(); err != nil {
|
||||
fmt.Printf("Failed to init ISRC cache DB: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) shutdown(ctx context.Context) {
|
||||
backend.CloseHistoryDB()
|
||||
backend.CloseISRCCacheDB()
|
||||
}
|
||||
|
||||
type SpotifyMetadataRequest struct {
|
||||
@@ -629,12 +633,8 @@ func (a *App) OpenFolder(path string) error {
|
||||
}
|
||||
|
||||
func (a *App) OpenConfigFolder() error {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
configDir, err := backend.EnsureAppDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get home directory: %v", err)
|
||||
}
|
||||
configDir := filepath.Join(homeDir, ".spotiflac")
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create config directory: %v", err)
|
||||
}
|
||||
return backend.OpenFolderInExplorer(configDir)
|
||||
|
||||
+18
-1
@@ -58,7 +58,7 @@ func ValidateExecutable(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetFFmpegDir() (string, error) {
|
||||
func GetAppDir() (string, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get home directory: %w", err)
|
||||
@@ -66,6 +66,23 @@ func GetFFmpegDir() (string, error) {
|
||||
return filepath.Join(homeDir, ".spotiflac"), nil
|
||||
}
|
||||
|
||||
func EnsureAppDir() (string, error) {
|
||||
appDir, err := GetAppDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(appDir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("failed to create app directory: %w", err)
|
||||
}
|
||||
|
||||
return appDir, nil
|
||||
}
|
||||
|
||||
func GetFFmpegDir() (string, error) {
|
||||
return EnsureAppDir()
|
||||
}
|
||||
|
||||
func GetFFmpegPath() (string, error) {
|
||||
ffmpegDir, err := GetFFmpegDir()
|
||||
if err != nil {
|
||||
|
||||
+1
-5
@@ -3,7 +3,6 @@ package backend
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
@@ -35,13 +34,10 @@ const (
|
||||
|
||||
func InitHistoryDB(appName string) error {
|
||||
|
||||
appDir, err := GetFFmpegDir()
|
||||
appDir, err := EnsureAppDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(appDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(appDir, 0755)
|
||||
}
|
||||
dbPath := filepath.Join(appDir, "history.db")
|
||||
|
||||
db, err := bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
const (
|
||||
isrcCacheDBFile = "isrc_cache.db"
|
||||
isrcCacheBucket = "SpotifyTrackISRC"
|
||||
)
|
||||
|
||||
type isrcCacheEntry struct {
|
||||
TrackID string `json:"track_id"`
|
||||
ISRC string `json:"isrc"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
var (
|
||||
isrcCacheDB *bolt.DB
|
||||
isrcCacheDBMu sync.Mutex
|
||||
)
|
||||
|
||||
func InitISRCCacheDB() error {
|
||||
isrcCacheDBMu.Lock()
|
||||
defer isrcCacheDBMu.Unlock()
|
||||
|
||||
if isrcCacheDB != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
appDir, err := EnsureAppDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(appDir, isrcCacheDBFile)
|
||||
db, err := bolt.Open(dbPath, 0o600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(isrcCacheBucket))
|
||||
return err
|
||||
}); err != nil {
|
||||
db.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
isrcCacheDB = db
|
||||
return nil
|
||||
}
|
||||
|
||||
func CloseISRCCacheDB() {
|
||||
isrcCacheDBMu.Lock()
|
||||
defer isrcCacheDBMu.Unlock()
|
||||
|
||||
if isrcCacheDB != nil {
|
||||
_ = isrcCacheDB.Close()
|
||||
isrcCacheDB = nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetCachedISRC(trackID string) (string, error) {
|
||||
normalizedTrackID := strings.TrimSpace(trackID)
|
||||
if normalizedTrackID == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if err := InitISRCCacheDB(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var cachedISRC string
|
||||
err := isrcCacheDB.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(isrcCacheBucket))
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := bucket.Get([]byte(normalizedTrackID))
|
||||
if len(value) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var entry isrcCacheEntry
|
||||
if err := json.Unmarshal(value, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cachedISRC = strings.ToUpper(strings.TrimSpace(entry.ISRC))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cachedISRC, nil
|
||||
}
|
||||
|
||||
func PutCachedISRC(trackID string, isrc string) error {
|
||||
normalizedTrackID := strings.TrimSpace(trackID)
|
||||
normalizedISRC := strings.ToUpper(strings.TrimSpace(isrc))
|
||||
if normalizedTrackID == "" || normalizedISRC == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := InitISRCCacheDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entry := isrcCacheEntry{
|
||||
TrackID: normalizedTrackID,
|
||||
ISRC: normalizedISRC,
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode ISRC cache entry: %w", err)
|
||||
}
|
||||
|
||||
return isrcCacheDB.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte(isrcCacheBucket))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put([]byte(normalizedTrackID), payload)
|
||||
})
|
||||
}
|
||||
+14
-8
@@ -54,6 +54,14 @@ func (s *SongLinkClient) lookupSpotifyISRC(spotifyTrackID string) (string, error
|
||||
return "", err
|
||||
}
|
||||
|
||||
cachedISRC, err := GetCachedISRC(normalizedTrackID)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: failed to read ISRC cache: %v\n", err)
|
||||
} else if cachedISRC != "" {
|
||||
fmt.Printf("Found ISRC in cache: %s\n", cachedISRC)
|
||||
return cachedISRC, nil
|
||||
}
|
||||
|
||||
payload, err := fetchSpotifyTrackRawData(s.client, normalizedTrackID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -65,6 +73,9 @@ func (s *SongLinkClient) lookupSpotifyISRC(spotifyTrackID string) (string, error
|
||||
}
|
||||
|
||||
fmt.Printf("Found ISRC via Spotify metadata: %s\n", isrc)
|
||||
if err := PutCachedISRC(normalizedTrackID, isrc); err != nil {
|
||||
fmt.Printf("Warning: failed to write ISRC cache: %v\n", err)
|
||||
}
|
||||
return isrc, nil
|
||||
}
|
||||
|
||||
@@ -158,17 +169,12 @@ func saveSpotifyCachedToken(token *spotifyAnonymousToken) error {
|
||||
}
|
||||
|
||||
func spotifyTokenCachePath() (string, error) {
|
||||
cacheDir, err := os.UserCacheDir()
|
||||
if err == nil && cacheDir != "" {
|
||||
return filepath.Join(cacheDir, "SpotiFLAC", spotifyTokenCacheFile), nil
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
appDir, err := EnsureAppDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to determine working directory: %w", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(wd, spotifyTokenCacheFile), nil
|
||||
return filepath.Join(appDir, spotifyTokenCacheFile), nil
|
||||
}
|
||||
|
||||
func spotifyTokenIsValid(token *spotifyAnonymousToken) bool {
|
||||
|
||||
Reference in New Issue
Block a user