spotify_cli/cache/
pins.rs1use std::fs;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6use crate::domain::pin::PinnedPlaylist;
7use crate::error::Result;
8
9#[derive(Debug, Clone)]
11pub struct PinStore {
12 path: PathBuf,
13}
14
15impl PinStore {
16 pub fn new(path: PathBuf) -> Self {
17 Self { path }
18 }
19
20 pub fn load(&self) -> Result<Pins> {
21 if !self.path.exists() {
22 return Ok(Pins::default());
23 }
24 let contents = fs::read_to_string(&self.path)?;
25 let pins = serde_json::from_str(&contents)?;
26 Ok(pins)
27 }
28
29 pub fn save(&self, pins: &Pins) -> Result<()> {
30 let payload = serde_json::to_string_pretty(pins)?;
31 fs::write(&self.path, payload)?;
32 Ok(())
33 }
34
35 pub fn add(&self, name: String, url: String) -> Result<()> {
36 let mut pins = self.load()?;
37 let lower = name.to_lowercase();
38 if let Some(existing) = pins
39 .items
40 .iter_mut()
41 .find(|item| item.name.to_lowercase() == lower)
42 {
43 existing.url = url;
44 existing.name = name;
45 } else {
46 pins.items.push(PinnedPlaylist { name, url });
47 }
48 self.save(&pins)
49 }
50
51 pub fn remove(&self, name: &str) -> Result<bool> {
52 let mut pins = self.load()?;
53 let before = pins.items.len();
54 let lower = name.to_lowercase();
55 pins.items.retain(|item| item.name.to_lowercase() != lower);
56 let removed = pins.items.len() != before;
57 if removed {
58 self.save(&pins)?;
59 }
60 Ok(removed)
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, Default)]
66pub struct Pins {
67 pub items: Vec<PinnedPlaylist>,
68}
69
70#[cfg(test)]
71mod tests {
72 use super::PinStore;
73 use std::fs;
74 use std::path::PathBuf;
75
76 fn temp_path(name: &str) -> PathBuf {
77 let mut path = std::env::temp_dir();
78 let stamp = std::time::SystemTime::now()
79 .duration_since(std::time::UNIX_EPOCH)
80 .unwrap()
81 .as_nanos();
82 path.push(format!("spotify-cli-{name}-{stamp}.json"));
83 path
84 }
85
86 #[test]
87 fn pin_store_add_and_remove() {
88 let path = temp_path("pins");
89 let store = PinStore::new(path.clone());
90
91 store
92 .add(
93 "Release Radar".to_string(),
94 "https://example.com".to_string(),
95 )
96 .unwrap();
97 let loaded = store.load().unwrap();
98 assert_eq!(loaded.items.len(), 1);
99
100 let removed = store.remove("Release Radar").unwrap();
101 assert!(removed);
102 let loaded = store.load().unwrap();
103 assert!(loaded.items.is_empty());
104
105 let _ = fs::remove_file(path);
106 }
107}