selene-core 0.3.1

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

use barber::{ProgressBar, ProgressRenderer};

use crate::{
    config::common::common_config,
    errors::LibraryError,
    library::track::{
        Track,
        core_impls::{TrackRenameError, calculate_rel_path},
    },
    utils::clean_empty_dirs,
};

pub fn migrate(
    tracks: &mut [Track],
    progress_renderer: Arc<dyn ProgressRenderer>,
    dry: bool,
) -> Result<(), TrackRenameError> {
    let library_dir = common_config()
        .library_dir()
        .ok_or(LibraryError::NoLibrary)?
        .to_path_buf();

    let mut map = Vec::new();
    let mut path_counter: HashMap<PathBuf, usize> = HashMap::new();

    for track in tracks {
        let Some(lib_container) = track.lib_container() else {
            continue;
        };

        let relative_path = calculate_rel_path(&track.metadata, lib_container)?;
        *path_counter.entry(relative_path.clone()).or_insert(0) += 1;
        map.push((track, relative_path));
    }

    let duplicates: Vec<(PathBuf, usize)> =
        path_counter.into_iter().filter(|(_, v)| *v > 1).collect();

    if !duplicates.is_empty() {
        let display_string = duplicates
            .iter()
            .map(|(path, count)| {
                format!(
                    "'{path}' is duplicated {count} time(s)",
                    path = path.display()
                )
            })
            .collect::<Vec<_>>()
            .join("\n");

        return Err(TrackRenameError::ConflictingNames(format!(
            "The migration could not be completed as the following paths are conflicted:\n{display_string}"
        )));
    }

    let progress_bar = ProgressBar::new(0, map.len(), progress_renderer);
    progress_bar.set_label("Migrating files...");

    for (track, path) in map {
        if track.relative_library_path == path {
            progress_bar.increment();
            continue;
        }

        track.relative_library_path = path;
        if !dry {
            track.migrate(&library_dir)?;
        }
        progress_bar.increment();
    }

    progress_bar.flush();

    clean_empty_dirs(library_dir)?;

    Ok(())
}