use std::{
collections::{HashMap, HashSet},
io,
path::PathBuf,
time::SystemTime,
};
use lunar_lib::{caching::Cachable, hash_file, id::Id, log::warn, paths::cache_dir};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::{config::common_config, library::track::Track};
pub mod album;
pub mod artist;
pub mod collectable;
pub mod collection;
pub mod track;
pub mod image_art;
pub mod metadata;
mod extract;
pub use extract::*;
pub mod loudnorm;
pub fn hash_source_files() -> io::Result<HashMap<Id<Track>, 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, (Id<Track>, u64)>;
let mut cached = Cached::load_from_cache(&cache_file);
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())
});
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());
}
if multithreaded {
let results: Vec<(PathBuf, (Id<Track>, 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();
Ok((source, (Id::<Track>::from(*hash.as_bytes()), 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, (Id::<Track>::from(*hash.as_bytes()), time));
}
}
if let Err(err) = cached.save_to_cache(cache_file) {
warn!("Failed to save to cache: {err}");
}
Ok(cached
.into_iter()
.map(|(path, (id, _))| (id, path))
.collect())
}
#[derive(Clone)]
pub struct Entry<T> {
pub entry: T,
pub id: Id<T>,
}
impl<T> std::ops::Deref for Entry<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.entry
}
}
impl<T> std::ops::DerefMut for Entry<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.entry
}
}
impl<T> PartialEq for Entry<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T> Eq for Entry<T> {}
impl<T> std::hash::Hash for Entry<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl<T> Entry<T> {
pub fn id(&self) -> Id<T> {
self.id
}
}
#[cfg(feature = "database-impls")]
impl<T: lunar_lib::database::DatabaseEntry> From<Entry<T>> for lunar_lib::database::Entry<T> {
fn from(value: Entry<T>) -> Self {
lunar_lib::database::Entry::new(value.entry, value.id)
}
}
#[cfg(feature = "database-impls")]
impl<T: lunar_lib::database::DatabaseEntry> From<lunar_lib::database::Entry<T>> for Entry<T> {
fn from(value: lunar_lib::database::Entry<T>) -> Self {
Entry::<T> {
id: value.id(),
entry: value.into_item(),
}
}
}
pub trait EntryExt {
#[must_use]
fn to_entry(self, id: Id<Self>) -> Entry<Self>
where
Self: Sized,
{
Entry { entry: self, id }
}
}
impl<T> EntryExt for T {}