Print magnet link to stdout with --link

Magnet links can be printed to standard output with:

    imdl torrent create --input PATH --link

type: added
This commit is contained in:
Casey Rodarmor 2020-03-18 00:34:37 -07:00
parent 901fa150ff
commit 0d7c1c0c27
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
5 changed files with 224 additions and 20 deletions

24
Cargo.lock generated
View File

@ -94,9 +94,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bstr"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "502ae1441a0a5adb8fbd38a5955a6416b9493e92b465de5e4a9bde6a539c2c48"
checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41"
dependencies = [
"memchr",
]
@ -195,9 +195,9 @@ checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]]
name = "doc-comment"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "807e5847c39ad6a11eac66de492ed1406f76a260eb8656e8740cad9eabc69c27"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "encode_unicode"
@ -265,9 +265,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "globset"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2"
checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120"
dependencies = [
"aho-corasick",
"bstr",
@ -379,9 +379,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.67"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
[[package]]
name = "log"
@ -589,9 +589,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "regex"
version = "1.3.4"
version = "1.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8"
checksum = "8900ebc1363efa7ea1c399ccc32daed870b4002651e0bed86e72d501ebbe0048"
dependencies = [
"aho-corasick",
"memchr",
@ -601,9 +601,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.16"
version = "0.6.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1"
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
[[package]]
name = "remove_dir_all"

View File

@ -8,6 +8,18 @@ pub(crate) struct MagnetLink {
}
impl MagnetLink {
pub(crate) fn from_metainfo(metainfo: &Metainfo) -> Result<MagnetLink> {
let mut link = Self::with_infohash(metainfo.infohash()?);
link.set_name(metainfo.info.name.clone());
for tracker in metainfo.trackers() {
link.add_tracker(tracker?);
}
Ok(link)
}
pub(crate) fn with_infohash(infohash: Infohash) -> MagnetLink {
MagnetLink {
infohash,
@ -57,12 +69,27 @@ impl MagnetLink {
}
}
impl Display for MagnetLink {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.to_url())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn display() {
let link = MagnetLink::with_infohash(Infohash::from_bencoded_info_dict("".as_bytes()));
assert_eq!(
link.to_string(),
"magnet:?xt=urn:btih:da39a3ee5e6b4b0d3255bfef95601890afd80709"
);
}
#[test]
fn basic() {
let link = MagnetLink::with_infohash(Infohash::from_bencoded_info_dict("".as_bytes()));

View File

@ -90,10 +90,18 @@ impl Metainfo {
}
pub(crate) fn trackers<'a>(&'a self) -> impl Iterator<Item = Result<Url>> + 'a {
let mut seen = HashSet::new();
iter::once(&self.announce)
.flatten()
.chain(self.announce_list.iter().flatten().flatten())
.map(|text| text.parse().context(error::AnnounceUrlParse))
.filter_map(move |text| {
if seen.contains(text) {
None
} else {
seen.insert(text.clone());
Some(text.parse().context(error::AnnounceUrlParse))
}
})
}
pub(crate) fn infohash(&self) -> Result<Infohash> {
@ -414,4 +422,46 @@ mod tests {
representation(value, want);
}
#[test]
fn trackers() {
let mut metainfo = Metainfo {
announce: Some("http://foo".into()),
announce_list: None,
nodes: None,
comment: None,
created_by: None,
creation_date: None,
encoding: None,
info: Info {
private: Some(false),
piece_length: Bytes(1024),
source: None,
name: "NAME".into(),
pieces: PieceList::from_pieces(&["fae50"]),
mode: Mode::Single {
length: Bytes(5),
md5sum: None,
},
},
};
let trackers = metainfo.trackers().collect::<Result<Vec<Url>>>().unwrap();
assert_eq!(trackers, &["http://foo".parse().unwrap()]);
metainfo.announce_list = Some(vec![
vec!["http://bar".into(), "http://baz".into()],
vec!["http://foo".into()],
]);
let trackers = metainfo.trackers().collect::<Result<Vec<Url>>>().unwrap();
assert_eq!(
trackers,
&[
"http://foo".parse().unwrap(),
"http://bar".parse().unwrap(),
"http://baz".parse().unwrap(),
],
);
}
}

View File

@ -112,6 +112,11 @@ pub(crate) struct Create {
parse(from_os_str)
)]
input: PathBuf,
#[structopt(
long = "link",
help = "Print created torrent `magnet:` URL to standard output"
)]
print_magnet_link: bool,
#[structopt(
long = "md5",
short = "M",
@ -152,6 +157,13 @@ pub(crate) struct Create {
parse(from_os_str)
)]
output: Option<OutputTarget>,
#[structopt(
long = "peer",
value_name = "PEER",
help = "Add `PEER` to magnet link.",
requires("print-magnet-link")
)]
peers: Vec<HostPort>,
#[structopt(
long = "piece-length",
short = "p",
@ -391,7 +403,15 @@ impl Create {
errln!(env, "\u{2728}\u{2728} Done! \u{2728}\u{2728}")?;
if self.show {
TorrentSummary::from_metainfo(metainfo)?.write(env)?;
TorrentSummary::from_metainfo(metainfo.clone())?.write(env)?;
}
if self.print_magnet_link {
let mut link = MagnetLink::from_metainfo(&metainfo)?;
for peer in self.peers {
link.add_peer(peer);
}
outln!(env, "{}", link)?;
}
if let OutputTarget::File(path) = output {
@ -2288,4 +2308,116 @@ Content Size 9 bytes
assert_matches!(env.run(), Ok(()));
}
#[test]
fn no_print_magnet_link() {
let mut env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
],
tree: {
foo: "",
},
};
assert_matches!(env.run(), Ok(()));
assert_eq!(env.out(), "");
}
#[test]
fn print_magnet_link() {
let mut env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
"--link",
],
tree: {
foo: "",
},
};
assert_matches!(env.run(), Ok(()));
assert_eq!(
env.out(),
"magnet:?xt=urn:btih:516735f4b80f2b5487eed5f226075bdcde33a54e&dn=foo\n"
);
}
#[test]
fn print_magnet_link_with_announce() {
let mut env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
"--link",
"--announce",
"http://foo.com/announce",
],
tree: {
foo: "",
},
};
assert_matches!(env.run(), Ok(()));
assert_eq!(
env.out(),
"magnet:\
?xt=urn:btih:516735f4b80f2b5487eed5f226075bdcde33a54e\
&dn=foo\
&tr=http://foo.com/announce\n"
);
}
#[test]
fn peer_requires_link() {
let mut env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
"--peer",
],
tree: {
foo: "",
},
};
assert_matches!(env.run(), Err(Error::Clap { .. }));
}
#[test]
fn link_with_peers() {
let mut env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
"--peer",
"foo:1337",
"--peer",
"bar:666",
"--link"
],
tree: {
foo: "",
},
};
assert_matches!(env.run(), Ok(()));
assert_eq!(
env.out(),
"magnet:?xt=urn:btih:516735f4b80f2b5487eed5f226075bdcde33a54e&dn=foo&x.pe=foo:1337&x.pe=bar:\
666\n"
);
}
}

View File

@ -41,13 +41,8 @@ impl Link {
link.set_name(&metainfo.info.name);
let mut trackers = HashSet::new();
for result in metainfo.trackers() {
let tracker = result?;
if !trackers.contains(&tracker) {
trackers.insert(tracker.clone());
link.add_tracker(tracker);
}
link.add_tracker(result?);
}
for peer in self.peers {