Skip to main content

file_organizer_lib/
lib.rs

1use std::collections::HashMap;
2use std::ffi::OsStr;
3use std::fs;
4use std::path::{Path, PathBuf};
5use thiserror::Error;
6use walkdir::WalkDir;
7
8#[derive(Error, Debug)]
9pub enum FileOrganizerError {
10    #[error("Invalid path: {0}")]
11    InvalidPath(String),
12    #[error("IO error: {0}")]
13    IoError(#[from] std::io::Error),
14}
15
16pub type Result<T> = std::result::Result<T, FileOrganizerError>;
17
18pub fn categorize_files<P: AsRef<Path>>(
19    source_dir: P,
20    recursive: bool,
21) -> Result<HashMap<String, Vec<PathBuf>>> {
22    let source_path = source_dir.as_ref();
23    
24    if !source_path.exists() {
25        return Err(FileOrganizerError::InvalidPath(format!(
26            "Direktori tidak ditemukan: {}",
27            source_path.display()
28        )));
29    }
30
31    if !source_path.is_dir() {
32        return Err(FileOrganizerError::InvalidPath(format!(
33            "Path bukan direktori: {}",
34            source_path.display()
35        )));
36    }
37
38    let mut file_map: HashMap<String, Vec<PathBuf>> = HashMap::new();
39    let walker = if recursive {
40        WalkDir::new(source_path)
41    } else {
42        WalkDir::new(source_path).max_depth(1)
43    };
44
45    for entry in walker.into_iter().filter_map(|e| e.ok()) {
46        let file_type = entry.file_type();
47        
48        if !file_type.is_file() {
49            continue;
50        }
51
52        let path = entry.path().to_path_buf();
53        let ext = path.extension()
54            .and_then(OsStr::to_str)
55            .map(|e| e.to_lowercase())
56            .unwrap_or_else(|| "no_extension".to_string());
57
58        file_map.entry(ext).or_default().push(path);
59    }
60
61    Ok(file_map)
62}
63
64pub fn organize_file<P: AsRef<Path>>(
65    source_file: P,
66    target_dir: P,
67) -> Result<PathBuf> {
68    let source = source_file.as_ref();
69    if !source.is_file() {
70        return Err(FileOrganizerError::InvalidPath(
71            "Source is not a file".into(),
72        ));
73    }
74
75    fs::create_dir_all(target_dir.as_ref())?;
76
77    let file_name = source
78        .file_name()
79        .and_then(OsStr::to_str)
80        .ok_or_else(|| FileOrganizerError::InvalidPath("Invalid file name".into()))?;
81
82    let target_path = target_dir.as_ref().join(file_name);
83    fs::rename(source, &target_path)?;
84
85    Ok(target_path)
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use std::fs::File;
92    use tempfile::tempdir;
93
94    #[test]
95    fn test_categorize_files() -> Result<()> {
96        let dir = tempdir()?;
97        let dir_path = dir.path();
98
99        File::create(dir_path.join("test1.txt"))?;
100        File::create(dir_path.join("test2.txt"))?;
101        File::create(dir_path.join("image.jpg"))?;
102
103        let files = categorize_files(dir_path, false)?;
104        
105        assert_eq!(files.get("txt").unwrap().len(), 2);
106        assert_eq!(files.get("jpg").unwrap().len(), 1);
107        Ok(())
108    }
109
110    #[test]
111    fn test_organize_file() -> Result<()> {
112        let temp_dir = tempdir()?;
113        let source_file = temp_dir.path().join("test.txt");
114        let target_dir = temp_dir.path().join("organized");
115        
116        File::create(&source_file)?;
117        organize_file(&source_file, &target_dir)?;
118
119        assert!(target_dir.join("test.txt").exists());
120        assert!(!source_file.exists());
121        Ok(())
122    }
123}