2020-03-17 03:02:02 -07:00
|
|
|
use crate::common::*;
|
|
|
|
|
|
|
|
#[derive(StructOpt)]
|
|
|
|
#[structopt(
|
|
|
|
help_message(consts::HELP_MESSAGE),
|
|
|
|
version_message(consts::VERSION_MESSAGE),
|
2020-04-11 04:17:09 -07:00
|
|
|
about("Generate a magnet link from a .torrent file.")
|
2020-03-17 03:02:02 -07:00
|
|
|
)]
|
|
|
|
pub(crate) struct Link {
|
|
|
|
#[structopt(
|
|
|
|
long = "input",
|
|
|
|
short = "i",
|
|
|
|
value_name = "METAINFO",
|
2020-04-05 18:17:38 -07:00
|
|
|
empty_values(false),
|
|
|
|
parse(try_from_os_str = InputTarget::try_from_os_str),
|
2020-03-18 02:48:57 -07:00
|
|
|
help = "Generate magnet link from metainfo at `PATH`. If `PATH` is `-`, read metainfo from \
|
|
|
|
standard input.",
|
2020-03-17 03:02:02 -07:00
|
|
|
)]
|
2020-03-18 02:48:57 -07:00
|
|
|
input: InputTarget,
|
2020-03-17 06:23:33 -07:00
|
|
|
#[structopt(
|
|
|
|
long = "open",
|
|
|
|
short = "O",
|
|
|
|
help = "Open generated magnet link. Uses `xdg-open`, `gnome-open`, or `kde-open` on Linux; \
|
|
|
|
`open` on macOS; and `cmd /C start` on Windows"
|
|
|
|
)]
|
|
|
|
open: bool,
|
2020-03-17 03:49:39 -07:00
|
|
|
#[structopt(
|
|
|
|
long = "peer",
|
|
|
|
short = "p",
|
|
|
|
value_name = "PEER",
|
|
|
|
help = "Add `PEER` to magnet link."
|
|
|
|
)]
|
|
|
|
peers: Vec<HostPort>,
|
2020-03-17 03:02:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Link {
|
|
|
|
pub(crate) fn run(self, env: &mut Env) -> Result<(), Error> {
|
2020-03-18 02:48:57 -07:00
|
|
|
let input = env.read(self.input.clone())?;
|
|
|
|
|
|
|
|
let infohash = Infohash::from_input(&input)?;
|
|
|
|
let metainfo = Metainfo::from_input(&input)?;
|
2020-03-17 03:02:02 -07:00
|
|
|
|
|
|
|
let mut link = MagnetLink::with_infohash(infohash);
|
|
|
|
|
2020-03-17 03:49:39 -07:00
|
|
|
link.set_name(&metainfo.info.name);
|
|
|
|
|
2020-03-17 03:02:02 -07:00
|
|
|
for result in metainfo.trackers() {
|
2020-03-18 00:34:37 -07:00
|
|
|
link.add_tracker(result?);
|
2020-03-17 03:02:02 -07:00
|
|
|
}
|
|
|
|
|
2020-03-17 03:49:39 -07:00
|
|
|
for peer in self.peers {
|
|
|
|
link.add_peer(peer);
|
|
|
|
}
|
|
|
|
|
2020-03-17 06:23:33 -07:00
|
|
|
let url = link.to_url();
|
|
|
|
|
|
|
|
outln!(env, "{}", url)?;
|
|
|
|
|
|
|
|
if self.open {
|
|
|
|
Platform::open_url(&url)?;
|
|
|
|
}
|
2020-03-17 03:02:02 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn no_announce() {
|
|
|
|
let mut env = test_env! {
|
|
|
|
args: [
|
|
|
|
"torrent",
|
|
|
|
"link",
|
|
|
|
"--input",
|
|
|
|
"foo.torrent",
|
|
|
|
],
|
|
|
|
tree: {
|
|
|
|
"foo.torrent": "d4:infod6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:ee",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-06 11:38:40 -07:00
|
|
|
env.assert_ok();
|
2020-03-17 03:02:02 -07:00
|
|
|
|
|
|
|
const INFO: &str = "d6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:e";
|
|
|
|
|
|
|
|
let infohash = Sha1Digest::from_data(INFO.as_bytes());
|
|
|
|
|
2020-03-17 03:49:39 -07:00
|
|
|
assert_eq!(
|
|
|
|
env.out(),
|
|
|
|
format!("magnet:?xt=urn:btih:{}&dn=foo\n", infohash),
|
|
|
|
);
|
2020-03-17 03:02:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn with_announce() {
|
|
|
|
let mut env = test_env! {
|
|
|
|
args: [
|
|
|
|
"torrent",
|
|
|
|
"link",
|
|
|
|
"--input",
|
|
|
|
"foo.torrent",
|
|
|
|
],
|
|
|
|
tree: {
|
|
|
|
"foo.torrent": "d\
|
|
|
|
8:announce24:https://foo.com/announce\
|
|
|
|
4:infod6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:e\
|
|
|
|
e",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-06 11:38:40 -07:00
|
|
|
env.assert_ok();
|
2020-03-17 03:02:02 -07:00
|
|
|
|
|
|
|
const INFO: &str = "d6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:e";
|
|
|
|
|
|
|
|
let infohash = Sha1Digest::from_data(INFO.as_bytes());
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
env.out(),
|
|
|
|
format!(
|
2020-03-17 03:49:39 -07:00
|
|
|
"magnet:?xt=urn:btih:{}&dn=foo&tr=https://foo.com/announce\n",
|
|
|
|
infohash
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unique_trackers() {
|
|
|
|
let mut env = test_env! {
|
|
|
|
args: [
|
|
|
|
"torrent",
|
|
|
|
"link",
|
|
|
|
"--input",
|
|
|
|
"foo.torrent",
|
|
|
|
],
|
|
|
|
tree: {
|
|
|
|
"foo.torrent": "d\
|
|
|
|
8:announce24:https://foo.com/announce\
|
|
|
|
13:announce-listll24:https://foo.com/announceel24:https://bar.com/announceee\
|
|
|
|
4:infod6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:e\
|
|
|
|
e",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-06 11:38:40 -07:00
|
|
|
env.assert_ok();
|
2020-03-17 03:49:39 -07:00
|
|
|
|
|
|
|
const INFO: &str = "d6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:e";
|
|
|
|
|
|
|
|
let infohash = Sha1Digest::from_data(INFO.as_bytes());
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
env.out(),
|
|
|
|
format!(
|
|
|
|
"magnet:?xt=urn:btih:{}&dn=foo&tr=https://foo.com/announce&tr=https://bar.com/announce\n",
|
|
|
|
infohash
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn with_peer() {
|
|
|
|
let mut env = test_env! {
|
|
|
|
args: [
|
|
|
|
"torrent",
|
|
|
|
"link",
|
|
|
|
"--input",
|
|
|
|
"foo.torrent",
|
|
|
|
"--peer",
|
|
|
|
"foo.com:1337",
|
|
|
|
],
|
|
|
|
tree: {
|
|
|
|
"foo.torrent": "d\
|
|
|
|
8:announce24:https://foo.com/announce\
|
|
|
|
4:infod6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:e\
|
|
|
|
e",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-06 11:38:40 -07:00
|
|
|
env.assert_ok();
|
2020-03-17 03:49:39 -07:00
|
|
|
|
|
|
|
const INFO: &str = "d6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:e";
|
|
|
|
|
|
|
|
let infohash = Sha1Digest::from_data(INFO.as_bytes());
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
env.out(),
|
|
|
|
format!(
|
|
|
|
"magnet:?xt=urn:btih:{}&dn=foo&tr=https://foo.com/announce&x.pe=foo.com:1337\n",
|
2020-03-17 03:02:02 -07:00
|
|
|
infohash
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn infohash_correct_with_nonstandard_info_dict() {
|
|
|
|
let mut env = test_env! {
|
|
|
|
args: [
|
|
|
|
"torrent",
|
|
|
|
"link",
|
|
|
|
"--input",
|
|
|
|
"foo.torrent",
|
|
|
|
],
|
|
|
|
tree: {
|
|
|
|
"foo.torrent": "d4:infod1:ai0e6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:ee",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-06 11:38:40 -07:00
|
|
|
env.assert_ok();
|
2020-03-17 03:02:02 -07:00
|
|
|
|
|
|
|
const INFO: &str = "d1:ai0e6:lengthi0e4:name3:foo12:piece lengthi1e6:pieces0:e";
|
|
|
|
|
|
|
|
let infohash = Sha1Digest::from_data(INFO.as_bytes());
|
|
|
|
|
2020-03-17 03:49:39 -07:00
|
|
|
assert_eq!(
|
|
|
|
env.out(),
|
|
|
|
format!("magnet:?xt=urn:btih:{}&dn=foo\n", infohash),
|
|
|
|
);
|
2020-03-17 03:02:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn bad_metainfo_error() {
|
|
|
|
let mut env = test_env! {
|
|
|
|
args: [
|
|
|
|
"torrent",
|
|
|
|
"link",
|
|
|
|
"--input",
|
|
|
|
"foo.torrent",
|
|
|
|
],
|
|
|
|
tree: {
|
|
|
|
"foo.torrent": "i0e",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_matches!(
|
2020-03-18 02:48:57 -07:00
|
|
|
env.run(), Err(Error::MetainfoValidate { input, source: MetainfoError::Type })
|
|
|
|
if input == "foo.torrent"
|
2020-03-17 03:02:02 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|