Skip to content

Commit 415ca1c

Browse files
committedJan 29, 2024
feat: [torrust#649] scrape req for the HTTP tracker client
```console cargo run --bin http_tracker_client scrape http://127.0.0.1:7070 9c38422213e30bff212b30c360d26f9a02136422 9c38422213e30bff212b30c360d26f9a02136423 | jq ``` ```json { "9c38422213e30bff212b30c360d26f9a02136422": { "complete": 0, "downloaded": 0, "incomplete": 0 }, "9c38422213e30bff212b30c360d26f9a02136423": { "complete": 0, "downloaded": 0, "incomplete": 0 } } ```
1 parent b05e2f5 commit 415ca1c

File tree

3 files changed

+73
-11
lines changed

3 files changed

+73
-11
lines changed
 

‎src/bin/http_tracker_client.rs

+22-8
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ use reqwest::Url;
2020
use torrust_tracker::shared::bit_torrent::info_hash::InfoHash;
2121
use torrust_tracker::shared::bit_torrent::tracker::http::client::requests::announce::QueryBuilder;
2222
use torrust_tracker::shared::bit_torrent::tracker::http::client::responses::announce::Announce;
23-
use torrust_tracker::shared::bit_torrent::tracker::http::client::Client;
23+
use torrust_tracker::shared::bit_torrent::tracker::http::client::responses::scrape;
24+
use torrust_tracker::shared::bit_torrent::tracker::http::client::{requests, Client};
2425

2526
#[derive(Parser, Debug)]
2627
#[command(author, version, about, long_about = None)]
@@ -47,14 +48,15 @@ async fn main() {
4748
tracker_url,
4849
info_hashes,
4950
} => {
50-
scrape_command(&tracker_url, &info_hashes);
51+
scrape_command(&tracker_url, &info_hashes).await;
5152
}
5253
}
5354
}
5455

5556
async fn announce_command(tracker_url: String, info_hash: String) {
5657
let base_url = Url::parse(&tracker_url).expect("Invalid HTTP tracker base URL");
57-
let info_hash = InfoHash::from_str(&info_hash).expect("Invalid infohash");
58+
let info_hash =
59+
InfoHash::from_str(&info_hash).expect("Invalid infohash. Example infohash: `9c38422213e30bff212b30c360d26f9a02136422`");
5860

5961
let response = Client::new(base_url)
6062
.announce(&QueryBuilder::with_default_values().with_info_hash(&info_hash).query())
@@ -63,15 +65,27 @@ async fn announce_command(tracker_url: String, info_hash: String) {
6365
let body = response.bytes().await.unwrap();
6466

6567
let announce_response: Announce = serde_bencode::from_bytes(&body)
66-
.unwrap_or_else(|_| panic!("response body should be a valid announce response, got \"{:#?}\"", &body));
68+
.unwrap_or_else(|_| panic!("response body should be a valid announce response, got: \"{:#?}\"", &body));
6769

6870
let json = serde_json::to_string(&announce_response).expect("announce response should be a valid JSON");
6971

7072
println!("{json}");
7173
}
7274

73-
fn scrape_command(tracker_url: &str, info_hashes: &[String]) {
74-
println!("URL: {tracker_url}");
75-
println!("Infohashes: {info_hashes:#?}");
76-
todo!();
75+
async fn scrape_command(tracker_url: &str, info_hashes: &[String]) {
76+
let base_url = Url::parse(tracker_url).expect("Invalid HTTP tracker base URL");
77+
78+
let query = requests::scrape::Query::try_from(info_hashes)
79+
.expect("All infohashes should be valid. Example infohash: `9c38422213e30bff212b30c360d26f9a02136422`");
80+
81+
let response = Client::new(base_url).scrape(&query).await;
82+
83+
let body = response.bytes().await.unwrap();
84+
85+
let scrape_response = scrape::Response::try_from_bencoded(&body)
86+
.unwrap_or_else(|_| panic!("response body should be a valid scrape response, got: \"{:#?}\"", &body));
87+
88+
let json = serde_json::to_string(&scrape_response).expect("scrape response should be a valid JSON");
89+
90+
println!("{json}");
7791
}

‎src/shared/bit_torrent/tracker/http/client/requests/scrape.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use std::fmt;
1+
use std::convert::TryFrom;
2+
use std::fmt::{self};
23
use std::str::FromStr;
34

45
use crate::shared::bit_torrent::info_hash::InfoHash;
@@ -14,6 +15,26 @@ impl fmt::Display for Query {
1415
}
1516
}
1617

18+
#[derive(Debug)]
19+
pub struct ConversionError(String);
20+
21+
impl TryFrom<&[String]> for Query {
22+
type Error = ConversionError;
23+
24+
fn try_from(info_hashes: &[String]) -> Result<Self, Self::Error> {
25+
let mut validated_info_hashes: Vec<ByteArray20> = Vec::new();
26+
27+
for info_hash in info_hashes {
28+
let validated_info_hash = InfoHash::from_str(info_hash).map_err(|_| ConversionError(info_hash.clone()))?;
29+
validated_info_hashes.push(validated_info_hash.0);
30+
}
31+
32+
Ok(Self {
33+
info_hash: validated_info_hashes,
34+
})
35+
}
36+
}
37+
1738
/// HTTP Tracker Scrape Request:
1839
///
1940
/// <https://www.bittorrent.org/beps/bep_0048.html>

‎src/shared/bit_torrent/tracker/http/client/responses/scrape.rs

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use std::collections::HashMap;
2+
use std::fmt::Write;
23
use std::str;
34

4-
use serde::{self, Deserialize, Serialize};
5+
use serde::ser::SerializeMap;
6+
use serde::{self, Deserialize, Serialize, Serializer};
57
use serde_bencode::value::Value;
68

79
use crate::shared::bit_torrent::tracker::http::{ByteArray20, InfoHash};
810

9-
#[derive(Debug, PartialEq, Default)]
11+
#[derive(Debug, PartialEq, Default, Deserialize)]
1012
pub struct Response {
1113
pub files: HashMap<ByteArray20, File>,
1214
}
@@ -60,6 +62,31 @@ struct DeserializedResponse {
6062
pub files: Value,
6163
}
6264

65+
// Custom serialization for Response
66+
impl Serialize for Response {
67+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68+
where
69+
S: Serializer,
70+
{
71+
let mut map = serializer.serialize_map(Some(self.files.len()))?;
72+
for (key, value) in &self.files {
73+
// Convert ByteArray20 key to hex string
74+
let hex_key = byte_array_to_hex_string(key);
75+
map.serialize_entry(&hex_key, value)?;
76+
}
77+
map.end()
78+
}
79+
}
80+
81+
// Helper function to convert ByteArray20 to hex string
82+
fn byte_array_to_hex_string(byte_array: &ByteArray20) -> String {
83+
let mut hex_string = String::with_capacity(byte_array.len() * 2);
84+
for byte in byte_array {
85+
write!(hex_string, "{byte:02x}").expect("Writing to string should never fail");
86+
}
87+
hex_string
88+
}
89+
6390
#[derive(Default)]
6491
pub struct ResponseBuilder {
6592
response: Response,

0 commit comments

Comments
 (0)
Please sign in to comment.