use parking_lot::Mutex;
use super::client::{SubsonicAuth, SubsonicError};
static REFUSED: Mutex<Vec<SubsonicAuth>> = Mutex::new(Vec::new());
pub fn is_refusal(e: &SubsonicError) -> bool {
matches!(
e,
SubsonicError::Api {
code: 40 | 41 | 44,
..
}
)
}
pub fn observe<T>(auth: &SubsonicAuth, result: &Result<T, SubsonicError>) {
match result {
Ok(_) => set(auth, false),
Err(e) => observe_error(auth, e),
}
}
pub fn observe_error(auth: &SubsonicAuth, e: &SubsonicError) {
if is_refusal(e) {
set(auth, true);
} else if matches!(e, SubsonicError::Api { .. }) {
set(auth, false);
}
}
fn set(auth: &SubsonicAuth, refused: bool) {
let changed = {
let mut held = REFUSED.lock();
let was = held.contains(auth);
if refused && !was {
held.push(auth.clone());
} else if !refused && was {
held.retain(|a| a != auth);
}
was != refused
};
if changed {
if refused {
log::warn!(
"remote: {} refused the credential for {}",
auth.base_url,
auth.username
);
}
crate::remote::devices::touch();
}
}
pub fn refused(auth: &SubsonicAuth) -> bool {
REFUSED.lock().contains(auth)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::remote::client::Credential;
fn auth(key: &str) -> SubsonicAuth {
SubsonicAuth::with(
"http://refusal.test",
"mate",
Credential::ApiKey(key.into()),
)
}
fn api(code: i32) -> Result<(), SubsonicError> {
Err(SubsonicError::Api {
code,
message: String::new(),
})
}
#[test]
fn a_refusal_is_held_until_the_same_credential_works() {
let old = auth("held-old");
observe(&old, &api(44));
assert!(refused(&old));
observe(&old, &api(70));
assert!(!refused(&old));
observe(&old, &api(40));
assert!(refused(&old));
observe(&old, &Ok(()));
assert!(!refused(&old));
}
#[test]
fn a_new_credential_is_not_refused_and_an_outage_says_nothing() {
let old = auth("outage-old");
observe(&old, &api(41));
assert!(!refused(&auth("outage-new")));
observe(&old, &Err::<(), _>(SubsonicError::BadResponse));
assert!(refused(&old));
}
}