rusty_sorter/
lib.rs

1use itertools::Itertools;
2use std::collections::HashMap;
3use std::env;
4use std::ffi::OsStr;
5use std::fs;
6use std::path;
7use std::process;
8use walkdir::WalkDir;
9
10pub fn get_os_sep() -> String {
11    if cfg!(windows) {
12        return String::from("\\");
13    } else if cfg!(unix) {
14        return String::from("/");
15    } else {
16        eprintln!("Your OS is not supported!");
17        process::exit(201);
18    }
19}
20
21pub fn get_dirs_recursively(path: &str, follow_links: bool, include_dot_files: bool) -> Vec<String> {
22    let mut files: Vec<String> = vec![];
23
24    for dir in WalkDir::new(&path)
25        .follow_links(follow_links)
26        .into_iter()
27        .filter_map(|dir| dir.ok())
28    {
29        // the unwrap is find here since we already checked the file (tested in depth)
30        if dir.metadata().unwrap().is_dir() {
31            if !include_dot_files {
32                if dir.file_name().to_str().unwrap().chars().next().unwrap().to_string() == "." {
33                    continue;
34                }
35            }
36            files.push(dir.path().display().to_string());
37        }
38    }
39
40    files
41}
42
43pub fn get_current_dir() -> String {
44    match env::current_dir() {
45        Ok(dir) => dir.display().to_string(),
46        Err(error) => {
47            eprintln!(
48                "[001] There was an error getting the current dir path, the given error is: {}",
49                error
50            );
51            process::exit(1);
52        }
53    }
54}
55
56pub fn get_files_in_dir(path: &str, include_dot_files: bool) -> Vec<path::PathBuf> {
57    let read = match fs::read_dir(&path) {
58        Ok(read) => read,
59        Err(error) => {
60            eprintln!(
61                "[002] There was an error reading a directory, the given error is: {}",
62                error
63            );
64            process::exit(2);
65        }
66    };
67
68    let mut files = Vec::new();
69    for entry in read.filter_map(|file| file.ok()) {
70        // the unwrap is find here since we already checked the file (tested in depth)
71        if entry.metadata().unwrap().is_file() {
72            if !include_dot_files {
73                if &entry
74                    .file_name()
75                    .to_str()
76                    .unwrap()
77                    .chars()
78                    .next()
79                    .unwrap()
80                    .to_string()
81                    == "."
82                {
83                    continue;
84                }
85            }
86            files.push(entry.path());
87        }
88    }
89
90    files
91}
92
93pub fn get_file_extensions(files: &Vec<path::PathBuf>) -> Vec<String> {
94    // let files = get_files_in_dir(&path);
95    let mut extensions = Vec::new();
96    for file in files {
97        // the unwrap is fine here since we already validated the metadata
98        let extension = match file.extension() {
99            Some(ext_os) => match ext_os.to_str() {
100                Some(ext) => ext,
101                None => {
102                    eprintln!("[003] There was an error getting the extension of a file, this may be bug, but please check your file extensions");
103                    process::exit(3);
104                }
105            },
106            None => "else",
107        };
108        extensions.push(extension.to_string().to_uppercase());
109    }
110
111    // dedup orig vector
112    // sort vector
113    extensions.sort();
114    // dedup vecotr
115    extensions.dedup();
116
117    extensions
118}
119
120pub fn get_file_ext_names(files: &Vec<path::PathBuf>) -> HashMap<String, String> {
121    let extensions = get_file_extensions(files);
122
123    // Check if the files aren't already sorted aka some extension across the whole folder
124    let ext_len = &extensions.len().to_string();
125    if ext_len == &"1" || ext_len == &"0" {
126        if &files.len().to_string() != "0" {
127            let parent = match files[0].parent() {
128                Some(root) => root.to_str().unwrap(),
129                None => "ROOT",
130            };
131            println!("ALREADY SORTED DIR: {}", parent);
132        }
133        return HashMap::new();
134    }
135
136    // copy of extensions dupped vec into iter
137    let mut extensions_dupped = Vec::new();
138    for file in files {
139        // the unwrap is fine here since we already validated the metadata
140        let extension = match file.extension() {
141            Some(ext_os) => match ext_os.to_str() {
142                Some(ext) => ext,
143                None => {
144                    eprintln!("[003] There was an error getting the extension of a file, this may be bug, but please check your file extensions");
145                    process::exit(3);
146                }
147            },
148            None => "else",
149        };
150        extensions_dupped.push(extension.to_string().to_uppercase());
151    }
152    // sort the dupped vec
153    extensions_dupped.sort();
154    // reverse the dupped extensions
155    extensions_dupped.reverse();
156
157    // compare and get how many items are for each file type.
158    let mut ext_count = Vec::new();
159    let mut last_count = 0;
160    for item in &extensions {
161        let index = &extensions_dupped.iter().position(|x| x == item).unwrap();
162        let count = &extensions_dupped.len() - index - last_count;
163        last_count = last_count + count;
164        ext_count.push((count, item));
165    }
166    // sort the ext_count
167    ext_count.sort();
168
169    // nubmer the extensions
170    let mut start_number = ext_count.len();
171    let mut ext_dir_names = HashMap::new();
172    for key in &ext_count {
173        ext_dir_names.insert(key.1.to_owned(), start_number.to_string() + "-" + key.1);
174        start_number = start_number - 1;
175    }
176
177    ext_dir_names
178}
179
180pub fn mass_make_dirs(path: &str, names: &Vec<&String>) {
181    match env::set_current_dir(&path) {
182        Ok(_) => (),
183        Err(error) => {
184            eprintln!(
185                "[004] There was an error changing the current dir, the given error is: {}",
186                error
187            );
188            process::exit(4);
189        }
190    };
191
192    for name in names {
193        match fs::create_dir(name) {
194            Ok(_) => (),
195            Err(error) => {
196                eprintln!("[005] There was an error creating a new dir inside the dir/one of the sub dirs, the given error is: {}", error);
197                process::exit(5);
198            }
199        };
200    }
201}
202
203pub fn sort_files_by_ext(path: &str, include_dot_files: bool) {
204    // get os separator
205    let os_sep = get_os_sep();
206
207    // change currrent dir to given path
208    match env::set_current_dir(&path) {
209        Ok(_) => (),
210        Err(error) => {
211            eprintln!(
212                "[004] There was an error changing the current dir, the given error is: {}",
213                error
214            );
215            process::exit(4);
216        }
217    };
218
219    // get the files in dir
220    let files = get_files_in_dir(&path, include_dot_files);
221
222    // get their extensions
223    let extensions = get_file_ext_names(&files);
224    // Check if the files aren't already separated!
225    let ext_len = &extensions.len().to_string();
226    if ext_len == &"1" || ext_len == &"0" {
227        return;
228    }
229
230    let folders = extensions.values().collect();
231
232    // create the new dirs
233    mass_make_dirs(&path, &folders);
234
235    // move files into the new dir
236    for file in files {
237        let file_name = match file.file_name() {
238            Some(name) => name,
239            None => {
240                eprintln!("[006] There was an issue getting a file's name, files that terminate in \"..\" are not supported!");
241                process::exit(6);
242            }
243        };
244
245        let root_path = match file.parent() {
246            Some(path) => match path.to_str() {
247                Some(str) => str,
248                None => {
249                    eprintln!("[007] There was a problem converting a path to string!");
250                    process::exit(7);
251                }
252            },
253            None => {
254                eprintln!("[008] There was a problem getting the parent directory of a path!");
255                process::exit(8);
256            }
257        };
258        // the first unwrap is find because we already validated the metadata, the second one is also fine because it would have already stopped from the operations before if it wasn't valid, the third unwrap is also valid since the whole path string got validated.
259        let extension = match &file.extension() {
260            Some(ext) => ext.to_str().unwrap(),
261            None => "else",
262        };
263        let destination = root_path.to_owned()
264            + &os_sep
265            + &extensions[&extension.to_uppercase()]
266            + &os_sep
267            + file_name.to_str().unwrap();
268
269        // copy the file to the destination
270        match fs::copy(&file, &destination) {
271            Ok(_) => {
272                println!("COPY: {} -> {}", &file.display().to_string(), &destination);
273            }
274            Err(error) => {
275                eprintln!("[008] There was an error copying a file to the newly created dir for it, the given error is: {}", error);
276                process::exit(8);
277            }
278        };
279
280        // now that the copy was successful we can delete the file from its previous location
281        match fs::remove_file(&file) {
282            Ok(_) => {
283                println!("DELETE: {}", &file.display().to_string());
284            }
285            Err(error) => {
286                eprintln!("There was a problem deleting a file that was successfully copied to it's new location, the given error is: {}", error);
287            }
288        };
289    }
290}