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)) }