Skip to content

Commit

Permalink
feat: [#649] scrape req for the HTTP tracker client
Browse files Browse the repository at this point in the history
```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
  }
}
```
  • Loading branch information
josecelano committed Jan 29, 2024
1 parent b05e2f5 commit 415ca1c
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 11 deletions.
30 changes: 22 additions & 8 deletions src/bin/http_tracker_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use reqwest::Url;
use torrust_tracker::shared::bit_torrent::info_hash::InfoHash;
use torrust_tracker::shared::bit_torrent::tracker::http::client::requests::announce::QueryBuilder;
use torrust_tracker::shared::bit_torrent::tracker::http::client::responses::announce::Announce;
use torrust_tracker::shared::bit_torrent::tracker::http::client::Client;
use torrust_tracker::shared::bit_torrent::tracker::http::client::responses::scrape;
use torrust_tracker::shared::bit_torrent::tracker::http::client::{requests, Client};

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
Expand All @@ -47,14 +48,15 @@ async fn main() {
tracker_url,
info_hashes,
} => {
scrape_command(&tracker_url, &info_hashes);
scrape_command(&tracker_url, &info_hashes).await;
}
}
}

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

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

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

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

println!("{json}");
}

fn scrape_command(tracker_url: &str, info_hashes: &[String]) {
println!("URL: {tracker_url}");
println!("Infohashes: {info_hashes:#?}");
todo!();
async fn scrape_command(tracker_url: &str, info_hashes: &[String]) {
let base_url = Url::parse(tracker_url).expect("Invalid HTTP tracker base URL");

let query = requests::scrape::Query::try_from(info_hashes)
.expect("All infohashes should be valid. Example infohash: `9c38422213e30bff212b30c360d26f9a02136422`");

let response = Client::new(base_url).scrape(&query).await;

let body = response.bytes().await.unwrap();

let scrape_response = scrape::Response::try_from_bencoded(&body)
.unwrap_or_else(|_| panic!("response body should be a valid scrape response, got: \"{:#?}\"", &body));

let json = serde_json::to_string(&scrape_response).expect("scrape response should be a valid JSON");

println!("{json}");
}
23 changes: 22 additions & 1 deletion src/shared/bit_torrent/tracker/http/client/requests/scrape.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fmt;
use std::convert::TryFrom;
use std::fmt::{self};
use std::str::FromStr;

use crate::shared::bit_torrent::info_hash::InfoHash;
Expand All @@ -14,6 +15,26 @@ impl fmt::Display for Query {
}
}

#[derive(Debug)]
pub struct ConversionError(String);

Check warning on line 19 in src/shared/bit_torrent/tracker/http/client/requests/scrape.rs

View check run for this annotation

Codecov / codecov/patch

src/shared/bit_torrent/tracker/http/client/requests/scrape.rs#L18-L19

Added lines #L18 - L19 were not covered by tests

impl TryFrom<&[String]> for Query {
type Error = ConversionError;

fn try_from(info_hashes: &[String]) -> Result<Self, Self::Error> {
let mut validated_info_hashes: Vec<ByteArray20> = Vec::new();

Check warning on line 25 in src/shared/bit_torrent/tracker/http/client/requests/scrape.rs

View check run for this annotation

Codecov / codecov/patch

src/shared/bit_torrent/tracker/http/client/requests/scrape.rs#L24-L25

Added lines #L24 - L25 were not covered by tests

for info_hash in info_hashes {
let validated_info_hash = InfoHash::from_str(info_hash).map_err(|_| ConversionError(info_hash.clone()))?;
validated_info_hashes.push(validated_info_hash.0);

Check warning on line 29 in src/shared/bit_torrent/tracker/http/client/requests/scrape.rs

View check run for this annotation

Codecov / codecov/patch

src/shared/bit_torrent/tracker/http/client/requests/scrape.rs#L27-L29

Added lines #L27 - L29 were not covered by tests
}

Ok(Self {
info_hash: validated_info_hashes,
})
}

Check warning on line 35 in src/shared/bit_torrent/tracker/http/client/requests/scrape.rs

View check run for this annotation

Codecov / codecov/patch

src/shared/bit_torrent/tracker/http/client/requests/scrape.rs#L32-L35

Added lines #L32 - L35 were not covered by tests
}

/// HTTP Tracker Scrape Request:
///
/// <https://www.bittorrent.org/beps/bep_0048.html>
Expand Down
31 changes: 29 additions & 2 deletions src/shared/bit_torrent/tracker/http/client/responses/scrape.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::collections::HashMap;
use std::fmt::Write;
use std::str;

use serde::{self, Deserialize, Serialize};
use serde::ser::SerializeMap;
use serde::{self, Deserialize, Serialize, Serializer};
use serde_bencode::value::Value;

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

#[derive(Debug, PartialEq, Default)]
#[derive(Debug, PartialEq, Default, Deserialize)]

Check warning on line 11 in src/shared/bit_torrent/tracker/http/client/responses/scrape.rs

View check run for this annotation

Codecov / codecov/patch

src/shared/bit_torrent/tracker/http/client/responses/scrape.rs#L11

Added line #L11 was not covered by tests
pub struct Response {
pub files: HashMap<ByteArray20, File>,
}
Expand Down Expand Up @@ -60,6 +62,31 @@ struct DeserializedResponse {
pub files: Value,
}

// Custom serialization for Response
impl Serialize for Response {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.files.len()))?;
for (key, value) in &self.files {
// Convert ByteArray20 key to hex string
let hex_key = byte_array_to_hex_string(key);
map.serialize_entry(&hex_key, value)?;
}
map.end()
}
}

// Helper function to convert ByteArray20 to hex string
fn byte_array_to_hex_string(byte_array: &ByteArray20) -> String {
let mut hex_string = String::with_capacity(byte_array.len() * 2);
for byte in byte_array {
write!(hex_string, "{byte:02x}").expect("Writing to string should never fail");
}
hex_string
}

Check warning on line 88 in src/shared/bit_torrent/tracker/http/client/responses/scrape.rs

View check run for this annotation

Codecov / codecov/patch

src/shared/bit_torrent/tracker/http/client/responses/scrape.rs#L82-L88

Added lines #L82 - L88 were not covered by tests

#[derive(Default)]
pub struct ResponseBuilder {
response: Response,
Expand Down

0 comments on commit 415ca1c

Please sign in to comment.