use std::{convert::Infallible, str::FromStr};
use blake3::Hash;
use lunar_lib::database::{DatabaseEntry, DatabaseError, EntryId};
use serde::{Deserialize, Serialize};
use crate::{
database::LibraryDb,
library::{
album::{Album, AlbumId},
artist::ArtistId,
collection::rules::{AlbumRule, RuleGroup, TrackRule},
image_art::ImageArt,
track::{Track, TrackId},
},
};
pub mod frontend_impls;
pub mod trait_impls;
pub mod rules;
mod hardcoded_dynamic_collections;
pub use hardcoded_dynamic_collections::*;
#[derive(Debug, thiserror::Error)]
pub enum CollectionCreationError {
#[error("Collection name resolved to an empty string")]
EmptyName,
#[error("Collection name resolved to a reserved collection name")]
ReservedName(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CollectionType {
Static { collectables: Vec<Collectable> },
Dynamic { rules: Vec<DynamicCollectionRules> },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DynamicCollectionRules {
Track(RuleGroup<TrackRule>),
Album(RuleGroup<AlbumRule>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Collection {
id: CollectionId,
pub name: String,
pub cover_art: Option<ImageArt>,
pub items: CollectionType,
read_only: bool,
}
impl Collection {
fn new_static(name: String) -> Result<Self, CollectionCreationError> {
let id = CollectionId::from_str(&name).unwrap();
Ok(Self {
id,
name,
cover_art: None,
items: CollectionType::Static {
collectables: Vec::new(),
},
read_only: false,
})
}
pub fn collectables(self, db: &LibraryDb) -> Result<Vec<Collectable>, DatabaseError> {
match self.items {
CollectionType::Static { collectables } => Ok(collectables),
CollectionType::Dynamic { rules } => Ok(resolve_rules(&rules, db)?),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum Collectable {
Track(TrackId),
Artist(ArtistId),
Album(AlbumId),
Collection(CollectionId),
}
impl Collectable {
#[must_use]
pub fn to_selene_id(&self) -> String {
match self {
Collectable::Track(track_id) => track_id.to_selene_id(),
Collectable::Artist(artist_id) => artist_id.to_selene_id(),
Collectable::Album(album_id) => album_id.to_selene_id(),
Collectable::Collection(collection_id) => collection_id.to_selene_id(),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CollectionId {
id: Hash,
}
impl CollectionId {
#[must_use]
pub fn to_selene_id(&self) -> String {
format!("collection:{}", self.id)
}
}
impl EntryId for CollectionId {
type Entry = Collection;
type IdDb = LibraryDb;
}
impl std::ops::Deref for CollectionId {
type Target = [u8; 32];
fn deref(&self) -> &Self::Target {
self.id.as_bytes()
}
}
impl FromStr for CollectionId {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self {
id: blake3::hash(s.as_bytes()),
})
}
}
pub fn resolve_rules(
rules: &[DynamicCollectionRules],
db: &LibraryDb,
) -> Result<Vec<Collectable>, DatabaseError> {
let tracks = Track::db_get_all_from(db)?;
let albums = Album::db_get_all_from(db)?;
let mut collectables = Vec::new();
for group in rules {
match group {
DynamicCollectionRules::Track(rule_group) => {
let tracks = rule_group.filter(&tracks);
collectables.extend(tracks.map(|t| Collectable::Track(t.id())));
}
DynamicCollectionRules::Album(rule_group) => {
let albums = rule_group.filter(&albums);
collectables.extend(albums.map(|t| Collectable::Album(t.id())));
}
}
}
Ok(collectables)
}