Skip to content

Commit

Permalink
Print magnet link to stdout with --link
Browse files Browse the repository at this point in the history
Magnet links can be printed to standard output with:

    imdl torrent create --input PATH --link

type: added
  • Loading branch information
casey committed Apr 8, 2020
1 parent 901fa15 commit 0d7c1c0
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 20 deletions.
24 changes: 12 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions src/magnet_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()));
Expand Down
52 changes: 51 additions & 1 deletion src/metainfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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(),
],
);
}
}
134 changes: 133 additions & 1 deletion src/subcommand/torrent/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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"
);
}
}
7 changes: 1 addition & 6 deletions src/subcommand/torrent/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 0d7c1c0

Please sign in to comment.