1use 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 let home = env::var("HOME")?;
68 Ok(PathBuf::from(home).join(".cache").join("spotify-cli"))
69}
70
71#[cfg(test)]
72mod tests {
73 use super::default_root;
74 use std::sync::Mutex;
75
76 static ENV_LOCK: Mutex<()> = Mutex::new(());
77
78 fn set_env(key: &str, value: &str) {
79 unsafe {
80 std::env::set_var(key, value);
81 }
82 }
83
84 fn remove_env(key: &str) {
85 unsafe {
86 std::env::remove_var(key);
87 }
88 }
89
90 fn restore_env(key: &str, value: Option<String>) {
91 match value {
92 Some(value) => set_env(key, &value),
93 None => remove_env(key),
94 }
95 }
96
97 #[test]
98 fn default_root_uses_custom_dir() {
99 let _lock = ENV_LOCK.lock().unwrap();
100 let prev_cache = std::env::var("SPOTIFY_CLI_CACHE_DIR").ok();
101 let prev_xdg = std::env::var("XDG_CACHE_HOME").ok();
102 set_env("SPOTIFY_CLI_CACHE_DIR", "/tmp/custom-cache");
103 remove_env("XDG_CACHE_HOME");
104 let root = default_root().expect("root");
105 assert_eq!(root.to_string_lossy(), "/tmp/custom-cache");
106 restore_env("SPOTIFY_CLI_CACHE_DIR", prev_cache);
107 restore_env("XDG_CACHE_HOME", prev_xdg);
108 }
109
110 #[test]
111 fn default_root_uses_xdg_cache() {
112 let _lock = ENV_LOCK.lock().unwrap();
113 let prev_cache = std::env::var("SPOTIFY_CLI_CACHE_DIR").ok();
114 let prev_xdg = std::env::var("XDG_CACHE_HOME").ok();
115 remove_env("SPOTIFY_CLI_CACHE_DIR");
116 set_env("XDG_CACHE_HOME", "/tmp/xdg-cache");
117 let root = default_root().expect("root");
118 assert_eq!(root.to_string_lossy(), "/tmp/xdg-cache/spotify-cli");
119 restore_env("SPOTIFY_CLI_CACHE_DIR", prev_cache);
120 restore_env("XDG_CACHE_HOME", prev_xdg);
121 }
122
123 #[test]
124 fn default_root_uses_home_cache() {
125 let _lock = ENV_LOCK.lock().unwrap();
126 let prev_cache = std::env::var("SPOTIFY_CLI_CACHE_DIR").ok();
127 let prev_xdg = std::env::var("XDG_CACHE_HOME").ok();
128 let prev_home = std::env::var("HOME").ok();
129 remove_env("SPOTIFY_CLI_CACHE_DIR");
130 remove_env("XDG_CACHE_HOME");
131 set_env("HOME", "/tmp/home");
132 let root = default_root().expect("root");
133 assert_eq!(root.to_string_lossy(), "/tmp/home/.cache/spotify-cli");
134 restore_env("SPOTIFY_CLI_CACHE_DIR", prev_cache);
135 restore_env("XDG_CACHE_HOME", prev_xdg);
136 restore_env("HOME", prev_home);
137 }
138}