file_organizer_lib/
lib.rs1use 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}