use std::{
collections::{HashMap, HashSet},
fs, io,
path::PathBuf,
sync::Arc,
time::SystemTime,
};
use barber::{ProgressBar, ProgressRenderer};
use lunar_lib::warn;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::{cache_dir, config::common_config, library::track::TrackId, utils::hash_file};
pub mod album;
pub mod artist;
pub mod collectable;
pub mod collection;
pub mod track;
pub mod image_art;
pub mod metadata;
mod clean;
pub use clean::*;
mod extract;
pub use extract::*;
mod linking;
pub use linking::*;
mod export;
pub use export::*;
pub mod loudnorm;
#[cfg(feature = "lrclib")]
pub mod lyric_download;
pub fn hash_source_files(
progress_renderer: Arc<dyn ProgressRenderer>,
) -> io::Result<HashMap<TrackId, PathBuf>> {
let (sources, multithreaded) = {
let common = common_config();
(common.get_source_files(), common.main.multithreading)
};
let cache_file = cache_dir().join("sources");
type Cached = HashMap<PathBuf, (TrackId, u64)>;
let mut cached = if cache_file.is_file() {
let bytes = std::fs::read(&cache_file)?;
let mut cached: Cached = match postcard::from_bytes(&bytes) {
Ok(cached) => cached,
Err(err) => {
warn!(
"Failed to read cache file '{file}': {err}",
file = cache_file.display()
);
HashMap::new()
}
};
cached.retain(|p, (_, ct)| {
p.metadata()
.ok()
.and_then(|p| p.modified().ok())
.and_then(|d| d.duration_since(SystemTime::UNIX_EPOCH).ok())
.is_some_and(|pt| *ct == pt.as_secs())
});
cached
} else {
HashMap::new()
};
let cached_sources: HashSet<&PathBuf> = cached.keys().collect();
let rehash: Vec<PathBuf> = sources
.iter()
.filter(|source| !cached_sources.contains(source))
.cloned()
.collect();
if rehash.is_empty() {
return Ok(cached
.into_iter()
.map(|(path, (id, _))| (id, path))
.collect());
}
let progress_bar = ProgressBar::new(0, rehash.len(), progress_renderer.clone());
progress_bar.set_label("Hashing sources...");
if multithreaded {
let results: Vec<(PathBuf, (TrackId, u64))> = rehash
.into_par_iter()
.map(|source| -> io::Result<_> {
let hash = hash_file(&source)?;
let time = source
.metadata()?
.modified()?
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
progress_bar.increment();
Ok((source, (TrackId::new(hash), time)))
})
.collect::<io::Result<_>>()?;
cached.extend(results);
} else {
for source in rehash {
let hash = hash_file(&source)?;
let time = source
.metadata()?
.modified()?
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
cached.insert(source, (TrackId::new(hash), time));
progress_bar.increment();
}
}
fs::create_dir_all(cache_dir())?;
let writer = fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(cache_file)?;
let _ = postcard::to_io(&cached, writer);
progress_bar.flush();
Ok(cached
.into_iter()
.map(|(path, (id, _))| (id, path))
.collect())
}