selene-core 0.2.0

selene-core is the backend for Selene, a local-first music player
Documentation
use sled::Tree;

use crate::{
    database::{
        CompareAndSwapTransaction, CompareAndSwapValue, Createable, DatabaseEntry, DatabaseError,
        Patchable, artist_tree, patch_option_replace, patch_replace, serialize_to_ivec,
    },
    library::{
        artist::{Artist, ArtistId},
        cover_art::CoverArt,
        track::Track,
    },
};

impl std::hash::Hash for Artist {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        state.write(self.id.as_bytes());
    }
}

impl PartialEq for Artist {
    fn eq(&self, other: &Self) -> bool {
        self.id() == other.id()
    }
}

impl Eq for Artist {}

impl DatabaseEntry for Artist {
    type Id = ArtistId;
    const VERSION_NUMBER: u32 = 1;

    fn tree() -> Tree {
        artist_tree()
    }

    fn id(&self) -> Self::Id {
        self.id
    }
}

pub struct PatchArtist {
    pub cover_art: Option<CoverArt>,
    pub description: Option<String>,
    pub name: Option<String>,
}

impl Patchable<PatchArtist> for Artist {
    fn patch(&mut self, patch: PatchArtist) {
        patch_option_replace(&mut self.cover_art, patch.cover_art);
        patch_option_replace(&mut self.description, patch.description);
        patch_replace(&mut self.name, patch.name);
    }
}

impl From<Artist> for PatchArtist {
    fn from(value: Artist) -> Self {
        PatchArtist {
            cover_art: value.cover_art,
            description: value.description,
            name: Some(value.name),
        }
    }
}

impl Patchable<Self> for Artist {
    fn patch(&mut self, patch: Self) {
        self.patch(PatchArtist::from(patch));
    }
}

impl std::ops::Deref for Artist {
    type Target = ArtistId;

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

pub struct ArtistCreateArgs {
    pub name: String,
    pub tracks: Vec<Track>,
}

impl ArtistCreateArgs {
    #[must_use]
    pub fn new(name: String, tracks: Vec<Track>) -> Self {
        Self { name, tracks }
    }
}

impl Createable for Artist {
    type CreateArgs = ArtistCreateArgs;

    fn create(
        cas_tx: &mut CompareAndSwapTransaction,
        args: Self::CreateArgs,
    ) -> Result<Self, DatabaseError> {
        // Create and upsert artist
        let artist = Artist::new(args.name).ok_or(DatabaseError::InvalidInput("Artist name cannot be empty".to_string()))?;

        if Artist::db_check(artist.id())? {
            return Err(DatabaseError::AlreadyInDatabase);
        }

        let artists_requests = cas_tx.get_or_new_request(Self::tree());
        artists_requests.swaps.insert(
            *artist.id().as_bytes(),
            CompareAndSwapValue {
                old: None,
                new: Some(serialize_to_ivec(&artist)),
            },
        );

        // Relink tracks
        let track_requests = cas_tx.get_or_new_request(Track::tree());

        args.tracks
            .into_iter()
            .try_for_each(|mut track| -> Result<(), DatabaseError> {
                let old_ivec = serialize_to_ivec(&track);
                track.metadata.artists.add_artist(artist.id());

                track_requests.swaps.insert(
                    *track.id().as_bytes(),
                    CompareAndSwapValue {
                        old: Some(old_ivec),
                        new: Some(serialize_to_ivec(&track)),
                    },
                );

                Ok(())
            })?;

        Ok(artist)
    }
}