Merge pull request #248 from PastaPastaPasta/feat/bep-53-support
feat: implement BEP-53 support
This commit is contained in:
commit
7ca3232aa6
4 changed files with 67 additions and 7 deletions
|
|
@ -868,7 +868,7 @@ impl Session {
|
||||||
) -> BoxFuture<'a, anyhow::Result<AddTorrentResponse>> {
|
) -> BoxFuture<'a, anyhow::Result<AddTorrentResponse>> {
|
||||||
async move {
|
async move {
|
||||||
// Magnet links are different in that we first need to discover the metadata.
|
// Magnet links are different in that we first need to discover the metadata.
|
||||||
let opts = opts.unwrap_or_default();
|
let mut opts = opts.unwrap_or_default();
|
||||||
|
|
||||||
let paused = opts.list_only || opts.paused;
|
let paused = opts.list_only || opts.paused;
|
||||||
|
|
||||||
|
|
@ -885,6 +885,12 @@ impl Session {
|
||||||
let info_hash = magnet
|
let info_hash = magnet
|
||||||
.as_id20()
|
.as_id20()
|
||||||
.context("magnet link didn't contain a BTv1 infohash")?;
|
.context("magnet link didn't contain a BTv1 infohash")?;
|
||||||
|
if let Some(so) = magnet.get_select_only() {
|
||||||
|
// Only overwrite opts.only_files if user didn't specify
|
||||||
|
if opts.only_files.is_none() {
|
||||||
|
opts.only_files = Some(so);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let peer_rx = self.make_peer_rx(
|
let peer_rx = self.make_peer_rx(
|
||||||
info_hash,
|
info_hash,
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,12 @@ impl SerializedTorrent {
|
||||||
let add_torrent = if !self.torrent_bytes.is_empty() {
|
let add_torrent = if !self.torrent_bytes.is_empty() {
|
||||||
AddTorrent::TorrentFileBytes(self.torrent_bytes)
|
AddTorrent::TorrentFileBytes(self.torrent_bytes)
|
||||||
} else {
|
} else {
|
||||||
let magnet =
|
let magnet = Magnet::from_id20(
|
||||||
Magnet::from_id20(self.info_hash, self.trackers.into_iter().collect()).to_string();
|
self.info_hash,
|
||||||
|
self.trackers.into_iter().collect(),
|
||||||
|
self.only_files.clone(),
|
||||||
|
)
|
||||||
|
.to_string();
|
||||||
AddTorrent::from_url(magnet)
|
AddTorrent::from_url(magnet)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ async fn _test_e2e_download(drop_checks: &DropChecks) {
|
||||||
.and_then(|v| v.parse().ok())
|
.and_then(|v| v.parse().ok())
|
||||||
.unwrap_or(1usize);
|
.unwrap_or(1usize);
|
||||||
|
|
||||||
let magnet = Magnet::from_id20(torrent_file.info_hash(), Vec::new()).to_string();
|
let magnet = Magnet::from_id20(torrent_file.info_hash(), Vec::new(), None).to_string();
|
||||||
|
|
||||||
// 3. Start a client with the initial peers, and download the file.
|
// 3. Start a client with the initial peers, and download the file.
|
||||||
for _ in 0..client_iters {
|
for _ in 0..client_iters {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ pub struct Magnet {
|
||||||
id20: Option<Id20>,
|
id20: Option<Id20>,
|
||||||
id32: Option<Id32>,
|
id32: Option<Id32>,
|
||||||
pub trackers: Vec<String>,
|
pub trackers: Vec<String>,
|
||||||
|
select_only: Option<Vec<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Magnet {
|
impl Magnet {
|
||||||
|
|
@ -19,12 +20,16 @@ impl Magnet {
|
||||||
pub fn as_id32(&self) -> Option<Id32> {
|
pub fn as_id32(&self) -> Option<Id32> {
|
||||||
self.id32
|
self.id32
|
||||||
}
|
}
|
||||||
|
pub fn get_select_only(&self) -> Option<Vec<usize>> {
|
||||||
|
self.select_only.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_id20(id20: Id20, trackers: Vec<String>) -> Self {
|
pub fn from_id20(id20: Id20, trackers: Vec<String>, select_only: Option<Vec<usize>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id20: Some(id20),
|
id20: Some(id20),
|
||||||
id32: None,
|
id32: None,
|
||||||
trackers,
|
trackers,
|
||||||
|
select_only,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,6 +43,7 @@ impl Magnet {
|
||||||
let mut id20: Option<Id20> = None;
|
let mut id20: Option<Id20> = None;
|
||||||
let mut id32: Option<Id32> = None;
|
let mut id32: Option<Id32> = None;
|
||||||
let mut trackers = Vec::<String>::new();
|
let mut trackers = Vec::<String>::new();
|
||||||
|
let mut files = Vec::<usize>::new();
|
||||||
for (key, value) in url.query_pairs() {
|
for (key, value) in url.query_pairs() {
|
||||||
match key.as_ref() {
|
match key.as_ref() {
|
||||||
"xt" => {
|
"xt" => {
|
||||||
|
|
@ -54,6 +60,28 @@ impl Magnet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"tr" => trackers.push(value.into()),
|
"tr" => trackers.push(value.into()),
|
||||||
|
"so" => {
|
||||||
|
// Process 'so' values, but silently ignore any which fail parsing
|
||||||
|
for file_desc in value.split(',') {
|
||||||
|
if file_desc.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Handling ranges of file indices
|
||||||
|
if let Some((start, end)) = file_desc.split_once('-') {
|
||||||
|
let maybe_start_idx: Result<usize, _> = start.parse();
|
||||||
|
let maybe_end_idx: Result<usize, _> = end.parse();
|
||||||
|
if let (Ok(start_idx), Ok(end_idx)) = (maybe_start_idx, maybe_end_idx) {
|
||||||
|
files.extend(start_idx..=end_idx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handling single file index
|
||||||
|
let idx = file_desc.parse();
|
||||||
|
if let Ok(idx) = idx {
|
||||||
|
files.push(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +90,11 @@ impl Magnet {
|
||||||
id20,
|
id20,
|
||||||
id32,
|
id32,
|
||||||
trackers,
|
trackers,
|
||||||
|
select_only: if files.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(files)
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
false => {
|
false => {
|
||||||
anyhow::bail!("did not find infohash")
|
anyhow::bail!("did not find infohash")
|
||||||
|
|
@ -97,6 +130,18 @@ impl std::fmt::Display for Magnet {
|
||||||
write_ampersand(f)?;
|
write_ampersand(f)?;
|
||||||
write!(f, "tr={tracker}")?;
|
write!(f, "tr={tracker}")?;
|
||||||
}
|
}
|
||||||
|
if let Some(select_only) = &self.select_only {
|
||||||
|
if !select_only.is_empty() {
|
||||||
|
write_ampersand(f)?;
|
||||||
|
write!(f, "so=")?;
|
||||||
|
for (index, file) in select_only.iter().enumerate() {
|
||||||
|
if index > 0 {
|
||||||
|
write!(f, ",")?; // Add a comma before all but the first index
|
||||||
|
}
|
||||||
|
write!(f, "{}", file)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -133,13 +178,18 @@ mod tests {
|
||||||
fn test_magnet_to_string() {
|
fn test_magnet_to_string() {
|
||||||
let id20 = Id20::from_str("a621779b5e3d486e127c3efbca9b6f8d135f52e5").unwrap();
|
let id20 = Id20::from_str("a621779b5e3d486e127c3efbca9b6f8d135f52e5").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&Magnet::from_id20(id20, Default::default()).to_string(),
|
&Magnet::from_id20(id20, Default::default(), None).to_string(),
|
||||||
"magnet:?xt=urn:btih:a621779b5e3d486e127c3efbca9b6f8d135f52e5"
|
"magnet:?xt=urn:btih:a621779b5e3d486e127c3efbca9b6f8d135f52e5"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&Magnet::from_id20(id20, vec!["foo".to_string(), "bar".to_string()]).to_string(),
|
&Magnet::from_id20(id20, vec!["foo".to_string(), "bar".to_string()], None).to_string(),
|
||||||
"magnet:?xt=urn:btih:a621779b5e3d486e127c3efbca9b6f8d135f52e5&tr=foo&tr=bar"
|
"magnet:?xt=urn:btih:a621779b5e3d486e127c3efbca9b6f8d135f52e5&tr=foo&tr=bar"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&Magnet::from_id20(id20, Default::default(), Some(vec![1, 2, 3])).to_string(),
|
||||||
|
"magnet:?xt=urn:btih:a621779b5e3d486e127c3efbca9b6f8d135f52e5&so=1,2,3"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue