selene-core 0.5.2

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

use chrono::{DateTime, Utc};
use lunar_lib::{
    database::{DatabaseEntry, DatabaseError},
    formatter::{FormatTable, Taggable},
    paths::sys::sanitize_str,
};
use serde::{Deserialize, Serialize};

use crate::{
    database::{LibraryDb, Patchable, patch_option_replace, patch_vec},
    library::{
        album::{Album, AlbumId},
        artist::{Artist, ArtistGroup},
        track::{UNKNOWN_TITLE, cover_art::CoverArt, lyric_data::LyricData},
    },
};

/// Track metadata. Defines metadata to be read from and stored on files
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct TrackMeta {
    pub(crate) album: Option<AlbumId>,
    pub(crate) artists: ArtistGroup,
    pub date: Option<DateTime<Utc>>,
    pub genre: Vec<String>,
    pub lyric_data: Option<LyricData>,
    pub art: Option<CoverArt>,
    pub other: Vec<(String, String)>,
    pub title: Option<String>,
}

impl TrackMeta {
    #[must_use]
    pub fn new() -> Self {
        Self {
            title: None,
            artists: ArtistGroup::new(),
            album: None,

            date: None,
            genre: Vec::new(),
            art: None,

            lyric_data: None,

            other: Vec::new(),
        }
    }

    pub fn album(&self, db: &LibraryDb) -> Result<Option<Album>, DatabaseError> {
        if let Some(album_id) = self.album {
            Ok(Some(
                Album::db_get_from(album_id, db)?.expect("Dangling album reference"),
            ))
        } else {
            Ok(None)
        }
    }

    pub fn main_artist(&self, db: &LibraryDb) -> Result<Option<Artist>, DatabaseError> {
        self.artists.main_artist(db)
    }

    pub fn artists(&self, db: &LibraryDb) -> Result<Vec<Artist>, DatabaseError> {
        self.artists.artists(db)
    }

    #[must_use]
    pub fn safe_title(&self) -> &str {
        self.title.as_deref().unwrap_or(UNKNOWN_TITLE)
    }
}

impl Patchable<TrackMeta> for TrackMeta {
    fn patch(&mut self, patch: TrackMeta) {
        let TrackMeta {
            album,
            artists,
            date,
            genre,
            lyric_data,
            art: cover_art,
            other,
            title,
        } = patch;

        patch_option_replace(&mut self.album, album);
        self.artists.patch(artists);
        patch_option_replace(&mut self.date, date);
        patch_vec(&mut self.genre, genre);
        patch_option_replace(&mut self.lyric_data, lyric_data);
        patch_option_replace(&mut self.art, cover_art);
        self.other.extend(other);
        patch_option_replace(&mut self.title, title);
    }
}

impl Taggable for TrackMeta {
    type Err = Infallible;

    fn fill_table(&self, table: &mut FormatTable) -> Result<(), Self::Err> {
        if let Some(value) = self.date {
            table.add_entry("date", value.to_string());
        }
        if let Some(value) = &self.title {
            table.add_entry("title", sanitize_str(value));
        }
        if !self.genre.is_empty() {
            table.add_entry("genre", sanitize_str(self.genre.join(";")));
        }
        table.add_table(self.other.clone());

        if let Some(lyric_data) = &self.lyric_data {
            match lyric_data {
                LyricData::Instrumental => table.add_entry("instrumental", "1"),
                LyricData::Plain(_) => {
                    table.add_entry("plain_lyrics", "1");
                    table.add_entry("lyrics", "1");
                }
                LyricData::Synced(_) => {
                    table.add_entry("synced_lyrics", "1");
                    table.add_entry("lyrics", "1");
                }
            }
        }

        Ok(())
    }
}

pub struct TrackAlbumInfo {
    pub album: Album,
    pub track_num: Option<u32>,
    pub disc_num: Option<u32>,
}