Add progress messages and bar to imdl torrent verify

type: added
This commit is contained in:
Casey Rodarmor 2020-03-12 22:05:49 -07:00
parent 5a0bd2dda7
commit 1daa18ef9a
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
13 changed files with 255 additions and 102 deletions

View File

@ -48,7 +48,7 @@ pub(crate) use crate::{consts, error};
// traits
pub(crate) use crate::{
into_u64::IntoU64, into_usize::IntoUsize, path_ext::PathExt,
platform_interface::PlatformInterface, reckoner::Reckoner,
platform_interface::PlatformInterface, reckoner::Reckoner, step::Step,
};
// structs and enums

View File

@ -25,3 +25,44 @@ pub(crate) const AUTHOR: &str = env!("CARGO_PKG_AUTHORS");
pub(crate) const HELP_MESSAGE: &str = "Print help message.";
pub(crate) const VERSION_MESSAGE: &str = "Print version number.";
/// The pogress chars are from the
/// [Block Elements unicode block](https://en.wikipedia.org/wiki/Block_Elements).
pub(crate) const PROGRESS_CHARS: &str = "█▉▊▋▌▍▎▏ ";
/// The tick chars are from the
/// [Braille Patterns unicode block](https://en.wikipedia.org/wiki/Braille_Patterns).
///
/// The chars are ordered to represent the 8 bit numbers in increasing
/// order. The individual braille cells represent bits, with empty cells
/// representing `0` and full cells representing `1`.
///
/// Digits are ordered from least significant to most significant from
/// top to bottom, and then left to right, like so:
///
/// ```
/// ╔═════╗
/// ║ 0 4 ║
/// ║ 1 5 ║
/// ║ 2 6 ║
/// ║ 3 7 ║
/// ╚═════╝
/// ```
pub(crate) const TICK_CHARS: &str = concat!(
"⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇", // 0b0000----
"⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏", // 0b0001----
"⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗", // 0b0010----
"⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟", // 0b0011----
"⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧", // 0b0100----
"⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯", // 0b0101----
"⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷", // 0b0110----
"⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿", // 0b0111----
"⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇", // 0b1000----
"⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏", // 0b1001----
"⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗", // 0b1010----
"⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟", // 0b1011----
"⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧", // 0b1100----
"⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯", // 0b1101----
"⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷", // 0b1110----
"⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿", // 0b1111----
);

View File

@ -21,3 +21,9 @@ pub(crate) struct Info {
#[serde(flatten)]
pub(crate) mode: Mode,
}
impl Info {
pub(crate) fn content_size(&self) -> Bytes {
self.mode.content_size()
}
}

View File

@ -79,6 +79,7 @@ mod platform_interface;
mod reckoner;
mod sha1_digest;
mod status;
mod step;
mod style;
mod subcommand;
mod table;

View File

@ -92,8 +92,12 @@ impl Metainfo {
}
}
pub(crate) fn verify(&self, base: &Path) -> Result<Status> {
Verifier::verify(self, base)
pub(crate) fn verify(&self, base: &Path, progress_bar: Option<ProgressBar>) -> Result<Status> {
Verifier::verify(self, base, progress_bar)
}
pub(crate) fn content_size(&self) -> Bytes {
self.info.content_size()
}
}

View File

@ -18,7 +18,7 @@ pub(crate) enum Mode {
}
impl Mode {
pub(crate) fn total_size(&self) -> Bytes {
pub(crate) fn content_size(&self) -> Bytes {
match self {
Self::Single { length, .. } => *length,
Self::Multiple { files } => files.iter().map(|file| file.length).sum(),

34
src/step.rs Normal file
View File

@ -0,0 +1,34 @@
use crate::common::*;
pub(crate) trait Step {
fn print(&self, env: &mut Env) -> Result<(), Error> {
let style = env.err_style();
let dim = style.dim();
let message = style.message();
err!(
env,
"{}[{}/{}]{} ",
dim.prefix(),
self.n(),
Self::total(),
dim.suffix()
)?;
err!(env, "{}{} ", message.prefix(), self.symbol())?;
self.write_message(&mut env.err).context(error::Stderr)?;
errln!(env, "{}", message.suffix())?;
Ok(())
}
fn n(&self) -> usize;
fn total() -> usize;
fn write_message(&self, write: &mut dyn Write) -> io::Result<()>;
fn symbol(&self) -> &str;
}

View File

@ -1,4 +1,7 @@
use crate::common::*;
use create_step::CreateStep;
mod create_step;
#[derive(StructOpt)]
#[structopt(
@ -203,12 +206,12 @@ impl Create {
announce_list.push(tier);
}
Step::Searching.print(env)?;
CreateStep::Searching.print(env)?;
let spinner = if env.err_is_term() {
let style = ProgressStyle::default_spinner()
.template("{spinner:.green} {msg:.bold}…")
.tick_chars(&Self::tick_chars());
.tick_chars(consts::TICK_CHARS);
Some(ProgressBar::new_spinner().with_style(style))
} else {
@ -295,7 +298,7 @@ impl Create {
Some(String::from(consts::CREATED_BY_DEFAULT))
};
Step::Hashing.print(env)?;
CreateStep::Hashing.print(env)?;
let progress_bar = if env.err_is_term() {
let style = ProgressStyle::default_bar()
@ -303,8 +306,8 @@ impl Create {
"{spinner:.green} ⟪{elapsed_precise}⟫ ⟦{bar:40.cyan}⟧ \
{binary_bytes}/{binary_total_bytes} {binary_bytes_per_sec}, {eta}",
)
.tick_chars(&Self::tick_chars())
.progress_chars("█▉▊▋▌▍▎▏ ");
.tick_chars(consts::TICK_CHARS)
.progress_chars(consts::PROGRESS_CHARS);
Some(ProgressBar::new(files.total_size().count()).with_style(style))
} else {
@ -318,7 +321,7 @@ impl Create {
progress_bar,
)?;
Step::Writing { output: &output }.print(env)?;
CreateStep::Writing { output: &output }.print(env)?;
let info = Info {
source: self.source,
@ -374,7 +377,7 @@ impl Create {
assert_eq!(deserialized, metainfo);
let status = metainfo.verify(&input)?;
let status = metainfo.verify(&input, None)?;
if !status.good() {
return Err(Error::Verify { status });
@ -395,82 +398,6 @@ impl Create {
Ok(())
}
fn tick_chars() -> &'static str {
// The tick chars are from the Braille Patterns unicode block:
// https://en.wikipedia.org/wiki/Braille_Patterns
//
// The chars are ordered to represent the 8 bit numbers in increasing
// order. The individual braille cells represent bits, with empty cells
// representing `0` and full cells representing `1`.
//
// Digits are ordered from least significant to most significant from
// top to bottom, and then left to right, like so:
//
// ```
// ╔═════╗
// ║ 0 4 ║
// ║ 1 5 ║
// ║ 2 6 ║
// ║ 3 7 ║
// ╚═════╝
// ```
concat!(
"⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇", // 0b0000----
"⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏", // 0b0001----
"⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗", // 0b0010----
"⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟", // 0b0011----
"⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧", // 0b0100----
"⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯", // 0b0101----
"⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷", // 0b0110----
"⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿", // 0b0111----
"⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇", // 0b1000----
"⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏", // 0b1001----
"⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗", // 0b1010----
"⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟", // 0b1011----
"⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧", // 0b1100----
"⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯", // 0b1101----
"⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷", // 0b1110----
"⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿", // 0b1111----
)
}
}
#[derive(Clone, Copy)]
enum Step<'output> {
Searching,
Hashing,
Writing { output: &'output OutputTarget },
}
impl<'output> Step<'output> {
fn print(self, env: &mut Env) -> Result<(), Error> {
let style = env.err_style();
let dim = style.dim();
let message = style.message();
err!(env, "{}[{}/3]{} ", dim.prefix(), self.n(), dim.suffix())?;
err!(env, "{}", message.prefix())?;
match self {
Self::Searching => err!(env, "\u{1F9FF} Searching for files…")?,
Self::Hashing => err!(env, "\u{1F9EE} Hashing pieces…")?,
Self::Writing { output } => err!(env, "\u{1F4BE} Writing metainfo to {}…", output)?,
}
errln!(env, "{}", message.suffix())?;
Ok(())
}
fn n(self) -> usize {
match self {
Self::Searching => 1,
Self::Hashing => 2,
Self::Writing { .. } => 3,
}
}
}
#[cfg(test)]

View File

@ -0,0 +1,38 @@
use crate::common::*;
#[derive(Clone, Copy)]
pub(crate) enum CreateStep<'output> {
Searching,
Hashing,
Writing { output: &'output OutputTarget },
}
impl<'output> Step for CreateStep<'output> {
fn n(&self) -> usize {
match self {
Self::Searching => 1,
Self::Hashing => 2,
Self::Writing { .. } => 3,
}
}
fn symbol(&self) -> &str {
match self {
Self::Searching => "\u{1F9FF}",
Self::Hashing => "\u{1F9EE}",
Self::Writing { .. } => "\u{1F4BE}",
}
}
fn total() -> usize {
3
}
fn write_message(&self, write: &mut dyn Write) -> io::Result<()> {
match self {
Self::Searching => write!(write, "Searching for files…"),
Self::Hashing => write!(write, "Hashing pieces…"),
Self::Writing { output } => write!(write, "Writing metainfo to {}…", output),
}
}
}

View File

@ -1,4 +1,7 @@
use crate::common::*;
use verify_step::VerifyStep;
mod verify_step;
#[derive(StructOpt)]
#[structopt(
@ -31,16 +34,40 @@ impl Verify {
let metainfo_path = env.resolve(&self.metainfo);
let metainfo = Metainfo::load(&metainfo_path)?;
VerifyStep::Loading {
metainfo: &metainfo_path,
}
.print(env)?;
let base = if let Some(content) = &self.content {
env.resolve(content)
} else {
metainfo_path.parent().unwrap().join(&metainfo.info.name)
};
let status = metainfo.verify(&base)?;
let progress_bar = if env.err_is_term() {
let style = ProgressStyle::default_bar()
.template(
"{spinner:.green} ⟪{elapsed_precise}⟫ ⟦{bar:40.cyan}⟧ \
{binary_bytes}/{binary_total_bytes} {binary_bytes_per_sec}, {eta}",
)
.tick_chars(consts::TICK_CHARS)
.progress_chars(consts::PROGRESS_CHARS);
Some(ProgressBar::new(metainfo.content_size().count()).with_style(style))
} else {
None
};
VerifyStep::Verifying { content: &base }.print(env)?;
let status = metainfo.verify(&base, progress_bar)?;
if status.good() {
errln!(env, "Verification succeeded.")?;
errln!(
env,
"\u{2728}\u{2728} Verification succeeded! \u{2728}\u{2728}"
)?;
Ok(())
} else {
Err(Error::Verify { status })
@ -90,14 +117,22 @@ mod tests {
"torrent",
"verify",
"--input",
torrent,
&torrent,
],
tree: {},
};
assert_matches!(verify_env.run(), Ok(()));
assert_eq!(verify_env.err(), "Verification succeeded.\n");
let want = format!(
"[1/2] \u{1F4BE} Loading metainfo from `{}`…\n[2/2] \u{1F9EE} Verifying pieces from \
`{}`\n\u{2728}\u{2728} Verification succeeded! \u{2728}\u{2728}\n",
torrent.display(),
create_env.resolve("foo").display()
);
assert_eq!(verify_env.err(), want);
assert_eq!(verify_env.out(), "");
Ok(())
}
@ -133,14 +168,21 @@ mod tests {
"torrent",
"verify",
"--input",
torrent,
&torrent,
],
tree: {},
};
assert_matches!(verify_env.run(), Err(Error::Verify { .. }));
assert_eq!(verify_env.err(), "");
let want = format!(
"[1/2] \u{1F4BE} Loading metainfo from `{}`…\n[2/2] \u{1F9EE} Verifying pieces from `{}`…\n",
torrent.display(),
create_env.resolve("foo").display()
);
assert_eq!(verify_env.err(), want);
assert_eq!(verify_env.out(), "");
Ok(())
}
@ -180,16 +222,24 @@ mod tests {
"torrent",
"verify",
"--input",
torrent,
&torrent,
"--content",
bar,
&bar,
],
tree: {},
};
assert_matches!(verify_env.run(), Ok(()));
assert_eq!(verify_env.err(), "Verification succeeded.\n");
let want = format!(
"[1/2] \u{1F4BE} Loading metainfo from `{}`…\n[2/2] \u{1F9EE} Verifying pieces from \
`{}`\n\u{2728}\u{2728} Verification succeeded! \u{2728}\u{2728}\n",
torrent.display(),
bar.display(),
);
assert_eq!(verify_env.err(), want);
assert_eq!(verify_env.out(), "");
Ok(())
}

View File

@ -0,0 +1,38 @@
use crate::common::*;
#[derive(Clone, Copy)]
pub(crate) enum VerifyStep<'a> {
Loading { metainfo: &'a Path },
Verifying { content: &'a Path },
}
impl<'a> Step for VerifyStep<'a> {
fn n(&self) -> usize {
match self {
Self::Loading { .. } => 1,
Self::Verifying { .. } => 2,
}
}
fn symbol(&self) -> &str {
match self {
Self::Loading { .. } => "\u{1F4BE}",
Self::Verifying { .. } => "\u{1F9EE}",
}
}
fn total() -> usize {
2
}
fn write_message(&self, write: &mut dyn Write) -> io::Result<()> {
match self {
Self::Loading { metainfo } => {
write!(write, "Loading metainfo from `{}`…", metainfo.display())
}
Self::Verifying { content } => {
write!(write, "Verifying pieces from `{}`…", content.display())
}
}
}
}

View File

@ -95,7 +95,7 @@ impl TorrentSummary {
table.size("Torrent Size", self.size);
table.size("Content Size", self.metainfo.info.mode.total_size());
table.size("Content Size", self.metainfo.content_size());
table.row(
"Private",

View File

@ -8,10 +8,15 @@ pub(crate) struct Verifier<'a> {
pieces: PieceList,
sha1: Sha1,
piece_bytes_hashed: usize,
progress_bar: Option<ProgressBar>,
}
impl<'a> Verifier<'a> {
fn new(metainfo: &'a Metainfo, base: &'a Path) -> Result<Verifier<'a>> {
fn new(
metainfo: &'a Metainfo,
base: &'a Path,
progress_bar: Option<ProgressBar>,
) -> Result<Verifier<'a>> {
let piece_length = metainfo.info.piece_length.as_piece_length()?.into_usize();
Ok(Verifier {
@ -22,11 +27,16 @@ impl<'a> Verifier<'a> {
base,
metainfo,
piece_length,
progress_bar,
})
}
pub(crate) fn verify(metainfo: &'a Metainfo, base: &'a Path) -> Result<Status> {
Self::new(metainfo, base)?.verify_metainfo()
pub(crate) fn verify(
metainfo: &'a Metainfo,
base: &'a Path,
progress_bar: Option<ProgressBar>,
) -> Result<Status> {
Self::new(metainfo, base, progress_bar)?.verify_metainfo()
}
fn verify_metainfo(mut self) -> Result<Status> {
@ -76,6 +86,10 @@ impl<'a> Verifier<'a> {
}
remaining -= buffer.len().into_u64();
if let Some(progress_bar) = &self.progress_bar {
progress_bar.inc(to_buffer.into_u64());
}
}
Ok(())
@ -110,7 +124,7 @@ mod tests {
let metainfo = env.load_metainfo("foo.torrent");
assert!(metainfo.verify(&env.resolve("foo"))?.good());
assert!(metainfo.verify(&env.resolve("foo"), None)?.good());
Ok(())
}
@ -141,7 +155,7 @@ mod tests {
let metainfo = env.load_metainfo("foo.torrent");
let status = metainfo.verify(&env.resolve("foo"))?;
let status = metainfo.verify(&env.resolve("foo"), None)?;
assert!(status.files().iter().all(FileStatus::good));