scrobble_scrubber/
recent_user_manager.rs1use serde::{Deserialize, Serialize};
2use std::fs;
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct RecentUserData {
8 pub username: String,
10 pub last_used: u64,
12 pub version: u32,
14}
15
16impl RecentUserData {
17 pub fn new(username: String) -> Self {
19 Self {
20 username,
21 last_used: std::time::SystemTime::now()
22 .duration_since(std::time::UNIX_EPOCH)
23 .unwrap_or_default()
24 .as_secs(),
25 version: 1,
26 }
27 }
28
29 pub fn update_last_used(&mut self) {
31 self.last_used = std::time::SystemTime::now()
32 .duration_since(std::time::UNIX_EPOCH)
33 .unwrap_or_default()
34 .as_secs();
35 }
36}
37
38pub struct RecentUserManager {
40 recent_user_file_path: PathBuf,
41}
42
43impl RecentUserManager {
44 pub fn new() -> Self {
46 let recent_user_file_path = Self::get_recent_user_file_path();
47 Self {
48 recent_user_file_path,
49 }
50 }
51
52 fn get_recent_user_file_path() -> PathBuf {
54 if let Some(data_dir) = dirs::data_dir() {
55 data_dir.join("scrobble-scrubber").join("recent_user.json")
56 } else {
57 PathBuf::from("recent_user.json")
59 }
60 }
61
62 pub fn load_recent_user(&self) -> Option<RecentUserData> {
64 if !self.recent_user_file_path.exists() {
65 log::debug!(
66 "No recent user file found at: {}",
67 self.recent_user_file_path.display()
68 );
69 return None;
70 }
71
72 match fs::read_to_string(&self.recent_user_file_path) {
73 Ok(content) => {
74 match serde_json::from_str::<RecentUserData>(&content) {
75 Ok(recent_user) => {
76 log::info!("Loaded recent user: {}", recent_user.username);
77 Some(recent_user)
78 }
79 Err(e) => {
80 log::warn!("Failed to parse recent user file: {e}. Starting fresh.");
81 let _ = fs::remove_file(&self.recent_user_file_path);
83 None
84 }
85 }
86 }
87 Err(e) => {
88 log::warn!("Failed to read recent user file: {e}. Starting fresh.");
89 None
90 }
91 }
92 }
93
94 pub fn save_recent_user(&self, username: &str) -> Result<(), std::io::Error> {
96 let recent_user = RecentUserData::new(username.to_string());
97
98 if let Some(parent) = self.recent_user_file_path.parent() {
100 fs::create_dir_all(parent)?;
101 }
102
103 let content = serde_json::to_string_pretty(&recent_user)
104 .map_err(|e| std::io::Error::other(format!("Failed to serialize recent user: {e}")))?;
105
106 fs::write(&self.recent_user_file_path, content)?;
107 log::info!("Recent user saved: {username}");
108 Ok(())
109 }
110
111 pub fn get_recent_username(&self) -> Option<String> {
113 self.load_recent_user().map(|data| data.username)
114 }
115
116 pub fn update_recent_user(&self, username: &str) -> Result<(), std::io::Error> {
118 self.save_recent_user(username)
119 }
120
121 pub fn clear_recent_user(&self) -> Result<(), std::io::Error> {
123 if self.recent_user_file_path.exists() {
124 fs::remove_file(&self.recent_user_file_path)?;
125 log::info!("Cleared recent user file");
126 }
127 Ok(())
128 }
129}
130
131impl Default for RecentUserManager {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test_log::test]
142 fn should_create_recent_user_data_with_current_timestamp() {
143 let user_data = RecentUserData::new("testuser".to_string());
144 assert_eq!(user_data.username, "testuser");
145 assert_eq!(user_data.version, 1);
146
147 let now = std::time::SystemTime::now()
149 .duration_since(std::time::UNIX_EPOCH)
150 .unwrap_or_default()
151 .as_secs();
152
153 assert!(user_data.last_used <= now && user_data.last_used >= (now - 10));
155 }
156
157 #[test_log::test]
158 fn should_update_last_used_timestamp() {
159 let mut user_data = RecentUserData::new("testuser".to_string());
160 let original_time = user_data.last_used;
161
162 std::thread::sleep(std::time::Duration::from_secs(1));
164
165 user_data.update_last_used();
166 assert!(user_data.last_used > original_time);
167 }
168}