spotify_cli/cache/
playlists.rs

1use std::fs;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6use crate::domain::playlist::Playlist;
7use crate::error::Result;
8
9/// JSON-backed playlist cache store.
10#[derive(Debug, Clone)]
11pub struct PlaylistCache {
12    path: PathBuf,
13}
14
15impl PlaylistCache {
16    pub fn new(path: PathBuf) -> Self {
17        Self { path }
18    }
19
20    pub fn load(&self) -> Result<Option<CacheSnapshot<Playlist>>> {
21        if !self.path.exists() {
22            return Ok(None);
23        }
24        let contents = fs::read_to_string(&self.path)?;
25        let snapshot = serde_json::from_str(&contents)?;
26        Ok(Some(snapshot))
27    }
28
29    pub fn save(&self, snapshot: &CacheSnapshot<Playlist>) -> Result<()> {
30        let payload = serde_json::to_string_pretty(snapshot)?;
31        fs::write(&self.path, payload)?;
32        Ok(())
33    }
34}
35
36/// Snapshot wrapper for cached items.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct CacheSnapshot<T> {
39    pub updated_at: u64,
40    pub items: Vec<T>,
41}
42
43#[cfg(test)]
44mod tests {
45    use super::{CacheSnapshot, PlaylistCache};
46    use crate::domain::playlist::Playlist;
47    use std::fs;
48    use std::path::PathBuf;
49
50    fn temp_path(name: &str) -> PathBuf {
51        let mut path = std::env::temp_dir();
52        let stamp = std::time::SystemTime::now()
53            .duration_since(std::time::UNIX_EPOCH)
54            .unwrap()
55            .as_nanos();
56        path.push(format!("spotify-cli-{name}-{stamp}.json"));
57        path
58    }
59
60    #[test]
61    fn playlist_cache_round_trip() {
62        let path = temp_path("playlists");
63        let cache = PlaylistCache::new(path.clone());
64        let snapshot = CacheSnapshot {
65            updated_at: 7,
66            items: vec![Playlist {
67                id: "1".to_string(),
68                name: "MyRadar".to_string(),
69                owner: Some("Me".to_string()),
70                collaborative: false,
71                public: Some(false),
72            }],
73        };
74        cache.save(&snapshot).expect("save");
75        let loaded = cache.load().expect("load").expect("snapshot");
76        assert_eq!(loaded.updated_at, 7);
77        assert_eq!(loaded.items.len(), 1);
78        assert_eq!(loaded.items[0].name, "MyRadar");
79        let _ = fs::remove_file(path);
80    }
81}