Files
wehub-resource-sync ead81af521
Deploy to GitHub Pages / deploy (push) Failing after 0s
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

112 lines
2.5 KiB
Go

package ytmusic
import (
"encoding/json"
"os"
"path/filepath"
"time"
"github.com/bjarneo/cliamp/internal/appdir"
"github.com/bjarneo/cliamp/playlist"
)
// cacheTTL is how long cached playlist/track data is considered fresh.
// After this, data is refetched from the API on next access.
const cacheTTL = 24 * time.Hour
// ytCache stores playlists and tracks on disk for fast startup.
// Path: ~/.config/cliamp/ytmusic_cache.json
type ytCache struct {
Playlists []playlistEntry `json:"playlists,omitempty"`
PlaylistsAt time.Time `json:"playlists_at"`
Tracks map[string]cachedTrackList `json:"tracks,omitempty"`
}
type cachedTrackList struct {
Items []playlist.Track `json:"items"`
FetchedAt time.Time `json:"fetched_at"`
}
func ytCachePath() string {
dir, err := appdir.Dir()
if err != nil {
return ""
}
return filepath.Join(dir, "ytmusic_cache.json")
}
func newYTCache() *ytCache {
return &ytCache{Tracks: make(map[string]cachedTrackList)}
}
func loadYTCache() *ytCache {
data, err := os.ReadFile(ytCachePath())
if err != nil {
return newYTCache()
}
var c ytCache
if err := json.Unmarshal(data, &c); err != nil {
return newYTCache()
}
if c.Tracks == nil {
c.Tracks = make(map[string]cachedTrackList)
}
return &c
}
// snapshot returns a JSON-encoded copy of the cache. Call under the mutex
// so json.Marshal doesn't race with concurrent setPlaylists/setTracks calls.
func (c *ytCache) snapshot() []byte {
data, err := json.Marshal(c)
if err != nil {
return nil
}
return data
}
// save writes previously-snapshotted data to disk. Safe to call without a lock.
func saveSnapshot(data []byte) {
if data == nil {
return
}
path := ytCachePath()
if path == "" {
return
}
os.MkdirAll(filepath.Dir(path), 0o700)
os.WriteFile(path, data, 0o600)
}
func (c *ytCache) playlistsFresh() bool {
return len(c.Playlists) > 0 && time.Since(c.PlaylistsAt) < cacheTTL
}
func (c *ytCache) tracksFresh(playlistID string) ([]playlist.Track, bool) {
ct, ok := c.Tracks[playlistID]
if !ok || len(ct.Items) == 0 {
return nil, false
}
if time.Since(ct.FetchedAt) >= cacheTTL {
return nil, false
}
return ct.Items, true
}
func (c *ytCache) setPlaylists(pl []playlistEntry) {
c.Playlists = pl
c.PlaylistsAt = time.Now()
}
func (c *ytCache) setTracks(playlistID string, tracks []playlist.Track) {
c.Tracks[playlistID] = cachedTrackList{
Items: tracks,
FetchedAt: time.Now(),
}
}
func (c *ytCache) clear() {
c.Playlists = nil
c.PlaylistsAt = time.Time{}
c.Tracks = make(map[string]cachedTrackList)
}