use std::{
collections::VecDeque,
fs::{self},
path::{Path, PathBuf},
};
use blake3::{Hash, Hasher};
use lunar_lib::ask;
use crate::media_container::{Codec, ContainerFormat};
pub fn hash_file(path: impl AsRef<Path>) -> std::io::Result<Hash> {
let mut hasher = Hasher::new();
hasher.update_mmap(path)?;
Ok(hasher.finalize())
}
pub fn recurse_list_from_root<P: AsRef<Path>>(
root: P,
yield_dirs: bool,
) -> impl Iterator<Item = PathBuf> {
let mut stack = VecDeque::new();
stack.push_back(root.as_ref().to_owned());
std::iter::from_fn(move || {
while let Some(path) = stack.pop_back() {
if path.is_dir() {
if let Ok(entries) = path.read_dir() {
let filtered = entries.filter_map(Result::ok).map(|a| a.path());
stack.extend(filtered);
}
if yield_dirs {
return Some(path);
}
} else if path.is_file() {
return Some(path);
}
}
None
})
}
pub fn recurse_list_dirs<P: AsRef<Path>>(root: P) -> impl Iterator<Item = PathBuf> {
let mut stack = VecDeque::new();
stack.push_back(root.as_ref().to_owned());
std::iter::from_fn(move || {
while let Some(path) = stack.pop_back() {
if path.is_dir() {
if let Ok(entries) = path.read_dir() {
let filtered = entries.filter_map(Result::ok).map(|a| a.path());
stack.extend(filtered);
}
return Some(path);
}
}
None
})
}
pub fn confirm_directory_delete(directory: impl AsRef<Path>) -> std::io::Result<bool> {
let directory = directory.as_ref();
if directory.is_dir() {
let count = recurse_list_from_root(directory, false).count();
let confirm = ask!(
false,
"Rebuilding the library will delete all files contained inside the library directory ({}). Are you sure you want to do this?\n{count} files with be deleted",
directory.display()
);
if !confirm.unwrap_or(false) {
println!("Aborting library rebuild");
return Ok(false);
}
fs::remove_dir_all(directory)?;
fs::create_dir(directory)?;
}
Ok(true)
}
#[must_use]
pub fn pair_extension(format: ContainerFormat, codec: Codec) -> Option<&'static str> {
let ext = match format {
ContainerFormat::Flac if codec.is_flac() => "flac",
ContainerFormat::Mpa if codec.is_mp3() => "mp3",
ContainerFormat::Ogg if codec.is_vorbis() => "ogg",
ContainerFormat::Ogg if codec.is_opus() => "opus",
ContainerFormat::Ogg if codec.is_flac() => "oga",
ContainerFormat::Wav if codec.is_pcm() => "wav",
_ => return None,
};
Some(ext)
}
pub fn list_dirs_to_string(dirs: &[impl AsRef<Path>]) -> String {
let mut buf = String::new();
dirs.iter().enumerate().for_each(|(i, path)| {
if i > 0 {
buf.push('\n');
}
buf.push_str("\t- ");
buf.push_str(&path.as_ref().display().to_string());
});
buf
}
pub fn clean_empty_dirs(root: impl AsRef<Path>) -> std::io::Result<()> {
let mut dirs: Vec<PathBuf> = recurse_list_dirs(root).collect();
dirs.sort_by_key(|d| std::cmp::Reverse(d.components().count()));
for dir in dirs {
match fs::remove_dir(dir) {
Ok(()) => (),
Err(e) if e.kind() == std::io::ErrorKind::DirectoryNotEmpty => (),
Err(e) => return Err(e),
}
}
Ok(())
}