use std::{
fs, io,
path::{Path, PathBuf},
};
use blake3::Hash;
use lunar_lib::formatter::{FormatError, FormatTable, format_str};
use thiserror::Error;
use crate::{
config::common::{LoudnormConfig, common_config},
database::{DatabaseEntry, DatabaseError},
errors::LibraryError,
library::{
artist::add_from_artists,
track::{Track, TrackId, track_meta::TrackMeta},
},
media_container::MediaContainer,
utils::pair_extension,
};
impl Track {
#[must_use]
pub fn new(
hash: Hash,
src_container: MediaContainer,
metadata: TrackMeta,
relative_path: PathBuf,
) -> Self {
Self {
id: TrackId::new(hash),
src_container,
lib_container: None,
relative_library_path: relative_path,
metadata,
cover_art: None,
loudnorm_analysis: None,
applied_loudnorm: None,
version: Track::VERSION_NUMBER,
}
}
}
impl Track {
#[must_use]
pub fn id(&self) -> TrackId {
self.id
}
#[must_use]
pub fn loudnorm(&self) -> Option<&LoudnormConfig> {
self.applied_loudnorm.as_ref()
}
#[must_use]
pub fn src_container(&self) -> &MediaContainer {
&self.src_container
}
#[must_use]
pub fn lib_container(&self) -> Option<&MediaContainer> {
self.lib_container.as_ref()
}
}
impl Track {
pub fn migrate(&mut self, library_dir: impl AsRef<Path>) -> Result<(), TrackRenameError> {
let Some(lib_container) = &mut self.lib_container else {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"A library container was not found for the input track".to_string(),
)
.into());
};
let absolute_path = library_dir.as_ref().join(&self.relative_library_path);
fs::create_dir_all(absolute_path.parent().expect("File cannot be root"))?;
if let Err(err) = fs::rename(lib_container.path(), &absolute_path) {
match err.kind() {
io::ErrorKind::NotFound if fs::symlink_metadata(&absolute_path)?.is_file() => {}
_ => return Err(err.into()),
}
}
lib_container.set_path(absolute_path);
self.db_patch()?;
Ok(())
}
}
#[derive(Debug, Error)]
pub enum TrackRenameError {
#[error("IoError: {0}")]
Io(#[from] std::io::Error),
#[error("DatabaseError: {0}")]
Database(#[from] DatabaseError),
#[error("FormatError: {0}")]
Format(#[from] FormatError),
#[error("LibraryError: {0}")]
Library(#[from] LibraryError),
#[error("{0}")]
ConflictingNames(String),
}
pub fn calculate_rel_path(
metadata: &TrackMeta,
container_ref: &MediaContainer,
) -> Result<PathBuf, TrackRenameError> {
let artists = metadata.artists.artists()?;
let album_reference = metadata.album;
let mut format_table = FormatTable::new();
format_table.extend_from_taggable(metadata);
add_from_artists(&mut format_table, &artists, "track");
if let Some(album) = album_reference {
format_table.extend_from_taggable(&album.lookup()?);
}
let mut path = PathBuf::from(format_str(
&common_config().track_name_config.format_string,
&format_table,
)?);
path.add_extension(
pair_extension(container_ref.container(), container_ref.codec())
.expect("Invalid container/codec pair when renaming"),
);
Ok(path)
}