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 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}