scrobble_scrubber/
session_manager.rs1use lastfm_edit::{
2 LastFmEditClientImpl, LastFmEditSession, Result as LastFmResult,
3 SessionManager as LastFmSessionManager,
4};
5
6pub struct SessionManager {
8 inner: LastFmSessionManager,
9 username: String,
10}
11
12impl SessionManager {
13 pub fn new(username: &str) -> Self {
15 let inner = LastFmSessionManager::new("scrobble-scrubber");
16 Self {
17 inner,
18 username: username.to_string(),
19 }
20 }
21
22 pub fn load_session(&self) -> Option<LastFmEditSession> {
24 match self.inner.load_session(&self.username) {
25 Ok(session) => {
26 log::info!("Loaded existing session for user: {}", session.username);
27 Some(session)
28 }
29 Err(e) => {
30 log::debug!("No existing session found: {e}");
31 None
32 }
33 }
34 }
35
36 pub fn save_session(&self, session: &LastFmEditSession) -> Result<(), std::io::Error> {
38 match self.inner.save_session(session) {
39 Ok(()) => {
40 log::info!("Session saved for user: {}", session.username);
41 Ok(())
42 }
43 Err(e) => {
44 log::warn!("Failed to save session: {e}");
45 Err(std::io::Error::other(format!(
46 "Failed to save session: {e}"
47 )))
48 }
49 }
50 }
51
52 pub async fn validate_session(&self, session: &LastFmEditSession) -> bool {
54 log::debug!("Validating existing session...");
55
56 match tokio::time::timeout(
57 std::time::Duration::from_secs(10),
58 self.check_session_validity(session),
59 )
60 .await
61 {
62 Ok(true) => {
63 log::info!("Session validation successful");
64 true
65 }
66 Ok(false) => {
67 log::warn!("Session validation failed - redirected to login");
68 false
69 }
70 Err(_) => {
71 log::warn!("Session validation timed out");
72 false
73 }
74 }
75 }
76
77 async fn check_session_validity(&self, session: &LastFmEditSession) -> bool {
79 use reqwest::Client;
80
81 let client = Client::new();
82 let test_url = "https://www.last.fm/settings/subscription/automatic-edits/tracks";
83
84 let cookie_header = session.cookies.join("; ");
86
87 let mut request_builder = client
88 .get(test_url)
89 .header("User-Agent", "Mozilla/5.0 (compatible; scrobble-scrubber)")
90 .header("Cookie", &cookie_header);
91
92 if let Some(csrf_token) = &session.csrf_token {
94 request_builder = request_builder.header("X-CSRFToken", csrf_token);
95 }
96
97 match request_builder.send().await {
98 Ok(response) => {
99 let final_url = response.url().to_string();
100 log::debug!("Session validation response URL: {final_url}");
101
102 !final_url.contains("/login")
105 }
106 Err(e) => {
107 log::warn!("Session validation request failed: {e}");
108 false
109 }
110 }
111 }
112
113 pub fn clear_session(&self) -> Result<(), std::io::Error> {
115 match self.inner.remove_session(&self.username) {
116 Ok(()) => {
117 log::info!("Cleared session for user: {}", self.username);
118 Ok(())
119 }
120 Err(e) => {
121 log::warn!("Failed to clear session: {e}");
122 Err(std::io::Error::other(format!(
123 "Failed to clear session: {e}"
124 )))
125 }
126 }
127 }
128
129 pub async fn try_restore_session(&self) -> Option<LastFmEditSession> {
131 let session = self.load_session()?;
132
133 log::info!("Validating loaded session...");
135
136 if self.validate_session(&session).await {
137 if let Err(e) = self.save_session(&session) {
139 log::warn!("Failed to update session: {e}");
140 }
141 Some(session)
142 } else {
143 log::warn!("Session validation failed, clearing stored session");
145 let _ = self.clear_session();
146 None
147 }
148 }
149
150 pub async fn create_and_save_session(
152 &self,
153 username: &str,
154 password: &str,
155 ) -> LastFmResult<LastFmEditSession> {
156 log::info!("Creating new Last.fm session...");
157
158 let http_client = http_client::native::NativeClient::new();
159 let client =
160 LastFmEditClientImpl::login_with_credentials(Box::new(http_client), username, password)
161 .await?;
162
163 let session = client.get_session();
164
165 if let Err(e) = self.save_session(&session) {
167 log::warn!("Failed to save session: {e}");
168 }
169
170 Ok(session)
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test_log::test]
179 fn should_create_session_manager_with_username() {
180 let manager = SessionManager::new("testuser");
181 assert_eq!(manager.username, "testuser");
182 }
183}