selene-core 0.5.2

selene-core is the backend for Selene, a local-first music player
Documentation
use std::{
    collections::{HashMap, HashSet},
    fs::{self, File},
    io::BufReader,
    path::PathBuf,
    sync::Arc,
};

use barber::{ProgressBar, ProgressRenderer};
use lunar_lib::warn;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};

use crate::{cache_dir, config::common::common_config, library::track::TrackId, utils::hash_file};

pub mod album;
pub mod artist;
pub mod collection;
pub mod track;

pub mod image_art;
pub mod metadata;

mod orphan_relinking;
pub use orphan_relinking::*;

mod extract;
pub use extract::*;

mod export;
pub use export::*;

pub mod loudnorm;

#[cfg(feature = "net-features")]
pub mod lyric_download;

pub fn hash_source_files(
    progress_renderer: Arc<dyn ProgressRenderer>,
) -> std::io::Result<HashMap<TrackId, PathBuf>> {
    let sources = common_config().get_source_files();
    let cache_file = cache_dir().join("source_hashes.cbor");

    let mut source_hashes = if cache_file.is_file() {
        let file = File::open(&cache_file)?;

        let reader = BufReader::new(file);
        let mut map: HashMap<TrackId, PathBuf> = match ciborium::from_reader(reader) {
            Ok(result) => result,
            Err(err) => {
                warn!(
                    "Failed to read cache file '{file}': {err}",
                    file = cache_file.display()
                );
                HashMap::new()
            }
        };

        map.retain(|_, v| v.exists());

        map
    } else {
        HashMap::new()
    };

    let cached_sources: HashSet<&PathBuf> = source_hashes.values().collect();
    let not_hashed: Vec<PathBuf> = sources
        .par_iter()
        .filter(|source| !cached_sources.contains(source))
        .cloned()
        .collect();

    if not_hashed.is_empty() {
        return Ok(source_hashes);
    }

    let progress_bar = ProgressBar::new(0, not_hashed.len(), progress_renderer.clone());
    progress_bar.set_label("Hashing sources...");

    for source in not_hashed {
        let hash = hash_file(&source)?;
        source_hashes.insert(TrackId::new(hash), source);

        progress_bar.increment();
    }

    fs::create_dir_all(cache_dir())?;
    let writer = fs::OpenOptions::new()
        .write(true)
        .truncate(true)
        .create(true)
        .open(cache_file)?;

    let _ = ciborium::into_writer(&source_hashes, writer);
    progress_bar.flush();
    Ok(source_hashes)
}