first commit
This commit is contained in:
150
cache.go
Normal file
150
cache.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user