Support adding DHT bootstrap nodes to created torrents
The --dht-node flag can be used to add DHT bootstrap nodes to new torrents. This is the only piece of metainfo-related functionality in BEP 5, so we can mark BEP 5 as implemented. type: added
This commit is contained in:
parent
6549850dac
commit
165a7ea444
|
@ -81,7 +81,7 @@ at any time.
|
||||||
| [02](http://bittorrent.org/beps/bep_0002.html) | :heavy_minus_sign: | Sample reStructured Text BEP Template |
|
| [02](http://bittorrent.org/beps/bep_0002.html) | :heavy_minus_sign: | Sample reStructured Text BEP Template |
|
||||||
| [03](http://bittorrent.org/beps/bep_0003.html) | :white_check_mark: | The BitTorrent Protocol Specification |
|
| [03](http://bittorrent.org/beps/bep_0003.html) | :white_check_mark: | The BitTorrent Protocol Specification |
|
||||||
| [04](http://bittorrent.org/beps/bep_0004.html) | :heavy_minus_sign: | Assigned Numbers |
|
| [04](http://bittorrent.org/beps/bep_0004.html) | :heavy_minus_sign: | Assigned Numbers |
|
||||||
| [05](http://bittorrent.org/beps/bep_0005.html) | [:x:](https://github.com/casey/intermodal/issues/90) | DHT Protocol |
|
| [05](http://bittorrent.org/beps/bep_0005.html) | :white_check_mark: | DHT Protocol |
|
||||||
| [06](http://bittorrent.org/beps/bep_0006.html) | :heavy_minus_sign: | Fast Extension |
|
| [06](http://bittorrent.org/beps/bep_0006.html) | :heavy_minus_sign: | Fast Extension |
|
||||||
| [07](http://bittorrent.org/beps/bep_0007.html) | :heavy_minus_sign: | IPv6 Tracker Extension |
|
| [07](http://bittorrent.org/beps/bep_0007.html) | :heavy_minus_sign: | IPv6 Tracker Extension |
|
||||||
| [08](http://bittorrent.org/beps/bep_0008.html) | :heavy_minus_sign: | Tracker Peer Obfuscation |
|
| [08](http://bittorrent.org/beps/bep_0008.html) | :heavy_minus_sign: | Tracker Peer Obfuscation |
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub(crate) use std::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
iter::{self, Sum},
|
iter::{self, Sum},
|
||||||
num::{ParseFloatError, TryFromIntError},
|
num::{ParseFloatError, ParseIntError, TryFromIntError},
|
||||||
ops::{AddAssign, Div, DivAssign, Mul, MulAssign, SubAssign},
|
ops::{AddAssign, Div, DivAssign, Mul, MulAssign, SubAssign},
|
||||||
path::{self, Path, PathBuf},
|
path::{self, Path, PathBuf},
|
||||||
process::{self, Command, ExitStatus},
|
process::{self, Command, ExitStatus},
|
||||||
|
@ -26,7 +26,7 @@ pub(crate) use chrono::{TimeZone, Utc};
|
||||||
pub(crate) use globset::{Glob, GlobMatcher};
|
pub(crate) use globset::{Glob, GlobMatcher};
|
||||||
pub(crate) use libc::EXIT_FAILURE;
|
pub(crate) use libc::EXIT_FAILURE;
|
||||||
pub(crate) use regex::{Regex, RegexSet};
|
pub(crate) use regex::{Regex, RegexSet};
|
||||||
pub(crate) use serde::{Deserialize, Serialize};
|
pub(crate) use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
pub(crate) use serde_hex::SerHex;
|
pub(crate) use serde_hex::SerHex;
|
||||||
pub(crate) use serde_with::rust::unwrap_or_skip;
|
pub(crate) use serde_with::rust::unwrap_or_skip;
|
||||||
pub(crate) use sha1::Sha1;
|
pub(crate) use sha1::Sha1;
|
||||||
|
@ -37,7 +37,7 @@ pub(crate) use structopt::{
|
||||||
StructOpt,
|
StructOpt,
|
||||||
};
|
};
|
||||||
pub(crate) use unicode_width::UnicodeWidthStr;
|
pub(crate) use unicode_width::UnicodeWidthStr;
|
||||||
pub(crate) use url::Url;
|
pub(crate) use url::{Host, Url};
|
||||||
pub(crate) use walkdir::WalkDir;
|
pub(crate) use walkdir::WalkDir;
|
||||||
|
|
||||||
// modules
|
// modules
|
||||||
|
@ -53,7 +53,7 @@ pub(crate) use crate::{
|
||||||
pub(crate) use crate::{
|
pub(crate) use crate::{
|
||||||
bytes::Bytes, env::Env, error::Error, file_info::FileInfo, file_path::FilePath,
|
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,
|
file_status::FileStatus, files::Files, hasher::Hasher, info::Info, lint::Lint, linter::Linter,
|
||||||
md5_digest::Md5Digest, metainfo::Metainfo, mode::Mode, opt::Opt,
|
md5_digest::Md5Digest, metainfo::Metainfo, mode::Mode, node::Node, opt::Opt,
|
||||||
piece_length_picker::PieceLengthPicker, platform::Platform, status::Status, style::Style,
|
piece_length_picker::PieceLengthPicker, platform::Platform, status::Status, style::Style,
|
||||||
table::Table, target::Target, torrent_summary::TorrentSummary, use_color::UseColor,
|
table::Table, target::Target, torrent_summary::TorrentSummary, use_color::UseColor,
|
||||||
verifier::Verifier, walker::Walker,
|
verifier::Verifier, walker::Walker,
|
||||||
|
|
17
src/error.rs
17
src/error.rs
|
@ -29,14 +29,25 @@ 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.to_string_lossy()))]
|
#[snafu(display("Filename was not valid unicode: {}", filename.display()))]
|
||||||
FilenameDecode { filename: OsString },
|
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("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 },
|
||||||
#[snafu(display("Invalid glob: {}", source))]
|
#[snafu(display("Invalid glob: {}", source))]
|
||||||
GlobParse { source: globset::Error },
|
GlobParse { source: globset::Error },
|
||||||
|
#[snafu(display("Unknown lint: {}", text))]
|
||||||
|
LintUnknown { text: String },
|
||||||
|
#[snafu(display("DHT node port missing: {}", text))]
|
||||||
|
NodeParsePortMissing { text: String },
|
||||||
|
#[snafu(display("Failed to parse DHT node host `{}`: {}", text, source))]
|
||||||
|
NodeParseHost {
|
||||||
|
text: String,
|
||||||
|
source: url::ParseError,
|
||||||
|
},
|
||||||
|
#[snafu(display("Failed to parse DHT node port `{}`: {}", text, source))]
|
||||||
|
NodeParsePort { text: String, source: ParseIntError },
|
||||||
#[snafu(display("Failed to find opener utility, please install one of {}", tried.join(",")))]
|
#[snafu(display("Failed to find opener utility, please install one of {}", tried.join(",")))]
|
||||||
OpenerMissing { tried: &'static [&'static str] },
|
OpenerMissing { tried: &'static [&'static str] },
|
||||||
#[snafu(display(
|
#[snafu(display(
|
||||||
|
@ -104,8 +115,6 @@ pub(crate) enum Error {
|
||||||
feature
|
feature
|
||||||
))]
|
))]
|
||||||
Unstable { feature: &'static str },
|
Unstable { feature: &'static str },
|
||||||
#[snafu(display("Unknown lint: {}", text))]
|
|
||||||
LintUnknown { text: String },
|
|
||||||
#[snafu(display("Torrent verification failed: {}", status))]
|
#[snafu(display("Torrent verification failed: {}", status))]
|
||||||
Verify { status: Status },
|
Verify { status: Status },
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ mod linter;
|
||||||
mod md5_digest;
|
mod md5_digest;
|
||||||
mod metainfo;
|
mod metainfo;
|
||||||
mod mode;
|
mod mode;
|
||||||
|
mod node;
|
||||||
mod opt;
|
mod opt;
|
||||||
mod path_ext;
|
mod path_ext;
|
||||||
mod piece_length_picker;
|
mod piece_length_picker;
|
||||||
|
|
|
@ -3,8 +3,8 @@ use crate::common::*;
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
|
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
|
||||||
pub(crate) struct Metainfo {
|
pub(crate) struct Metainfo {
|
||||||
pub(crate) announce: String,
|
pub(crate) announce: String,
|
||||||
#[serde(rename = "announce-list")]
|
|
||||||
#[serde(
|
#[serde(
|
||||||
|
rename = "announce-list",
|
||||||
skip_serializing_if = "Option::is_none",
|
skip_serializing_if = "Option::is_none",
|
||||||
default,
|
default,
|
||||||
with = "unwrap_or_skip"
|
with = "unwrap_or_skip"
|
||||||
|
@ -16,15 +16,15 @@ pub(crate) struct Metainfo {
|
||||||
with = "unwrap_or_skip"
|
with = "unwrap_or_skip"
|
||||||
)]
|
)]
|
||||||
pub(crate) comment: Option<String>,
|
pub(crate) comment: Option<String>,
|
||||||
#[serde(rename = "created by")]
|
|
||||||
#[serde(
|
#[serde(
|
||||||
|
rename = "created by",
|
||||||
skip_serializing_if = "Option::is_none",
|
skip_serializing_if = "Option::is_none",
|
||||||
default,
|
default,
|
||||||
with = "unwrap_or_skip"
|
with = "unwrap_or_skip"
|
||||||
)]
|
)]
|
||||||
pub(crate) created_by: Option<String>,
|
pub(crate) created_by: Option<String>,
|
||||||
#[serde(rename = "creation date")]
|
|
||||||
#[serde(
|
#[serde(
|
||||||
|
rename = "creation date",
|
||||||
skip_serializing_if = "Option::is_none",
|
skip_serializing_if = "Option::is_none",
|
||||||
default,
|
default,
|
||||||
with = "unwrap_or_skip"
|
with = "unwrap_or_skip"
|
||||||
|
@ -37,6 +37,12 @@ pub(crate) struct Metainfo {
|
||||||
)]
|
)]
|
||||||
pub(crate) encoding: Option<String>,
|
pub(crate) encoding: Option<String>,
|
||||||
pub(crate) info: Info,
|
pub(crate) info: Info,
|
||||||
|
#[serde(
|
||||||
|
skip_serializing_if = "Option::is_none",
|
||||||
|
default,
|
||||||
|
with = "unwrap_or_skip"
|
||||||
|
)]
|
||||||
|
pub(crate) nodes: Option<Vec<Node>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Metainfo {
|
impl Metainfo {
|
||||||
|
@ -103,6 +109,7 @@ mod tests {
|
||||||
created_by: Some("created by".into()),
|
created_by: Some("created by".into()),
|
||||||
creation_date: Some(1),
|
creation_date: Some(1),
|
||||||
encoding: Some("UTF-8".into()),
|
encoding: Some("UTF-8".into()),
|
||||||
|
nodes: Some(vec!["x:12".parse().unwrap(), "1.1.1.1:16".parse().unwrap()]),
|
||||||
info: Info {
|
info: Info {
|
||||||
private: Some(true),
|
private: Some(true),
|
||||||
piece_length: Bytes(16 * 1024),
|
piece_length: Bytes(16 * 1024),
|
||||||
|
@ -130,6 +137,7 @@ mod tests {
|
||||||
let value = Metainfo {
|
let value = Metainfo {
|
||||||
announce: "announce".into(),
|
announce: "announce".into(),
|
||||||
announce_list: Some(vec![vec!["announce".into(), "b".into()], vec!["c".into()]]),
|
announce_list: Some(vec![vec!["announce".into(), "b".into()], vec!["c".into()]]),
|
||||||
|
nodes: Some(vec!["x:12".parse().unwrap(), "1.1.1.1:16".parse().unwrap()]),
|
||||||
comment: Some("comment".into()),
|
comment: Some("comment".into()),
|
||||||
created_by: Some("created by".into()),
|
created_by: Some("created by".into()),
|
||||||
creation_date: Some(1),
|
creation_date: Some(1),
|
||||||
|
|
148
src/node.rs
Normal file
148
src/node.rs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub(crate) struct Node {
|
||||||
|
host: Host,
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Node {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||||
|
let socket_address_re = Regex::new(
|
||||||
|
r"(?x)
|
||||||
|
^
|
||||||
|
(?P<host>.*?)
|
||||||
|
:
|
||||||
|
(?P<port>\d+?)
|
||||||
|
$
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(captures) = socket_address_re.captures(text) {
|
||||||
|
let host_text = captures.name("host").unwrap().as_str();
|
||||||
|
let port_text = captures.name("port").unwrap().as_str();
|
||||||
|
|
||||||
|
let host = Host::parse(&host_text).context(error::NodeParseHost {
|
||||||
|
text: text.to_owned(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let port = port_text.parse::<u16>().context(error::NodeParsePort {
|
||||||
|
text: text.to_owned(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self { host, port })
|
||||||
|
} else {
|
||||||
|
Err(Error::NodeParsePortMissing {
|
||||||
|
text: text.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Node {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.host, self.port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Tuple(String, u16);
|
||||||
|
|
||||||
|
impl From<&Node> for Tuple {
|
||||||
|
fn from(node: &Node) -> Self {
|
||||||
|
let host = match &node.host {
|
||||||
|
Host::Domain(domain) => domain.to_string(),
|
||||||
|
Host::Ipv4(ipv4) => ipv4.to_string(),
|
||||||
|
Host::Ipv6(ipv6) => ipv6.to_string(),
|
||||||
|
};
|
||||||
|
Self(host, node.port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Node {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
Tuple::from(self).serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Node {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let tuple = Tuple::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
let host = if tuple.0.contains(':') {
|
||||||
|
Host::parse(&format!("[{}]", tuple.0))
|
||||||
|
} else {
|
||||||
|
Host::parse(&tuple.0)
|
||||||
|
}
|
||||||
|
.map_err(|error| D::Error::custom(format!("Failed to parse node host: {}", error)))?;
|
||||||
|
|
||||||
|
Ok(Node {
|
||||||
|
host,
|
||||||
|
port: tuple.1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
|
fn case(host: Host, port: u16, text: &str, bencode: &str) {
|
||||||
|
let node = Node { host, port };
|
||||||
|
let parsed: Node = text.parse().expect(&format!("Failed to parse {}", text));
|
||||||
|
assert_eq!(parsed, node);
|
||||||
|
let ser = bendy::serde::to_bytes(&node).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
ser,
|
||||||
|
bencode.as_bytes(),
|
||||||
|
"Unexpected serialization: {} != {}",
|
||||||
|
String::from_utf8_lossy(&ser),
|
||||||
|
bencode,
|
||||||
|
);
|
||||||
|
let de = bendy::serde::from_bytes::<Node>(&ser).unwrap();
|
||||||
|
assert_eq!(de, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_domain() {
|
||||||
|
case(
|
||||||
|
Host::Domain("imdl.com".to_owned()),
|
||||||
|
12,
|
||||||
|
"imdl.com:12",
|
||||||
|
"l8:imdl.comi12ee",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ipv4() {
|
||||||
|
case(
|
||||||
|
Host::Ipv4(Ipv4Addr::new(1, 2, 3, 4)),
|
||||||
|
100,
|
||||||
|
"1.2.3.4:100",
|
||||||
|
"l7:1.2.3.4i100ee",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ipv6() {
|
||||||
|
case(
|
||||||
|
Host::Ipv6(Ipv6Addr::new(
|
||||||
|
0x1234, 0x5678, 0x9ABC, 0xDEF0, 0x1234, 0x5678, 0x9ABC, 0xDEF0,
|
||||||
|
)),
|
||||||
|
65000,
|
||||||
|
"[1234:5678:9abc:def0:1234:5678:9abc:def0]:65000",
|
||||||
|
"l39:1234:5678:9abc:def0:1234:5678:9abc:def0i65000ee",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,6 +43,16 @@ Note: Many BitTorrent clients do not implement the behavior described in BEP 12.
|
||||||
long_help = "Include `COMMENT` in generated `.torrent` file. Stored under `comment` key of top-level metainfo dictionary."
|
long_help = "Include `COMMENT` in generated `.torrent` file. Stored under `comment` key of top-level metainfo dictionary."
|
||||||
)]
|
)]
|
||||||
comment: Option<String>,
|
comment: Option<String>,
|
||||||
|
#[structopt(
|
||||||
|
name = "NODE",
|
||||||
|
long = "dht-node",
|
||||||
|
help = "Add DHT bootstrap node `NODE` to torrent. `NODE` should be in the form `HOST:PORT`.",
|
||||||
|
long_help = "Add DHT bootstrap node `NODE` to torrent. `NODE` should be in the form `HOST:PORT`, where `HOST` is a domain name, an IPv4 address, or an IPv6 address surrounded by brackets. May be given more than once to add multiple bootstrap nodes. Examples:
|
||||||
|
`--dht-node router.example.com:1337`
|
||||||
|
`--dht-node 203.0.113.0:2290`
|
||||||
|
`--dht-node [2001:db8:4275:7920:6269:7463:6f69:6e21]:8832`"
|
||||||
|
)]
|
||||||
|
dht_nodes: Vec<Node>,
|
||||||
#[structopt(
|
#[structopt(
|
||||||
name = "FOLLOW-SYMLINKS",
|
name = "FOLLOW-SYMLINKS",
|
||||||
long = "follow-symlinks",
|
long = "follow-symlinks",
|
||||||
|
@ -197,7 +207,7 @@ impl Create {
|
||||||
None => filename
|
None => filename
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or_else(|| Error::FilenameDecode {
|
.ok_or_else(|| Error::FilenameDecode {
|
||||||
filename: filename.to_os_string(),
|
filename: PathBuf::from(filename),
|
||||||
})?
|
})?
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
};
|
};
|
||||||
|
@ -255,6 +265,11 @@ impl Create {
|
||||||
} else {
|
} else {
|
||||||
Some(announce_list)
|
Some(announce_list)
|
||||||
},
|
},
|
||||||
|
nodes: if self.dht_nodes.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.dht_nodes)
|
||||||
|
},
|
||||||
creation_date,
|
creation_date,
|
||||||
created_by,
|
created_by,
|
||||||
info,
|
info,
|
||||||
|
@ -370,7 +385,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.private, None);
|
assert_eq!(metainfo.info.private, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +398,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.private, Some(true));
|
assert_eq!(metainfo.info.private, Some(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +422,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.announce, "http://bar/");
|
assert_eq!(metainfo.announce, "http://bar/");
|
||||||
assert!(metainfo.announce_list.is_none());
|
assert!(metainfo.announce_list.is_none());
|
||||||
}
|
}
|
||||||
|
@ -426,7 +441,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.announce,
|
metainfo.announce,
|
||||||
"udp://tracker.opentrackr.org:1337/announce"
|
"udp://tracker.opentrackr.org:1337/announce"
|
||||||
|
@ -439,7 +454,7 @@ mod tests {
|
||||||
let mut env = environment(&["--input", "foo", "--announce", "wss://tracker.btorrent.xyz"]);
|
let mut env = environment(&["--input", "foo", "--announce", "wss://tracker.btorrent.xyz"]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.announce, "wss://tracker.btorrent.xyz/");
|
assert_eq!(metainfo.announce, "wss://tracker.btorrent.xyz/");
|
||||||
assert!(metainfo.announce_list.is_none());
|
assert!(metainfo.announce_list.is_none());
|
||||||
}
|
}
|
||||||
|
@ -456,7 +471,7 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.announce, "http://bar/");
|
assert_eq!(metainfo.announce, "http://bar/");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.announce_list,
|
metainfo.announce_list,
|
||||||
|
@ -478,7 +493,7 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.announce, "http://bar/");
|
assert_eq!(metainfo.announce, "http://bar/");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.announce_list,
|
metainfo.announce_list,
|
||||||
|
@ -494,7 +509,7 @@ mod tests {
|
||||||
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.comment, None);
|
assert_eq!(metainfo.comment, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,7 +525,7 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.comment.unwrap(), "Hello, world!");
|
assert_eq!(metainfo.comment.unwrap(), "Hello, world!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,7 +534,7 @@ mod tests {
|
||||||
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,7 +550,7 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,7 +566,7 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,7 +582,7 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.name, "foo");
|
assert_eq!(metainfo.info.name, "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,7 +600,7 @@ mod tests {
|
||||||
fs::create_dir(&dir).unwrap();
|
fs::create_dir(&dir).unwrap();
|
||||||
fs::write(dir.join("bar"), "").unwrap();
|
fs::write(dir.join("bar"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo/bar.torrent");
|
let metainfo = env.load_metainfo("foo/bar.torrent");
|
||||||
assert_eq!(metainfo.info.name, "bar");
|
assert_eq!(metainfo.info.name, "bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,7 +616,7 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
env.load_torrent("x.torrent");
|
env.load_metainfo("x.torrent");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -609,7 +624,7 @@ mod tests {
|
||||||
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -624,7 +639,7 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.created_by, None);
|
assert_eq!(metainfo.created_by, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,7 +648,7 @@ mod tests {
|
||||||
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -646,7 +661,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs();
|
.as_secs();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("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);
|
||||||
}
|
}
|
||||||
|
@ -662,7 +677,7 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.creation_date, None);
|
assert_eq!(metainfo.creation_date, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,7 +687,7 @@ mod tests {
|
||||||
let contents = "bar";
|
let contents = "bar";
|
||||||
fs::write(env.resolve("foo"), contents).unwrap();
|
fs::write(env.resolve("foo"), contents).unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, Sha1::from(contents).digest().bytes());
|
assert_eq!(metainfo.info.pieces, Sha1::from(contents).digest().bytes());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -698,7 +713,7 @@ mod tests {
|
||||||
let contents = "bar";
|
let contents = "bar";
|
||||||
fs::write(env.resolve("foo"), contents).unwrap();
|
fs::write(env.resolve("foo"), contents).unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
let pieces = Sha1::from("b")
|
let pieces = Sha1::from("b")
|
||||||
.digest()
|
.digest()
|
||||||
.bytes()
|
.bytes()
|
||||||
|
@ -724,7 +739,7 @@ mod tests {
|
||||||
let contents = "";
|
let contents = "";
|
||||||
fs::write(env.resolve("foo"), contents).unwrap();
|
fs::write(env.resolve("foo"), contents).unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces.len(), 0);
|
assert_eq!(metainfo.info.pieces.len(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
|
@ -741,7 +756,7 @@ mod tests {
|
||||||
let dir = env.resolve("foo");
|
let dir = env.resolve("foo");
|
||||||
fs::create_dir(&dir).unwrap();
|
fs::create_dir(&dir).unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces.len(), 0);
|
assert_eq!(metainfo.info.pieces.len(), 0);
|
||||||
assert_eq!(metainfo.info.mode, Mode::Multiple { files: Vec::new() })
|
assert_eq!(metainfo.info.mode, Mode::Multiple { files: Vec::new() })
|
||||||
}
|
}
|
||||||
|
@ -755,7 +770,7 @@ mod tests {
|
||||||
let contents = "bar";
|
let contents = "bar";
|
||||||
fs::write(file, contents).unwrap();
|
fs::write(file, contents).unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, Sha1::from(contents).digest().bytes());
|
assert_eq!(metainfo.info.pieces, Sha1::from(contents).digest().bytes());
|
||||||
match metainfo.info.mode {
|
match metainfo.info.mode {
|
||||||
Mode::Multiple { files } => {
|
Mode::Multiple { files } => {
|
||||||
|
@ -781,7 +796,7 @@ mod tests {
|
||||||
let contents = "bar";
|
let contents = "bar";
|
||||||
fs::write(file, contents).unwrap();
|
fs::write(file, contents).unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, Sha1::from(contents).digest().bytes());
|
assert_eq!(metainfo.info.pieces, Sha1::from(contents).digest().bytes());
|
||||||
match metainfo.info.mode {
|
match metainfo.info.mode {
|
||||||
Mode::Multiple { files } => {
|
Mode::Multiple { files } => {
|
||||||
|
@ -807,7 +822,7 @@ mod tests {
|
||||||
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.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metainfo.info.pieces,
|
metainfo.info.pieces,
|
||||||
Sha1::from("abchijxyz").digest().bytes()
|
Sha1::from("abchijxyz").digest().bytes()
|
||||||
|
@ -924,7 +939,7 @@ mod tests {
|
||||||
let dir = env.resolve("foo");
|
let dir = env.resolve("foo");
|
||||||
fs::create_dir(&dir).unwrap();
|
fs::create_dir(&dir).unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
env.load_torrent("foo.torrent");
|
env.load_metainfo("foo.torrent");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -972,7 +987,7 @@ mod tests {
|
||||||
let dir = env.resolve("foo");
|
let dir = env.resolve("foo");
|
||||||
fs::create_dir(&dir).unwrap();
|
fs::create_dir(&dir).unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
env.load_torrent("foo.torrent");
|
env.load_metainfo("foo.torrent");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1053,7 +1068,7 @@ Content Size 9 bytes
|
||||||
fs::write(env.resolve("foo"), "").unwrap();
|
fs::write(env.resolve("foo"), "").unwrap();
|
||||||
fs::write(env.resolve("foo.torrent"), "foo").unwrap();
|
fs::write(env.resolve("foo.torrent"), "foo").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
env.load_torrent("foo.torrent");
|
env.load_metainfo("foo.torrent");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1064,7 +1079,7 @@ Content Size 9 bytes
|
||||||
fs::write(dir.join("Thumbs.db"), "abc").unwrap();
|
fs::write(dir.join("Thumbs.db"), "abc").unwrap();
|
||||||
fs::write(dir.join("Desktop.ini"), "abc").unwrap();
|
fs::write(dir.join("Desktop.ini"), "abc").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.is_empty()
|
Mode::Multiple { files } if files.is_empty()
|
||||||
|
@ -1086,7 +1101,7 @@ Content Size 9 bytes
|
||||||
fs::write(dir.join("Thumbs.db"), "abc").unwrap();
|
fs::write(dir.join("Thumbs.db"), "abc").unwrap();
|
||||||
fs::write(dir.join("Desktop.ini"), "abc").unwrap();
|
fs::write(dir.join("Desktop.ini"), "abc").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.len() == 2
|
Mode::Multiple { files } if files.len() == 2
|
||||||
|
@ -1121,7 +1136,7 @@ Content Size 9 bytes
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.len() == 0
|
Mode::Multiple { files } if files.len() == 0
|
||||||
|
@ -1142,7 +1157,7 @@ Content Size 9 bytes
|
||||||
fs::create_dir(&dir).unwrap();
|
fs::create_dir(&dir).unwrap();
|
||||||
fs::write(dir.join(".hidden"), "abc").unwrap();
|
fs::write(dir.join(".hidden"), "abc").unwrap();
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.len() == 1
|
Mode::Multiple { files } if files.len() == 1
|
||||||
|
@ -1185,7 +1200,7 @@ Content Size 9 bytes
|
||||||
let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--md5sum"]);
|
let mut env = environment(&["--input", "foo", "--announce", "http://bar", "--md5sum"]);
|
||||||
populate_symlinks(&env);
|
populate_symlinks(&env);
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.is_empty()
|
Mode::Multiple { files } if files.is_empty()
|
||||||
|
@ -1206,7 +1221,7 @@ Content Size 9 bytes
|
||||||
]);
|
]);
|
||||||
populate_symlinks(&env);
|
populate_symlinks(&env);
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_eq!(metainfo.info.pieces, Sha1::from("barbaz").digest().bytes());
|
assert_eq!(metainfo.info.pieces, Sha1::from("barbaz").digest().bytes());
|
||||||
match metainfo.info.mode {
|
match metainfo.info.mode {
|
||||||
Mode::Multiple { files } => {
|
Mode::Multiple { files } => {
|
||||||
|
@ -1253,7 +1268,7 @@ Content Size 9 bytes
|
||||||
env.create_dir("foo/.bar");
|
env.create_dir("foo/.bar");
|
||||||
env.create_file("foo/.bar/baz", "baz");
|
env.create_file("foo/.bar/baz", "baz");
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.is_empty()
|
Mode::Multiple { files } if files.is_empty()
|
||||||
|
@ -1286,7 +1301,7 @@ Content Size 9 bytes
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.is_empty()
|
Mode::Multiple { files } if files.is_empty()
|
||||||
|
@ -1302,7 +1317,7 @@ Content Size 9 bytes
|
||||||
env.create_file("foo/b", "b");
|
env.create_file("foo/b", "b");
|
||||||
env.create_file("foo/c", "c");
|
env.create_file("foo/c", "c");
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.len() == 2
|
Mode::Multiple { files } if files.len() == 2
|
||||||
|
@ -1318,7 +1333,7 @@ Content Size 9 bytes
|
||||||
env.create_file("foo/b", "b");
|
env.create_file("foo/b", "b");
|
||||||
env.create_file("foo/c", "c");
|
env.create_file("foo/c", "c");
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.len() == 3
|
Mode::Multiple { files } if files.len() == 3
|
||||||
|
@ -1341,7 +1356,7 @@ Content Size 9 bytes
|
||||||
env.create_file("foo/b", "b");
|
env.create_file("foo/b", "b");
|
||||||
env.create_file("foo/c", "c");
|
env.create_file("foo/c", "c");
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.len() == 2
|
Mode::Multiple { files } if files.len() == 2
|
||||||
|
@ -1357,7 +1372,7 @@ Content Size 9 bytes
|
||||||
env.create_file("foo/b", "b");
|
env.create_file("foo/b", "b");
|
||||||
env.create_file("foo/c", "c");
|
env.create_file("foo/c", "c");
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.is_empty()
|
Mode::Multiple { files } if files.is_empty()
|
||||||
|
@ -1384,11 +1399,68 @@ Content Size 9 bytes
|
||||||
env.create_file("foo/b", "b");
|
env.create_file("foo/b", "b");
|
||||||
env.create_file("foo/c", "c");
|
env.create_file("foo/c", "c");
|
||||||
env.run().unwrap();
|
env.run().unwrap();
|
||||||
let metainfo = env.load_torrent("foo.torrent");
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
metainfo.info.mode,
|
metainfo.info.mode,
|
||||||
Mode::Multiple { files } if files.len() == 1
|
Mode::Multiple { files } if files.len() == 1
|
||||||
);
|
);
|
||||||
assert_eq!(metainfo.info.pieces, Sha1::from("a").digest().bytes());
|
assert_eq!(metainfo.info.pieces, Sha1::from("a").digest().bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nodes_default() {
|
||||||
|
let mut env = env! {
|
||||||
|
args: ["--input", "foo", "--announce", "http://bar"],
|
||||||
|
tree: {
|
||||||
|
foo: "",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
env.run().unwrap();
|
||||||
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
|
assert!(metainfo.nodes.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nodes_invalid() {
|
||||||
|
let mut env = env! {
|
||||||
|
args: ["--input", "foo", "--announce", "http://bar", "--dht-node", "blah"],
|
||||||
|
tree: {
|
||||||
|
foo: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert_matches!(env.run(), Err(Error::Clap { .. }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nodes_valid() {
|
||||||
|
let mut env = env! {
|
||||||
|
args: [
|
||||||
|
"--input",
|
||||||
|
"foo",
|
||||||
|
"--announce",
|
||||||
|
"http://bar",
|
||||||
|
"--dht-node",
|
||||||
|
"router.example.com:1337",
|
||||||
|
"--dht-node",
|
||||||
|
"203.0.113.0:2290",
|
||||||
|
"--dht-node",
|
||||||
|
"[2001:db8:4275:7920:6269:7463:6f69:6e21]:8832",
|
||||||
|
],
|
||||||
|
tree: {
|
||||||
|
foo: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
env.run().unwrap();
|
||||||
|
let metainfo = env.load_metainfo("foo.torrent");
|
||||||
|
assert_eq!(
|
||||||
|
metainfo.nodes,
|
||||||
|
Some(vec![
|
||||||
|
"router.example.com:1337".parse().unwrap(),
|
||||||
|
"203.0.113.0:2290".parse().unwrap(),
|
||||||
|
"[2001:db8:4275:7920:6269:7463:6f69:6e21]:8832"
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,11 @@ mod tests {
|
||||||
let metainfo = Metainfo {
|
let metainfo = Metainfo {
|
||||||
announce: "announce".into(),
|
announce: "announce".into(),
|
||||||
announce_list: Some(vec![vec!["announce".into(), "b".into()], vec!["c".into()]]),
|
announce_list: Some(vec![vec!["announce".into(), "b".into()], vec!["c".into()]]),
|
||||||
|
nodes: Some(vec![
|
||||||
|
"x:12".parse().unwrap(),
|
||||||
|
"1.1.1.1:16".parse().unwrap(),
|
||||||
|
"[2001:0db8:85a3::0000:8a2e:0370]:7334".parse().unwrap(),
|
||||||
|
]),
|
||||||
comment: Some("comment".into()),
|
comment: Some("comment".into()),
|
||||||
created_by: Some("created by".into()),
|
created_by: Some("created by".into()),
|
||||||
creation_date: Some(1),
|
creation_date: Some(1),
|
||||||
|
@ -74,12 +79,15 @@ mod tests {
|
||||||
Created By created by
|
Created By created by
|
||||||
Source source
|
Source source
|
||||||
Info Hash b7595205a46491b3e8686e10b28efe7144d066cc
|
Info Hash b7595205a46491b3e8686e10b28efe7144d066cc
|
||||||
Torrent Size 252 bytes
|
Torrent Size 319 bytes
|
||||||
Content Size 20 bytes
|
Content Size 20 bytes
|
||||||
Private yes
|
Private yes
|
||||||
Trackers Tier 1: announce
|
Trackers Tier 1: announce
|
||||||
b
|
b
|
||||||
Tier 2: c
|
Tier 2: c
|
||||||
|
DHT Nodes x:12
|
||||||
|
1.1.1.1:16
|
||||||
|
[2001:db8:85a3::8a2e:370]:7334
|
||||||
Piece Size 16 KiB
|
Piece Size 16 KiB
|
||||||
Piece Count 1
|
Piece Count 1
|
||||||
File Count 1
|
File Count 1
|
||||||
|
@ -108,10 +116,11 @@ Created\t1970-01-01 00:00:01 UTC
|
||||||
Created By\tcreated by
|
Created By\tcreated by
|
||||||
Source\tsource
|
Source\tsource
|
||||||
Info Hash\tb7595205a46491b3e8686e10b28efe7144d066cc
|
Info Hash\tb7595205a46491b3e8686e10b28efe7144d066cc
|
||||||
Torrent Size\t252
|
Torrent Size\t319
|
||||||
Content Size\t20
|
Content Size\t20
|
||||||
Private\tyes
|
Private\tyes
|
||||||
Trackers\tannounce\tb\tc
|
Trackers\tannounce\tb\tc
|
||||||
|
DHT Nodes\tx:12\t1.1.1.1:16\t[2001:db8:85a3::8a2e:370]:7334
|
||||||
Piece Size\t16384
|
Piece Size\t16384
|
||||||
Piece Count\t1
|
Piece Count\t1
|
||||||
File Count\t1
|
File Count\t1
|
||||||
|
@ -129,6 +138,11 @@ Files\tfoo
|
||||||
announce_list: Some(vec![vec!["x".into()], vec!["y".into()], vec!["z".into()]]),
|
announce_list: Some(vec![vec!["x".into()], vec!["y".into()], vec!["z".into()]]),
|
||||||
comment: Some("comment".into()),
|
comment: Some("comment".into()),
|
||||||
created_by: Some("created by".into()),
|
created_by: Some("created by".into()),
|
||||||
|
nodes: Some(vec![
|
||||||
|
"x:12".parse().unwrap(),
|
||||||
|
"1.1.1.1:16".parse().unwrap(),
|
||||||
|
"[2001:0db8:85a3::0000:8a2e:0370]:7334".parse().unwrap(),
|
||||||
|
]),
|
||||||
creation_date: Some(1),
|
creation_date: Some(1),
|
||||||
encoding: Some("UTF-8".into()),
|
encoding: Some("UTF-8".into()),
|
||||||
info: Info {
|
info: Info {
|
||||||
|
@ -165,13 +179,16 @@ Files\tfoo
|
||||||
Created By created by
|
Created By created by
|
||||||
Source source
|
Source source
|
||||||
Info Hash b7595205a46491b3e8686e10b28efe7144d066cc
|
Info Hash b7595205a46491b3e8686e10b28efe7144d066cc
|
||||||
Torrent Size 240 bytes
|
Torrent Size 307 bytes
|
||||||
Content Size 20 bytes
|
Content Size 20 bytes
|
||||||
Private yes
|
Private yes
|
||||||
Trackers a
|
Trackers a
|
||||||
x
|
x
|
||||||
y
|
y
|
||||||
z
|
z
|
||||||
|
DHT Nodes x:12
|
||||||
|
1.1.1.1:16
|
||||||
|
[2001:db8:85a3::8a2e:370]:7334
|
||||||
Piece Size 16 KiB
|
Piece Size 16 KiB
|
||||||
Piece Count 1
|
Piece Count 1
|
||||||
File Count 1
|
File Count 1
|
||||||
|
@ -200,10 +217,11 @@ Created\t1970-01-01 00:00:01 UTC
|
||||||
Created By\tcreated by
|
Created By\tcreated by
|
||||||
Source\tsource
|
Source\tsource
|
||||||
Info Hash\tb7595205a46491b3e8686e10b28efe7144d066cc
|
Info Hash\tb7595205a46491b3e8686e10b28efe7144d066cc
|
||||||
Torrent Size\t240
|
Torrent Size\t307
|
||||||
Content Size\t20
|
Content Size\t20
|
||||||
Private\tyes
|
Private\tyes
|
||||||
Trackers\ta\tx\ty\tz
|
Trackers\ta\tx\ty\tz
|
||||||
|
DHT Nodes\tx:12\t1.1.1.1:16\t[2001:db8:85a3::8a2e:370]:7334
|
||||||
Piece Size\t16384
|
Piece Size\t16384
|
||||||
Piece Count\t1
|
Piece Count\t1
|
||||||
File Count\t1
|
File Count\t1
|
||||||
|
@ -220,6 +238,11 @@ Files\tfoo
|
||||||
announce: "a".into(),
|
announce: "a".into(),
|
||||||
announce_list: Some(vec![vec!["b".into()], vec!["c".into()], vec!["a".into()]]),
|
announce_list: Some(vec![vec!["b".into()], vec!["c".into()], vec!["a".into()]]),
|
||||||
comment: Some("comment".into()),
|
comment: Some("comment".into()),
|
||||||
|
nodes: Some(vec![
|
||||||
|
"x:12".parse().unwrap(),
|
||||||
|
"1.1.1.1:16".parse().unwrap(),
|
||||||
|
"[2001:0db8:85a3::8a2e:0370]:7334".parse().unwrap(),
|
||||||
|
]),
|
||||||
created_by: Some("created by".into()),
|
created_by: Some("created by".into()),
|
||||||
creation_date: Some(1),
|
creation_date: Some(1),
|
||||||
encoding: Some("UTF-8".into()),
|
encoding: Some("UTF-8".into()),
|
||||||
|
@ -257,12 +280,15 @@ Files\tfoo
|
||||||
Created By created by
|
Created By created by
|
||||||
Source source
|
Source source
|
||||||
Info Hash b7595205a46491b3e8686e10b28efe7144d066cc
|
Info Hash b7595205a46491b3e8686e10b28efe7144d066cc
|
||||||
Torrent Size 240 bytes
|
Torrent Size 307 bytes
|
||||||
Content Size 20 bytes
|
Content Size 20 bytes
|
||||||
Private yes
|
Private yes
|
||||||
Trackers b
|
Trackers b
|
||||||
c
|
c
|
||||||
a
|
a
|
||||||
|
DHT Nodes x:12
|
||||||
|
1.1.1.1:16
|
||||||
|
[2001:db8:85a3::8a2e:370]:7334
|
||||||
Piece Size 16 KiB
|
Piece Size 16 KiB
|
||||||
Piece Count 1
|
Piece Count 1
|
||||||
File Count 1
|
File Count 1
|
||||||
|
@ -291,10 +317,11 @@ Created\t1970-01-01 00:00:01 UTC
|
||||||
Created By\tcreated by
|
Created By\tcreated by
|
||||||
Source\tsource
|
Source\tsource
|
||||||
Info Hash\tb7595205a46491b3e8686e10b28efe7144d066cc
|
Info Hash\tb7595205a46491b3e8686e10b28efe7144d066cc
|
||||||
Torrent Size\t240
|
Torrent Size\t307
|
||||||
Content Size\t20
|
Content Size\t20
|
||||||
Private\tyes
|
Private\tyes
|
||||||
Trackers\tb\tc\ta
|
Trackers\tb\tc\ta
|
||||||
|
DHT Nodes\tx:12\t1.1.1.1:16\t[2001:db8:85a3::8a2e:370]:7334
|
||||||
Piece Size\t16384
|
Piece Size\t16384
|
||||||
Piece Count\t1
|
Piece Count\t1
|
||||||
File Count\t1
|
File Count\t1
|
||||||
|
|
|
@ -31,7 +31,7 @@ impl TestEnv {
|
||||||
fs::write(self.env.resolve(path), bytes.as_ref()).unwrap();
|
fs::write(self.env.resolve(path), bytes.as_ref()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_torrent(&self, filename: impl AsRef<Path>) -> Metainfo {
|
pub(crate) fn load_metainfo(&self, filename: impl AsRef<Path>) -> Metainfo {
|
||||||
Metainfo::load(self.env.resolve(filename.as_ref())).unwrap()
|
Metainfo::load(self.env.resolve(filename.as_ref())).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,16 @@ impl TorrentSummary {
|
||||||
None => table.row("Tracker", &self.metainfo.announce),
|
None => table.row("Tracker", &self.metainfo.announce),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(nodes) = &self.metainfo.nodes {
|
||||||
|
table.list(
|
||||||
|
"DHT Nodes",
|
||||||
|
nodes
|
||||||
|
.iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
table.size("Piece Size", self.metainfo.info.piece_length);
|
table.size("Piece Size", self.metainfo.info.piece_length);
|
||||||
|
|
||||||
table.row("Piece Count", self.metainfo.info.pieces.len() / 20);
|
table.row("Piece Count", self.metainfo.info.pieces.len() / 20);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user