Skip to content

Commit

Permalink
feat: [#294] add canonical info-hash group top torrent details API re…
Browse files Browse the repository at this point in the history
…sponse

```json
{
    "data": {
        "torrent_id": 2,
        "uploader": "admin",
        "info_hash": "0c90fbf036e28370c1ec773401bc7620146b1d48",
        "title": "Test 01",
        "description": "Test 01",
        "category": {
            "id": 5,
            "category_id": 5,
            "name": "software",
            "num_torrents": 1
        },
        "upload_date": "2024-03-05 16:05:00",
        "file_size": 602515,
        "seeders": 0,
        "leechers": 0,
        "files": [
            {
                "path": [
                    "mandelbrot_set_01"
                ],
                "length": 602515,
                "md5sum": null
            }
        ],
        "trackers": [
            "udp://localhost:6969"
        ],
        "magnet_link": "magnet:?xt=urn:btih:0c90fbf036e28370c1ec773401bc7620146b1d48&dn=Test%2001&tr=udp%3A%2F%2Flocalhost%3A6969",
        "tags": [],
        "name": "mandelbrot_set_01",
        "comment": "Mandelbrot Set 01",
        "creation_date": 1687937540,
        "created_by": "Transmission/3.00 (bb6b5a062e)",
        "encoding": "UTF-8",
        "canonical_info_hash_group": [
            "d5eaff5bc75ed274da7c5294de3f6641dc0a90ce",
            "e126f473a9dee89217d7ae5982f9b21490ed2c3f"
        ]
    }
}
```

Notice the new field: `canonical_info_hash_group` at the end of the
JSON.
  • Loading branch information
josecelano committed Mar 5, 2024
1 parent a149f21 commit 601c358
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 84 deletions.
13 changes: 12 additions & 1 deletion src/models/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::databases::database::Category as DatabaseCategory;
use crate::models::torrent::TorrentListing;
use crate::models::torrent_file::TorrentFile;
use crate::models::torrent_tag::TorrentTag;
use crate::services::torrent::CanonicalInfoHashGroup;

pub enum OkResponses {
TokenResponse(TokenResponse),
Expand Down Expand Up @@ -67,11 +68,16 @@ pub struct TorrentResponse {
pub creation_date: Option<i64>,
pub created_by: Option<String>,
pub encoding: Option<String>,
pub canonical_info_hash_group: Vec<String>,
}

impl TorrentResponse {
#[must_use]
pub fn from_listing(torrent_listing: TorrentListing, category: Option<DatabaseCategory>) -> TorrentResponse {
pub fn from_listing(
torrent_listing: TorrentListing,
category: Option<DatabaseCategory>,
canonical_info_hash_group: &CanonicalInfoHashGroup,
) -> TorrentResponse {
TorrentResponse {
torrent_id: torrent_listing.torrent_id,
uploader: torrent_listing.uploader,
Expand All @@ -92,6 +98,11 @@ impl TorrentResponse {
creation_date: torrent_listing.creation_date,
created_by: torrent_listing.created_by,
encoding: torrent_listing.encoding,
canonical_info_hash_group: canonical_info_hash_group
.original_info_hashes
.iter()
.map(super::info_hash::InfoHash::to_hex_string)
.collect(),
}
}

Expand Down
190 changes: 108 additions & 82 deletions src/services/torrent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,82 +328,9 @@ impl Index {
) -> Result<TorrentResponse, ServiceError> {
let torrent_listing = self.torrent_listing_generator.one_torrent_by_info_hash(info_hash).await?;

let torrent_id = torrent_listing.torrent_id;

let category = match torrent_listing.category_id {
Some(category_id) => Some(self.category_repository.get_by_id(&category_id).await?),
None => None,
};

let mut torrent_response = TorrentResponse::from_listing(torrent_listing, category);

// Add files

torrent_response.files = self.torrent_file_repository.get_by_torrent_id(&torrent_id).await?;

if torrent_response.files.len() == 1 {
let torrent_info = self.torrent_info_repository.get_by_info_hash(info_hash).await?;

torrent_response
.files
.iter_mut()
.for_each(|v| v.path = vec![torrent_info.name.to_string()]);
}

// Add trackers

// code-review: duplicate logic. We have to check the same in the
// download torrent file endpoint. Here he have only one list of tracker
// like the `announce_list` in the torrent file.

torrent_response.trackers = self.torrent_announce_url_repository.get_by_torrent_id(&torrent_id).await?;

let tracker_url = self.get_tracker_url().await;
let tracker_mode = self.get_tracker_mode().await;

if tracker_mode.is_open() {
torrent_response.include_url_as_main_tracker(&tracker_url);
} else {
// Add main tracker URL
match opt_user_id {
Some(user_id) => {
let personal_announce_url = self.tracker_service.get_personal_announce_url(user_id).await?;

torrent_response.include_url_as_main_tracker(&personal_announce_url);
}
None => {
torrent_response.include_url_as_main_tracker(&tracker_url);
}
}
}

// Add magnet link

// todo: extract a struct or function to build the magnet links
let mut magnet = format!(
"magnet:?xt=urn:btih:{}&dn={}",
torrent_response.info_hash,
urlencoding::encode(&torrent_response.title)
);

// Add trackers from torrent file to magnet link
for tracker in &torrent_response.trackers {
magnet.push_str(&format!("&tr={}", urlencoding::encode(tracker)));
}

torrent_response.magnet_link = magnet;

// Get realtime seeders and leechers
if let Ok(torrent_info) = self
.tracker_statistics_importer
.import_torrent_statistics(torrent_response.torrent_id, &torrent_response.info_hash)
.await
{
torrent_response.seeders = torrent_info.seeders;
torrent_response.leechers = torrent_info.leechers;
}

torrent_response.tags = self.torrent_tag_repository.get_tags_for_torrent(&torrent_id).await?;
let torrent_response = self
.build_full_torrent_response(torrent_listing, info_hash, opt_user_id)
.await?;

Ok(torrent_response)
}
Expand Down Expand Up @@ -497,12 +424,7 @@ impl Index {
.one_torrent_by_torrent_id(&torrent_listing.torrent_id)
.await?;

let category = match torrent_listing.category_id {
Some(category_id) => Some(self.category_repository.get_by_id(&category_id).await?),
None => None,
};

let torrent_response = TorrentResponse::from_listing(torrent_listing, category);
let torrent_response = self.build_short_torrent_response(torrent_listing, info_hash).await?;

Ok(torrent_response)
}
Expand All @@ -516,6 +438,109 @@ impl Index {
let settings = self.configuration.settings.read().await;
settings.tracker.mode.clone()
}

async fn build_short_torrent_response(
&self,
torrent_listing: TorrentListing,
info_hash: &InfoHash,
) -> Result<TorrentResponse, ServiceError> {
let category = match torrent_listing.category_id {
Some(category_id) => Some(self.category_repository.get_by_id(&category_id).await?),
None => None,
};

let canonical_info_hash_group = self
.torrent_info_hash_repository
.get_canonical_info_hash_group(info_hash)
.await?;

Ok(TorrentResponse::from_listing(
torrent_listing,
category,
&canonical_info_hash_group,
))
}

async fn build_full_torrent_response(
&self,
torrent_listing: TorrentListing,
info_hash: &InfoHash,
opt_user_id: Option<UserId>,
) -> Result<TorrentResponse, ServiceError> {
let torrent_id: i64 = torrent_listing.torrent_id;

let mut torrent_response = self.build_short_torrent_response(torrent_listing, info_hash).await?;

// Add files

torrent_response.files = self.torrent_file_repository.get_by_torrent_id(&torrent_id).await?;

if torrent_response.files.len() == 1 {
let torrent_info = self.torrent_info_repository.get_by_info_hash(info_hash).await?;

torrent_response
.files
.iter_mut()
.for_each(|v| v.path = vec![torrent_info.name.to_string()]);
}

// Add trackers

// code-review: duplicate logic. We have to check the same in the
// download torrent file endpoint. Here he have only one list of tracker
// like the `announce_list` in the torrent file.

torrent_response.trackers = self.torrent_announce_url_repository.get_by_torrent_id(&torrent_id).await?;

let tracker_url = self.get_tracker_url().await;
let tracker_mode = self.get_tracker_mode().await;

if tracker_mode.is_open() {
torrent_response.include_url_as_main_tracker(&tracker_url);
} else {
// Add main tracker URL
match opt_user_id {
Some(user_id) => {
let personal_announce_url = self.tracker_service.get_personal_announce_url(user_id).await?;

torrent_response.include_url_as_main_tracker(&personal_announce_url);
}
None => {
torrent_response.include_url_as_main_tracker(&tracker_url);
}
}
}

// Add magnet link

// todo: extract a struct or function to build the magnet links
let mut magnet = format!(
"magnet:?xt=urn:btih:{}&dn={}",
torrent_response.info_hash,
urlencoding::encode(&torrent_response.title)
);

// Add trackers from torrent file to magnet link
for tracker in &torrent_response.trackers {
magnet.push_str(&format!("&tr={}", urlencoding::encode(tracker)));
}

torrent_response.magnet_link = magnet;

// Get realtime seeders and leechers
if let Ok(torrent_info) = self
.tracker_statistics_importer
.import_torrent_statistics(torrent_response.torrent_id, &torrent_response.info_hash)
.await
{
torrent_response.seeders = torrent_info.seeders;
torrent_response.leechers = torrent_info.leechers;
}

torrent_response.tags = self.torrent_tag_repository.get_tags_for_torrent(&torrent_id).await?;

Ok(torrent_response)
}
}

pub struct DbTorrentRepository {
Expand Down Expand Up @@ -579,6 +604,7 @@ pub struct DbTorrentInfoHash {
/// This function returns the original infohashes of a canonical infohash.
///
/// The relationship is 1 canonical infohash -> N original infohashes.
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
pub struct CanonicalInfoHashGroup {
pub canonical_info_hash: InfoHash,
/// The list of original infohashes associated to the canonical one.
Expand Down
1 change: 1 addition & 0 deletions tests/common/contexts/torrent/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub struct TorrentDetails {
pub creation_date: Option<i64>,
pub created_by: Option<String>,
pub encoding: Option<String>,
pub canonical_info_hash_group: Vec<String>,
}

#[derive(Deserialize, PartialEq, Debug)]
Expand Down
3 changes: 2 additions & 1 deletion tests/e2e/web/api/v1/contexts/torrent/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ mod for_guests {
md5sum: None,
}],
// code-review: why is this duplicated? It seems that is adding the
// same tracker twice because first ti adds all trackers and then
// same tracker twice because first it adds all trackers and then
// it adds the tracker with the personal announce url, if the user
// is logged in. If the user is not logged in, it adds the default
// tracker again, and it ends up with two trackers.
Expand All @@ -215,6 +215,7 @@ mod for_guests {
creation_date: test_torrent.file_info.creation_date,
created_by: test_torrent.file_info.created_by.clone(),
encoding: test_torrent.file_info.encoding.clone(),
canonical_info_hash_group: vec![test_torrent.file_info.info_hash.to_lowercase()],
};

assert_expected_torrent_details(&torrent_details_response.data, &expected_torrent);
Expand Down

0 comments on commit 601c358

Please sign in to comment.