selene-core 0.5.7

selene-core is the backend for Selene, a local-first music player
Documentation
use std::{ops::Deref, str::FromStr};

use blake3::{Hash, hash};
use chrono::{DateTime, Utc};
use lunar_lib::database::{DatabaseEntry, DatabaseError, EntryId};
use serde::{Deserialize, Serialize};

use crate::{
    database::{LibraryDb, Patchable, patch_option_replace},
    library::{
        artist::ArtistGroup,
        image_art::ImageArt,
        track::{Track, TrackId},
    },
};

pub mod frontend_impls;
pub mod trait_impls;

pub const UNKNOWN_ALBUM: &str = "UNKNOWN ALBUM";

#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AlbumId {
    id: Hash,
}

impl EntryId for AlbumId {
    type Entry = Album;
    type IdDb = LibraryDb;
}

impl Deref for AlbumId {
    type Target = [u8; 32];

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

impl FromStr for AlbumId {
    type Err = <Hash as FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self {
            id: Hash::from_str(s)?,
        })
    }
}

impl AlbumId {
    fn new(name: &str) -> Self {
        Self {
            id: hash(name.as_bytes()),
        }
    }

    #[must_use]
    pub fn to_hash(&self) -> Hash {
        self.id
    }

    #[must_use]
    pub fn to_selene_id(&self) -> String {
        format!("album:{}", self.id)
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Album {
    id: AlbumId,

    pub name: String,
    pub cover_art: Option<ImageArt>,

    /// Do not modify this variable without handling dangling references
    pub(crate) tracks: Vec<TrackReference>,

    pub(crate) artist_group: ArtistGroup,
    pub disc_total: Option<u32>,
    pub genre: Vec<String>,
    pub track_total: Option<u32>,
    pub date: Option<DateTime<Utc>>,

    version: usize,
}

// Core
impl Album {
    pub(crate) fn new(name: String, artists: ArtistGroup, tracks: Vec<TrackReference>) -> Self {
        let hash = AlbumId::new(&name);

        Self {
            artist_group: artists,
            cover_art: None,
            disc_total: None,
            genre: Vec::new(),
            id: hash,
            name,
            track_total: None,
            version: 1,
            date: None,
            tracks,
        }
    }

    pub fn tracks(&self, db: &LibraryDb) -> Result<Vec<Track>, DatabaseError> {
        Track::db_get_batch_from(self.track_refs().iter().map(|t| t.id), db)
    }
}

// Accessors
impl Album {
    #[must_use]
    pub fn id(&self) -> AlbumId {
        self.id
    }

    #[must_use]
    pub fn name(&self) -> &str {
        &self.name
    }

    #[must_use]
    pub fn artists(&self) -> &ArtistGroup {
        &self.artist_group
    }

    #[must_use]
    pub fn track_refs(&self) -> &[TrackReference] {
        &self.tracks
    }
}

#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
pub struct TrackReference {
    pub id: TrackId,
    pub track_num: Option<u32>,
    pub disc_num: Option<u32>,
}

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

impl Eq for TrackReference {}

impl Patchable<Self> for TrackReference {
    fn patch(&mut self, patch: Self) {
        let Self {
            id: _,
            track_num,
            disc_num,
        } = patch;

        patch_option_replace(&mut self.track_num, track_num);
        patch_option_replace(&mut self.disc_num, disc_num);
    }
}