use std::{
collections::BTreeMap,
ops::Deref,
sync::{Arc, LazyLock, RwLock},
};
use crate::Database;
pub static PROFILES: LazyLock<DatabaseProfiles> = LazyLock::new(DatabaseProfiles::default);
#[allow(clippy::module_name_repetitions)]
#[derive(Default)]
pub struct DatabaseProfiles {
profiles: Arc<RwLock<BTreeMap<String, LibraryDatabase>>>,
}
impl DatabaseProfiles {
pub fn add(&self, profile: String, database: Arc<Box<dyn Database>>) {
moosicbox_profiles::PROFILES.add(profile.clone());
self.profiles
.write()
.unwrap()
.insert(profile, LibraryDatabase { database });
}
pub fn remove(&self, profile: &str) {
self.profiles.write().unwrap().retain(|p, _| p != profile);
}
#[must_use]
pub fn add_fetch(&self, profile: &str, database: Arc<Box<dyn Database>>) -> LibraryDatabase {
self.add(profile.to_owned(), database);
self.get(profile).unwrap()
}
#[must_use]
pub fn get(&self, profile: &str) -> Option<LibraryDatabase> {
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()
}
}
#[derive(Debug, Clone)]
pub struct LibraryDatabase {
pub database: Arc<Box<dyn Database>>,
}
impl From<&LibraryDatabase> for Arc<Box<dyn Database>> {
fn from(value: &LibraryDatabase) -> Self {
value.database.clone()
}
}
impl From<LibraryDatabase> for Arc<Box<dyn Database>> {
fn from(value: LibraryDatabase) -> Self {
value.database
}
}
impl From<Arc<Box<dyn Database>>> for LibraryDatabase {
fn from(value: Arc<Box<dyn Database>>) -> Self {
Self { database: value }
}
}
impl<'a> From<&'a LibraryDatabase> for &'a dyn Database {
fn from(value: &'a LibraryDatabase) -> Self {
&**value.database
}
}
impl Deref for LibraryDatabase {
type Target = dyn Database;
fn deref(&self) -> &Self::Target {
&**self.database
}
}
#[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::{LibraryDatabase, PROFILES};
impl FromRequest for LibraryDatabase {
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(database) = PROFILES.get(&profile.0) else {
return err(ErrorBadRequest("Invalid profile"));
};
ok(database)
}
}
}
#[cfg(all(test, feature = "simulator"))]
mod tests {
use super::*;
use crate::simulator::SimulationDatabase;
#[test_log::test]
fn test_database_profiles_add_and_get() {
let profiles = DatabaseProfiles::default();
let db = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
profiles.add("test_profile".to_string(), db.clone());
let retrieved = profiles.get("test_profile");
assert!(retrieved.is_some());
}
#[test_log::test]
fn test_database_profiles_get_nonexistent() {
let profiles = DatabaseProfiles::default();
let retrieved = profiles.get("nonexistent_profile");
assert!(retrieved.is_none());
}
#[test_log::test]
fn test_database_profiles_remove() {
let profiles = DatabaseProfiles::default();
let db = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
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_database_profiles_remove_nonexistent() {
let profiles = DatabaseProfiles::default();
profiles.remove("nonexistent_profile");
}
#[test_log::test]
fn test_database_profiles_add_fetch() {
let profiles = DatabaseProfiles::default();
let db = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
let retrieved = profiles.add_fetch("test_profile", db);
assert!(std::ptr::addr_eq(
std::ptr::addr_of!(**retrieved.database),
std::ptr::addr_of!(**profiles.get("test_profile").unwrap().database)
));
}
#[test_log::test]
fn test_database_profiles_names() {
let profiles = DatabaseProfiles::default();
let db1 = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
let db2 = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
profiles.add("profile1".to_string(), db1);
profiles.add("profile2".to_string(), db2);
let names = profiles.names();
assert_eq!(names.len(), 2);
assert!(names.contains(&"profile1".to_string()));
assert!(names.contains(&"profile2".to_string()));
}
#[test_log::test]
fn test_database_profiles_names_empty() {
let profiles = DatabaseProfiles::default();
let names = profiles.names();
assert!(names.is_empty());
}
#[test_log::test]
fn test_database_profiles_replace_existing() {
let profiles = DatabaseProfiles::default();
let db1 = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
let db2 = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
profiles.add("test_profile".to_string(), db1.clone());
let first = profiles.get("test_profile").unwrap();
profiles.add("test_profile".to_string(), db2.clone());
let second = profiles.get("test_profile").unwrap();
assert!(!std::ptr::addr_eq(
std::ptr::addr_of!(**first.database),
std::ptr::addr_of!(**second.database)
));
}
#[test_log::test]
fn test_library_database_from_arc() {
let db = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
let library_db: LibraryDatabase = db.clone().into();
assert!(std::ptr::addr_eq(
std::ptr::addr_of!(**library_db.database),
std::ptr::addr_of!(**db)
));
}
#[test_log::test]
fn test_library_database_into_arc() {
let db = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
let library_db = LibraryDatabase {
database: db.clone(),
};
let arc_db: Arc<Box<dyn Database>> = library_db.into();
assert!(std::ptr::addr_eq(
std::ptr::addr_of!(**arc_db),
std::ptr::addr_of!(**db)
));
}
#[test_log::test]
fn test_library_database_deref() {
let db = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
let library_db = LibraryDatabase {
database: db.clone(),
};
let _db_ref: &dyn Database = &*library_db;
}
#[test_log::test]
fn test_library_database_ref_into_dyn_database() {
let db = Arc::new(
Box::new(SimulationDatabase::new_for_path(None).unwrap()) as Box<dyn Database>
);
let library_db = LibraryDatabase { database: db };
let db_ref: &dyn Database = (&library_db).into();
let _ = db_ref;
}
}