diff --git a/src/bytes.rs b/src/bytes.rs index 3d062a8..deb4788 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -30,6 +30,14 @@ impl Bytes { .try_into() .context(error::PieceLengthTooLarge { bytes: self }) } + + pub(crate) fn absolute_difference(self, other: Bytes) -> Bytes { + if self > other { + self - other + } else { + other - self + } + } } fn float_to_int(x: f64) -> u64 { @@ -101,6 +109,14 @@ impl Div for Bytes { } } +impl Sub for Bytes { + type Output = Bytes; + + fn sub(self, rhs: Bytes) -> Bytes { + Bytes(self.count() - rhs.count()) + } +} + impl Div for Bytes { type Output = Bytes; diff --git a/src/common.rs b/src/common.rs index 8fe1c0d..af114f8 100644 --- a/src/common.rs +++ b/src/common.rs @@ -11,9 +11,9 @@ pub(crate) use std::{ fs::{self, File}, hash::Hash, io::{self, Read, Write}, - iter::{self, Sum}, + iter::Sum, num::{ParseFloatError, ParseIntError, TryFromIntError}, - ops::{AddAssign, Div, DivAssign, Mul, MulAssign, SubAssign}, + ops::{AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}, path::{self, Path, PathBuf}, process::{self, Command, ExitStatus}, str::{self, FromStr}, @@ -48,18 +48,18 @@ 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, step::Step, + platform_interface::PlatformInterface, print::Print, reckoner::Reckoner, step::Step, }; // structs and enums pub(crate) use crate::{ - arguments::Arguments, bytes::Bytes, env::Env, error::Error, file_info::FileInfo, - file_path::FilePath, file_status::FileStatus, files::Files, hasher::Hasher, info::Info, - lint::Lint, linter::Linter, md5_digest::Md5Digest, metainfo::Metainfo, mode::Mode, node::Node, - options::Options, output_target::OutputTarget, piece_length_picker::PieceLengthPicker, - piece_list::PieceList, platform::Platform, sha1_digest::Sha1Digest, status::Status, style::Style, - subcommand::Subcommand, table::Table, torrent_summary::TorrentSummary, use_color::UseColor, - verifier::Verifier, walker::Walker, + arguments::Arguments, bytes::Bytes, env::Env, error::Error, file_error::FileError, + file_info::FileInfo, file_path::FilePath, file_status::FileStatus, files::Files, hasher::Hasher, + info::Info, lint::Lint, linter::Linter, md5_digest::Md5Digest, metainfo::Metainfo, mode::Mode, + node::Node, options::Options, output_stream::OutputStream, output_target::OutputTarget, + piece_length_picker::PieceLengthPicker, piece_list::PieceList, platform::Platform, + sha1_digest::Sha1Digest, status::Status, style::Style, subcommand::Subcommand, table::Table, + torrent_summary::TorrentSummary, use_color::UseColor, verifier::Verifier, walker::Walker, }; // type aliases diff --git a/src/env.rs b/src/env.rs index d6917f7..20433d3 100644 --- a/src/env.rs +++ b/src/env.rs @@ -3,12 +3,8 @@ use crate::common::*; pub(crate) struct Env { args: Vec, dir: PathBuf, - pub(crate) err: Box, - pub(crate) out: Box, - err_style: Style, - out_style: Style, - out_is_term: bool, - err_is_term: bool, + err: OutputStream, + out: OutputStream, } impl Env { @@ -18,34 +14,13 @@ impl Env { Err(error) => panic!("Failed to get current directory: {}", error), }; - let no_color = env::var_os("NO_COLOR").is_some() - || env::var_os("TERM").as_deref() == Some(OsStr::new("dumb")); + let style = env::var_os("NO_COLOR").is_none() + && env::var_os("TERM").as_deref() != Some(OsStr::new("dumb")); - let err_style = if no_color || !atty::is(atty::Stream::Stderr) { - Style::inactive() - } else { - Style::active() - }; + let out_stream = OutputStream::stdout(style); + let err_stream = OutputStream::stderr(style); - let out_style = if no_color || !atty::is(atty::Stream::Stdout) { - Style::inactive() - } else { - Style::active() - }; - - let out_is_term = atty::is(atty::Stream::Stdout); - let err_is_term = atty::is(atty::Stream::Stderr); - - Self::new( - dir, - io::stdout(), - out_style, - out_is_term, - io::stderr(), - err_style, - err_is_term, - env::args(), - ) + Self::new(dir, env::args(), out_stream, err_stream) } pub(crate) fn run(&mut self) -> Result<(), Error> { @@ -57,40 +32,23 @@ impl Env { let args = Arguments::from_iter_safe(&self.args)?; - match args.options().use_color { - UseColor::Always => self.err_style = Style::active(), - UseColor::Auto => {} - UseColor::Never => self.err_style = Style::inactive(), - } + let use_color = args.options().use_color; + self.err.set_use_color(use_color); + self.out.set_use_color(use_color); args.run(self) } - pub(crate) fn new( - dir: PathBuf, - out: O, - out_style: Style, - out_is_term: bool, - err: E, - err_style: Style, - err_is_term: bool, - args: I, - ) -> Self + pub(crate) fn new(dir: PathBuf, args: I, out: OutputStream, err: OutputStream) -> Self where - O: Write + 'static, - E: Write + 'static, S: Into, I: IntoIterator, { Self { args: args.into_iter().map(Into::into).collect(), - err: Box::new(err), - out: Box::new(out), dir, - out_style, - out_is_term, - err_style, - err_is_term, + out, + err, } } @@ -109,21 +67,24 @@ impl Env { _ => Err(EXIT_FAILURE), } } else { + let style = self.err.style(); writeln!( &mut self.err, "{}{}: {}{}", - self.err_style.error().paint("error"), - self.err_style.message().prefix(), + style.error().paint("error"), + style.message().prefix(), error, - self.err_style.message().suffix(), + style.message().suffix(), ) .ok(); + error.print_body(self).ok(); + if let Some(lint) = error.lint() { writeln!( &mut self.err, "{}: This check can be disabled with `--allow {}`.", - self.err_style.message().paint("note"), + style.message().paint("note"), lint.name() ) .ok(); @@ -144,20 +105,20 @@ impl Env { self.dir().join(path).clean() } - pub(crate) fn out_is_term(&self) -> bool { - self.out_is_term + pub(crate) fn err(&self) -> &OutputStream { + &self.err } - pub(crate) fn out_style(&self) -> Style { - self.out_style + pub(crate) fn err_mut(&mut self) -> &mut OutputStream { + &mut self.err } - pub(crate) fn err_is_term(&self) -> bool { - self.err_is_term + pub(crate) fn out(&self) -> &OutputStream { + &self.out } - pub(crate) fn err_style(&self) -> Style { - self.err_style + pub(crate) fn out_mut(&mut self) -> &mut OutputStream { + &mut self.out } } diff --git a/src/err.rs b/src/err.rs index 79594aa..fff0ed6 100644 --- a/src/err.rs +++ b/src/err.rs @@ -1,8 +1,8 @@ macro_rules! err { ($env:expr, $fmt:expr) => { - write!($env.err, $fmt).context(crate::error::Stderr) + write!($env.err_mut(), $fmt).context(crate::error::Stderr) }; ($env:expr, $fmt:expr, $($arg:tt)*) => { - write!($env.err, $fmt, $($arg)*).context(crate::error::Stderr) + write!($env.err_mut(), $fmt, $($arg)*).context(crate::error::Stderr) }; } diff --git a/src/errln.rs b/src/errln.rs index cda980d..3d6d8c2 100644 --- a/src/errln.rs +++ b/src/errln.rs @@ -1,11 +1,11 @@ macro_rules! errln { ($env:expr) => { - writeln!($env.err, "").context(crate::error::Stderr) + writeln!($env.err_mut(), "").context(crate::error::Stderr) }; ($env:expr, $fmt:expr) => { - writeln!($env.err, $fmt).context(crate::error::Stderr) + writeln!($env.err_mut(), $fmt).context(crate::error::Stderr) }; ($env:expr, $fmt:expr, $($arg:tt)*) => { - writeln!($env.err, $fmt, $($arg)*).context(crate::error::Stderr) + writeln!($env.err_mut(), $fmt, $($arg)*).context(crate::error::Stderr) }; } diff --git a/src/error.rs b/src/error.rs index edd1b9e..fc91728 100644 --- a/src/error.rs +++ b/src/error.rs @@ -119,7 +119,7 @@ pub(crate) enum Error { feature ))] Unstable { feature: &'static str }, - #[snafu(display("Torrent verification failed: {}", status))] + #[snafu(display("Torrent verification failed."))] Verify { status: Status }, } @@ -133,10 +133,18 @@ impl Error { } pub(crate) fn internal(message: impl Into) -> Error { - Error::Internal { + Self::Internal { message: message.into(), } } + + pub(crate) fn print_body(&self, env: &mut Env) -> Result<()> { + if let Self::Verify { status } = self { + status.print_body(env)?; + } + + Ok(()) + } } impl From for Error { diff --git a/src/file_error.rs b/src/file_error.rs new file mode 100644 index 0000000..f9778a6 --- /dev/null +++ b/src/file_error.rs @@ -0,0 +1,98 @@ +use crate::common::*; + +#[derive(Debug)] +pub(crate) enum FileError { + Io(io::Error), + Missing, + Directory, + Surfeit(Bytes), + Dearth(Bytes), + Md5 { + expected: Md5Digest, + actual: Md5Digest, + }, +} + +impl FileError { + pub(crate) fn verify( + path: &Path, + expected_length: Bytes, + expected_md5: Option, + ) -> Result<(), FileError> { + let metadata = match path.metadata() { + Ok(metadata) => metadata, + Err(error) => { + if error.kind() == io::ErrorKind::NotFound { + return Err(FileError::Missing); + } else { + return Err(FileError::Io(error)); + } + } + }; + + if metadata.is_dir() { + return Err(FileError::Directory); + } + + let actual = Bytes(metadata.len()); + + let difference = actual.absolute_difference(expected_length); + + if actual > expected_length { + return Err(FileError::Surfeit(difference)); + } + + if actual < expected_length { + return Err(FileError::Dearth(difference)); + } + + if let Some(expected) = expected_md5 { + let mut reader = File::open(path)?; + let mut context = md5::Context::new(); + io::copy(&mut reader, &mut context)?; + let actual = context.compute().into(); + + if actual != expected { + return Err(FileError::Md5 { actual, expected }); + } + } + + Ok(()) + } +} + +impl From for FileError { + fn from(io_error: io::Error) -> Self { + Self::Io(io_error) + } +} + +impl Display for FileError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Io(io_error) => write!(f, "{}", io_error), + Self::Missing => write!(f, "File missing"), + Self::Directory => write!(f, "Expected file but found directory"), + Self::Surfeit(difference) => write!(f, "Extra bytes: {}", difference), + Self::Dearth(difference) => write!(f, "Missing bytes: {}", difference), + Self::Md5 { actual, expected } => write!( + f, + "MD5 checksum mismatch: {} (expected {})", + actual, expected + ), + } + } +} + +impl Print for FileError { + fn print(&self, stream: &mut OutputStream) -> io::Result<()> { + let style = stream.style(); + write!( + stream, + "{}{}{}", + style.error().prefix(), + self, + style.error().suffix(), + ) + } +} diff --git a/src/file_status.rs b/src/file_status.rs index 17d0471..5bb32cd 100644 --- a/src/file_status.rs +++ b/src/file_status.rs @@ -2,80 +2,35 @@ use crate::common::*; #[derive(Debug)] pub(crate) struct FileStatus { - path: PathBuf, - error: Option, - present: bool, - file: bool, - length_expected: Bytes, - length_actual: Option, - md5_expected: Option, - md5_actual: Option, + path: FilePath, + error: Option, } impl FileStatus { pub(crate) fn status( - path: &Path, - length_expected: Bytes, - md5_expected: Option, + absolute: &Path, + path: FilePath, + length: Bytes, + md5: Option, ) -> Self { - let mut status = Self::new(path.to_owned(), length_expected, md5_expected); + let error = FileError::verify(absolute, length, md5).err(); - if let Err(error) = status.verify() { - status.error = Some(error); - } - - status + FileStatus { path, error } } - fn new(path: PathBuf, length_expected: Bytes, md5_expected: Option) -> Self { - Self { - error: None, - file: false, - md5_actual: None, - present: false, - length_actual: None, - length_expected, - md5_expected, - path, - } + pub(crate) fn is_good(&self) -> bool { + self.error.is_none() } - fn verify(&mut self) -> io::Result<()> { - let metadata = self.path.metadata()?; - - self.present = true; - - if !metadata.is_file() { - return Ok(()); - } - - self.file = true; - - self.length_actual = Some(metadata.len().into()); - - if self.md5_expected.is_some() { - let mut reader = File::open(&self.path)?; - let mut context = md5::Context::new(); - io::copy(&mut reader, &mut context)?; - self.md5_actual = Some(context.compute().into()); - } - - Ok(()) + pub(crate) fn is_bad(&self) -> bool { + !self.is_good() } - fn md5(&self) -> bool { - match (self.md5_actual, self.md5_expected) { - (Some(actual), Some(expected)) => actual == expected, - (None, None) => true, - _ => unreachable!(), - } + pub(crate) fn error(&self) -> Option<&FileError> { + self.error.as_ref() } - pub(crate) fn good(&self) -> bool { - self.error.is_none() && self.present && self.file && self.md5() - } - - pub(crate) fn bad(&self) -> bool { - !self.good() + pub(crate) fn path(&self) -> &FilePath { + &self.path } } diff --git a/src/main.rs b/src/main.rs index 9eaa382..93f0887 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,7 @@ mod common; mod consts; mod env; mod error; +mod file_error; mod file_info; mod file_path; mod file_status; @@ -70,12 +71,14 @@ mod metainfo; mod mode; mod node; mod options; +mod output_stream; mod output_target; mod path_ext; mod piece_length_picker; mod piece_list; mod platform; mod platform_interface; +mod print; mod reckoner; mod sha1_digest; mod status; diff --git a/src/md5_digest.rs b/src/md5_digest.rs index 792db79..3e19907 100644 --- a/src/md5_digest.rs +++ b/src/md5_digest.rs @@ -34,6 +34,16 @@ impl From for Md5Digest { } } +impl Display for Md5Digest { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for byte in &self.bytes { + write!(f, "{:x}", byte)?; + } + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/metainfo.rs b/src/metainfo.rs index 1b4a75c..2c8b593 100644 --- a/src/metainfo.rs +++ b/src/metainfo.rs @@ -75,23 +75,6 @@ impl Metainfo { Self::deserialize("", bytes).unwrap() } - pub(crate) fn files<'a>( - &'a self, - base: &'a Path, - ) -> Box)> + 'a> { - match &self.info.mode { - Mode::Single { length, md5sum } => Box::new(iter::once((base.to_owned(), *length, *md5sum))), - Mode::Multiple { files } => { - let base = base.to_owned(); - Box::new( - files - .iter() - .map(move |file| (file.path.absolute(&base), file.length, file.md5sum)), - ) - } - } - } - pub(crate) fn verify(&self, base: &Path, progress_bar: Option) -> Result { Verifier::verify(self, base, progress_bar) } diff --git a/src/outln.rs b/src/outln.rs index dd7f94b..1e03daa 100644 --- a/src/outln.rs +++ b/src/outln.rs @@ -1,11 +1,11 @@ macro_rules! outln { ($env:expr) => { - writeln!($env.out, "").context(crate::error::Stderr) + writeln!($env.out_mut(), "").context(crate::error::Stdout) }; ($env:expr, $fmt:expr) => { - writeln!($env.out, $fmt).context(crate::error::Stderr) + writeln!($env.out_mut(), $fmt).context(crate::error::Stdout) }; ($env:expr, $fmt:expr, $($arg:tt)*) => { - writeln!($env.out, $fmt, $($arg)*).context(crate::error::Stderr) + writeln!($env.out_mut(), $fmt, $($arg)*).context(crate::error::Stdout) }; } diff --git a/src/output_stream.rs b/src/output_stream.rs new file mode 100644 index 0000000..84f5cf2 --- /dev/null +++ b/src/output_stream.rs @@ -0,0 +1,65 @@ +use crate::common::*; + +pub(crate) struct OutputStream { + stream: Box, + style: bool, + term: bool, +} + +impl OutputStream { + pub(crate) fn stdout(style: bool) -> OutputStream { + let term = atty::is(atty::Stream::Stdout); + Self { + stream: Box::new(io::stdout()), + style: style && term, + term, + } + } + + pub(crate) fn stderr(style: bool) -> OutputStream { + Self { + term: style && atty::is(atty::Stream::Stderr), + stream: Box::new(io::stderr()), + style, + } + } + + #[cfg(test)] + pub(crate) fn new(stream: Box, style: bool, term: bool) -> OutputStream { + Self { + stream, + style, + term, + } + } + + pub(crate) fn set_use_color(&mut self, use_color: UseColor) { + match use_color { + UseColor::Always => self.style = true, + UseColor::Auto => {} + UseColor::Never => self.style = false, + } + } + + pub(crate) fn is_term(&self) -> bool { + self.term + } + + pub(crate) fn is_styled(&self) -> bool { + self.style + } + + pub(crate) fn style(&self) -> Style { + Style::from_active(self.style) + } +} + +impl Write for OutputStream { + fn write(&mut self, data: &[u8]) -> io::Result { + self.stream.write(data) + } + + fn flush(&mut self) -> io::Result<()> { + self.stream.flush() + } +} diff --git a/src/print.rs b/src/print.rs new file mode 100644 index 0000000..999ecb8 --- /dev/null +++ b/src/print.rs @@ -0,0 +1,11 @@ +use crate::common::*; + +pub(crate) trait Print { + fn print(&self, stream: &mut OutputStream) -> io::Result<()>; + + fn println(&self, stream: &mut OutputStream) -> io::Result<()> { + self.print(stream)?; + writeln!(stream)?; + Ok(()) + } +} diff --git a/src/status.rs b/src/status.rs index fd995fa..b19afdc 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,46 +1,89 @@ use crate::common::*; #[derive(Debug)] -pub(crate) struct Status { - pieces: bool, - files: Vec, +pub(crate) enum Status { + Single { + pieces: bool, + error: Option, + }, + Multiple { + pieces: bool, + files: Vec, + }, } impl Status { - pub(crate) fn new(pieces: bool, files: Vec) -> Self { - Self { pieces, files } + pub(crate) fn single(pieces: bool, error: Option) -> Self { + Status::Single { pieces, error } + } + + pub(crate) fn multiple(pieces: bool, files: Vec) -> Self { + Status::Multiple { pieces, files } } pub(crate) fn pieces(&self) -> bool { - self.pieces - } - - #[cfg(test)] - pub(crate) fn files(&self) -> &[FileStatus] { - &self.files + match self { + Self::Single { pieces, .. } | Self::Multiple { pieces, .. } => *pieces, + } } pub(crate) fn good(&self) -> bool { - self.pieces && self.files.iter().all(FileStatus::good) + self.pieces() + && match self { + Self::Single { error, .. } => error.is_none(), + Self::Multiple { files, .. } => files.iter().all(FileStatus::is_good), + } } -} -impl Display for Status { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let bad = self.files.iter().filter(|status| status.bad()).count(); + #[cfg(test)] + pub(crate) fn count_bad(&self) -> usize { + match self { + Self::Single { error, .. } => { + if error.is_some() { + 1 + } else { + 0 + } + } + Self::Multiple { files, .. } => files.iter().filter(|file| file.is_bad()).count(), + } + } - if bad != 0 { - write!(f, "{} of {} files corrupted", bad, self.files.len())?; - return Ok(()); + pub(crate) fn print_body(&self, env: &mut Env) -> Result<()> { + match self { + Self::Single { error, .. } => { + if let Some(error) = error { + error.println(env.err_mut()).context(error::Stderr)?; + } + } + Self::Multiple { files, .. } => { + for file in files { + if let Some(error) = file.error() { + let style = env.err().style(); + err!( + env, + "{}{}:{} ", + style.message().prefix(), + file.path(), + style.message().suffix(), + )?; + error.println(env.err_mut()).context(error::Stderr)?; + } + } + + errln!( + env, + "{}/{} files corrupted.", + files.iter().filter(|file| file.is_bad()).count(), + files.len(), + )?; + } } if !self.pieces() { - write!(f, "pieces corrupted")?; - return Ok(()); + errln!(env, "Pieces corrupted.")?; } - write!(f, "ok")?; - Ok(()) } } diff --git a/src/step.rs b/src/step.rs index 798eaca..a80bfc3 100644 --- a/src/step.rs +++ b/src/step.rs @@ -2,7 +2,7 @@ use crate::common::*; pub(crate) trait Step { fn print(&self, env: &mut Env) -> Result<(), Error> { - let style = env.err_style(); + let style = env.err().style(); let dim = style.dim(); let message = style.message(); @@ -17,7 +17,7 @@ pub(crate) trait Step { err!(env, "{}{} ", message.prefix(), self.symbol())?; - self.write_message(&mut env.err).context(error::Stderr)?; + self.write_message(env.err_mut()).context(error::Stderr)?; errln!(env, "{}", message.suffix())?; diff --git a/src/style.rs b/src/style.rs index cdc08f3..c167959 100644 --- a/src/style.rs +++ b/src/style.rs @@ -4,10 +4,16 @@ pub(crate) struct Style { } impl Style { + pub(crate) fn from_active(active: bool) -> Self { + Self { active } + } + + #[cfg(test)] pub(crate) fn active() -> Self { Self { active: true } } + #[cfg(test)] pub(crate) fn inactive() -> Self { Self { active: false } } diff --git a/src/subcommand/torrent/create.rs b/src/subcommand/torrent/create.rs index 0c29f6f..f4989b7 100644 --- a/src/subcommand/torrent/create.rs +++ b/src/subcommand/torrent/create.rs @@ -208,7 +208,7 @@ impl Create { CreateStep::Searching.print(env)?; - let spinner = if env.err_is_term() { + let spinner = if env.err().is_styled() { let style = ProgressStyle::default_spinner() .template("{spinner:.green} {msg:.bold}…") .tick_chars(consts::TICK_CHARS); @@ -300,7 +300,7 @@ impl Create { CreateStep::Hashing.print(env)?; - let progress_bar = if env.err_is_term() { + let progress_bar = if env.err().is_styled() { let style = ProgressStyle::default_bar() .template( "{spinner:.green} ⟪{elapsed_precise}⟫ ⟦{bar:40.cyan}⟧ \ @@ -368,7 +368,7 @@ impl Create { .and_then(|mut file| file.write_all(&bytes)) .context(error::Filesystem { path })?; } - OutputTarget::Stdout => env.out.write_all(&bytes).context(error::Stdout)?, + OutputTarget::Stdout => env.out_mut().write_all(&bytes).context(error::Stdout)?, } #[cfg(test)] diff --git a/src/subcommand/torrent/verify.rs b/src/subcommand/torrent/verify.rs index 16ac090..7988ca2 100644 --- a/src/subcommand/torrent/verify.rs +++ b/src/subcommand/torrent/verify.rs @@ -45,7 +45,7 @@ impl Verify { metainfo_path.parent().unwrap().join(&metainfo.info.name) }; - let progress_bar = if env.err_is_term() { + let progress_bar = if env.err().is_styled() { let style = ProgressStyle::default_bar() .template( "{spinner:.green} ⟪{elapsed_precise}⟫ ⟦{bar:40.cyan}⟧ \ diff --git a/src/test_env_builder.rs b/src/test_env_builder.rs index f9e50d5..8315c1e 100644 --- a/src/test_env_builder.rs +++ b/src/test_env_builder.rs @@ -58,21 +58,16 @@ impl TestEnvBuilder { tempdir.path().to_owned() }; - let env = Env::new( - current_dir, - out.clone(), - if self.use_color && self.out_is_term { - Style::active() - } else { - Style::inactive() - }, + let out_stream = OutputStream::new( + Box::new(out.clone()), + self.use_color && self.out_is_term, self.out_is_term, - err.clone(), - Style::inactive(), - false, - self.args, ); + let err_stream = OutputStream::new(Box::new(err.clone()), false, false); + + let env = Env::new(current_dir, self.args, out_stream, err_stream); + TestEnv::new(tempdir, env, err, out) } } diff --git a/src/torrent_summary.rs b/src/torrent_summary.rs index 7d0dafc..2f2a5b6 100644 --- a/src/torrent_summary.rs +++ b/src/torrent_summary.rs @@ -46,14 +46,14 @@ impl TorrentSummary { pub(crate) fn write(&self, env: &mut Env) -> Result<(), Error> { let table = self.table(); - if env.out_is_term() { - let out_style = env.out_style(); + if env.out().is_term() { + let style = env.out().style(); table - .write_human_readable(&mut env.out, out_style) + .write_human_readable(env.out_mut(), style) .context(error::Stdout)?; } else { table - .write_tab_delimited(&mut env.out) + .write_tab_delimited(env.out_mut()) .context(error::Stdout)?; } diff --git a/src/verifier.rs b/src/verifier.rs index 8559a79..b131934 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -40,22 +40,34 @@ impl<'a> Verifier<'a> { } fn verify_metainfo(mut self) -> Result { - let mut status = Vec::new(); + match &self.metainfo.info.mode { + Mode::Single { length, md5sum } => { + self.hash(&self.base).ok(); + let error = FileError::verify(&self.base, *length, *md5sum).err(); - for (path, len, md5sum) in self.metainfo.files(&self.base) { - status.push(FileStatus::status(&path, len, md5sum)); - self.hash(&path).ok(); + let pieces = self.finish(); + Ok(Status::single(pieces, error)) + } + Mode::Multiple { files } => { + let mut status = Vec::new(); + + for file in files { + let path = file.path.absolute(self.base); + self.hash(&path).ok(); + + status.push(FileStatus::status( + &path, + file.path.clone(), + file.length, + file.md5sum, + )); + } + + let pieces = self.finish(); + + Ok(Status::multiple(pieces, status)) + } } - - if self.piece_bytes_hashed > 0 { - self.pieces.push(self.sha1.digest().into()); - self.sha1.reset(); - self.piece_bytes_hashed = 0; - } - - let pieces = self.pieces == self.metainfo.info.pieces; - - Ok(Status::new(pieces, status)) } pub(crate) fn hash(&mut self, path: &Path) -> io::Result<()> { @@ -94,6 +106,16 @@ impl<'a> Verifier<'a> { Ok(()) } + + fn finish(&mut self) -> bool { + if self.piece_bytes_hashed > 0 { + self.pieces.push(self.sha1.digest().into()); + self.sha1.reset(); + self.piece_bytes_hashed = 0; + } + + self.pieces == self.metainfo.info.pieces + } } #[cfg(test)] @@ -157,7 +179,7 @@ mod tests { let status = metainfo.verify(&env.resolve("foo"), None)?; - assert!(status.files().iter().all(FileStatus::good)); + assert_eq!(status.count_bad(), 0); assert!(!status.pieces());