selene-core 0.5.2

selene-core is the backend for Selene, a local-first music player
Documentation
use serde::Deserialize;
use sled::{Db, IVec, Transactional, Tree, transaction::ConflictableTransactionResult};

use crate::{
    database::{DatabaseEntry, DatabaseError, deserialize_from_ivec, serialize_to_ivec},
    library::collection::{Collection, STATIC_COLLECTIONS, STATIC_COLLECTIONS_VERSION},
};

pub type DbKey = [u8; 32];

/// Clears the item at `key` from `tree`
///
/// # Warning
///
/// This function can create detatched references if not used correctly. To safely remove an item from the database, see [`DatabaseOps::db_remove()`]
pub(crate) fn sled_clear(tree: &Tree, key: &DbKey) -> sled::Result<()> {
    tree.remove(key).map(|_| ())
}

/// Returns the raw bytes of the instance of [`Self`] in the database with the matching `key`
///
/// # Errors
///
/// Errors of [`sled`] fails to get the key. See [`sled::Tree::get()`] for more information
pub(crate) fn sled_get_raw(tree: &Tree, key: &DbKey) -> sled::Result<Option<IVec>> {
    tree.get(key)
}

/// Returns the raw bytes of all instances of [`Self`] in the database
///
/// # Errors
///
/// Errors if [`sled`] fails to read an entry in the tree
pub(crate) fn sled_get_all_raw(tree: &Tree) -> sled::Result<Vec<IVec>> {
    tree.iter().values().collect()
}

/// Returns a batch of [`Self`] from `ids`
///
/// # Errors
///
/// This function will error if [`sled`] fails to retrieve an entry, or any key is not found
pub(crate) fn sled_get_batch_raw<I>(tree: &Tree, keys: I) -> Result<Vec<IVec>, DatabaseError>
where
    I: IntoIterator<Item = DbKey>,
{
    keys.into_iter()
        .map(|id| tree.get(id)?.ok_or(DatabaseError::MissingEntry))
        .collect::<Result<Vec<IVec>, DatabaseError>>()
}

/// Returns the version of of something given its raw bytes
///
/// # Errors
///
/// This function will error if [`sled`] fails to retrieve an entry
///
/// # Panics
///
/// This function will panic if the serialized `raw` does not have a u32 `version` field
pub(crate) fn sled_version_from_raw(raw: IVec) -> u32 {
    #[derive(Deserialize)]
    struct Version {
        version: u32,
    }

    deserialize_from_ivec::<Version>(raw).version
}

pub(crate) fn ensure_hardcoded_collections(db: &Db) -> Result<(), DatabaseError> {
    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;
        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;

                    collection_tree
                        .insert(collection.id().as_bytes(), serialize_to_ivec(collection))?;
                }

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

                Ok(())
            },
        )?;
    }

    Ok(())
}