use std::{
collections::BTreeMap,
sync::{Arc, LazyLock, RwLock},
};
use switchy_database::profiles::LibraryDatabase;
use crate::LibraryMusicApi;
pub static PROFILES: LazyLock<LibraryMusicApiProfiles> =
LazyLock::new(LibraryMusicApiProfiles::default);
#[allow(clippy::module_name_repetitions)]
#[derive(Default)]
pub struct LibraryMusicApiProfiles {
profiles: Arc<RwLock<BTreeMap<String, LibraryMusicApi>>>,
}
impl LibraryMusicApiProfiles {
pub fn add(&self, profile: String, db: LibraryDatabase) {
moosicbox_profiles::PROFILES.add(profile.clone());
self.profiles
.write()
.unwrap()
.insert(profile, LibraryMusicApi { db });
}
pub fn remove(&self, profile: &str) {
self.profiles.write().unwrap().remove(profile);
}
#[must_use]
pub fn add_fetch(&self, profile: &str, db: LibraryDatabase) -> LibraryMusicApi {
self.add(profile.to_owned(), db);
self.get(profile).unwrap()
}
#[must_use]
pub fn get(&self, profile: &str) -> Option<LibraryMusicApi> {
self.profiles
.read()
.unwrap()
.iter()
.find_map(|(p, db)| if p == profile { Some(db.clone()) } else { None })
}
#[must_use]
pub fn names(&self) -> Vec<String> {
self.profiles.read().unwrap().keys().cloned().collect()
}
}
#[cfg(feature = "api")]
pub mod api {
use actix_web::{FromRequest, HttpRequest, dev::Payload, error::ErrorBadRequest};
use futures::future::{Ready, err, ok};
use moosicbox_profiles::api::ProfileName;
use super::{LibraryMusicApi, PROFILES};
impl FromRequest for LibraryMusicApi {
type Error = actix_web::Error;
type Future = Ready<Result<Self, actix_web::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let profile = ProfileName::from_request_inner(req);
let profile = match profile {
Ok(profile) => profile,
Err(e) => {
return err(e);
}
};
let Some(music_apis) = PROFILES.get(&profile.0) else {
return err(ErrorBadRequest("Invalid profile"));
};
ok(music_apis)
}
}
}
#[cfg(all(test, feature = "simulator"))]
mod tests {
use std::sync::Arc;
use moosicbox_music_api::MusicApi;
use switchy_database::{Database, profiles::LibraryDatabase, simulator::SimulationDatabase};
use super::*;
fn create_test_db() -> LibraryDatabase {
let db = Arc::new(Box::new(SimulationDatabase::new().unwrap()) as Box<dyn Database>);
LibraryDatabase::from(db)
}
#[test_log::test]
fn test_library_music_api_profiles_add_and_get() {
let profiles = LibraryMusicApiProfiles::default();
let db = create_test_db();
profiles.add("test_profile".to_string(), db);
let retrieved = profiles.get("test_profile");
assert!(retrieved.is_some());
}
#[test_log::test]
fn test_library_music_api_profiles_get_nonexistent() {
let profiles = LibraryMusicApiProfiles::default();
let retrieved = profiles.get("nonexistent_profile");
assert!(retrieved.is_none());
}
#[test_log::test]
fn test_library_music_api_profiles_remove() {
let profiles = LibraryMusicApiProfiles::default();
let db = create_test_db();
profiles.add("test_profile".to_string(), db);
assert!(profiles.get("test_profile").is_some());
profiles.remove("test_profile");
assert!(profiles.get("test_profile").is_none());
}
#[test_log::test]
fn test_library_music_api_profiles_remove_nonexistent() {
let profiles = LibraryMusicApiProfiles::default();
profiles.remove("nonexistent_profile");
}
#[test_log::test]
fn test_library_music_api_profiles_add_fetch() {
let profiles = LibraryMusicApiProfiles::default();
let db = create_test_db();
let api = profiles.add_fetch("test_profile", db);
assert!(api.source().is_library());
let retrieved = profiles.get("test_profile");
assert!(retrieved.is_some());
}
#[test_log::test]
fn test_library_music_api_profiles_names() {
let profiles = LibraryMusicApiProfiles::default();
let db1 = create_test_db();
let db2 = create_test_db();
profiles.add("profile_alpha".to_string(), db1);
profiles.add("profile_beta".to_string(), db2);
let names = profiles.names();
assert_eq!(names.len(), 2);
assert!(names.contains(&"profile_alpha".to_string()));
assert!(names.contains(&"profile_beta".to_string()));
}
#[test_log::test]
fn test_library_music_api_profiles_names_empty() {
let profiles = LibraryMusicApiProfiles::default();
let names = profiles.names();
assert!(names.is_empty());
}
#[test_log::test]
fn test_library_music_api_profiles_replace_existing() {
let profiles = LibraryMusicApiProfiles::default();
let db1 = create_test_db();
let db2 = create_test_db();
profiles.add("test_profile".to_string(), db1);
let _first = profiles.get("test_profile").unwrap();
profiles.add("test_profile".to_string(), db2);
let _second = profiles.get("test_profile").unwrap();
let names = profiles.names();
assert_eq!(names.iter().filter(|n| *n == "test_profile").count(), 1);
}
#[test_log::test]
fn test_library_music_api_profiles_names_sorted() {
let profiles = LibraryMusicApiProfiles::default();
profiles.add("zebra".to_string(), create_test_db());
profiles.add("apple".to_string(), create_test_db());
profiles.add("mango".to_string(), create_test_db());
let names = profiles.names();
assert_eq!(names, vec!["apple", "mango", "zebra"]);
}
}