Use attractive paths in user-facing messages
If a user passes `--input foo`, print "Searching `foo` for files…", instead of the resolved, absolute path to `foo`, since the former is what the user typed in. This was way harder, and had way more edge cases, than I thought it would be! One takaway, lexical path cleaning is excellent. type: changed fixes: - https://github.com/casey/intermodal/issues/252 - https://github.com/casey/intermodal/issues/332
This commit is contained in:
parent
1cfc021453
commit
8e3f5516af
|
@ -18,11 +18,11 @@ pub(crate) enum Error {
|
||||||
CommandInvoke { command: String, source: io::Error },
|
CommandInvoke { command: String, source: io::Error },
|
||||||
#[snafu(display("Command `{}` returned bad exit status: {}", command, status))]
|
#[snafu(display("Command `{}` returned bad exit status: {}", command, status))]
|
||||||
CommandStatus { command: String, status: ExitStatus },
|
CommandStatus { command: String, status: ExitStatus },
|
||||||
#[snafu(display("Filename was not valid unicode: {}", filename.display()))]
|
#[snafu(display("Filename was not valid unicode: `{}`", filename.display()))]
|
||||||
FilenameDecode { filename: PathBuf },
|
FilenameDecode { filename: PathBuf },
|
||||||
#[snafu(display("Path had no file name: {}", path.display()))]
|
#[snafu(display("Path had no file name: `{}`", path.display()))]
|
||||||
FilenameExtract { path: PathBuf },
|
FilenameExtract { path: PathBuf },
|
||||||
#[snafu(display("Unknown file ordering: {}", text))]
|
#[snafu(display("Unknown file ordering: `{}`", text))]
|
||||||
FileOrderUnknown { text: String },
|
FileOrderUnknown { text: String },
|
||||||
#[snafu(display("I/O error at `{}`: {}", path.display(), source))]
|
#[snafu(display("I/O error at `{}`: {}", path.display(), source))]
|
||||||
Filesystem { source: io::Error, path: PathBuf },
|
Filesystem { source: io::Error, path: PathBuf },
|
||||||
|
|
|
@ -7,13 +7,6 @@ pub(crate) enum InputTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputTarget {
|
impl InputTarget {
|
||||||
pub(crate) fn resolve(&self, env: &Env) -> Result<Self> {
|
|
||||||
match self {
|
|
||||||
Self::Path(path) => Ok(Self::Path(env.resolve(path)?)),
|
|
||||||
Self::Stdin => Ok(Self::Stdin),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn try_from_os_str(text: &OsStr) -> Result<Self, OsString> {
|
pub(crate) fn try_from_os_str(text: &OsStr) -> Result<Self, OsString> {
|
||||||
text
|
text
|
||||||
.try_into()
|
.try_into()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub(crate) enum OutputTarget {
|
pub(crate) enum OutputTarget {
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
Stdout,
|
Stdout,
|
||||||
|
|
|
@ -8,12 +8,23 @@ pub(crate) trait PathExt {
|
||||||
|
|
||||||
impl PathExt for &Path {
|
impl PathExt for &Path {
|
||||||
fn clean(self) -> PathBuf {
|
fn clean(self) -> PathBuf {
|
||||||
|
if self.components().count() <= 1 {
|
||||||
|
return self.to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
let mut components = Vec::new();
|
let mut components = Vec::new();
|
||||||
|
|
||||||
for component in self.components() {
|
for component in self
|
||||||
|
.components()
|
||||||
|
.filter(|component| component != &Component::CurDir)
|
||||||
|
{
|
||||||
if component == Component::ParentDir {
|
if component == Component::ParentDir {
|
||||||
if let Some(Component::Normal(_)) = components.last() {
|
match components.last() {
|
||||||
components.pop();
|
Some(Component::Normal(_)) => {
|
||||||
|
components.pop();
|
||||||
|
}
|
||||||
|
Some(Component::ParentDir) | None => components.push(component),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
components.push(component);
|
components.push(component);
|
||||||
|
@ -29,27 +40,50 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn clean() {
|
#[rustfmt::skip]
|
||||||
let cases = &[
|
fn prefix_suffix() {
|
||||||
("/", "foo", "/foo"),
|
fn case(prefix: &str, suffix: &str, want: &str) {
|
||||||
("/", ".", "/"),
|
|
||||||
("/", "foo/./bar", "/foo/bar"),
|
|
||||||
("/foo/./bar", ".", "/foo/bar"),
|
|
||||||
("/bar", "/foo", "/foo"),
|
|
||||||
("//foo", "bar//baz", "/foo/bar/baz"),
|
|
||||||
("/", "..", "/"),
|
|
||||||
("/", "/..", "/"),
|
|
||||||
("/..", "", "/"),
|
|
||||||
("/../../../..", "../../../", "/"),
|
|
||||||
("/.", "./", "/"),
|
|
||||||
("/foo/../", "bar", "/bar"),
|
|
||||||
("/foo/bar", "..", "/foo"),
|
|
||||||
("/foo/bar/", "..", "/foo"),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (prefix, suffix, want) in cases {
|
|
||||||
let have = Path::new(prefix).join(Path::new(suffix)).clean();
|
let have = Path::new(prefix).join(Path::new(suffix)).clean();
|
||||||
assert_eq!(have, Path::new(want));
|
assert_eq!(have, Path::new(want));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
case("/", "foo", "/foo");
|
||||||
|
case("/", "." , "/");
|
||||||
|
case("/", "foo/./bar", "/foo/bar");
|
||||||
|
case("/foo/./bar", ".", "/foo/bar");
|
||||||
|
case("/bar", "/foo", "/foo");
|
||||||
|
case("//foo", "bar//baz", "/foo/bar/baz");
|
||||||
|
case("/", "..", "/");
|
||||||
|
case("/", "/..", "/");
|
||||||
|
case("/..", "", "/");
|
||||||
|
case("/../../../..", "../../../", "/");
|
||||||
|
case("/.", "./", "/");
|
||||||
|
case("/foo/../", "bar", "/bar");
|
||||||
|
case("/foo/bar", "..", "/foo");
|
||||||
|
case("/foo/bar/", "..", "/foo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn simple() {
|
||||||
|
fn case(path: &str, want: &str) {
|
||||||
|
assert_eq!(Path::new(path).clean(), Path::new(want));
|
||||||
|
}
|
||||||
|
|
||||||
|
case("./..", "..");
|
||||||
|
case("./././.", ".");
|
||||||
|
case("./../.", "..");
|
||||||
|
case("..", "..");
|
||||||
|
case("", "");
|
||||||
|
case("foo", "foo");
|
||||||
|
case(".", ".");
|
||||||
|
case("foo/./bar", "foo/bar");
|
||||||
|
case("/foo", "/foo");
|
||||||
|
case("bar//baz", "bar/baz");
|
||||||
|
case("/..", "/");
|
||||||
|
case("../../../", "../../..");
|
||||||
|
case("./", ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
use create_content::CreateContent;
|
||||||
use create_step::CreateStep;
|
use create_step::CreateStep;
|
||||||
|
|
||||||
|
mod create_content;
|
||||||
mod create_step;
|
mod create_step;
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
|
@ -242,12 +244,6 @@ Sort in ascending order by size, break ties in descending path order:
|
||||||
|
|
||||||
impl Create {
|
impl Create {
|
||||||
pub(crate) fn run(self, env: &mut Env) -> Result<(), Error> {
|
pub(crate) fn run(self, env: &mut Env) -> Result<(), Error> {
|
||||||
let input = self.input.resolve(env)?;
|
|
||||||
let output = match self.output {
|
|
||||||
Some(output) => Some(output.resolve(env)?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut linter = Linter::new();
|
let mut linter = Linter::new();
|
||||||
linter.allow(self.allowed_lints.iter().cloned());
|
linter.allow(self.allowed_lints.iter().cloned());
|
||||||
|
|
||||||
|
@ -268,100 +264,24 @@ impl Create {
|
||||||
return Err(Error::PrivateTrackerless);
|
return Err(Error::PrivateTrackerless);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateStep::Searching.print(env)?;
|
CreateStep::Searching { input: &self.input }.print(env)?;
|
||||||
|
|
||||||
let spinner = if env.err().is_styled_term() {
|
let content = CreateContent::from_create(&self, env)?;
|
||||||
let style = ProgressStyle::default_spinner()
|
|
||||||
.template("{spinner:.green} {msg:.bold}…")
|
|
||||||
.tick_chars(consts::TICK_CHARS);
|
|
||||||
|
|
||||||
Some(ProgressBar::new_spinner().with_style(style))
|
let output = content.output.resolve(env)?;
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let files;
|
if content.piece_length.count() == 0 {
|
||||||
let piece_length;
|
|
||||||
let progress_bar;
|
|
||||||
let name;
|
|
||||||
let output = match &input {
|
|
||||||
InputTarget::Path(path) => {
|
|
||||||
let files_inner = Walker::new(&path)
|
|
||||||
.include_junk(self.include_junk)
|
|
||||||
.include_hidden(self.include_hidden)
|
|
||||||
.follow_symlinks(self.follow_symlinks)
|
|
||||||
.sort_by(self.sort_by)
|
|
||||||
.globs(&self.globs)?
|
|
||||||
.spinner(spinner)
|
|
||||||
.files()?;
|
|
||||||
|
|
||||||
piece_length = self
|
|
||||||
.piece_length
|
|
||||||
.unwrap_or_else(|| PieceLengthPicker::from_content_size(files_inner.total_size()));
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
progress_bar = ProgressBar::new(files_inner.total_size().count()).with_style(style);
|
|
||||||
|
|
||||||
let filename = path
|
|
||||||
.file_name()
|
|
||||||
.ok_or_else(|| Error::FilenameExtract { path: path.clone() })?;
|
|
||||||
|
|
||||||
name = match &self.name {
|
|
||||||
Some(name) => name.clone(),
|
|
||||||
None => filename
|
|
||||||
.to_str()
|
|
||||||
.ok_or_else(|| Error::FilenameDecode {
|
|
||||||
filename: PathBuf::from(filename),
|
|
||||||
})?
|
|
||||||
.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
files = Some(files_inner);
|
|
||||||
|
|
||||||
output.unwrap_or_else(|| {
|
|
||||||
let mut torrent_name = name.to_owned();
|
|
||||||
torrent_name.push_str(".torrent");
|
|
||||||
|
|
||||||
OutputTarget::Path(path.parent().unwrap().join(torrent_name))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
InputTarget::Stdin => {
|
|
||||||
files = None;
|
|
||||||
piece_length = self.piece_length.unwrap_or(Bytes::kib() * 256);
|
|
||||||
|
|
||||||
let style = ProgressStyle::default_bar()
|
|
||||||
.template("{spinner:.green} ⟪{elapsed_precise}⟫ {binary_bytes} ⟨{binary_bytes_per_sec}⟩")
|
|
||||||
.tick_chars(consts::TICK_CHARS);
|
|
||||||
|
|
||||||
progress_bar = ProgressBar::new_spinner().with_style(style);
|
|
||||||
|
|
||||||
name = self
|
|
||||||
.name
|
|
||||||
.ok_or_else(|| Error::internal("Expected `--name` to be set when `--input -`."))?;
|
|
||||||
|
|
||||||
output.ok_or_else(|| Error::internal("Expected `--output` to be set when `--input -`."))?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if piece_length.count() == 0 {
|
|
||||||
return Err(Error::PieceLengthZero);
|
return Err(Error::PieceLengthZero);
|
||||||
}
|
}
|
||||||
|
|
||||||
if linter.is_denied(Lint::UnevenPieceLength) && !piece_length.count().is_power_of_two() {
|
if linter.is_denied(Lint::UnevenPieceLength) && !content.piece_length.count().is_power_of_two()
|
||||||
|
{
|
||||||
return Err(Error::PieceLengthUneven {
|
return Err(Error::PieceLengthUneven {
|
||||||
bytes: piece_length,
|
bytes: content.piece_length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if linter.is_denied(Lint::SmallPieceLength) && piece_length.count() < 16 * 1024 {
|
if linter.is_denied(Lint::SmallPieceLength) && content.piece_length.count() < 16 * 1024 {
|
||||||
return Err(Error::PieceLengthSmall);
|
return Err(Error::PieceLengthSmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,28 +315,31 @@ impl Create {
|
||||||
|
|
||||||
let hasher = Hasher::new(
|
let hasher = Hasher::new(
|
||||||
self.md5sum,
|
self.md5sum,
|
||||||
piece_length.as_piece_length()?.into_usize(),
|
content.piece_length.as_piece_length()?.into_usize(),
|
||||||
if env.err().is_styled_term() {
|
if env.err().is_styled_term() {
|
||||||
Some(progress_bar)
|
Some(content.progress_bar)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let (mode, pieces) = if let Some(files) = files {
|
let (mode, pieces) = if let Some(files) = content.files {
|
||||||
hasher.hash_files(&files)?
|
hasher.hash_files(&files)?
|
||||||
} else {
|
} else {
|
||||||
hasher.hash_stdin(&mut env.input())?
|
hasher.hash_stdin(&mut env.input())?
|
||||||
};
|
};
|
||||||
|
|
||||||
CreateStep::Writing { output: &output }.print(env)?;
|
CreateStep::Writing {
|
||||||
|
output: &content.output,
|
||||||
|
}
|
||||||
|
.print(env)?;
|
||||||
|
|
||||||
let info = Info {
|
let info = Info {
|
||||||
source: self.source,
|
source: self.source,
|
||||||
piece_length,
|
piece_length: content.piece_length,
|
||||||
|
name: content.name,
|
||||||
mode,
|
mode,
|
||||||
pieces,
|
pieces,
|
||||||
name,
|
|
||||||
private,
|
private,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -463,12 +386,12 @@ impl Create {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
{
|
{
|
||||||
if let InputTarget::Path(path) = &input {
|
if let InputTarget::Path(path) = &self.input {
|
||||||
let deserialized = bendy::serde::de::from_bytes::<Metainfo>(&bytes).unwrap();
|
let deserialized = bendy::serde::de::from_bytes::<Metainfo>(&bytes).unwrap();
|
||||||
|
|
||||||
assert_eq!(deserialized, metainfo);
|
assert_eq!(deserialized, metainfo);
|
||||||
|
|
||||||
let status = metainfo.verify(path, None)?;
|
let status = metainfo.verify(&env.resolve(path)?, None)?;
|
||||||
|
|
||||||
status.print(env)?;
|
status.print(env)?;
|
||||||
|
|
||||||
|
@ -562,7 +485,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let torrent = env.resolve("foo.torrent")?;
|
let torrent = env.resolve("foo.torrent")?;
|
||||||
let bytes = fs::read(torrent).unwrap();
|
let bytes = fs::read(torrent).unwrap();
|
||||||
let value = Value::from_bencode(&bytes).unwrap();
|
let value = Value::from_bencode(&bytes).unwrap();
|
||||||
|
@ -588,10 +511,11 @@ mod tests {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("../dir.torrent");
|
// let metainfo = env.load_metainfo("../dir.torrent");
|
||||||
assert_eq!(metainfo.info.name, "dir");
|
// assert_eq!(metainfo.info.name, "dir");
|
||||||
assert_matches!(metainfo.info.mode, Mode::Multiple{files} if files.len() == 1);
|
// assert_matches!(metainfo.info.mode, Mode::Multiple{files} if files.len()
|
||||||
|
// == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -614,7 +538,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("../../a.torrent");
|
let metainfo = env.load_metainfo("../../a.torrent");
|
||||||
assert_eq!(metainfo.info.name, "a");
|
assert_eq!(metainfo.info.name, "a");
|
||||||
assert_matches!(metainfo.info.mode, Mode::Multiple{files} if files.len() == 1);
|
assert_matches!(metainfo.info.mode, Mode::Multiple{files} if files.len() == 1);
|
||||||
|
@ -628,7 +552,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.private, None);
|
assert_eq!(metainfo.info.private, None);
|
||||||
}
|
}
|
||||||
|
@ -641,7 +565,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.private, Some(true));
|
assert_eq!(metainfo.info.private, Some(true));
|
||||||
}
|
}
|
||||||
|
@ -665,7 +589,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.announce, Some("http://bar/".into()));
|
assert_eq!(metainfo.announce, Some("http://bar/".into()));
|
||||||
assert!(metainfo.announce_list.is_none());
|
assert!(metainfo.announce_list.is_none());
|
||||||
|
@ -686,7 +610,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.announce.as_deref(),
|
metainfo.announce.as_deref(),
|
||||||
|
@ -710,7 +634,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.announce.as_deref(),
|
metainfo.announce.as_deref(),
|
||||||
|
@ -736,7 +660,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.announce.as_deref(), Some("http://bar/"));
|
assert_eq!(metainfo.announce.as_deref(), Some("http://bar/"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -764,7 +688,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.announce.as_deref(), Some("http://bar/"));
|
assert_eq!(metainfo.announce.as_deref(), Some("http://bar/"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -791,7 +715,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.comment, None);
|
assert_eq!(metainfo.comment, None);
|
||||||
}
|
}
|
||||||
|
@ -813,7 +737,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.comment.unwrap(), "Hello, world!");
|
assert_eq!(metainfo.comment.unwrap(), "Hello, world!");
|
||||||
}
|
}
|
||||||
|
@ -833,7 +757,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.piece_length, Bytes::from(16 * 2u32.pow(10)));
|
assert_eq!(metainfo.info.piece_length, Bytes::from(16 * 2u32.pow(10)));
|
||||||
}
|
}
|
||||||
|
@ -855,7 +779,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.piece_length, Bytes(64 * 1024));
|
assert_eq!(metainfo.info.piece_length, Bytes(64 * 1024));
|
||||||
}
|
}
|
||||||
|
@ -877,7 +801,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.piece_length, Bytes(512 * 1024));
|
assert_eq!(metainfo.info.piece_length, Bytes(512 * 1024));
|
||||||
}
|
}
|
||||||
|
@ -899,7 +823,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.name, "foo");
|
assert_eq!(metainfo.info.name, "foo");
|
||||||
}
|
}
|
||||||
|
@ -923,7 +847,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo/bar.torrent");
|
let metainfo = env.load_metainfo("foo/bar.torrent");
|
||||||
assert_eq!(metainfo.info.name, "bar");
|
assert_eq!(metainfo.info.name, "bar");
|
||||||
}
|
}
|
||||||
|
@ -945,7 +869,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
env.load_metainfo("x.torrent");
|
env.load_metainfo("x.torrent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -964,7 +888,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.created_by.unwrap(), consts::CREATED_BY_DEFAULT);
|
assert_eq!(metainfo.created_by.unwrap(), consts::CREATED_BY_DEFAULT);
|
||||||
}
|
}
|
||||||
|
@ -985,7 +909,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.created_by, None);
|
assert_eq!(metainfo.created_by, None);
|
||||||
}
|
}
|
||||||
|
@ -1005,7 +929,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.encoding, Some("UTF-8".into()));
|
assert_eq!(metainfo.encoding, Some("UTF-8".into()));
|
||||||
}
|
}
|
||||||
|
@ -1029,7 +953,7 @@ mod tests {
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs();
|
.as_secs();
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert!(metainfo.creation_date.unwrap() < now + 10);
|
assert!(metainfo.creation_date.unwrap() < now + 10);
|
||||||
assert!(metainfo.creation_date.unwrap() > now - 10);
|
assert!(metainfo.creation_date.unwrap() > now - 10);
|
||||||
|
@ -1051,7 +975,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.creation_date, None);
|
assert_eq!(metainfo.creation_date, None);
|
||||||
}
|
}
|
||||||
|
@ -1075,7 +999,7 @@ mod tests {
|
||||||
foo: "123",
|
foo: "123",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["123"]));
|
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["123"]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1106,7 +1030,7 @@ mod tests {
|
||||||
foo: "1234",
|
foo: "1234",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["1234"]));
|
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["1234"]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1137,7 +1061,7 @@ mod tests {
|
||||||
foo: "1234",
|
foo: "1234",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["12", "34"]));
|
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["12", "34"]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1172,7 +1096,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("dir.torrent");
|
let metainfo = env.load_metainfo("dir.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["56781234"]));
|
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["56781234"]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1209,7 +1133,7 @@ mod tests {
|
||||||
foo: "bar",
|
foo: "bar",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["bar"]));
|
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["bar"]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1240,7 +1164,7 @@ mod tests {
|
||||||
foo: "bar",
|
foo: "bar",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.info.pieces,
|
metainfo.info.pieces,
|
||||||
|
@ -1270,7 +1194,7 @@ mod tests {
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces.count(), 0);
|
assert_eq!(metainfo.info.pieces.count(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1297,7 +1221,7 @@ mod tests {
|
||||||
foo: {},
|
foo: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces.count(), 0);
|
assert_eq!(metainfo.info.pieces.count(), 0);
|
||||||
assert_eq!(metainfo.info.mode, Mode::Multiple { files: Vec::new() })
|
assert_eq!(metainfo.info.mode, Mode::Multiple { files: Vec::new() })
|
||||||
|
@ -1321,7 +1245,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["bar"]));
|
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["bar"]));
|
||||||
match metainfo.info.mode {
|
match metainfo.info.mode {
|
||||||
|
@ -1356,7 +1280,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["bar"]));
|
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["bar"]));
|
||||||
match metainfo.info.mode {
|
match metainfo.info.mode {
|
||||||
|
@ -1466,7 +1390,7 @@ mod tests {
|
||||||
foo: {},
|
foo: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
env.load_metainfo("foo.torrent");
|
env.load_metainfo("foo.torrent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1529,7 +1453,7 @@ mod tests {
|
||||||
foo: {},
|
foo: {},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
env.load_metainfo("foo.torrent");
|
env.load_metainfo("foo.torrent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1554,7 +1478,7 @@ mod tests {
|
||||||
fs::write(dir.join("a"), "abc").unwrap();
|
fs::write(dir.join("a"), "abc").unwrap();
|
||||||
fs::write(dir.join("x"), "xyz").unwrap();
|
fs::write(dir.join("x"), "xyz").unwrap();
|
||||||
fs::write(dir.join("h"), "hij").unwrap();
|
fs::write(dir.join("h"), "hij").unwrap();
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
assert_eq!(env.out(), "");
|
assert_eq!(env.out(), "");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1581,7 +1505,7 @@ mod tests {
|
||||||
fs::write(dir.join("a"), "abc").unwrap();
|
fs::write(dir.join("a"), "abc").unwrap();
|
||||||
fs::write(dir.join("x"), "xyz").unwrap();
|
fs::write(dir.join("x"), "xyz").unwrap();
|
||||||
fs::write(dir.join("h"), "hij").unwrap();
|
fs::write(dir.join("h"), "hij").unwrap();
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let have = env.out();
|
let have = env.out();
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let want = format!(
|
let want = format!(
|
||||||
|
@ -1624,7 +1548,8 @@ Content Size 9 bytes
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
|
|
||||||
let bytes = env.out_bytes();
|
let bytes = env.out_bytes();
|
||||||
Metainfo::from_bytes(&bytes);
|
Metainfo::from_bytes(&bytes);
|
||||||
}
|
}
|
||||||
|
@ -1670,7 +1595,7 @@ Content Size 9 bytes
|
||||||
"foo.torrent": "foo",
|
"foo.torrent": "foo",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
env.load_metainfo("foo.torrent");
|
env.load_metainfo("foo.torrent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1692,7 +1617,7 @@ Content Size 9 bytes
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -1720,7 +1645,7 @@ Content Size 9 bytes
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -1764,7 +1689,7 @@ Content Size 9 bytes
|
||||||
fs::remove_file(env.resolve("foo/hidden")?).unwrap();
|
fs::remove_file(env.resolve("foo/hidden")?).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
|
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
|
|
||||||
|
@ -1810,7 +1735,7 @@ Content Size 9 bytes
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -1867,7 +1792,7 @@ Content Size 9 bytes
|
||||||
tree: {},
|
tree: {},
|
||||||
};
|
};
|
||||||
populate_symlinks(&env)?;
|
populate_symlinks(&env)?;
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -1894,7 +1819,7 @@ Content Size 9 bytes
|
||||||
tree: {},
|
tree: {},
|
||||||
};
|
};
|
||||||
populate_symlinks(&env)?;
|
populate_symlinks(&env)?;
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
let mut pieces = PieceList::new();
|
let mut pieces = PieceList::new();
|
||||||
pieces.push(Sha1::from("barbaz").digest().into());
|
pieces.push(Sha1::from("barbaz").digest().into());
|
||||||
|
@ -2021,7 +1946,7 @@ Content Size 9 bytes
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -2052,7 +1977,7 @@ Content Size 9 bytes
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -2085,7 +2010,7 @@ Content Size 9 bytes
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -2117,7 +2042,7 @@ Content Size 9 bytes
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -2149,7 +2074,7 @@ Content Size 9 bytes
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -2183,7 +2108,7 @@ Content Size 9 bytes
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -2207,7 +2132,7 @@ Content Size 9 bytes
|
||||||
foo: "",
|
foo: "",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert!(metainfo.nodes.is_none());
|
assert!(metainfo.nodes.is_none());
|
||||||
}
|
}
|
||||||
|
@ -2253,7 +2178,7 @@ Content Size 9 bytes
|
||||||
foo: "",
|
foo: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.assert_ok();
|
||||||
let metainfo = env.load_metainfo("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.nodes,
|
metainfo.nodes,
|
||||||
|
@ -2267,34 +2192,6 @@ Content Size 9 bytes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn create_progress_messages() -> Result<()> {
|
|
||||||
let mut env = TestEnvBuilder::new()
|
|
||||||
.arg_slice(&[
|
|
||||||
"imdl",
|
|
||||||
"torrent",
|
|
||||||
"create",
|
|
||||||
"--input",
|
|
||||||
"foo",
|
|
||||||
"--announce",
|
|
||||||
"http://bar",
|
|
||||||
])
|
|
||||||
.build();
|
|
||||||
|
|
||||||
fs::write(env.resolve("foo")?, "").unwrap();
|
|
||||||
|
|
||||||
let want = format!(
|
|
||||||
"[1/3] \u{1F9FF} Searching for files…\n[2/3] \u{1F9EE} Hashing pieces…\n[3/3] \u{1F4BE} \
|
|
||||||
Writing metainfo to `{}`…\n\u{2728}\u{2728} Done! \u{2728}\u{2728}\n",
|
|
||||||
env.resolve("foo.torrent")?.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
env.run().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(env.err(), want);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn private_requires_announce() {
|
fn private_requires_announce() {
|
||||||
let mut env = test_env! {
|
let mut env = test_env! {
|
||||||
|
@ -2714,4 +2611,227 @@ Content Size 9 bytes
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_messages_path() -> Result<()> {
|
||||||
|
let mut env = test_env! {
|
||||||
|
args: [
|
||||||
|
"torrent",
|
||||||
|
"create",
|
||||||
|
"--input",
|
||||||
|
"foo",
|
||||||
|
"--announce",
|
||||||
|
"https://bar",
|
||||||
|
],
|
||||||
|
tree: {
|
||||||
|
foo: "",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let want = format!(
|
||||||
|
"[1/3] \u{1F9FF} Searching `foo` for files…\n[2/3] \u{1F9EE} Hashing pieces…\n[3/3] \
|
||||||
|
\u{1F4BE} Writing metainfo to `foo.torrent`…\n\u{2728}\u{2728} Done! \u{2728}\u{2728}\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
env.assert_ok();
|
||||||
|
|
||||||
|
assert_eq!(env.err(), want);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_messages_subdir() -> Result<()> {
|
||||||
|
let mut env = test_env! {
|
||||||
|
args: [
|
||||||
|
"torrent",
|
||||||
|
"create",
|
||||||
|
"--input",
|
||||||
|
"foo/bar",
|
||||||
|
"--announce",
|
||||||
|
"https://bar",
|
||||||
|
],
|
||||||
|
tree: {
|
||||||
|
foo: {
|
||||||
|
bar: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let want = format!(
|
||||||
|
"[1/3] \u{1F9FF} Searching `foo/bar` for files…\n[2/3] \u{1F9EE} Hashing pieces…\n[3/3] \
|
||||||
|
\u{1F4BE} Writing metainfo to `{}`…\n\u{2728}\u{2728} Done! \u{2728}\u{2728}\n",
|
||||||
|
Path::new("foo").join("bar.torrent").display(),
|
||||||
|
);
|
||||||
|
|
||||||
|
env.assert_ok();
|
||||||
|
|
||||||
|
assert_eq!(env.err(), want);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_messages_dot() -> Result<()> {
|
||||||
|
let mut env = test_env! {
|
||||||
|
args: [
|
||||||
|
"torrent",
|
||||||
|
"create",
|
||||||
|
"--input",
|
||||||
|
".",
|
||||||
|
"--announce",
|
||||||
|
"https://bar",
|
||||||
|
],
|
||||||
|
cwd: "dir",
|
||||||
|
tree: {
|
||||||
|
dir: {
|
||||||
|
foo: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
env.assert_ok();
|
||||||
|
let metainfo = env.load_metainfo("../dir.torrent");
|
||||||
|
assert_eq!(metainfo.info.name, "dir");
|
||||||
|
assert_matches!(metainfo.info.mode, Mode::Multiple{files} if files.len() == 1);
|
||||||
|
|
||||||
|
let want = format!(
|
||||||
|
"[1/3] \u{1F9FF} Searching `.` for files…\n[2/3] \u{1F9EE} Hashing pieces…\n[3/3] \u{1F4BE} \
|
||||||
|
Writing metainfo to `{}`…\n\u{2728}\u{2728} Done! \u{2728}\u{2728}\n",
|
||||||
|
Path::new("..").join("dir.torrent").display(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(env.err(), want);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_messages_dot_dot() -> Result<()> {
|
||||||
|
let mut env = test_env! {
|
||||||
|
args: [
|
||||||
|
"torrent",
|
||||||
|
"create",
|
||||||
|
"--input",
|
||||||
|
"..",
|
||||||
|
"--announce",
|
||||||
|
"https://bar",
|
||||||
|
],
|
||||||
|
cwd: "a/b",
|
||||||
|
tree: {
|
||||||
|
a: {
|
||||||
|
b: {
|
||||||
|
foo: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
env.assert_ok();
|
||||||
|
let metainfo = env.load_metainfo("../../a.torrent");
|
||||||
|
assert_eq!(metainfo.info.name, "a");
|
||||||
|
assert_matches!(metainfo.info.mode, Mode::Multiple{files} if files.len() == 1);
|
||||||
|
|
||||||
|
let want = format!(
|
||||||
|
"[1/3] \u{1F9FF} Searching `..` for files…\n[2/3] \u{1F9EE} Hashing pieces…\n[3/3] \
|
||||||
|
\u{1F4BE} Writing metainfo to `{}`…\n\u{2728}\u{2728} Done! \u{2728}\u{2728}\n",
|
||||||
|
Path::new("..").join("..").join("a.torrent").display(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(env.err(), want);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_messages_absolute() -> Result<()> {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
|
||||||
|
let input = dir.path().join("foo");
|
||||||
|
|
||||||
|
fs::write(&input, "").unwrap();
|
||||||
|
|
||||||
|
let mut env = test_env! {
|
||||||
|
args: [
|
||||||
|
"torrent",
|
||||||
|
"create",
|
||||||
|
"--input",
|
||||||
|
&input,
|
||||||
|
"--announce",
|
||||||
|
"https://bar",
|
||||||
|
],
|
||||||
|
tree: {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let torrent = dir.path().join("foo.torrent");
|
||||||
|
|
||||||
|
env.assert_ok();
|
||||||
|
|
||||||
|
let metainfo = env.load_metainfo(&torrent);
|
||||||
|
assert_eq!(metainfo.info.name, "foo");
|
||||||
|
|
||||||
|
let want = format!(
|
||||||
|
"[1/3] \u{1F9FF} Searching `{}` for files…\n[2/3] \u{1F9EE} Hashing pieces…\n[3/3] \
|
||||||
|
\u{1F4BE} Writing metainfo to `{}`…\n\u{2728}\u{2728} Done! \u{2728}\u{2728}\n",
|
||||||
|
input.display(),
|
||||||
|
torrent.display(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(env.err(), want);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_messages_stdio() -> Result<()> {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
|
||||||
|
let input = dir.path().join("foo");
|
||||||
|
|
||||||
|
fs::write(&input, "").unwrap();
|
||||||
|
|
||||||
|
let mut env = test_env! {
|
||||||
|
args: [
|
||||||
|
"torrent",
|
||||||
|
"create",
|
||||||
|
"--input",
|
||||||
|
"-",
|
||||||
|
"--announce",
|
||||||
|
"https://bar",
|
||||||
|
"--name",
|
||||||
|
"foo",
|
||||||
|
"--output",
|
||||||
|
"-",
|
||||||
|
"--md5",
|
||||||
|
],
|
||||||
|
input: "hello",
|
||||||
|
tree: {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
env.assert_ok();
|
||||||
|
|
||||||
|
let bytes = env.out_bytes();
|
||||||
|
let metainfo = Metainfo::from_bytes(&bytes);
|
||||||
|
|
||||||
|
assert_eq!(metainfo.info.pieces, PieceList::from_pieces(&["hello"]));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
metainfo.info.mode,
|
||||||
|
Mode::Single {
|
||||||
|
length: Bytes(5),
|
||||||
|
md5sum: Some(Md5Digest::from_data("hello")),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let want = format!(
|
||||||
|
"[1/3] \u{1F9FF} Creating single-file torrent from standard input…\n[2/3] \u{1F9EE} Hashing \
|
||||||
|
pieces…\n[3/3] \u{1F4BE} Writing metainfo to standard output…\n\u{2728}\u{2728} Done! \
|
||||||
|
\u{2728}\u{2728}\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(env.err(), want);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
153
src/subcommand/torrent/create/create_content.rs
Normal file
153
src/subcommand/torrent/create/create_content.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
use super::Create;
|
||||||
|
|
||||||
|
pub(crate) struct CreateContent {
|
||||||
|
pub(crate) files: Option<Files>,
|
||||||
|
pub(crate) piece_length: Bytes,
|
||||||
|
pub(crate) progress_bar: ProgressBar,
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) output: OutputTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateContent {
|
||||||
|
pub(crate) fn from_create(create: &Create, env: &mut Env) -> Result<Self> {
|
||||||
|
match &create.input {
|
||||||
|
InputTarget::Path(path) => {
|
||||||
|
let spinner = if env.err().is_styled_term() {
|
||||||
|
let style = ProgressStyle::default_spinner()
|
||||||
|
.template("{spinner:.green} {msg:.bold}…")
|
||||||
|
.tick_chars(consts::TICK_CHARS);
|
||||||
|
|
||||||
|
Some(ProgressBar::new_spinner().with_style(style))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let files = Walker::new(&env.resolve(path)?)
|
||||||
|
.include_junk(create.include_junk)
|
||||||
|
.include_hidden(create.include_hidden)
|
||||||
|
.follow_symlinks(create.follow_symlinks)
|
||||||
|
.sort_by(create.sort_by.clone())
|
||||||
|
.globs(&create.globs)?
|
||||||
|
.spinner(spinner)
|
||||||
|
.files()?;
|
||||||
|
|
||||||
|
let piece_length = create
|
||||||
|
.piece_length
|
||||||
|
.unwrap_or_else(|| PieceLengthPicker::from_content_size(files.total_size()));
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let progress_bar = ProgressBar::new(files.total_size().count()).with_style(style);
|
||||||
|
|
||||||
|
let resolved = env.resolve(path)?;
|
||||||
|
|
||||||
|
let filename = resolved
|
||||||
|
.file_name()
|
||||||
|
.ok_or_else(|| Error::FilenameExtract { path: path.clone() })?;
|
||||||
|
|
||||||
|
let name = match &create.name {
|
||||||
|
Some(name) => name.clone(),
|
||||||
|
None => filename
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| Error::FilenameDecode {
|
||||||
|
filename: PathBuf::from(filename),
|
||||||
|
})?
|
||||||
|
.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = create
|
||||||
|
.output
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| OutputTarget::Path(Self::torrent_path(path, &name)));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
files: Some(files),
|
||||||
|
piece_length,
|
||||||
|
progress_bar,
|
||||||
|
name,
|
||||||
|
output,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
InputTarget::Stdin => {
|
||||||
|
let files = None;
|
||||||
|
let piece_length = create.piece_length.unwrap_or(Bytes::kib() * 256);
|
||||||
|
|
||||||
|
let style = ProgressStyle::default_bar()
|
||||||
|
.template("{spinner:.green} ⟪{elapsed_precise}⟫ {binary_bytes} ⟨{binary_bytes_per_sec}⟩")
|
||||||
|
.tick_chars(consts::TICK_CHARS);
|
||||||
|
|
||||||
|
let progress_bar = ProgressBar::new_spinner().with_style(style);
|
||||||
|
|
||||||
|
let name = create
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| Error::internal("Expected `--name` to be set when `--input -`."))?;
|
||||||
|
|
||||||
|
let output = create
|
||||||
|
.output
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| Error::internal("Expected `--output` to be set when `--input -`."))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
files,
|
||||||
|
piece_length,
|
||||||
|
progress_bar,
|
||||||
|
name,
|
||||||
|
output,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn torrent_path(input: &Path, name: &str) -> PathBuf {
|
||||||
|
input.join("..").clean().join(format!("{}.torrent", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn torrent_path() {
|
||||||
|
fn case(path: &str, name: &str, expected: impl AsRef<Path>) {
|
||||||
|
let expected = expected.as_ref();
|
||||||
|
assert_eq!(
|
||||||
|
CreateContent::torrent_path(Path::new(path), name),
|
||||||
|
expected,
|
||||||
|
"{} + {} != {}",
|
||||||
|
path,
|
||||||
|
name,
|
||||||
|
expected.display(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
use path::Component;
|
||||||
|
|
||||||
|
case("foo", "foo", "foo.torrent");
|
||||||
|
case("foo", "foo", "foo.torrent");
|
||||||
|
case("foo", "bar", "bar.torrent");
|
||||||
|
case("foo/bar", "foo", Path::new("foo").join("foo.torrent"));
|
||||||
|
case("foo/bar", "bar", Path::new("foo").join("bar.torrent"));
|
||||||
|
case(
|
||||||
|
"/foo/bar",
|
||||||
|
"bar",
|
||||||
|
Path::new(&Component::RootDir)
|
||||||
|
.join("foo")
|
||||||
|
.join("bar.torrent"),
|
||||||
|
);
|
||||||
|
case(".", "foo", Path::new("..").join("foo.torrent"));
|
||||||
|
case("..", "foo", Path::new("..").join("..").join("foo.torrent"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,16 @@
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(crate) enum CreateStep<'output> {
|
pub(crate) enum CreateStep<'a> {
|
||||||
Searching,
|
Searching { input: &'a InputTarget },
|
||||||
Hashing,
|
Hashing,
|
||||||
Writing { output: &'output OutputTarget },
|
Writing { output: &'a OutputTarget },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'output> Step for CreateStep<'output> {
|
impl<'a> Step for CreateStep<'a> {
|
||||||
fn n(&self) -> usize {
|
fn n(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::Searching => 1,
|
Self::Searching { .. } => 1,
|
||||||
Self::Hashing => 2,
|
Self::Hashing => 2,
|
||||||
Self::Writing { .. } => 3,
|
Self::Writing { .. } => 3,
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ impl<'output> Step for CreateStep<'output> {
|
||||||
|
|
||||||
fn symbol(&self) -> &str {
|
fn symbol(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::Searching => "\u{1F9FF}",
|
Self::Searching { .. } => "\u{1F9FF}",
|
||||||
Self::Hashing => "\u{1F9EE}",
|
Self::Hashing => "\u{1F9EE}",
|
||||||
Self::Writing { .. } => "\u{1F4BE}",
|
Self::Writing { .. } => "\u{1F4BE}",
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,11 @@ impl<'output> Step for CreateStep<'output> {
|
||||||
|
|
||||||
fn write_message(&self, write: &mut dyn Write) -> io::Result<()> {
|
fn write_message(&self, write: &mut dyn Write) -> io::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::Searching => write!(write, "Searching for files…"),
|
Self::Searching { input } => match input {
|
||||||
|
InputTarget::Path(path) => write!(write, "Searching `{}` for files…", path.display()),
|
||||||
|
InputTarget::Stdin => write!(write, "Creating single-file torrent from standard input…"),
|
||||||
|
},
|
||||||
|
|
||||||
Self::Hashing => write!(write, "Hashing pieces…"),
|
Self::Hashing => write!(write, "Hashing pieces…"),
|
||||||
Self::Writing { output } => write!(write, "Writing metainfo to {}…", output),
|
Self::Writing { output } => write!(write, "Writing metainfo to {}…", output),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user