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 |
|
||||
| [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 |
|
||||
| [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 |
|
||||
| [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 |
|
||||
|
|
|
@ -11,7 +11,7 @@ pub(crate) use std::{
|
|||
hash::Hash,
|
||||
io::{self, Read, Write},
|
||||
iter::{self, Sum},
|
||||
num::{ParseFloatError, TryFromIntError},
|
||||
num::{ParseFloatError, ParseIntError, TryFromIntError},
|
||||
ops::{AddAssign, Div, DivAssign, Mul, MulAssign, SubAssign},
|
||||
path::{self, Path, PathBuf},
|
||||
process::{self, Command, ExitStatus},
|
||||
|
@ -26,7 +26,7 @@ pub(crate) use chrono::{TimeZone, Utc};
|
|||
pub(crate) use globset::{Glob, GlobMatcher};
|
||||
pub(crate) use libc::EXIT_FAILURE;
|
||||
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_with::rust::unwrap_or_skip;
|
||||
pub(crate) use sha1::Sha1;
|
||||
|
@ -37,7 +37,7 @@ pub(crate) use structopt::{
|
|||
StructOpt,
|
||||
};
|
||||
pub(crate) use unicode_width::UnicodeWidthStr;
|
||||
pub(crate) use url::Url;
|
||||
pub(crate) use url::{Host, Url};
|
||||
pub(crate) use walkdir::WalkDir;
|
||||
|
||||
// modules
|
||||
|
@ -53,7 +53,7 @@ pub(crate) use crate::{
|
|||
pub(crate) use crate::{
|
||||
bytes::Bytes, env::Env, error::Error, file_info::FileInfo, file_path::FilePath,
|
||||
file_status::FileStatus, files::Files, hasher::Hasher, info::Info, lint::Lint, linter::Linter,
|
||||
md5_digest::Md5Digest, metainfo::Metainfo, mode::Mode, opt::Opt,
|
||||
md5_digest::Md5Digest, metainfo::Metainfo, mode::Mode, node::Node, opt::Opt,
|
||||
piece_length_picker::PieceLengthPicker, platform::Platform, status::Status, style::Style,
|
||||
table::Table, target::Target, torrent_summary::TorrentSummary, use_color::UseColor,
|
||||
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 },
|
||||
#[snafu(display("Command `{}` returned bad exit status: {}", command, status))]
|
||||
CommandStatus { command: String, status: ExitStatus },
|
||||
#[snafu(display("Filename was not valid unicode: {}", filename.to_string_lossy()))]
|
||||
FilenameDecode { filename: OsString },
|
||||
#[snafu(display("Filename was not valid unicode: {}", filename.display()))]
|
||||
FilenameDecode { filename: PathBuf },
|
||||
#[snafu(display("Path had no file name: {}", path.display()))]
|
||||
FilenameExtract { path: PathBuf },
|
||||
#[snafu(display("I/O error at `{}`: {}", path.display(), source))]
|
||||
Filesystem { source: io::Error, path: PathBuf },
|
||||
#[snafu(display("Invalid glob: {}", source))]
|
||||
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(",")))]
|
||||
OpenerMissing { tried: &'static [&'static str] },
|
||||
#[snafu(display(
|
||||
|
@ -104,8 +115,6 @@ pub(crate) enum Error {
|
|||
feature
|
||||
))]
|
||||
Unstable { feature: &'static str },
|
||||
#[snafu(display("Unknown lint: {}", text))]
|
||||
LintUnknown { text: String },
|
||||
#[snafu(display("Torrent verification failed: {}", status))]
|
||||
Verify { status: Status },
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ mod linter;
|
|||
mod md5_digest;
|
||||
mod metainfo;
|
||||
mod mode;
|
||||
mod node;
|
||||
mod opt;
|
||||
mod path_ext;
|
||||
mod piece_length_picker;
|
||||
|
|
|
@ -3,8 +3,8 @@ use crate::common::*;
|
|||
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
|
||||
pub(crate) struct Metainfo {
|
||||
pub(crate) announce: String,
|
||||
#[serde(rename = "announce-list")]
|
||||
#[serde(
|
||||
rename = "announce-list",
|
||||
skip_serializing_if = "Option::is_none",
|
||||
default,
|
||||
with = "unwrap_or_skip"
|
||||
|
@ -16,15 +16,15 @@ pub(crate) struct Metainfo {
|
|||
with = "unwrap_or_skip"
|
||||
)]
|
||||
pub(crate) comment: Option<String>,
|
||||
#[serde(rename = "created by")]
|
||||
#[serde(
|
||||
rename = "created by",
|
||||
skip_serializing_if = "Option::is_none",
|
||||
default,
|
||||
with = "unwrap_or_skip"
|
||||
)]
|
||||
pub(crate) created_by: Option<String>,
|
||||
#[serde(rename = "creation date")]
|
||||
#[serde(
|
||||
rename = "creation date",
|
||||
skip_serializing_if = "Option::is_none",
|
||||
default,
|
||||
with = "unwrap_or_skip"
|
||||
|
@ -37,6 +37,12 @@ pub(crate) struct Metainfo {
|
|||
)]
|
||||
pub(crate) encoding: Option<String>,
|
||||
pub(crate) info: Info,
|
||||
#[serde(
|
||||
skip_serializing_if = "Option::is_none",
|
||||
default,
|
||||
with = "unwrap_or_skip"
|
||||
)]
|
||||
pub(crate) nodes: Option<Vec<Node>>,
|
||||
}
|
||||
|
||||
impl Metainfo {
|
||||
|
@ -103,6 +109,7 @@ mod tests {
|
|||
created_by: Some("created by".into()),
|
||||
creation_date: Some(1),
|
||||
encoding: Some("UTF-8".into()),
|
||||
nodes: Some(vec!["x:12".parse().unwrap(), "1.1.1.1:16".parse().unwrap()]),
|
||||
info: Info {
|
||||
private: Some(true),
|
||||
piece_length: Bytes(16 * 1024),
|
||||
|
@ -130,6 +137,7 @@ mod tests {
|
|||
let value = Metainfo {
|
||||
announce: "announce".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()),
|
||||
created_by: Some("created by".into()),
|
||||
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."
|
||||
)]
|
||||
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(
|
||||
name = "FOLLOW-SYMLINKS",
|
||||
long = "follow-symlinks",
|
||||
|
@ -197,7 +207,7 @@ impl Create {
|
|||
None => filename
|
||||
.to_str()
|
||||
.ok_or_else(|| Error::FilenameDecode {
|
||||
filename: filename.to_os_string(),
|
||||
filename: PathBuf::from(filename),
|
||||
})?
|
||||
.to_owned(),
|
||||
};
|
||||
|
@ -255,6 +265,11 @@ impl Create {
|
|||
} else {
|
||||
Some(announce_list)
|
||||
},
|
||||
nodes: if self.dht_nodes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.dht_nodes)
|
||||
},
|
||||
creation_date,
|
||||
created_by,
|
||||
info,
|
||||
|
@ -370,7 +385,7 @@ mod tests {
|
|||
}
|
||||
};
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_eq!(metainfo.info.private, None);
|
||||
}
|
||||
|
||||
|
@ -383,7 +398,7 @@ mod tests {
|
|||
}
|
||||
};
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_eq!(metainfo.info.private, Some(true));
|
||||
}
|
||||
|
||||
|
@ -407,7 +422,7 @@ mod tests {
|
|||
}
|
||||
};
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_eq!(metainfo.announce, "http://bar/");
|
||||
assert!(metainfo.announce_list.is_none());
|
||||
}
|
||||
|
@ -426,7 +441,7 @@ mod tests {
|
|||
}
|
||||
};
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_eq!(
|
||||
metainfo.announce,
|
||||
"udp://tracker.opentrackr.org:1337/announce"
|
||||
|
@ -439,7 +454,7 @@ mod tests {
|
|||
let mut env = environment(&["--input", "foo", "--announce", "wss://tracker.btorrent.xyz"]);
|
||||
fs::write(env.resolve("foo"), "").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!(metainfo.announce_list.is_none());
|
||||
}
|
||||
|
@ -456,7 +471,7 @@ mod tests {
|
|||
]);
|
||||
fs::write(env.resolve("foo"), "").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_list,
|
||||
|
@ -478,7 +493,7 @@ mod tests {
|
|||
]);
|
||||
fs::write(env.resolve("foo"), "").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_list,
|
||||
|
@ -494,7 +509,7 @@ mod tests {
|
|||
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
||||
fs::write(env.resolve("foo"), "").unwrap();
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_eq!(metainfo.comment, None);
|
||||
}
|
||||
|
||||
|
@ -510,7 +525,7 @@ mod tests {
|
|||
]);
|
||||
fs::write(env.resolve("foo"), "").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!");
|
||||
}
|
||||
|
||||
|
@ -519,7 +534,7 @@ mod tests {
|
|||
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
||||
fs::write(env.resolve("foo"), "").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)));
|
||||
}
|
||||
|
||||
|
@ -535,7 +550,7 @@ mod tests {
|
|||
]);
|
||||
fs::write(env.resolve("foo"), "").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));
|
||||
}
|
||||
|
||||
|
@ -551,7 +566,7 @@ mod tests {
|
|||
]);
|
||||
fs::write(env.resolve("foo"), "").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));
|
||||
}
|
||||
|
||||
|
@ -567,7 +582,7 @@ mod tests {
|
|||
]);
|
||||
fs::write(env.resolve("foo"), "").unwrap();
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_eq!(metainfo.info.name, "foo");
|
||||
}
|
||||
|
||||
|
@ -585,7 +600,7 @@ mod tests {
|
|||
fs::create_dir(&dir).unwrap();
|
||||
fs::write(dir.join("bar"), "").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");
|
||||
}
|
||||
|
||||
|
@ -601,7 +616,7 @@ mod tests {
|
|||
]);
|
||||
fs::write(env.resolve("foo"), "").unwrap();
|
||||
env.run().unwrap();
|
||||
env.load_torrent("x.torrent");
|
||||
env.load_metainfo("x.torrent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -609,7 +624,7 @@ mod tests {
|
|||
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
||||
fs::write(env.resolve("foo"), "").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);
|
||||
}
|
||||
|
||||
|
@ -624,7 +639,7 @@ mod tests {
|
|||
]);
|
||||
fs::write(env.resolve("foo"), "").unwrap();
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_eq!(metainfo.created_by, None);
|
||||
}
|
||||
|
||||
|
@ -633,7 +648,7 @@ mod tests {
|
|||
let mut env = environment(&["--input", "foo", "--announce", "http://bar"]);
|
||||
fs::write(env.resolve("foo"), "").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()));
|
||||
}
|
||||
|
||||
|
@ -646,7 +661,7 @@ mod tests {
|
|||
.unwrap()
|
||||
.as_secs();
|
||||
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);
|
||||
}
|
||||
|
@ -662,7 +677,7 @@ mod tests {
|
|||
]);
|
||||
fs::write(env.resolve("foo"), "").unwrap();
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_eq!(metainfo.creation_date, None);
|
||||
}
|
||||
|
||||
|
@ -672,7 +687,7 @@ mod tests {
|
|||
let contents = "bar";
|
||||
fs::write(env.resolve("foo"), contents).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.mode,
|
||||
|
@ -698,7 +713,7 @@ mod tests {
|
|||
let contents = "bar";
|
||||
fs::write(env.resolve("foo"), contents).unwrap();
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
let pieces = Sha1::from("b")
|
||||
.digest()
|
||||
.bytes()
|
||||
|
@ -724,7 +739,7 @@ mod tests {
|
|||
let contents = "";
|
||||
fs::write(env.resolve("foo"), contents).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.mode,
|
||||
|
@ -741,7 +756,7 @@ mod tests {
|
|||
let dir = env.resolve("foo");
|
||||
fs::create_dir(&dir).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.mode, Mode::Multiple { files: Vec::new() })
|
||||
}
|
||||
|
@ -755,7 +770,7 @@ mod tests {
|
|||
let contents = "bar";
|
||||
fs::write(file, contents).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());
|
||||
match metainfo.info.mode {
|
||||
Mode::Multiple { files } => {
|
||||
|
@ -781,7 +796,7 @@ mod tests {
|
|||
let contents = "bar";
|
||||
fs::write(file, contents).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());
|
||||
match metainfo.info.mode {
|
||||
Mode::Multiple { files } => {
|
||||
|
@ -807,7 +822,7 @@ mod tests {
|
|||
fs::write(dir.join("x"), "xyz").unwrap();
|
||||
fs::write(dir.join("h"), "hij").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("abchijxyz").digest().bytes()
|
||||
|
@ -924,7 +939,7 @@ mod tests {
|
|||
let dir = env.resolve("foo");
|
||||
fs::create_dir(&dir).unwrap();
|
||||
env.run().unwrap();
|
||||
env.load_torrent("foo.torrent");
|
||||
env.load_metainfo("foo.torrent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -972,7 +987,7 @@ mod tests {
|
|||
let dir = env.resolve("foo");
|
||||
fs::create_dir(&dir).unwrap();
|
||||
env.run().unwrap();
|
||||
env.load_torrent("foo.torrent");
|
||||
env.load_metainfo("foo.torrent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1053,7 +1068,7 @@ Content Size 9 bytes
|
|||
fs::write(env.resolve("foo"), "").unwrap();
|
||||
fs::write(env.resolve("foo.torrent"), "foo").unwrap();
|
||||
env.run().unwrap();
|
||||
env.load_torrent("foo.torrent");
|
||||
env.load_metainfo("foo.torrent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1064,7 +1079,7 @@ Content Size 9 bytes
|
|||
fs::write(dir.join("Thumbs.db"), "abc").unwrap();
|
||||
fs::write(dir.join("Desktop.ini"), "abc").unwrap();
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
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("Desktop.ini"), "abc").unwrap();
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
Mode::Multiple { files } if files.len() == 2
|
||||
|
@ -1121,7 +1136,7 @@ Content Size 9 bytes
|
|||
.unwrap();
|
||||
}
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
Mode::Multiple { files } if files.len() == 0
|
||||
|
@ -1142,7 +1157,7 @@ Content Size 9 bytes
|
|||
fs::create_dir(&dir).unwrap();
|
||||
fs::write(dir.join(".hidden"), "abc").unwrap();
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
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"]);
|
||||
populate_symlinks(&env);
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
Mode::Multiple { files } if files.is_empty()
|
||||
|
@ -1206,7 +1221,7 @@ Content Size 9 bytes
|
|||
]);
|
||||
populate_symlinks(&env);
|
||||
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());
|
||||
match metainfo.info.mode {
|
||||
Mode::Multiple { files } => {
|
||||
|
@ -1253,7 +1268,7 @@ Content Size 9 bytes
|
|||
env.create_dir("foo/.bar");
|
||||
env.create_file("foo/.bar/baz", "baz");
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
Mode::Multiple { files } if files.is_empty()
|
||||
|
@ -1286,7 +1301,7 @@ Content Size 9 bytes
|
|||
.unwrap();
|
||||
}
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
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/c", "c");
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
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/c", "c");
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
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/c", "c");
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
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/c", "c");
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
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/c", "c");
|
||||
env.run().unwrap();
|
||||
let metainfo = env.load_torrent("foo.torrent");
|
||||
let metainfo = env.load_metainfo("foo.torrent");
|
||||
assert_matches!(
|
||||
metainfo.info.mode,
|
||||
Mode::Multiple { files } if files.len() == 1
|
||||
);
|
||||
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 {
|
||||
announce: "announce".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()),
|
||||
created_by: Some("created by".into()),
|
||||
creation_date: Some(1),
|
||||
|
@ -74,12 +79,15 @@ mod tests {
|
|||
Created By created by
|
||||
Source source
|
||||
Info Hash b7595205a46491b3e8686e10b28efe7144d066cc
|
||||
Torrent Size 252 bytes
|
||||
Torrent Size 319 bytes
|
||||
Content Size 20 bytes
|
||||
Private yes
|
||||
Trackers Tier 1: announce
|
||||
b
|
||||
Tier 2: c
|
||||
DHT Nodes x:12
|
||||
1.1.1.1:16
|
||||
[2001:db8:85a3::8a2e:370]:7334
|
||||
Piece Size 16 KiB
|
||||
Piece Count 1
|
||||
File Count 1
|
||||
|
@ -108,10 +116,11 @@ Created\t1970-01-01 00:00:01 UTC
|
|||
Created By\tcreated by
|
||||
Source\tsource
|
||||
Info Hash\tb7595205a46491b3e8686e10b28efe7144d066cc
|
||||
Torrent Size\t252
|
||||
Torrent Size\t319
|
||||
Content Size\t20
|
||||
Private\tyes
|
||||
Trackers\tannounce\tb\tc
|
||||
DHT Nodes\tx:12\t1.1.1.1:16\t[2001:db8:85a3::8a2e:370]:7334
|
||||
Piece Size\t16384
|
||||
Piece Count\t1
|
||||
File Count\t1
|
||||
|
@ -129,6 +138,11 @@ Files\tfoo
|
|||
announce_list: Some(vec![vec!["x".into()], vec!["y".into()], vec!["z".into()]]),
|
||||
comment: Some("comment".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),
|
||||
encoding: Some("UTF-8".into()),
|
||||
info: Info {
|
||||
|
@ -165,13 +179,16 @@ Files\tfoo
|
|||
Created By created by
|
||||
Source source
|
||||
Info Hash b7595205a46491b3e8686e10b28efe7144d066cc
|
||||
Torrent Size 240 bytes
|
||||
Torrent Size 307 bytes
|
||||
Content Size 20 bytes
|
||||
Private yes
|
||||
Trackers a
|
||||
x
|
||||
y
|
||||
z
|
||||
DHT Nodes x:12
|
||||
1.1.1.1:16
|
||||
[2001:db8:85a3::8a2e:370]:7334
|
||||
Piece Size 16 KiB
|
||||
Piece Count 1
|
||||
File Count 1
|
||||
|
@ -200,10 +217,11 @@ Created\t1970-01-01 00:00:01 UTC
|
|||
Created By\tcreated by
|
||||
Source\tsource
|
||||
Info Hash\tb7595205a46491b3e8686e10b28efe7144d066cc
|
||||
Torrent Size\t240
|
||||
Torrent Size\t307
|
||||
Content Size\t20
|
||||
Private\tyes
|
||||
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 Count\t1
|
||||
File Count\t1
|
||||
|
@ -220,6 +238,11 @@ Files\tfoo
|
|||
announce: "a".into(),
|
||||
announce_list: Some(vec![vec!["b".into()], vec!["c".into()], vec!["a".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()),
|
||||
creation_date: Some(1),
|
||||
encoding: Some("UTF-8".into()),
|
||||
|
@ -257,12 +280,15 @@ Files\tfoo
|
|||
Created By created by
|
||||
Source source
|
||||
Info Hash b7595205a46491b3e8686e10b28efe7144d066cc
|
||||
Torrent Size 240 bytes
|
||||
Torrent Size 307 bytes
|
||||
Content Size 20 bytes
|
||||
Private yes
|
||||
Trackers b
|
||||
c
|
||||
a
|
||||
DHT Nodes x:12
|
||||
1.1.1.1:16
|
||||
[2001:db8:85a3::8a2e:370]:7334
|
||||
Piece Size 16 KiB
|
||||
Piece Count 1
|
||||
File Count 1
|
||||
|
@ -291,10 +317,11 @@ Created\t1970-01-01 00:00:01 UTC
|
|||
Created By\tcreated by
|
||||
Source\tsource
|
||||
Info Hash\tb7595205a46491b3e8686e10b28efe7144d066cc
|
||||
Torrent Size\t240
|
||||
Torrent Size\t307
|
||||
Content Size\t20
|
||||
Private\tyes
|
||||
Trackers\tb\tc\ta
|
||||
DHT Nodes\tx:12\t1.1.1.1:16\t[2001:db8:85a3::8a2e:370]:7334
|
||||
Piece Size\t16384
|
||||
Piece Count\t1
|
||||
File Count\t1
|
||||
|
|
|
@ -31,7 +31,7 @@ impl TestEnv {
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,6 +142,16 @@ impl TorrentSummary {
|
|||
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.row("Piece Count", self.metainfo.info.pieces.len() / 20);
|
||||
|
|
Loading…
Reference in New Issue
Block a user