From 6172ffe248041425d05e1e4893c4670afdfcd9fc Mon Sep 17 00:00:00 2001 From: Mark <> Date: Tue, 24 Dec 2024 01:37:18 +0100 Subject: [PATCH] show sizes in list of changes; sort; progress bar in the list of changes, "add file" and "add dir" type changes now show the size of the file to be copied or the sum of the directory content sizes. changes are now sorted from largest to smallest progress bar now shows the smaller and larger progress of change-count or transferred size, where the larger progress is before the '>' and the smaller one is on the last '=' before the '-'s start (which indicate larger-not-smaller) [========----------> ] --- src/apply_indexchanges.rs | 29 +++++++++------- src/indexchanges.rs | 2 +- src/main.rs | 61 +++++++++++++++++++++++++++------ src/update_index.rs | 71 +++++++++++++++++++++++++++++---------- 4 files changed, 121 insertions(+), 42 deletions(-) diff --git a/src/apply_indexchanges.rs b/src/apply_indexchanges.rs index 6f46b20..d204700 100755 --- a/src/apply_indexchanges.rs +++ b/src/apply_indexchanges.rs @@ -37,7 +37,7 @@ fn eprint_status( changes_len_width: usize, gib_len_width: usize, ) { - let leftpad = prog_width.min( + let leftpad_min = prog_width.min( (prog_width as f64 * f64::min( changes_applied as f64 / changes_total as f64, @@ -45,15 +45,24 @@ fn eprint_status( )) .round() as usize, ); + let leftpad_max = prog_width.min( + (prog_width as f64 + * f64::max( + changes_applied as f64 / changes_total as f64, + gib_transferred / gib_total, + )) + .round() as usize, + ); let changes_applied = changes_applied.to_string(); let changes_pad = " ".repeat(changes_len_width - changes_applied.len()); let gib_transferred = format!("{gib_transferred:.1}"); let gib_pad = " ".repeat(gib_len_width - gib_transferred.len()); - let rightpad = prog_width - leftpad; - let completed_prog = "-".repeat(leftpad); + let rightpad = prog_width - leftpad_max; + let completed_prog_min = "=".repeat(leftpad_min); + let completed_prog_max = "-".repeat(leftpad_max - leftpad_min); let pending_prog = " ".repeat(rightpad); eprint!( - "\r{changes_pad}{changes_applied}/{changes_total} | {gib_pad}{gib_transferred}/{gib_total:.1}GiB [{completed_prog}>{pending_prog}]", + "\r{changes_pad}{changes_applied}/{changes_total} | {gib_pad}{gib_transferred}/{gib_total:.1}GiB [{completed_prog_min}{completed_prog_max}>{pending_prog}]", ); } @@ -91,16 +100,10 @@ pub fn apply_indexchanges_int( ); for (i, change) in changes.iter().enumerate() { match change { - IndexChange::AddDir(dir) => { + IndexChange::AddDir(dir, _) => { let ok = if let Some(target) = target { let t = target.join(dir); - if let Some(e) = fs::create_dir(&t).err().and_then(|e| { - if matches!(e.kind(), io::ErrorKind::AlreadyExists) { - None - } else { - Some(e) - } - }) { + if let Err(e) = fs::create_dir_all(&t) { eprintln!("\n[warn] couldn't create directory {t:?}: {e}"); false } else { @@ -110,7 +113,7 @@ pub fn apply_indexchanges_int( true }; if ok { - fs::create_dir(&index.join(dir))?; + fs::create_dir_all(&index.join(dir))?; } } IndexChange::AddFile(file, index_file) => { diff --git a/src/indexchanges.rs b/src/indexchanges.rs index 09e2844..326ab91 100755 --- a/src/indexchanges.rs +++ b/src/indexchanges.rs @@ -5,7 +5,7 @@ use crate::indexfile::IndexFile; #[derive(Debug)] pub enum IndexChange { /// Ensure a directory with this path exists (at least if all its parent directories exist). - AddDir(PathBuf), + AddDir(PathBuf, u64), /// Add or update a file AddFile(PathBuf, IndexFile), /// Remove a file diff --git a/src/main.rs b/src/main.rs index 6087012..5abd729 100755 --- a/src/main.rs +++ b/src/main.rs @@ -71,12 +71,17 @@ fn main() { } else { Ignore(vec![]) }; - let changes = match perform_index_diff( + let (total_size, changes) = match perform_index_diff( &source, &index, target.as_ref().map(|v| v.as_path()), ignore, &args.settings, + if args.settings.dont_sort { + None + } else { + Some(!args.settings.smallest_first) + }, ) { Ok(c) => c, Err((what, path, err)) => { @@ -92,12 +97,42 @@ fn main() { } else { eprintln!("done! found {} changes:", changes.len()); // display the changes - for change in &changes { + if args.settings.dont_reverse_output { + for change in &changes { + show_change(change, false); + } + } else { + for change in changes.iter().rev() { + show_change(change, true); + } + } + fn show_change(change: &IndexChange, rev: bool) { match change { - IndexChange::AddDir(v) => eprintln!(" >> {}", v.display()), - IndexChange::AddFile(v, _) => eprintln!(" + {}", v.display()), + IndexChange::AddDir(v, s) => { + let mut path_str = v.display().to_string(); + if !path_str.ends_with(['/', '\\']) { + path_str.push('/'); + } + eprintln!( + "{}>> {} [{:.2} GiB]", + if rev { "^" } else { "v" }, + path_str, + *s as f64 / (1024 * 1024 * 1024) as f64 + ); + } + IndexChange::AddFile(v, f) => eprintln!( + " + {} ({:.3} GiB)", + v.display(), + f.size as f64 / (1024 * 1024 * 1024) as f64 + ), IndexChange::RemoveFile(v) => eprintln!(" - {}", v.display()), - IndexChange::RemoveDir(v) => eprintln!(" [-] {}", v.display()), + IndexChange::RemoveDir(v) => { + let mut path_str = v.display().to_string(); + if !path_str.ends_with(['/', '\\']) { + path_str.push('/'); + } + eprintln!(" [-] {}", path_str); + } } } eprintln!(" - - - - -"); @@ -105,8 +140,15 @@ fn main() { .iter() .filter(|c| matches!(c, IndexChange::AddDir(..))) .count(); - eprintln!(" >> add directory | {add_dir_count}x"); - let (add_file_count, add_file_total_size_gib) = changes + eprintln!( + " {}>> add directory | {add_dir_count}x", + if args.settings.dont_reverse_output { + "v" + } else { + "^" + } + ); + let add_file_count = changes .iter() .filter_map(|c| { if let IndexChange::AddFile(_, f) = c { @@ -115,9 +157,8 @@ fn main() { None } }) - .fold((0, 0.0f64), |(c, s), f| { - (c + 1, s + f.size as f64 / (1024 * 1024 * 1024) as f64) - }); + .count(); + let add_file_total_size_gib = total_size as f64 / (1024 * 1024 * 1024) as f64; eprintln!(" + add/update file | {add_file_count}x ({add_file_total_size_gib:.1} GiB)"); let remove_file_count = changes .iter() diff --git a/src/update_index.rs b/src/update_index.rs index 7e93ded..ba001c5 100755 --- a/src/update_index.rs +++ b/src/update_index.rs @@ -14,6 +14,16 @@ use crate::{ #[derive(Clone, Default, Args)] pub struct Settings { + /// don't sort the changes that form the backup. disables sort options. + #[arg(long)] + pub dont_sort: bool, + /// start with smaller directories rather than larger ones + #[arg(long)] + pub smallest_first: bool, + /// show changes in the order in which they will be applied, not reversed + #[arg(long)] + pub dont_reverse_output: bool, + /// don't update files just because their timestamp is different #[arg(long)] pub ignore_timestamp: bool, @@ -37,8 +47,8 @@ pub fn perform_index_diff<'a>( target: Option<&'a Path>, mut ignore: Ignore, settings: &Settings, -) -> Result, (String, PathBuf, io::Error)> { - let mut changes = Vec::new(); + sort_by_size_largest: Option, +) -> Result<(u64, Vec), (String, PathBuf, io::Error)> { if let Ok(inner_index) = index.strip_prefix(source) { eprintln!("[info] source contains index at {inner_index:?}, but index will not be part of the backup."); ignore.0.push(Specifier::InDir { @@ -55,15 +65,15 @@ pub fn perform_index_diff<'a>( }); } } - rec( + let (total_size, changes) = rec( source.as_ref(), Path::new(""), index, - &mut changes, &ignore, settings, + sort_by_size_largest, )?; - Ok(changes) + Ok((total_size, changes)) } fn rec( // location of source files @@ -72,18 +82,17 @@ fn rec( rel_path: &Path, // location of the index index_files: &Path, - // list of changes to be made - changes: &mut Vec, ignore: &Ignore, settings: &Settings, -) -> Result<(), (String, PathBuf, io::Error)> { + sort_by_size_largest: Option, +) -> Result<(u64, Vec), (String, PathBuf, io::Error)> { + let mut removals = vec![]; + let mut ichanges = vec![]; + let mut total_size = 0; // used to find removals let index_rel_path = index_files.join(rel_path); let mut index_entries = match fs::read_dir(&index_rel_path) { - Err(_) => { - changes.push(IndexChange::AddDir(rel_path.to_path_buf())); - HashMap::new() - } + Err(_) => HashMap::new(), Ok(e) => e .into_iter() .filter_map(|v| v.ok()) @@ -128,29 +137,55 @@ fn rec( if metadata.is_dir() { if let Some(false) = in_index_and_is_dir { // is dir, but was file -> remove file - changes.push(IndexChange::RemoveFile(rel_path.clone())); + removals.push(IndexChange::RemoveFile(rel_path.clone())); } - rec(source, &rel_path, index_files, changes, ignore, settings)?; + let (rec_size, rec_changes) = rec( + source, + &rel_path, + index_files, + ignore, + settings, + sort_by_size_largest, + )?; + total_size += rec_size; + ichanges.push((rec_size, rec_changes)); } else { if let Some(true) = in_index_and_is_dir { // is file, but was dir -> remove dir - changes.push(IndexChange::RemoveDir(rel_path.clone())); + removals.push(IndexChange::RemoveDir(rel_path.clone())); } let newif = IndexFile::new_from_metadata(&metadata); let oldif = IndexFile::from_path(&index_files.join(&rel_path)); match oldif { Ok(Ok(oldif)) if !newif.should_be_updated(&oldif, settings) => {} - _ => changes.push(IndexChange::AddFile(rel_path, newif)), + _ => { + total_size += newif.size; + ichanges.push((newif.size, vec![IndexChange::AddFile(rel_path, newif)])); + } } } } // removals for (removed_file, is_dir) in index_entries { - changes.push(if is_dir { + removals.push(if is_dir { IndexChange::RemoveDir(rel_path.join(removed_file)) } else { IndexChange::RemoveFile(rel_path.join(removed_file)) }); } - Ok(()) + // sorting + if let Some(sort_largest_first) = sort_by_size_largest { + if sort_largest_first { + ichanges.sort_by(|a, b| b.0.cmp(&a.0)); + } else { + ichanges.sort_by_key(|v| v.0); + } + } + // combine everything + let changes = [IndexChange::AddDir(rel_path.to_path_buf(), total_size)] + .into_iter() + .chain(removals.into_iter()) + .chain(ichanges.into_iter().flat_map(|(_, v)| v)) + .collect(); + Ok((total_size, changes)) }