diff --git a/foldiff/src/cliutils.rs b/foldiff/src/cliutils.rs index 05e9ae6..946caf1 100644 --- a/foldiff/src/cliutils.rs +++ b/foldiff/src/cliutils.rs @@ -1,8 +1,8 @@ use anyhow::Result; use dialoguer::Confirm; -use indicatif::{ProgressBar, ProgressStyle}; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use std::sync::LazyLock; -use std::time::Duration; +use libfoldiff::reporting::{CanBeWrappedBy, Reporter, ReporterSized, ReportingMultiWrapper}; pub fn confirm(msg: &str) -> Result { Ok(Confirm::new().with_prompt(msg).interact()?) @@ -45,34 +45,120 @@ static PROGRESS_STYLE_FINISHED: LazyLock = LazyLock::new(|| { ).unwrap().tick_strings(SPINNER_TICKS) }); -pub fn create_spinner(msg: &str, count: bool, auto: bool) -> ProgressBar { - let s = ProgressBar::new_spinner().with_message(msg.to_string()).with_style( - if count { SPINNER_STYLE_COUNT.clone() } else { SPINNER_STYLE_SIMPLE.clone() } - ); - if auto { - s.enable_steady_tick(Duration::from_millis(50)); - s.tick(); +// implement libfoldiff::reporting for indicatif + +pub struct Spinner(ProgressBar); + +impl Reporter for Spinner { + fn new(msg: &str) -> Self { + Self(ProgressBar::new_spinner() + .with_message(msg.to_string()) + .with_style( + if COUNT { SPINNER_STYLE_COUNT.clone() } else { SPINNER_STYLE_SIMPLE.clone() } + )) + } + + fn incr(&self, n: usize) { + // TODO: retain normal steady-tick implementation of incr() not ticking it + self.0.inc(n as u64); + } + + fn count(&self) -> usize { + self.0.position() as usize + } + + fn tick(&self) { + self.0.tick(); + } + + fn done_clear(&self) { + self.0.finish_and_clear(); + } + + fn done(&self) { + self.0.set_style( + if COUNT { SPINNER_STYLE_FINISHED_COUNT.clone() } else { SPINNER_STYLE_FINISHED_SIMPLE.clone() } + ); + self.0.abandon(); + } + + fn suspend R, R>(&self, f: F) -> R { + self.0.suspend(f) } - s } -pub fn finish_spinner(s: &ProgressBar, count: bool) { - s.set_style( - if count { SPINNER_STYLE_FINISHED_COUNT.clone() } else { SPINNER_STYLE_FINISHED_SIMPLE.clone() } - ); - s.abandon(); +pub struct Bar(ProgressBar); + +impl Reporter for Bar { + fn new(msg: &str) -> Self { + Self(ProgressBar::new(0) + .with_message(msg.to_string()) + .with_style(PROGRESS_STYLE.clone())) + } + + fn incr(&self, n: usize) { + self.0.inc(n as u64); + } + + fn count(&self) -> usize { + self.0.position() as usize + } + + fn tick(&self) { + self.0.tick(); + } + + fn done_clear(&self) { + self.0.finish_and_clear(); + } + + fn done(&self) { + self.0.set_style(PROGRESS_STYLE_FINISHED.clone()); + self.0.abandon(); + } + + fn suspend R, R>(&self, f: F) -> R { + self.0.suspend(f) + } +} + +impl ReporterSized for Bar { + fn new(msg: &str, len: usize) -> Self { + Self(ProgressBar::new(len as u64) + .with_message(msg.to_string()) + .with_style(PROGRESS_STYLE.clone())) + } + + fn set_len(&self, len: usize) { + self.0.set_length(len as u64); + } + + fn length(&self) -> usize { + self.0.length().unwrap() as usize + } +} + +// this is really unnecessary but ok rust, sure, foreign trait implementation rules +pub struct MultiWrapper(MultiProgress); + +impl ReportingMultiWrapper for MultiWrapper { + fn new() -> Self { + MultiWrapper(MultiProgress::new()) + } + + fn suspend R, R>(&self, f: F) -> R { + self.0.suspend(f) + } } -pub fn create_bar(msg: &str, len: u64, auto: bool) -> ProgressBar { - let b = ProgressBar::new(len).with_message(msg.to_string()).with_style(PROGRESS_STYLE.clone()); - if auto { - b.enable_steady_tick(Duration::from_millis(50)); - b.tick(); +impl CanBeWrappedBy for Spinner { + fn add_to(self, w: &MultiWrapper) -> Self { + Spinner(w.0.add(self.0)) } - b } -pub fn finish_bar(b: &ProgressBar) { - b.set_style(PROGRESS_STYLE_FINISHED.clone()); - b.abandon(); +impl CanBeWrappedBy for Bar { + fn add_to(self, w: &MultiWrapper) -> Self { + Bar(w.0.add(self.0)) + } } \ No newline at end of file diff --git a/foldiff/src/main.rs b/foldiff/src/main.rs index b518500..999945c 100644 --- a/foldiff/src/main.rs +++ b/foldiff/src/main.rs @@ -132,11 +132,11 @@ fn main() -> Result<()> { } // scan the file system - let mut diff_state = libfoldiff::diffing::scan_to_diff(old_root, new_root)?; + let mut diff_state = libfoldiff::diffing::scan_to_diff::>(old_root, new_root)?; //println!("{diff_state:?}"); // emit the diff to disk - diff_state.write_to_file(Path::new(diff), &cfg)?; + diff_state.write_to_file::>(Path::new(diff), &cfg)?; } Commands::Apply { old, diff, new } => { @@ -159,16 +159,20 @@ fn main() -> Result<()> { } let mut diff_state = libfoldiff::applying::read_diff_from_file(&PathBuf::from(diff))?; - diff_state.apply(old_root, new_root)?; + diff_state.apply::< + cliutils::MultiWrapper, + cliutils::Spinner, + cliutils::Bar + >(old_root, new_root)?; }, Commands::Verify { new, old, diff } => { if let Some(diff) = diff { let f = File::open(diff).context("Failed to open diff file to verify with")?; let manifest = DiffManifest::read_from(f).context("Failed to read diff file to verify with")?; - libfoldiff::verify::verify_against_diff(Path::new(old), Path::new(new), &manifest)?; + libfoldiff::verify::verify_against_diff::>(Path::new(old), Path::new(new), &manifest)?; } else { - libfoldiff::verify::test_dir_equality(Path::new(old), Path::new(new))?; + libfoldiff::verify::test_dir_equality::>(Path::new(old), Path::new(new))?; } }, Commands::Upgrade { new, old } => { @@ -186,7 +190,7 @@ fn main() -> Result<()> { let fold = File::open(old).context("Failed to open old diff file")?; let fnew = File::create(new).context("Failed to create destination file")?; - libfoldiff::upgrade::auto_upgrade(fold, fnew)?; + libfoldiff::upgrade::auto_upgrade::>(fold, fnew)?; }, } diff --git a/libfoldiff/src/applying.rs b/libfoldiff/src/applying.rs index caa788e..d75d557 100644 --- a/libfoldiff/src/applying.rs +++ b/libfoldiff/src/applying.rs @@ -1,14 +1,14 @@ +use crate::common::create_file; +use crate::manifest::DiffManifest; +use crate::reporting::{AutoSpin, CanBeWrappedBy, Reporter, ReporterSized, ReportingMultiWrapper}; +use crate::{aggregate_errors, handle_res_parit, hash, zstddiff}; +use anyhow::{anyhow, Context}; +use memmap2::Mmap; +use rayon::prelude::*; use std::fs::File; use std::io::{Cursor, Read, Seek}; use std::path::{Path, PathBuf}; use std::sync::Mutex; -use std::time::Duration; -use anyhow::{anyhow, Context}; -use memmap2::Mmap; -use crate::manifest::DiffManifest; -use rayon::prelude::*; -use crate::{aggregate_errors, handle_res_parit, hash, zstddiff}; -use crate::common::create_file; /// An in-memory representation of a diff, used for the applying process #[derive(Debug, Default)] @@ -22,7 +22,11 @@ pub struct ApplyingDiff { } impl ApplyingDiff { - pub fn apply(&mut self, old_root: PathBuf, new_root: PathBuf) -> anyhow::Result<()> { + pub fn apply< + TWrap: ReportingMultiWrapper, + TSpin: Reporter + CanBeWrappedBy + Sync, + TBar: ReporterSized + CanBeWrappedBy + Sync + >(&mut self, old_root: PathBuf, new_root: PathBuf) -> anyhow::Result<()> { self.old_root = old_root; self.new_root = new_root; @@ -31,31 +35,31 @@ impl ApplyingDiff { let num_duped_files: u64 = self.manifest.duplicated_files.iter().map(|d| d.new_paths.len() as u64).sum(); // incr bar and finish if done - let inc_n = |n: u64, b: &ProgressBar| { - b.inc(n); - if Some(b.position()) == b.length() { - cliutils::finish_bar(b); + let inc_n = |n: usize, b: &TBar| { + b.incr(n); + if b.count() == b.length() { + b.done(); } }; - let inc = |b: &ProgressBar| inc_n(1, b); + let inc = |b: &TBar| inc_n(1, b); // progress reporting - let wrap = MultiProgress::new(); - let spn = wrap.add(cliutils::create_spinner("Applying diff", false, false)); - let bar_untouched = wrap.add(cliutils::create_bar("Copying unchanged files", (self.manifest.untouched_files.len() as u64) + num_duped_files, false)); - let bar_new = wrap.add(cliutils::create_bar("Creating new files", self.manifest.new_files.len() as u64, false)); - let bar_patched = wrap.add(cliutils::create_bar("Applying patched files", self.manifest.patched_files.len() as u64, false)); - - // need to do this manually because of it being in a wrap - for b in [&spn, &bar_untouched, &bar_new, &bar_patched] { - b.enable_steady_tick(Duration::from_millis(50)); - } + let wrap = TWrap::new(); + let spn = TSpin::new("Applying diff").add_to(&wrap); + let bar_untouched = ::new("Copying unchanged files", self.manifest.untouched_files.len() + (num_duped_files as usize)).add_to(&wrap); + let bar_new = ::new("Creating new files", self.manifest.new_files.len()).add_to(&wrap); + let bar_patched = ::new("Applying patched files", self.manifest.patched_files.len()).add_to(&wrap); + + let as1 = AutoSpin::spin(&spn); + let as2 = AutoSpin::spin(&bar_untouched); + let as3 = AutoSpin::spin(&bar_new); + let as4 = AutoSpin::spin(&bar_patched); // let's spawn some threads! let errs = Mutex::new(Vec::new()); rayon::scope(|s| { if self.manifest.untouched_files.is_empty() && self.manifest.duplicated_files.is_empty() { - bar_untouched.finish_and_clear(); + bar_untouched.done_clear(); } else { s.spawn(|_| { @@ -161,12 +165,12 @@ impl ApplyingDiff { return; } - inc_n(d.new_paths.len() as u64, &bar_untouched); + inc_n(d.new_paths.len(), &bar_untouched); } }); } if self.manifest.new_files.is_empty() { - bar_new.finish_and_clear(); + bar_new.done_clear(); } else { s.spawn(|_| { @@ -212,7 +216,7 @@ impl ApplyingDiff { }); } if self.manifest.patched_files.is_empty() { - bar_patched.finish_and_clear(); + bar_patched.done_clear(); } else { s.spawn(|_| { @@ -267,7 +271,10 @@ impl ApplyingDiff { aggregate_errors!(errs.into_inner()?); - cliutils::finish_spinner(&spn, false); + as1.all_good(); + drop(as2); + drop(as3); + drop(as4); Ok(()) } } diff --git a/libfoldiff/src/diffing.rs b/libfoldiff/src/diffing.rs index 4846026..4ce992a 100644 --- a/libfoldiff/src/diffing.rs +++ b/libfoldiff/src/diffing.rs @@ -2,7 +2,6 @@ use std::collections::BTreeMap; use std::fs::File; use std::io::{copy, Seek, Write}; use std::path::{Path, PathBuf}; -use std::time::Duration; use anyhow::{anyhow, bail, Context}; use rmp_serde::Serializer; use serde::Serialize; @@ -10,6 +9,7 @@ use zstd::Encoder; use crate::common::{FoldiffCfg, MAGIC_BYTES, VERSION_NUMBER_LATEST}; use crate::manifest::{DiffManifest, DuplicatedFile, NewFile, PatchedFile}; use crate::{hash, zstddiff}; +use crate::reporting::{AutoSpin, Reporter, ReporterSized}; /// An in-memory representation of a diff, used for the diff creation process #[derive(Clone, Debug, Default)] @@ -44,7 +44,7 @@ impl DiffingDiff { /// handles finalising an in-memory diffing state to disk /// takes mut as it also has to set blobs_new and blobs_patch - pub fn write_to(&mut self, writer: &mut (impl Write + Seek), cfg: &FoldiffCfg) -> anyhow::Result<()> { + pub fn write_to(&mut self, writer: &mut (impl Write + Seek), cfg: &FoldiffCfg) -> anyhow::Result<()> { writer.write_all(&MAGIC_BYTES)?; // write version number, includes null byte @@ -55,7 +55,7 @@ impl DiffingDiff { let mut wr = countio::Counter::new(&mut *writer); let mut serializer = Serializer::new(Encoder::new(&mut wr, 19)?.auto_finish()); self - .generate_manifest()? + .generate_manifest::()? .serialize(&mut serializer) .context("Failed to serialize diff format into file")?; @@ -70,7 +70,7 @@ impl DiffingDiff { writer.write_all(&(self.blobs_new.len() as u64).to_be_bytes())?; if !self.blobs_new.is_empty() { - let bar = cliutils::create_bar("Compressing new files", self.blobs_new.len() as u64, true); + let bar = ::new("Compressing new files", self.blobs_new.len()); for path in &self.blobs_new { let mut f = File::open(self.new_root.join(path)).context("Failed to open file while copying newly added files")?; @@ -83,7 +83,7 @@ impl DiffingDiff { enc.set_pledged_src_size(Some(f.metadata()?.len()))?; enc.include_checksum(false)?; enc.include_contentsize(false)?; - enc.multithread(cfg.threads)?; + enc.multithread(cfg.threads as u32)?; copy(&mut f, &mut enc)?; enc.finish()?; @@ -94,9 +94,9 @@ impl DiffingDiff { writer.write_all(&bytes.to_be_bytes())?; writer.seek_relative(bytes as i64)?; - bar.inc(1); + bar.incr(1); } - cliutils::finish_bar(&bar); + bar.done(); } // write patches @@ -105,7 +105,7 @@ impl DiffingDiff { // perform diffing if !self.blobs_patch.is_empty() { - let bar = cliutils::create_bar("Diffing changed files", self.blobs_patch.len() as u64, true); + let bar = ::new("Diffing changed files", self.blobs_patch.len()); for p in &self.blobs_patch { let mut old = File::open(self.old_root.join(p)).context("Failed to open old file for diffing")?; let mut new = File::open(self.new_root.join(p)).context("Failed to open new file for diffing")?; @@ -115,24 +115,24 @@ impl DiffingDiff { zstddiff::diff(&mut old, &mut new, &mut *writer, Some(cfg.level_diff), Some(cfg.threads), Some(ol), Some(nl)) .context("Failed to perform diff")?; - bar.inc(1); + bar.incr(1); } - cliutils::finish_bar(&bar); + bar.done(); } Ok(()) } - pub fn write_to_file(&mut self, path: &Path, cfg: &FoldiffCfg) -> anyhow::Result<()> { + pub fn write_to_file(&mut self, path: &Path, cfg: &FoldiffCfg) -> anyhow::Result<()> { // create file let mut f = File::create_new(path).context("Failed to create file to save diff")?; - self.write_to(&mut f, cfg) + self.write_to::(&mut f, cfg) } /// generates the on-disk manifest format from the in-memory working data /// also populates self.blobs_new and self.blobs_patch - pub fn generate_manifest(&mut self) -> anyhow::Result { + pub fn generate_manifest(&mut self) -> anyhow::Result { // generally, the on-disk manifest is a really annoying data structure for building diffs // so instead, we work with a map from hash to file data, as if every file was a duplicated one // this function will figure out which files fall into which category, @@ -152,10 +152,9 @@ impl DiffingDiff { let mut manifest = DiffManifest::default(); - // this is *so* fast that i'm not even going to bother with a progress bar. - //let bar = cliutils::create_bar("Sorting scanned files", self.files.len() as u64); - let spn = cliutils::create_spinner("Sorting scanned files", false, true); - spn.enable_steady_tick(Duration::from_millis(150)); + // this is *so* fast that i'm not even going to bother with a progress bar, a spinner is fine. + let spn = TSpin::new("Sorting scanned files"); + let spn = AutoSpin::spin(&spn); for (hash, entry) in &self.files { // step 1: are we unchanged? @@ -245,8 +244,8 @@ impl DiffingDiff { bail!("All potential scan entry cases should have been handled, but this entry is slipping through the cracks:\n{entry:?}"); } - cliutils::finish_spinner(&spn, false); - + spn.all_good(); + // we're done! Ok(manifest) } @@ -291,7 +290,7 @@ impl DiffingDiff { Ok(()) } - fn scan_internal(&mut self, dir: &Path, new: bool, spinner: Option<&ProgressBar>) -> anyhow::Result<()> { + fn scan_internal(&mut self, dir: &Path, new: bool, spn: &impl Reporter) -> anyhow::Result<()> { let root = if new { &self.new_root } else { &self.old_root }; // we need to clone this, aw let root = root.clone(); @@ -302,11 +301,8 @@ impl DiffingDiff { for entry in entries { let entry = entry.with_context(|| format!("Failed to read entry while scanning {dir:?}"))?; - // tick! - if let Some(s) = spinner { - s.inc(1); - } - + spn.incr(1); + // are we a directory or a file? let ftype = entry.file_type().context("While reading entry type")?; if ftype.is_symlink() { @@ -317,7 +313,7 @@ impl DiffingDiff { let path = path.strip_prefix(&root)?; if ftype.is_dir() { // recurse - self.scan_internal(&path, new, spinner)?; + self.scan_internal(&path, new, spn)?; } else { // file found! @@ -329,16 +325,18 @@ impl DiffingDiff { } } -pub fn scan_to_diff(old_root: PathBuf, new_root: PathBuf) -> anyhow::Result { +pub fn scan_to_diff(old_root: PathBuf, new_root: PathBuf) -> anyhow::Result { let mut new_self = DiffingDiff::new(old_root, new_root); - let bar = cliutils::create_spinner("Scanning old files", true, true); - new_self.scan_internal(Path::new(""), false, Some(&bar))?; - cliutils::finish_spinner(&bar, true); + let spn = TSpin::new("Scanning old files"); + let aspn = AutoSpin::spin(&spn); + new_self.scan_internal(Path::new(""), false, &spn)?; + aspn.all_good(); - let bar = cliutils::create_spinner("Scanning new files", true, true); - new_self.scan_internal(Path::new(""), true, Some(&bar))?; - cliutils::finish_spinner(&bar, true); + let spn = TSpin::new("Scanning new files"); + let aspn = AutoSpin::spin(&spn); + new_self.scan_internal(Path::new(""), true, &spn)?; + aspn.all_good(); Ok(new_self) } \ No newline at end of file diff --git a/libfoldiff/src/lib.rs b/libfoldiff/src/lib.rs index d1ddaac..d7974b6 100644 --- a/libfoldiff/src/lib.rs +++ b/libfoldiff/src/lib.rs @@ -7,6 +7,7 @@ pub mod applying; mod threading; pub mod upgrade; pub mod verify; +pub mod reporting; pub use crate::threading::set_num_threads; pub use crate::common::FoldiffCfg; \ No newline at end of file diff --git a/libfoldiff/src/manifest.rs b/libfoldiff/src/manifest.rs index 4d64da0..6e7eda1 100644 --- a/libfoldiff/src/manifest.rs +++ b/libfoldiff/src/manifest.rs @@ -22,14 +22,14 @@ pub struct DiffManifest { type HashAndPath = (u64, String); #[derive(Clone, Debug, Serialize, Deserialize, Default)] -pub(crate) struct NewFile { +pub struct NewFile { pub hash: u64, pub index: u64, pub path: String, } #[derive(Clone, Debug, Serialize, Deserialize, Default)] -pub(crate) struct DuplicatedFile { +pub struct DuplicatedFile { pub hash: u64, pub idx: u64, // u64::MAX == none pub old_paths: Vec, @@ -37,7 +37,7 @@ pub(crate) struct DuplicatedFile { } #[derive(Clone, Debug, Serialize, Deserialize, Default)] -pub(crate) struct PatchedFile { +pub struct PatchedFile { pub old_hash: u64, pub new_hash: u64, pub index: u64, diff --git a/libfoldiff/src/reporting.rs b/libfoldiff/src/reporting.rs new file mode 100644 index 0000000..9552444 --- /dev/null +++ b/libfoldiff/src/reporting.rs @@ -0,0 +1,91 @@ +use std::mem::MaybeUninit; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::thread::JoinHandle; + +pub trait ReportingMultiWrapper { + fn new() -> Self; + // one could provide an implementation if you can get the list of bars, + // but we can't assume we can, so, don't. + fn suspend R, R>(&self, f: F) -> R; +} + +// represents a generic progress reporting struct +pub trait Reporter { + fn new(msg: &str) -> Self; + // screw you, non-mut references, build a wrapper with mutex if its an issue. + fn incr(&self, n: usize); + fn count(&self) -> usize; + fn tick(&self); + fn done_clear(&self); + fn done(&self); + fn suspend R, R>(&self, f: F) -> R; +} + +// a progress reporter that has a size, eg a bar +pub trait ReporterSized: Reporter { + fn new(msg: &str, len: usize) -> Self; + fn set_len(&self, len: usize); + fn length(&self) -> usize; +} + +pub trait CanBeWrappedBy : Reporter { + fn add_to(self, w: &W) -> Self; +} + +pub(crate) struct AutoSpin<'a, R: Reporter+Sync> { + run: Box, + jh: MaybeUninit>, + rep: &'a R, // stored exclusively for all_good(). + //_ph: PhantomData<&'a R>, +} + +impl<'a, R: Reporter+Sync> AutoSpin<'a, R> { + pub fn spin(r: &'a R) -> Self { + // construct self + let mut s = Self { + // box so the ptr never moves + run: Box::new(AtomicBool::new(true)), + rep: r, + jh: MaybeUninit::zeroed(), + //_ph: PhantomData::default() + }; + + // man wtf + // i hate this language some of the time. not most of it, but some of it. + // i'll be looking into a less ass way of doing this. + // i am very sure this is safe, i just don't know how to convince rustc of that. + // i really could do with a version of std::thread::scope that used a struct's scope or something + let run_ptr = std::ptr::from_mut(s.run.as_mut()) as usize; + let rep_ptr = std::ptr::from_ref(r) as usize; + + s.jh.write(thread::spawn(move || { + let run_ptr = run_ptr as *mut AtomicBool; + let rep_ptr = rep_ptr as *const R; + + while unsafe { (*run_ptr).load(Ordering::Acquire) } { + unsafe { &*rep_ptr }.tick(); + thread::sleep_ms(50); + } + })); + + s + } + + /// finishes autospinning then calls done() on the internal object. + /// mainly useful to extend the lifetime of autospin in a neater way than explicit drop(). + pub fn all_good(self) { + self.rep.done(); + // self drops here to finish autospinning + } +} + +impl<'a, R: Reporter+Sync> Drop for AutoSpin<'a, R> { + fn drop(&mut self) { + // tell the thread to stop + self.run.store(false, Ordering::Release); + // wait for it to stop + let uninit = std::mem::replace(&mut self.jh, MaybeUninit::zeroed()); + unsafe { uninit.assume_init() }.join().unwrap(); + } +} diff --git a/libfoldiff/src/upgrade.rs b/libfoldiff/src/upgrade.rs index 7cb324f..62520f0 100644 --- a/libfoldiff/src/upgrade.rs +++ b/libfoldiff/src/upgrade.rs @@ -3,10 +3,12 @@ use crate::common::{MAGIC_BYTES, VERSION_NUMBER_1_1_0, VERSION_NUMBER_1_0_0_R, V use anyhow::{bail, Context, Result}; use std::io::{Read, Seek, Write}; use zstd::Encoder; +use crate::reporting::{AutoSpin, Reporter}; // 1.0.0-r to v1.1.0 -fn upgrade_100r_110(mut src: impl Read+Seek, mut dst: impl Write+Seek) -> Result<()> { - let s = cliutils::create_spinner("Upgrading from FLDF 1.0.0-r to FLDF 1.1.0", false, true); +fn upgrade_100r_110(mut src: impl Read+Seek, mut dst: impl Write+Seek) -> Result<()> { + let s = TSpin::new("Upgrading from FLDF 1.0.0-r to FLDF 1.1.0"); + let s = AutoSpin::spin(&s); // write magic bytes and version number to dst dst.write_all(&MAGIC_BYTES).context("Failed to write to destination file")?; @@ -35,16 +37,16 @@ fn upgrade_100r_110(mut src: impl Read+Seek, mut dst: impl Write+Seek) -> Result // copy the rest of the data over (blobs) std::io::copy(&mut src, &mut dst)?; - cliutils::finish_spinner(&s, false); + s.all_good(); Ok(()) } -pub fn auto_upgrade(mut src: impl Read+Seek, dst: impl Write+Seek) -> Result<()> { +pub fn auto_upgrade(mut src: impl Read+Seek, dst: impl Write+Seek) -> Result<()> { let ver = DiffManifest::verify_and_read_ver(&mut src)?; match ver { VERSION_NUMBER_LATEST => bail!("Diff is up to date! (FLDF v{}.{}.{})", ver[1], ver[2], ver[3]), - VERSION_NUMBER_1_0_0_R => upgrade_100r_110(src, dst), + VERSION_NUMBER_1_0_0_R => upgrade_100r_110::(src, dst), _ => unreachable!(), } } \ No newline at end of file diff --git a/libfoldiff/src/verify.rs b/libfoldiff/src/verify.rs index 6e2e41e..d986a89 100644 --- a/libfoldiff/src/verify.rs +++ b/libfoldiff/src/verify.rs @@ -6,16 +6,18 @@ use rayon::prelude::*; use std::collections::BTreeSet; use std::fs; use std::path::Path; +use crate::reporting::{AutoSpin, Reporter}; /// Checks if two directories are identical, printing results to stdout -pub fn test_dir_equality(r1: &Path, r2: &Path) -> Result<()> { - let spinner = cliutils::create_spinner("Scanning folders", true, true); - test_equality_internal(r1, r2, Path::new(""), &spinner)?; - cliutils::finish_spinner(&spinner, true); +pub fn test_dir_equality(r1: &Path, r2: &Path) -> Result<()> { + let spn = TSpin::new("Scanning folders"); + let aspn = AutoSpin::spin(&spn); + test_equality_internal(r1, r2, Path::new(""), &spn)?; + aspn.all_good(); Ok(()) } -fn test_equality_internal(r1: &Path, r2: &Path, p: &Path, spn: &ProgressBar) -> Result<()> { +fn test_equality_internal(r1: &Path, r2: &Path, p: &Path, spn: &(impl Reporter+Sync)) -> Result<()> { // stat both paths let path1 = r1.join(p); let path2 = r2.join(p); @@ -29,7 +31,7 @@ fn test_equality_internal(r1: &Path, r2: &Path, p: &Path, spn: &ProgressBar) -> bail!("Found a symlink at {:?}", path2); } - spn.inc(1); + spn.incr(1); if type1.is_file() { if type2.is_file() { @@ -79,7 +81,7 @@ fn test_equality_internal(r1: &Path, r2: &Path, p: &Path, spn: &ProgressBar) -> spn.suspend(|| { println!("{:?} only exists in the first folder.", p.join(f)); }); - spn.inc(1); + spn.incr(1); } else { // we have both! recurse. @@ -96,7 +98,7 @@ fn test_equality_internal(r1: &Path, r2: &Path, p: &Path, spn: &ProgressBar) -> spn.suspend(|| { println!("{:?} only exists in the second folder.", p.join(f)); }); - spn.inc(1); + spn.incr(1); } }), ); @@ -108,8 +110,9 @@ fn test_equality_internal(r1: &Path, r2: &Path, p: &Path, spn: &ProgressBar) -> } /// Checks if two directories match the given manifest, printing results to stdout -pub fn verify_against_diff(r1: &Path, r2: &Path, manifest: &DiffManifest) -> Result<()> { - let spn = cliutils::create_spinner("Verifying files", true, true); +pub fn verify_against_diff(r1: &Path, r2: &Path, manifest: &DiffManifest) -> Result<()> { + let spn = TSpin::new("Verifying files"); + let aspn = AutoSpin::spin(&spn); let errors: Vec<_> = manifest.untouched_files @@ -147,7 +150,7 @@ pub fn verify_against_diff(r1: &Path, r2: &Path, manifest: &DiffManifest) -> Res println!("{p:?} is not as expected"); }) } - spn.inc(1); + spn.incr(1); anyhow::Ok(()) }) .filter_map(|r| match r { @@ -156,7 +159,7 @@ pub fn verify_against_diff(r1: &Path, r2: &Path, manifest: &DiffManifest) -> Res }) .collect(); - cliutils::finish_spinner(&spn, true); + aspn.all_good(); aggregate_errors!(errors);