use std::{collections::HashMap, convert::Infallible};
use chrono::{DateTime, Utc};
use lunar_lib::{
formatter::{FormatTable, Taggable},
paths::sys::sanitize_str,
};
use serde::{Deserialize, Serialize};
use crate::{
database::{DatabaseEntry, DatabaseError, Patchable, patch_option_replace},
errors::MetadataError,
library::{
album::{Album, AlbumId},
artist::{Artist, ArtistGroup},
metadata::{
ALBUM_ARTIST_KEY, ALBUM_KEY, ARTIST_KEY, DATE_KEY, DISC_NUM_KEY, GENRE_KEY,
MetadataKey, TITLE_KEY, TRACK_NUM_KEY,
},
track::lyric_data::LyricData,
},
};
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct TrackMeta {
pub album: Option<AlbumReference>,
pub artists: ArtistGroup,
pub date: Option<DateTime<Utc>>,
pub genre: Option<String>,
pub lyric_data: Option<LyricData>,
pub other: HashMap<String, String>,
pub title: Option<String>,
}
impl TrackMeta {
#[must_use]
pub fn new() -> Self {
Self {
album: None,
artists: ArtistGroup::new(),
date: None,
genre: None,
lyric_data: None,
other: HashMap::new(),
title: None,
}
}
pub fn to_key_values(&self) -> Result<HashMap<String, String>, DatabaseError> {
let mut map = HashMap::new();
if let Some(album_reference) = self.album {
let album = Album::db_get(album_reference.album)?.expect("AlbumReference pointed to an album that didnt exist. This shouldn't be possible unless an error happened internally");
if let Some(name) = &album.name {
map.insert(ALBUM_KEY, name.clone());
}
let artists = album
.artists
.artists()?
.iter()
.map(Artist::name)
.collect::<Vec<_>>()
.join(",");
map.insert(ALBUM_ARTIST_KEY, artists);
if let Some(track_num) = album_reference.track_num
&& let Some(track_total) = album.track_total
{
map.insert(TRACK_NUM_KEY, format!("{track_num}/{track_total}"));
}
if let Some(disc_num) = album_reference.disc_num
&& let Some(disc_total) = album.disc_total
{
map.insert(DISC_NUM_KEY, format!("{disc_num}/{disc_total}"));
}
}
let artists = self
.artists
.artists()?
.iter()
.map(Artist::name)
.collect::<Vec<_>>()
.join(",");
map.insert(ARTIST_KEY, artists);
if let Some(date) = self.date {
map.insert(DATE_KEY, date.to_string());
}
if let Some(genre) = &self.genre {
map.insert(GENRE_KEY, genre.to_owned());
}
if let Some(title) = &self.title {
map.insert(TITLE_KEY, title.to_owned());
}
if let Some(lyric_data) = &self.lyric_data {
let (k, v) = lyric_data.get_metadata_value();
map.insert(k, v);
}
let mut collected = self.other.clone();
collected.extend(map.into_iter().map(|(k, v)| (k.to_owned(), v)));
Ok(collected)
}
pub fn apply_metadata_key(&mut self, key: MetadataKey) -> Result<(), MetadataError> {
match key {
MetadataKey::Album(v) => {
self.album = v.map(|v| AlbumReference {
album: v.id(),
track_num: None,
disc_num: None,
});
}
MetadataKey::Artist(v) => self.artists = ArtistGroup::from_artists(&v),
MetadataKey::Date(v) => self.date = v,
MetadataKey::DiscNum(v) => {
if let Some(album_reference) = &mut self.album {
album_reference.disc_num = v;
} else {
return Err(MetadataError::MissingAlbum(
"disc cannot be set because track has no album".to_owned(),
));
}
}
MetadataKey::Genre(v) => self.genre = v,
MetadataKey::Lyrics(v) => self.lyric_data = v,
MetadataKey::Instrumental(v) => self.lyric_data = v.then_some(LyricData::Instrumental),
MetadataKey::Title(v) => self.title = v,
MetadataKey::TrackNum(v) => {
if let Some(album_reference) = &mut self.album {
album_reference.track_num = v;
} else {
return Err(MetadataError::MissingAlbum(
"track cannot be set because track has no album".to_owned(),
));
}
}
MetadataKey::Other(key, v) => {
if let Some(v) = v {
self.other.insert(key, v);
} else {
self.other.remove(&key);
}
}
_ => {
return Err(MetadataError::KeyNotAllowed(format!(
"{key} cannot be used on track metadata",
key = key.to_key()
)));
}
}
Ok(())
}
}
impl Patchable<TrackMeta> for TrackMeta {
fn patch(&mut self, patch: TrackMeta) {
patch_option_replace(&mut self.album, patch.album);
self.artists.patch(patch.artists);
patch_option_replace(&mut self.date, patch.date);
patch_option_replace(&mut self.genre, patch.genre);
patch_option_replace(&mut self.lyric_data, patch.lyric_data);
self.other.extend(patch.other);
patch_option_replace(&mut self.title, patch.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.album {
if let Some(track_num) = value.track_num {
table.add_entry("track_num", track_num.to_string());
}
if let Some(disc_num) = value.disc_num {
table.add_entry("disc_num", disc_num.to_string());
}
}
if let Some(value) = &self.title {
table.add_entry("title", sanitize_str(value));
}
if let Some(value) = &self.genre {
table.add_entry("genre", sanitize_str(value));
}
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(())
}
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
pub struct AlbumReference {
pub album: AlbumId,
pub track_num: Option<u16>,
pub disc_num: Option<u16>,
}
impl AlbumReference {
#[must_use]
pub fn new(album: AlbumId, track_num: Option<u16>, disc_num: Option<u16>) -> Self {
Self {
album,
track_num,
disc_num,
}
}
pub fn lookup(&self) -> Result<Album, DatabaseError> {
let album = Album::db_get(self.album)?.expect("Album reference was pointing to an invalid album. This can only happen if modfications could not be completed or the database was modified externally");
Ok(album)
}
}