.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 {
|
if err := backend.InitHistoryDB("SpotiFLAC"); err != nil {
|
||||||
fmt.Printf("Failed to init history DB: %v\n", err)
|
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) {
|
func (a *App) shutdown(ctx context.Context) {
|
||||||
backend.CloseHistoryDB()
|
backend.CloseHistoryDB()
|
||||||
|
backend.CloseISRCCacheDB()
|
||||||
}
|
}
|
||||||
|
|
||||||
type SpotifyMetadataRequest struct {
|
type SpotifyMetadataRequest struct {
|
||||||
@@ -629,12 +633,8 @@ func (a *App) OpenFolder(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) OpenConfigFolder() error {
|
func (a *App) OpenConfigFolder() error {
|
||||||
homeDir, err := os.UserHomeDir()
|
configDir, err := backend.EnsureAppDir()
|
||||||
if err != nil {
|
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 fmt.Errorf("failed to create config directory: %v", err)
|
||||||
}
|
}
|
||||||
return backend.OpenFolderInExplorer(configDir)
|
return backend.OpenFolderInExplorer(configDir)
|
||||||
|
|||||||
+18
-1
@@ -58,7 +58,7 @@ func ValidateExecutable(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFFmpegDir() (string, error) {
|
func GetAppDir() (string, error) {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get home directory: %w", err)
|
return "", fmt.Errorf("failed to get home directory: %w", err)
|
||||||
@@ -66,6 +66,23 @@ func GetFFmpegDir() (string, error) {
|
|||||||
return filepath.Join(homeDir, ".spotiflac"), nil
|
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) {
|
func GetFFmpegPath() (string, error) {
|
||||||
ffmpegDir, err := GetFFmpegDir()
|
ffmpegDir, err := GetFFmpegDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+1
-5
@@ -3,7 +3,6 @@ package backend
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
@@ -35,13 +34,10 @@ const (
|
|||||||
|
|
||||||
func InitHistoryDB(appName string) error {
|
func InitHistoryDB(appName string) error {
|
||||||
|
|
||||||
appDir, err := GetFFmpegDir()
|
appDir, err := EnsureAppDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(appDir); os.IsNotExist(err) {
|
|
||||||
os.MkdirAll(appDir, 0755)
|
|
||||||
}
|
|
||||||
dbPath := filepath.Join(appDir, "history.db")
|
dbPath := filepath.Join(appDir, "history.db")
|
||||||
|
|
||||||
db, err := bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
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
|
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)
|
payload, err := fetchSpotifyTrackRawData(s.client, normalizedTrackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -65,6 +73,9 @@ func (s *SongLinkClient) lookupSpotifyISRC(spotifyTrackID string) (string, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Found ISRC via Spotify metadata: %s\n", isrc)
|
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
|
return isrc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,17 +169,12 @@ func saveSpotifyCachedToken(token *spotifyAnonymousToken) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func spotifyTokenCachePath() (string, error) {
|
func spotifyTokenCachePath() (string, error) {
|
||||||
cacheDir, err := os.UserCacheDir()
|
appDir, err := EnsureAppDir()
|
||||||
if err == nil && cacheDir != "" {
|
|
||||||
return filepath.Join(cacheDir, "SpotiFLAC", spotifyTokenCacheFile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
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 {
|
func spotifyTokenIsValid(token *spotifyAnonymousToken) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user