first commit

This commit is contained in:
2026-04-09 01:42:37 +02:00
commit 75ae131e2a
7 changed files with 1055 additions and 0 deletions

150
cache.go Normal file
View File

@@ -0,0 +1,150 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// NoLyricsCache tracks songs that previously had no results.
type NoLyricsCache struct {
Tracks map[string]time.Time
mutex sync.RWMutex
}
func loadCache(cacheFile string) (*NoLyricsCache, error) {
cache := &NoLyricsCache{Tracks: make(map[string]time.Time)}
if _, err := os.Stat(cacheFile); os.IsNotExist(err) {
return cache, nil
}
data, err := os.ReadFile(cacheFile)
if err != nil {
return cache, err
}
if len(data) == 0 {
return cache, nil
}
err = json.Unmarshal(data, &cache.Tracks)
if cache.Tracks == nil {
cache.Tracks = make(map[string]time.Time)
}
return cache, err
}
func (c *NoLyricsCache) SaveCache(cacheFile string, ttl time.Duration) error {
cacheSnapshot := c.prunedCopy(ttl)
data, err := json.MarshalIndent(cacheSnapshot, "", " ")
if err != nil {
return err
}
return writeFileAtomically(cacheFile, data, 0644)
}
func (c *NoLyricsCache) IsTrackedAsNoLyrics(trackID string, ttl time.Duration) bool {
c.mutex.RLock()
timestamp, exists := c.Tracks[trackID]
c.mutex.RUnlock()
if !exists {
return false
}
if isCacheEntryExpired(timestamp, ttl) {
c.mutex.Lock()
if ts, ok := c.Tracks[trackID]; ok && isCacheEntryExpired(ts, ttl) {
delete(c.Tracks, trackID)
}
c.mutex.Unlock()
return false
}
return true
}
func (c *NoLyricsCache) AddNoLyrics(trackID string) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.Tracks[trackID] = time.Now()
}
func (c *NoLyricsCache) prunedCopy(ttl time.Duration) map[string]time.Time {
c.mutex.Lock()
defer c.mutex.Unlock()
for trackID, timestamp := range c.Tracks {
if isCacheEntryExpired(timestamp, ttl) {
delete(c.Tracks, trackID)
}
}
cacheSnapshot := make(map[string]time.Time, len(c.Tracks))
for trackID, timestamp := range c.Tracks {
cacheSnapshot[trackID] = timestamp
}
return cacheSnapshot
}
func createTrackIdentifier(metadata Metadata) string {
return fmt.Sprintf("%s-%s-%s",
strings.ToLower(strings.TrimSpace(metadata.ArtistName)),
strings.ToLower(strings.TrimSpace(metadata.AlbumName)),
strings.ToLower(strings.TrimSpace(metadata.TrackName)))
}
func isCacheEntryExpired(timestamp time.Time, ttl time.Duration) bool {
if ttl <= 0 {
return false
}
return time.Since(timestamp) > ttl
}
func writeFileAtomically(path string, data []byte, perm os.FileMode) error {
dir := filepath.Dir(path)
base := filepath.Base(path)
tempFile, err := os.CreateTemp(dir, base+".tmp-*")
if err != nil {
return err
}
tempPath := tempFile.Name()
cleanup := func() {
tempFile.Close()
os.Remove(tempPath)
}
if _, err := tempFile.Write(data); err != nil {
cleanup()
return err
}
if err := tempFile.Chmod(perm); err != nil {
cleanup()
return err
}
if err := tempFile.Close(); err != nil {
os.Remove(tempPath)
return err
}
if err := os.Rename(tempPath, path); err != nil {
os.Remove(tempPath)
return err
}
return nil
}