selene-core 0.8.0

selene-core is the backend for Selene, a local-first music player
Documentation
use std::{
    ops::Deref,
    path::PathBuf,
    sync::{LazyLock, Mutex},
};

use lunar_lib::database::{
    ConflictableTransactionResult, Database, DatabaseEntry, DatabaseError, SledDb, Transactional,
    caching::DbCache, serialize_to_ivec,
};

use crate::library::collection::STATIC_COLLECTIONS;

mod tx_extensions;
pub use tx_extensions::*;

mod resolveable;
pub use resolveable::*;

use crate::{
    data_dir,
    library::{
        album::Album,
        artist::Artist,
        collection::{Collection, STATIC_COLLECTIONS_VERSION},
        track::Track,
    },
};

pub mod validator;

#[derive(Clone)]
pub struct LibraryDb {
    db: SledDb,
}

impl Deref for LibraryDb {
    type Target = SledDb;

    fn deref(&self) -> &Self::Target {
        &self.db
    }
}

impl Database for LibraryDb {
    const RETRY_MAX_ATTEMPTS: Option<usize> = None;

    const RETRY_DURATION: std::time::Duration = std::time::Duration::from_millis(1);

    fn new(db: SledDb) -> Self {
        Self { db }
    }

    fn db(&self) -> &lunar_lib::database::SledDb {
        &self.db
    }

    fn pre_open(db: &LibraryDb) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        ensure_hardcoded_collections(db)?;
        Ok(())
    }

    fn path() -> PathBuf {
        data_dir().join("library_data")
    }
}

pub(crate) static TRACK_CACHE: LazyLock<Mutex<DbCache<Track>>> =
    LazyLock::new(|| Mutex::new(DbCache::new()));

pub(crate) static ALBUM_CACHE: LazyLock<Mutex<DbCache<Album>>> =
    LazyLock::new(|| Mutex::new(DbCache::new()));

pub(crate) static ARTIST_CACHE: LazyLock<Mutex<DbCache<Artist>>> =
    LazyLock::new(|| Mutex::new(DbCache::new()));

pub(crate) static COLLECTION_CACHE: LazyLock<Mutex<DbCache<Collection>>> =
    LazyLock::new(|| Mutex::new(DbCache::new()));

fn ensure_hardcoded_collections(
    db: &LibraryDb,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    const KEY: &[u8] = b"hardcoded_collections_version";

    if db
        .get(KEY)?
        .is_none_or(|v| *v != STATIC_COLLECTIONS_VERSION.to_be_bytes())
    {
        let db_tree = &*db.db;
        let collection_tree = Collection::__tree(db);

        (db_tree, &collection_tree).transaction(
            |(db_tree, collection_tree)| -> ConflictableTransactionResult<(), DatabaseError> {
                for collection in STATIC_COLLECTIONS.iter() {
                    let collection = *collection;

                    let ivec = serialize_to_ivec(collection);
                    collection_tree.insert(&*collection.id(), &*ivec)?;
                }

                db_tree.insert(KEY, &STATIC_COLLECTIONS_VERSION.to_be_bytes())?;

                Ok(())
            },
        )?;
    }

    Ok(())
}