use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use thiserror::Error;
use walkdir::WalkDir;
#[derive(Error, Debug)]
pub enum FileOrganizerError {
#[error("Invalid path: {0}")]
InvalidPath(String),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
}
pub type Result<T> = std::result::Result<T, FileOrganizerError>;
pub fn categorize_files<P: AsRef<Path>>(
source_dir: P,
recursive: bool,
) -> Result<HashMap<String, Vec<PathBuf>>> {
let source_path = source_dir.as_ref();
if !source_path.exists() {
return Err(FileOrganizerError::InvalidPath(format!(
"Direktori tidak ditemukan: {}",
source_path.display()
)));
}
if !source_path.is_dir() {
return Err(FileOrganizerError::InvalidPath(format!(
"Path bukan direktori: {}",
source_path.display()
)));
}
let mut file_map: HashMap<String, Vec<PathBuf>> = HashMap::new();
let walker = if recursive {
WalkDir::new(source_path)
} else {
WalkDir::new(source_path).max_depth(1)
};
for entry in walker.into_iter().filter_map(|e| e.ok()) {
let file_type = entry.file_type();
if !file_type.is_file() {
continue;
}
let path = entry.path().to_path_buf();
let ext = path.extension()
.and_then(OsStr::to_str)
.map(|e| e.to_lowercase())
.unwrap_or_else(|| "no_extension".to_string());
file_map.entry(ext).or_default().push(path);
}
Ok(file_map)
}
pub fn organize_file<P: AsRef<Path>>(
source_file: P,
target_dir: P,
) -> Result<PathBuf> {
let source = source_file.as_ref();
if !source.is_file() {
return Err(FileOrganizerError::InvalidPath(
"Source is not a file".into(),
));
}
fs::create_dir_all(target_dir.as_ref())?;
let file_name = source
.file_name()
.and_then(OsStr::to_str)
.ok_or_else(|| FileOrganizerError::InvalidPath("Invalid file name".into()))?;
let target_path = target_dir.as_ref().join(file_name);
fs::rename(source, &target_path)?;
Ok(target_path)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use tempfile::tempdir;
#[test]
fn test_categorize_files() -> Result<()> {
let dir = tempdir()?;
let dir_path = dir.path();
File::create(dir_path.join("test1.txt"))?;
File::create(dir_path.join("test2.txt"))?;
File::create(dir_path.join("image.jpg"))?;
let files = categorize_files(dir_path, false)?;
assert_eq!(files.get("txt").unwrap().len(), 2);
assert_eq!(files.get("jpg").unwrap().len(), 1);
Ok(())
}
#[test]
fn test_organize_file() -> Result<()> {
let temp_dir = tempdir()?;
let source_file = temp_dir.path().join("test.txt");
let target_dir = temp_dir.path().join("organized");
File::create(&source_file)?;
organize_file(&source_file, &target_dir)?;
assert!(target_dir.join("test.txt").exists());
assert!(!source_file.exists());
Ok(())
}
}