spotify_cli/cache/
mod.rs

1//! Cache storage for devices, playlists, pins, search results, and metadata.
2use std::env;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6use crate::cache::devices::DeviceCache;
7use crate::cache::metadata::MetadataStore;
8use crate::cache::playlists::PlaylistCache;
9use crate::error::Result;
10
11pub mod devices;
12pub mod metadata;
13pub mod pins;
14pub mod playlists;
15pub mod search;
16
17#[derive(Debug, Clone)]
18pub struct Cache {
19    root: PathBuf,
20}
21
22impl Cache {
23    pub fn new() -> Result<Self> {
24        let root = default_root()?;
25        Ok(Self { root })
26    }
27
28    pub fn ensure_dirs(&self) -> Result<()> {
29        fs::create_dir_all(self.root())?;
30        Ok(())
31    }
32
33    pub fn root(&self) -> &Path {
34        &self.root
35    }
36
37    pub fn metadata_store(&self) -> MetadataStore {
38        MetadataStore::new(self.root.join("metadata.json"))
39    }
40
41    pub fn device_cache(&self) -> DeviceCache {
42        DeviceCache::new(self.root.join("devices.json"))
43    }
44
45    pub fn playlist_cache(&self) -> PlaylistCache {
46        PlaylistCache::new(self.root.join("playlists.json"))
47    }
48
49    pub fn pin_store(&self) -> pins::PinStore {
50        pins::PinStore::new(self.root.join("pins.json"))
51    }
52
53    pub fn search_store(&self) -> search::SearchStore {
54        search::SearchStore::new(self.root.join("search.json"))
55    }
56}
57
58fn default_root() -> Result<PathBuf> {
59    if let Ok(custom) = env::var("SPOTIFY_CLI_CACHE_DIR") {
60        return Ok(PathBuf::from(custom));
61    }
62
63    if let Ok(xdg_cache) = env::var("XDG_CACHE_HOME") {
64        return Ok(PathBuf::from(xdg_cache).join("spotify-cli"));
65    }
66
67    if cfg!(windows) {
68        if let Ok(local) = env::var("LOCALAPPDATA") {
69            return Ok(PathBuf::from(local).join("spotify-cli"));
70        }
71
72        if let Ok(roaming) = env::var("APPDATA") {
73            return Ok(PathBuf::from(roaming).join("spotify-cli"));
74        }
75
76        if let Ok(profile) = env::var("USERPROFILE") {
77            return Ok(PathBuf::from(profile).join(".cache").join("spotify-cli"));
78        }
79    }
80
81    let home = env::var("HOME")?;
82    Ok(PathBuf::from(home).join(".cache").join("spotify-cli"))
83}
84
85#[cfg(test)]
86mod tests {
87    use super::default_root;
88    use std::sync::Mutex;
89
90    static ENV_LOCK: Mutex<()> = Mutex::new(());
91
92    fn set_env(key: &str, value: &str) {
93        unsafe {
94            std::env::set_var(key, value);
95        }
96    }
97
98    fn remove_env(key: &str) {
99        unsafe {
100            std::env::remove_var(key);
101        }
102    }
103
104    fn restore_env(key: &str, value: Option<String>) {
105        match value {
106            Some(value) => set_env(key, &value),
107            None => remove_env(key),
108        }
109    }
110
111    #[test]
112    fn default_root_uses_custom_dir() {
113        let _lock = ENV_LOCK.lock().unwrap();
114        let prev_cache = std::env::var("SPOTIFY_CLI_CACHE_DIR").ok();
115        let prev_xdg = std::env::var("XDG_CACHE_HOME").ok();
116        set_env("SPOTIFY_CLI_CACHE_DIR", "/tmp/custom-cache");
117        remove_env("XDG_CACHE_HOME");
118        let root = default_root().expect("root");
119        assert_eq!(root.to_string_lossy(), "/tmp/custom-cache");
120        restore_env("SPOTIFY_CLI_CACHE_DIR", prev_cache);
121        restore_env("XDG_CACHE_HOME", prev_xdg);
122    }
123
124    #[test]
125    #[cfg(not(windows))]
126    fn default_root_uses_xdg_cache() {
127        let _lock = ENV_LOCK.lock().unwrap();
128        let prev_cache = std::env::var("SPOTIFY_CLI_CACHE_DIR").ok();
129        let prev_xdg = std::env::var("XDG_CACHE_HOME").ok();
130        remove_env("SPOTIFY_CLI_CACHE_DIR");
131        set_env("XDG_CACHE_HOME", "/tmp/xdg-cache");
132        let root = default_root().expect("root");
133        assert_eq!(root.to_string_lossy(), "/tmp/xdg-cache/spotify-cli");
134        restore_env("SPOTIFY_CLI_CACHE_DIR", prev_cache);
135        restore_env("XDG_CACHE_HOME", prev_xdg);
136    }
137
138    #[test]
139    #[cfg(not(windows))]
140    fn default_root_uses_home_cache() {
141        let _lock = ENV_LOCK.lock().unwrap();
142        let prev_cache = std::env::var("SPOTIFY_CLI_CACHE_DIR").ok();
143        let prev_xdg = std::env::var("XDG_CACHE_HOME").ok();
144        let prev_home = std::env::var("HOME").ok();
145        remove_env("SPOTIFY_CLI_CACHE_DIR");
146        remove_env("XDG_CACHE_HOME");
147        set_env("HOME", "/tmp/home");
148        let root = default_root().expect("root");
149        assert_eq!(root.to_string_lossy(), "/tmp/home/.cache/spotify-cli");
150        restore_env("SPOTIFY_CLI_CACHE_DIR", prev_cache);
151        restore_env("XDG_CACHE_HOME", prev_xdg);
152        restore_env("HOME", prev_home);
153    }
154}