selene-core 0.3.1

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

use blake3::{Hash, hash};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::{
    database::{DatabaseEntry, DatabaseError, EntryId, Patchable, patch_option_replace},
    library::{
        artist::ArtistGroup,
        cover_art::CoverArt,
        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;
}

impl Deref for AlbumId {
    type Target = Hash;

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

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

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

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

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

    pub(crate) artist_group: ArtistGroup,
    pub disc_total: Option<u16>,
    pub genre: Option<String>,
    pub track_total: Option<u16>,
    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: None,
            id: hash,
            name,
            track_total: None,
            version: 1,
            date: None,
            tracks,
        }
    }

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

// 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<u16>,
    pub disc_num: Option<u16>,
}

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);
    }
}