Print individual file torrent verification errors
If torrent verification fails, print all errors with individual files. type: changed
This commit is contained in:
parent
f8e3fd594b
commit
1532113782
16
src/bytes.rs
16
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<Bytes> for Bytes {
|
|||
}
|
||||
}
|
||||
|
||||
impl Sub<Bytes> for Bytes {
|
||||
type Output = Bytes;
|
||||
|
||||
fn sub(self, rhs: Bytes) -> Bytes {
|
||||
Bytes(self.count() - rhs.count())
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<u64> for Bytes {
|
||||
type Output = Bytes;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
95
src/env.rs
95
src/env.rs
|
@ -3,12 +3,8 @@ use crate::common::*;
|
|||
pub(crate) struct Env {
|
||||
args: Vec<OsString>,
|
||||
dir: PathBuf,
|
||||
pub(crate) err: Box<dyn Write>,
|
||||
pub(crate) out: Box<dyn Write>,
|
||||
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<O, E, S, I>(
|
||||
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<S, I>(dir: PathBuf, args: I, out: OutputStream, err: OutputStream) -> Self
|
||||
where
|
||||
O: Write + 'static,
|
||||
E: Write + 'static,
|
||||
S: Into<OsString>,
|
||||
I: IntoIterator<Item = S>,
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
12
src/error.rs
12
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<String>) -> 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<clap::Error> for Error {
|
||||
|
|
98
src/file_error.rs
Normal file
98
src/file_error.rs
Normal file
|
@ -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<Md5Digest>,
|
||||
) -> 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<io::Error> 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(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -2,80 +2,35 @@ use crate::common::*;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FileStatus {
|
||||
path: PathBuf,
|
||||
error: Option<io::Error>,
|
||||
present: bool,
|
||||
file: bool,
|
||||
length_expected: Bytes,
|
||||
length_actual: Option<Bytes>,
|
||||
md5_expected: Option<Md5Digest>,
|
||||
md5_actual: Option<Md5Digest>,
|
||||
path: FilePath,
|
||||
error: Option<FileError>,
|
||||
}
|
||||
|
||||
impl FileStatus {
|
||||
pub(crate) fn status(
|
||||
path: &Path,
|
||||
length_expected: Bytes,
|
||||
md5_expected: Option<Md5Digest>,
|
||||
absolute: &Path,
|
||||
path: FilePath,
|
||||
length: Bytes,
|
||||
md5: Option<Md5Digest>,
|
||||
) -> 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<Md5Digest>) -> 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -34,6 +34,16 @@ impl From<md5::Digest> 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::*;
|
||||
|
|
|
@ -75,23 +75,6 @@ impl Metainfo {
|
|||
Self::deserialize("<TEST>", bytes).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn files<'a>(
|
||||
&'a self,
|
||||
base: &'a Path,
|
||||
) -> Box<dyn Iterator<Item = (PathBuf, Bytes, Option<Md5Digest>)> + '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<ProgressBar>) -> Result<Status> {
|
||||
Verifier::verify(self, base, progress_bar)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
65
src/output_stream.rs
Normal file
65
src/output_stream.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use crate::common::*;
|
||||
|
||||
pub(crate) struct OutputStream {
|
||||
stream: Box<dyn Write>,
|
||||
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<dyn Write>, 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<usize> {
|
||||
self.stream.write(data)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.stream.flush()
|
||||
}
|
||||
}
|
11
src/print.rs
Normal file
11
src/print.rs
Normal file
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -1,46 +1,89 @@
|
|||
use crate::common::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Status {
|
||||
pieces: bool,
|
||||
files: Vec<FileStatus>,
|
||||
pub(crate) enum Status {
|
||||
Single {
|
||||
pieces: bool,
|
||||
error: Option<FileError>,
|
||||
},
|
||||
Multiple {
|
||||
pieces: bool,
|
||||
files: Vec<FileStatus>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub(crate) fn new(pieces: bool, files: Vec<FileStatus>) -> Self {
|
||||
Self { pieces, files }
|
||||
pub(crate) fn single(pieces: bool, error: Option<FileError>) -> Self {
|
||||
Status::Single { pieces, error }
|
||||
}
|
||||
|
||||
pub(crate) fn multiple(pieces: bool, files: Vec<FileStatus>) -> 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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())?;
|
||||
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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}⟧ \
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,22 +40,34 @@ impl<'a> Verifier<'a> {
|
|||
}
|
||||
|
||||
fn verify_metainfo(mut self) -> Result<Status> {
|
||||
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());
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user