filesorters/src/main.rs

261 lines
7.2 KiB
Rust

use std::{
env, fs,
io::{self, Write},
path::{Path, PathBuf},
process,
sync::OnceLock,
thread,
};
use filesorters::Config;
static CONFIG: OnceLock<Config> = OnceLock::new();
const VERSION: &str = env!("CARGO_PKG_VERSION");
fn main() {
let config_path = get_config_path();
let args: Vec<String> = env::args().collect();
if args.len() > 1
&& (args.contains(&"-v".to_string()) || args.contains(&"--version".to_string()))
{
println!("Filesorters version {}", VERSION);
return;
}
if args.len() > 1
&& (args.contains(&"-c".to_string()) || args.contains(&"--config".to_string()))
{
println!("Config location: {}", config_path.display());
return;
}
if args.len() > 1 && (args.contains(&"-h".to_string()) || args.contains(&"--help".to_string()))
{
print!(
"Options:
-h, --help - show this help message
-v, --version - show the version
-c, --config-path - config file location
"
);
return;
}
if !config_path.exists() {
println!("Config file does not exist, creating...");
filesorters::Config::create(&config_path).unwrap();
}
match filesorters::Config::parse(config_path) {
Ok(parsed_config) => {
CONFIG.set(parsed_config).unwrap();
println!("Config file loaded successfully");
}
Err(err) => {
eprintln!("Error: {}", err);
process::exit(1);
}
}
{
let mut counter = 1;
for alias in CONFIG.get().unwrap().sources.keys() {
println!("{}: {}", counter, alias);
counter += 1;
}
}
let mut actual_selection: Vec<String> = Vec::new();
loop {
print!("Enter: ");
io::stdout().flush().unwrap();
let mut selected = String::new();
io::stdin()
.read_line(&mut selected)
.expect("Failed to read line");
let parsed_selection: Vec<usize> = selected
.split(" ")
.filter(|y| !y.is_empty())
.map(|y| y.replace("\n", ""))
.flat_map(|x| x.parse::<usize>())
.collect();
if parsed_selection.is_empty() {
println!("Invalid selection");
continue;
}
if parsed_selection.len() > CONFIG.get().unwrap().sources.keys().len() {
println!("Too many selections made");
continue;
}
if parsed_selection
.iter()
.any(|x| *x > CONFIG.get().unwrap().sources.keys().len())
{
println!("Invalid selection");
continue;
}
for selection in parsed_selection {
actual_selection.push(
CONFIG
.get()
.unwrap()
.sources
.keys()
.nth(selection - 1)
.unwrap()
.to_string(),
);
}
break;
}
let recursive: bool;
loop {
print!("Recursive (yes/no): ");
io::stdout().flush().unwrap();
let mut selected = String::new();
io::stdin()
.read_line(&mut selected)
.expect("Failed to read line");
if selected.trim().starts_with('y') {
recursive = true;
break;
} else if selected.trim().starts_with('n') {
recursive = false;
break;
}
println!("Invalid answer");
}
println!("Selected: {}", actual_selection.join(", "));
let mut thread_pool: Vec<thread::JoinHandle<()>> = Vec::new();
for selection in actual_selection {
let thread = thread::spawn(move || match sort_files(selection, recursive) {
Ok(_) => {}
Err(err) => eprintln!("{}", err),
});
thread_pool.push(thread);
}
for thread in thread_pool {
thread.join().unwrap();
}
}
fn sort_files(selection: String, recursive: bool) -> Result<(), Box<dyn std::error::Error>> {
let search_path = CONFIG
.get()
.and_then(|config| config.sources.get(&selection))
.cloned()
.unwrap();
let file_types = [
(
"Picture",
filesorters::PICTURE_EXTENTIONS.to_vec(),
CONFIG.get().unwrap().pictures_dir.clone(),
),
(
"Book",
filesorters::BOOK_EXTENTIONS.to_vec(),
CONFIG.get().unwrap().books_dir.clone(),
),
(
"Video",
filesorters::VIDEO_EXTENTIONS.to_vec(),
CONFIG.get().unwrap().videos_dir.clone(),
),
(
"Music",
filesorters::SOUND_EXTENTIONS.to_vec(),
CONFIG.get().unwrap().music_dir.clone(),
),
];
fn process_directory(
path: &std::path::Path,
file_types: &[(&str, Vec<&str>, Option<std::path::PathBuf>)],
recursive: bool,
) -> Result<(), Box<dyn std::error::Error>> {
for entry in fs::read_dir(path)?.flatten() {
let metadata = entry.metadata()?;
if metadata.is_file() {
if let Some(extension) = entry.path().extension().and_then(|e| e.to_str()) {
let file_path = entry.path();
for (_label, valid_extensions, target_dir_option) in file_types {
if let Some(target_dir) = target_dir_option {
if valid_extensions.contains(&extension) {
move_file_to_directory(&file_path, target_dir);
}
}
}
}
} else if metadata.is_dir() && recursive {
// Recurse into the directory
process_directory(&entry.path(), file_types, recursive)?;
}
}
Ok(())
}
process_directory(&search_path, &file_types, recursive)
}
fn move_file_to_directory(path: &Path, dir: &Path) {
if !path.exists() {
eprintln!("Source file does not exist");
return;
}
if !dir.exists() {
match fs::create_dir(dir) {
Ok(_) => {}
Err(err) => eprintln!("Error creating destination directory: {}", err),
}
return;
}
let destination = dir.join(path.file_name().unwrap_or_default());
match fs::rename(path, &destination) {
Ok(()) => {}
Err(err) => eprintln!("Error moving file: {}", err),
}
}
#[inline]
fn get_config_path() -> PathBuf {
if cfg!(target_os = "windows") {
let username = env::var("USERNAME").unwrap_or_else(|_| "Default".to_string());
PathBuf::from(format!(
"C:\\Users\\{}\\AppData\\Local\\filesorters\\filesorters.toml",
username
))
} else {
let xdg_config_home = env::var("XDG_CONFIG_HOME").ok();
let home = env::var("HOME").ok();
let user = env::var("USER").unwrap_or_else(|_| "default".to_string());
xdg_config_home
.map(|x| PathBuf::from(format!("{}/filesorters.toml", x)))
.or_else(|| home.map(|h| PathBuf::from(format!("{}/.config/filesorters.toml", h))))
.unwrap_or_else(|| PathBuf::from(format!("/home/{}/.config/filesorters.toml", user)))
}
}