selene-core 0.3.0

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

use lunar_lib::{ask, error, info};

use crate::{config::common::CommonConfig, utils::list_dirs_to_string};

impl CommonConfig {
    /// Sets your library directory to the input path after validation
    ///
    /// The directory is invalid if:
    /// - The library directory starts with a source
    /// - Any source starts with the library directory
    /// - The library directory is equal to any source
    /// - The path is relative
    ///
    /// # Errors
    ///
    /// Errors when sources or the library fail to canonicalize via `std::path::Path::canonicalize()`
    /// Paths that do not exist on the file system are not canonicalized
    pub fn set_library_directory(&mut self, path: impl Into<PathBuf>) -> io::Result<&mut Self> {
        let path = path.into();
        let library_dir = self.library_dir.as_ref();

        if Some(&path) == library_dir {
            info!(
                "The library directory is already set to '{}'",
                path.display()
            );
            return Ok(self);
        }

        if !self.valid_library_dir(&path)? {
            error!(
                "Could not set the library directory to '{}': Path is not valid",
                path.display()
            );
            return Ok(self);
        }

        self.library_dir = Some(path);

        Ok(self)
    }

    /// Sets your sources to the input sources after validation
    ///
    /// Sources are invalid if:
    /// - The library directory starts with the source
    /// - The source starts with the library directory
    /// - The library directory and source are equal
    /// - The source is a relative path
    ///
    /// # Errors
    ///
    /// Errors when sources or the library fail to canonicalize via `std::path::Path::canonicalize()`
    /// Paths that do not exist on the file system are not canonicalized
    pub fn set_library_sources(&mut self, paths: &[PathBuf]) -> io::Result<&mut Self> {
        if paths.is_empty() {
            self.source_dirs = Vec::new();
            return Ok(self);
        }

        let validity_checked_paths: Vec<(PathBuf, bool)> = paths
            .iter()
            .map(|p| {
                let is_valid = self.valid_source_dir(p)?;
                Ok((p.clone(), is_valid))
            })
            .collect::<Result<Vec<_>, io::Error>>()?;

        let validated: Vec<PathBuf> = validity_checked_paths
            .iter()
            .filter_map(|(source, valid)| if *valid { Some(source.clone()) } else { None })
            .collect();

        let invalidated: Vec<&PathBuf> = validity_checked_paths
            .iter()
            .filter_map(|(source, valid)| if *valid { None } else { Some(source) })
            .collect();

        let validated_string = list_dirs_to_string(&validated);
        let invalidated_string = list_dirs_to_string(&invalidated);

        if validated.is_empty() {
            error!(
                "Could not set source directories: All inputs are invalid:\n{invalidated_string}"
            );
        } else {
            if !invalidated.is_empty() {
                let confirm = ask!(
                    true,
                    "Invalid directories detected:\n{invalidated_string}\n\nWould you still like to set the valid source directories?:\n{validated_string}"
                );

                if !confirm.unwrap_or(false) {
                    return Ok(self);
                }
            }

            info!("Sucessfully set the source directories to:\n{validated_string}");
            self.source_dirs = validated;
        }

        Ok(self)
    }
}