From c3a8ade31adbfdea8d3439767b62dbfea3cda578 Mon Sep 17 00:00:00 2001 From: wardwouts Date: Tue, 26 Nov 2024 22:17:15 +0100 Subject: [PATCH] refactor --- src/dirlist.rs | 146 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 154 +++++++++---------------------------------------- 2 files changed, 172 insertions(+), 128 deletions(-) create mode 100644 src/dirlist.rs diff --git a/src/dirlist.rs b/src/dirlist.rs new file mode 100644 index 0000000..1593acb --- /dev/null +++ b/src/dirlist.rs @@ -0,0 +1,146 @@ +use std::fs; +use std::fs::File; +use std::path::{Path, PathBuf}; +use std::io::{ErrorKind, BufRead, BufReader}; +use std::collections::HashMap; +use rand::distributions::{Alphanumeric, DistString}; + + + +const POSTFIX_TMP_FILE: &str = ".mvwrap"; + +pub struct DirList { + safe_source: bool, + pub entries: Vec, +} + +impl DirList { + pub fn from_current_dir() -> Self { + let paths : Vec = match fs::read_dir(".") { + Err(e) if e.kind() == ErrorKind::NotFound => Vec::new(), + Err(e) => panic!("Unexpected Error! {:?}", e), + Ok(entries) => entries.filter_map(|e| e.ok()) + .map(|e| e.path()) + .collect() + }; + + let mut path_list : Vec = vec![]; + for path in paths { + let path_string = path.display().to_string(); + if path_string != format!("./{}", POSTFIX_TMP_FILE) { + path_list.push(path_string[2..].to_string()); + } + } + path_list.sort(); + + Self { + safe_source: true, + entries: path_list, + } + } + + pub fn from_file(src_file: &String) -> Self { + let file = File::open(src_file).expect("no such file"); + let buf = BufReader::new(file); + let lines = buf.lines() + .map(|l| l.expect("Could not parse line")) + .collect(); + + Self { + safe_source: true, + entries: lines, + } + } + + pub fn from_list(list: &Vec) -> Self { + Self { + safe_source: false, + entries: list.to_vec(), + } + } + + pub fn move_to(&mut self, target_list: &DirList) -> Result<(), String> { + let src_len = self.entries.len(); + let mut intermediate_files: HashMap = HashMap::new(); + + self.run_checks(&target_list)?; + + for i in 0..src_len { + if self.entries[i] != target_list.entries[i] { + // is the destination already in the source list? + // if so, an intermediate file is needed + if self.entries.iter().any(|j| j==&target_list.entries[i]) { + let unique = Self::unique_entry(&target_list); + intermediate_files.insert(unique.clone(), target_list.entries[i].clone()); + fs::rename(&self.entries[i], &unique).expect("failed to rename file"); + println!("Moving {} -> {}", self.entries[i], unique); + } else { + fs::rename(&self.entries[i], &target_list.entries[i]).expect("failed to rename file"); + println!("Moving {} -> {}", self.entries[i], target_list.entries[i]); + } + } + } + for (src, dst) in intermediate_files.iter() { + fs::rename(src, dst).expect("failed to rename file"); + println!("Moving {} -> {}", src, dst); + } + Ok(()) + } + + fn unique_entry(target_list: &DirList) -> String { + let mut string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); + + // Generate unique name that does not exist in dir and is not in dst_paths + while target_list.entries.iter().any(|j| j==&string) || Path::new(&string).is_file() { + string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); + } + string + } + + fn run_checks(&self, target_list: &DirList) -> Result<(), String> { + // Make sure there are an equal number of sources and destinations + if self.entries.len() != target_list.entries.len() { + return Err("ERROR: Source and target list don't have the same number of + lines.".to_string()); + } + + // Make sure destination names are not empty + if target_list.entries.iter().any(|i| i.is_empty()) { + return Err("ERROR: You can't move to empty names.".to_string()); + } + + // Make sure all destination files are unique + if target_list.entries.len() != target_list.unique_length() { + let doubles = self.show_doubles(&target_list); + let error = format!("ERROR: You're trying to move multiple files to the same name.\n{}", doubles); + return Err(error); + } + Ok(()) + } + + fn unique_length(&self) -> usize { + let map = self.entries.iter().map(|x| (x, x)).collect::>(); + map.len() + } + + + fn show_doubles(&self, target_list: &DirList) -> String { + let mut paths = HashMap::new(); + let mut doubles = String::new(); + + for (i, _) in target_list.entries.iter().enumerate() { + paths.entry(&target_list.entries[i]).or_insert(Vec::new()); + paths.get_mut(&target_list.entries[i]).unwrap().push(i); + } + + for (path, lines) in paths.iter() { + if lines.len() > 1 { + for i in lines.iter() { + doubles = format!("{doubles}\n[line: {}] {} -> {}", i+1, self.entries[*i], path); + } + doubles = format!("{doubles}\n"); + } + } + doubles + } +} diff --git a/src/main.rs b/src/main.rs index 3f6f105..f18df08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ +mod dirlist; + +pub use crate::dirlist::*; + use std::fs; use std::fs::File; -use std::path::{Path, PathBuf}; -use std::io::{ErrorKind, Write, BufRead, BufReader}; +use std::path::Path; +use std::io::Write; use std::process; use std::process::Command; -use std::collections::HashMap; use tempfile::NamedTempFile; use dialoguer::Confirm; -use rand::distributions::{Alphanumeric, DistString}; const LOCK_FILE: &str = ".mvwrap"; @@ -19,26 +21,6 @@ impl Drop for Cleanup { } } -fn read_current_dir() -> Vec { - let paths : Vec = match fs::read_dir(".") { - Err(e) if e.kind() == ErrorKind::NotFound => Vec::new(), - Err(e) => panic!("Unexpected Error! {:?}", e), - Ok(entries) => entries.filter_map(|e| e.ok()) - .map(|e| e.path()) - .collect() - }; - - let mut path_list : Vec = vec![]; - for path in paths { - let path_string = path.display().to_string(); - if path_string != format!("./{}", LOCK_FILE) { - path_list.push(path_string[2..].to_string()); - } - } - path_list.sort(); - path_list -} - fn lock_dir() { if Path::new(LOCK_FILE).is_file() { panic!("Lock file \"{}\" already exists!", LOCK_FILE); @@ -80,94 +62,6 @@ fn edit_temp_file(tmpfile: &String) { } -fn read_temp_file(tmpfile: &String) -> Vec { - let file = File::open(tmpfile).expect("no such file"); - let buf = BufReader::new(file); - buf.lines() - .map(|l| l.expect("Could not parse line")) - .collect() -} - -fn unique_length(dst_paths: &[String]) -> usize { - let map = dst_paths.iter().map(|x| (x, x)).collect::>(); - map.len() -} - -fn unique_filename(dst_paths: &[String]) -> String { - let mut string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); - - // Generate unique name that does not exist in dir and is not in dst_paths - while dst_paths.iter().any(|j| j==&string) || Path::new(&string).is_file() { - string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); - } - string -} - -fn move_safely(src_paths: &[String], dst_paths: &[String]) { - let src_len = src_paths.len(); - let mut intermediate_files: HashMap = HashMap::new(); - - for i in 0..src_len { - if src_paths[i] != dst_paths[i] { - // is the destination already in the source list? - // if so, an intermediate file is needed - if src_paths.iter().any(|j| j==&dst_paths[i]) { - let unique = unique_filename(dst_paths); - intermediate_files.insert(unique.clone(), dst_paths[i].clone()); - fs::rename(&src_paths[i], &unique).expect("failed to rename file"); - println!("Moving {} -> {}", src_paths[i], unique); - } else { - fs::rename(&src_paths[i], &dst_paths[i]).expect("failed to rename file"); - println!("Moving {} -> {}", src_paths[i], dst_paths[i]); - } - } - } - for (src, dst) in intermediate_files.iter() { - fs::rename(src, dst).expect("failed to rename file"); - println!("Moving {} -> {}", src, dst); - } -} - -fn run_checks(src_paths: &[String], dst_paths: &[String]) -> bool { - // Make sure there are an equal number of sources and destinations - if src_paths.len() != dst_paths.len() { - println!("ERROR: Source and target list don't have the same number of lines."); - return true - } - - // Make sure destination names are not empty - if dst_paths.iter().any(|i| i.is_empty()) { - println!("ERROR: You can't move to empty names."); - return true - } - - // Make sure all destination files are unique - let dst_paths_length_unique = unique_length(dst_paths); - if dst_paths.len() != dst_paths_length_unique { - println!("ERROR: You're trying to move multiple files to the same name."); - show_doubles(src_paths, dst_paths); - return true - } - false -} - -fn show_doubles(src_paths: &[String], dst_paths: &[String]) { - let mut paths = HashMap::new(); - - for (i, _) in dst_paths.iter().enumerate() { - paths.entry(&dst_paths[i]).or_insert(Vec::new()); - paths.get_mut(&dst_paths[i]).unwrap().push(i); - } - - for (path, lines) in paths.iter() { - if lines.len() > 1 { - for i in lines.iter() { - println!("[line: {}] {} -> {}", i+1, src_paths[*i], path); - } - } - } - -} fn main() { // Place lockfile to not have multiple mvw processes running in the same dir at the same time @@ -175,36 +69,40 @@ fn main() { let _cleanup = Cleanup; // Cleanup LOCK_FILE on panic // read directory contents into vector of Strings - let paths = read_current_dir(); + let mut source_list = DirList::from_current_dir(); // create named tempfile and fill with paths - let temp_file = create_temp_file(&paths); + let temp_file = create_temp_file(&source_list.entries); //display_temp_file(&temp_file); edit_temp_file(&temp_file); - let mut new_paths = read_temp_file(&temp_file); + let mut target_list = DirList::from_file(&temp_file); - while run_checks(&paths, &new_paths) { - let confirmation = Confirm::new() - .with_prompt("Continue editing?") - .interact() - .unwrap(); + loop { + match source_list.move_to(&target_list) { + Ok(_) => break, + Err(e) => { + println!("{}", e); + let confirmation = Confirm::new() + .with_prompt("Continue editing?") + .interact() + .unwrap(); - if ! confirmation { - unlock_dir(); - process::exit(1); + if ! confirmation { + unlock_dir(); + process::exit(1); + } + + edit_temp_file(&temp_file); + target_list = DirList::from_file(&temp_file); + } } - - edit_temp_file(&temp_file); - new_paths = read_temp_file(&temp_file); } // (also don't overwrite existing files that are not in the input list!) - move_safely(&paths, &new_paths); - //display_temp_file(&temp_file); remove_temp_file(&temp_file);