use std::{
collections::HashMap,
fs, io,
path::{Path, PathBuf},
sync::Arc,
};
use barber::{ProgressBar, ProgressRenderer};
use lunar_lib::iterator_ext::IteratorExtensions;
use crate::{
database::album_add_track,
library::{
LinkingError,
album::{Album, AlbumId},
image_art::ImageArt,
track::Track,
},
};
pub(crate) fn album_track_linking(
progress_renderer: Arc<dyn ProgressRenderer + 'static>,
all_albums: &mut HashMap<AlbumId, (Album, bool)>,
track_groups: &mut HashMap<PathBuf, Vec<(&mut Track, &mut bool)>>,
) -> Result<(), LinkingError> {
let album_map = build_folder_album_map(&track_groups);
let progress_bar = ProgressBar::new(0, album_map.len(), progress_renderer);
Ok(for (directory, album_id) in &album_map {
let (album, album_changed) = all_albums.get_mut(album_id).unwrap();
progress_bar.set_label(&format!("Linking album data for '{}'", album.name()));
progress_bar.increment();
let group = track_groups.get_mut(directory).unwrap();
if album.art.is_none() {
album.art = art_from_dir(directory, group.len()).ok().flatten();
}
*album_changed = true;
for (track, track_changed) in group {
album_add_track(album, track.id());
**track_changed = true;
track.metadata.album = Some(*album_id)
}
})
}
pub fn build_folder_album_map(
track_groups: &HashMap<PathBuf, Vec<(&mut Track, &mut bool)>>,
) -> HashMap<PathBuf, AlbumId> {
let mut map = HashMap::new();
for (parent, group) in track_groups {
let Some(first_album) = group.iter().find_map(|(track, _)| track.metadata().album) else {
continue;
};
if group
.iter()
.all(|(track, _)| track.metadata().album.is_none_or(|a| a == first_album))
{
map.insert(parent.to_owned(), first_album);
}
}
let mut value_counts: HashMap<AlbumId, usize> = HashMap::new();
for album_id in map.values() {
*value_counts.entry(*album_id).or_insert(0) += 1;
}
map.retain(|_, album_id| value_counts[album_id] == 1);
map
}
fn art_from_dir(dir: impl AsRef<Path>, expected_file_count: usize) -> io::Result<Option<ImageArt>> {
const COVER_LOOKUP_EXT: [&str; 3] = ["png", "jpeg", "jpg"];
let read_dir = fs::read_dir(dir.as_ref())?.flatten().to_vec();
if read_dir.len() != expected_file_count && read_dir.len() != expected_file_count + 1 {
return Ok(None);
}
let mut image = None;
for entry in read_dir {
if !entry.file_type().is_ok_and(|t| t.is_file()) {
continue;
}
let path = entry.path();
if path
.extension()
.and_then(std::ffi::OsStr::to_str)
.is_some_and(|ext| COVER_LOOKUP_EXT.contains(&ext))
&& let Ok(image_art) = ImageArt::from_file(path)
{
image = Some(image_art);
}
}
Ok(image)
}