refactor
This commit is contained in:
parent
a823844538
commit
c3a8ade31a
2 changed files with 172 additions and 128 deletions
146
src/dirlist.rs
Normal file
146
src/dirlist.rs
Normal file
|
|
@ -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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DirList {
|
||||||
|
pub fn from_current_dir() -> Self {
|
||||||
|
let paths : Vec<PathBuf> = 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<String> = 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<String>) -> 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<String, String> = 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::<HashMap<_, _>>();
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
154
src/main.rs
154
src/main.rs
|
|
@ -1,13 +1,15 @@
|
||||||
|
mod dirlist;
|
||||||
|
|
||||||
|
pub use crate::dirlist::*;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
use std::io::{ErrorKind, Write, BufRead, BufReader};
|
use std::io::Write;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::collections::HashMap;
|
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use dialoguer::Confirm;
|
use dialoguer::Confirm;
|
||||||
use rand::distributions::{Alphanumeric, DistString};
|
|
||||||
|
|
||||||
const LOCK_FILE: &str = ".mvwrap";
|
const LOCK_FILE: &str = ".mvwrap";
|
||||||
|
|
||||||
|
|
@ -19,26 +21,6 @@ impl Drop for Cleanup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_current_dir() -> Vec<String> {
|
|
||||||
let paths : Vec<PathBuf> = 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<String> = 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() {
|
fn lock_dir() {
|
||||||
if Path::new(LOCK_FILE).is_file() {
|
if Path::new(LOCK_FILE).is_file() {
|
||||||
panic!("Lock file \"{}\" already exists!", LOCK_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<String> {
|
|
||||||
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::<HashMap<_, _>>();
|
|
||||||
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<String, String> = 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() {
|
fn main() {
|
||||||
// Place lockfile to not have multiple mvw processes running in the same dir at the same time
|
// 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
|
let _cleanup = Cleanup; // Cleanup LOCK_FILE on panic
|
||||||
|
|
||||||
// read directory contents into vector of Strings
|
// 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
|
// 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);
|
//display_temp_file(&temp_file);
|
||||||
|
|
||||||
edit_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) {
|
loop {
|
||||||
let confirmation = Confirm::new()
|
match source_list.move_to(&target_list) {
|
||||||
.with_prompt("Continue editing?")
|
Ok(_) => break,
|
||||||
.interact()
|
Err(e) => {
|
||||||
.unwrap();
|
println!("{}", e);
|
||||||
|
let confirmation = Confirm::new()
|
||||||
|
.with_prompt("Continue editing?")
|
||||||
|
.interact()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
if ! confirmation {
|
if ! confirmation {
|
||||||
unlock_dir();
|
unlock_dir();
|
||||||
process::exit(1);
|
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!)
|
// (also don't overwrite existing files that are not in the input list!)
|
||||||
|
|
||||||
move_safely(&paths, &new_paths);
|
|
||||||
|
|
||||||
//display_temp_file(&temp_file);
|
//display_temp_file(&temp_file);
|
||||||
|
|
||||||
remove_temp_file(&temp_file);
|
remove_temp_file(&temp_file);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue