Skip to content

Commit 601c358

Browse files
committed
feat: [#294] add canonical info-hash group top torrent details API response
```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.
1 parent a149f21 commit 601c358

File tree

4 files changed

+123
-84
lines changed

4 files changed

+123
-84
lines changed

src/models/response.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::databases::database::Category as DatabaseCategory;
66
use crate::models::torrent::TorrentListing;
77
use crate::models::torrent_file::TorrentFile;
88
use crate::models::torrent_tag::TorrentTag;
9+
use crate::services::torrent::CanonicalInfoHashGroup;
910

1011
pub enum OkResponses {
1112
TokenResponse(TokenResponse),
@@ -67,11 +68,16 @@ pub struct TorrentResponse {
6768
pub creation_date: Option<i64>,
6869
pub created_by: Option<String>,
6970
pub encoding: Option<String>,
71+
pub canonical_info_hash_group: Vec<String>,
7072
}
7173

7274
impl TorrentResponse {
7375
#[must_use]
74-
pub fn from_listing(torrent_listing: TorrentListing, category: Option<DatabaseCategory>) -> TorrentResponse {
76+
pub fn from_listing(
77+
torrent_listing: TorrentListing,
78+
category: Option<DatabaseCategory>,
79+
canonical_info_hash_group: &CanonicalInfoHashGroup,
80+
) -> TorrentResponse {
7581
TorrentResponse {
7682
torrent_id: torrent_listing.torrent_id,
7783
uploader: torrent_listing.uploader,
@@ -92,6 +98,11 @@ impl TorrentResponse {
9298
creation_date: torrent_listing.creation_date,
9399
created_by: torrent_listing.created_by,
94100
encoding: torrent_listing.encoding,
101+
canonical_info_hash_group: canonical_info_hash_group
102+
.original_info_hashes
103+
.iter()
104+
.map(super::info_hash::InfoHash::to_hex_string)
105+
.collect(),
95106
}
96107
}
97108

src/services/torrent.rs

+108-82
Original file line numberDiff line numberDiff line change
@@ -328,82 +328,9 @@ impl Index {
328328
) -> Result<TorrentResponse, ServiceError> {
329329
let torrent_listing = self.torrent_listing_generator.one_torrent_by_info_hash(info_hash).await?;
330330

331-
let torrent_id = torrent_listing.torrent_id;
332-
333-
let category = match torrent_listing.category_id {
334-
Some(category_id) => Some(self.category_repository.get_by_id(&category_id).await?),
335-
None => None,
336-
};
337-
338-
let mut torrent_response = TorrentResponse::from_listing(torrent_listing, category);
339-
340-
// Add files
341-
342-
torrent_response.files = self.torrent_file_repository.get_by_torrent_id(&torrent_id).await?;
343-
344-
if torrent_response.files.len() == 1 {
345-
let torrent_info = self.torrent_info_repository.get_by_info_hash(info_hash).await?;
346-
347-
torrent_response
348-
.files
349-
.iter_mut()
350-
.for_each(|v| v.path = vec![torrent_info.name.to_string()]);
351-
}
352-
353-
// Add trackers
354-
355-
// code-review: duplicate logic. We have to check the same in the
356-
// download torrent file endpoint. Here he have only one list of tracker
357-
// like the `announce_list` in the torrent file.
358-
359-
torrent_response.trackers = self.torrent_announce_url_repository.get_by_torrent_id(&torrent_id).await?;
360-
361-
let tracker_url = self.get_tracker_url().await;
362-
let tracker_mode = self.get_tracker_mode().await;
363-
364-
if tracker_mode.is_open() {
365-
torrent_response.include_url_as_main_tracker(&tracker_url);
366-
} else {
367-
// Add main tracker URL
368-
match opt_user_id {
369-
Some(user_id) => {
370-
let personal_announce_url = self.tracker_service.get_personal_announce_url(user_id).await?;
371-
372-
torrent_response.include_url_as_main_tracker(&personal_announce_url);
373-
}
374-
None => {
375-
torrent_response.include_url_as_main_tracker(&tracker_url);
376-
}
377-
}
378-
}
379-
380-
// Add magnet link
381-
382-
// todo: extract a struct or function to build the magnet links
383-
let mut magnet = format!(
384-
"magnet:?xt=urn:btih:{}&dn={}",
385-
torrent_response.info_hash,
386-
urlencoding::encode(&torrent_response.title)
387-
);
388-
389-
// Add trackers from torrent file to magnet link
390-
for tracker in &torrent_response.trackers {
391-
magnet.push_str(&format!("&tr={}", urlencoding::encode(tracker)));
392-
}
393-
394-
torrent_response.magnet_link = magnet;
395-
396-
// Get realtime seeders and leechers
397-
if let Ok(torrent_info) = self
398-
.tracker_statistics_importer
399-
.import_torrent_statistics(torrent_response.torrent_id, &torrent_response.info_hash)
400-
.await
401-
{
402-
torrent_response.seeders = torrent_info.seeders;
403-
torrent_response.leechers = torrent_info.leechers;
404-
}
405-
406-
torrent_response.tags = self.torrent_tag_repository.get_tags_for_torrent(&torrent_id).await?;
331+
let torrent_response = self
332+
.build_full_torrent_response(torrent_listing, info_hash, opt_user_id)
333+
.await?;
407334

408335
Ok(torrent_response)
409336
}
@@ -497,12 +424,7 @@ impl Index {
497424
.one_torrent_by_torrent_id(&torrent_listing.torrent_id)
498425
.await?;
499426

500-
let category = match torrent_listing.category_id {
501-
Some(category_id) => Some(self.category_repository.get_by_id(&category_id).await?),
502-
None => None,
503-
};
504-
505-
let torrent_response = TorrentResponse::from_listing(torrent_listing, category);
427+
let torrent_response = self.build_short_torrent_response(torrent_listing, info_hash).await?;
506428

507429
Ok(torrent_response)
508430
}
@@ -516,6 +438,109 @@ impl Index {
516438
let settings = self.configuration.settings.read().await;
517439
settings.tracker.mode.clone()
518440
}
441+
442+
async fn build_short_torrent_response(
443+
&self,
444+
torrent_listing: TorrentListing,
445+
info_hash: &InfoHash,
446+
) -> Result<TorrentResponse, ServiceError> {
447+
let category = match torrent_listing.category_id {
448+
Some(category_id) => Some(self.category_repository.get_by_id(&category_id).await?),
449+
None => None,
450+
};
451+
452+
let canonical_info_hash_group = self
453+
.torrent_info_hash_repository
454+
.get_canonical_info_hash_group(info_hash)
455+
.await?;
456+
457+
Ok(TorrentResponse::from_listing(
458+
torrent_listing,
459+
category,
460+
&canonical_info_hash_group,
461+
))
462+
}
463+
464+
async fn build_full_torrent_response(
465+
&self,
466+
torrent_listing: TorrentListing,
467+
info_hash: &InfoHash,
468+
opt_user_id: Option<UserId>,
469+
) -> Result<TorrentResponse, ServiceError> {
470+
let torrent_id: i64 = torrent_listing.torrent_id;
471+
472+
let mut torrent_response = self.build_short_torrent_response(torrent_listing, info_hash).await?;
473+
474+
// Add files
475+
476+
torrent_response.files = self.torrent_file_repository.get_by_torrent_id(&torrent_id).await?;
477+
478+
if torrent_response.files.len() == 1 {
479+
let torrent_info = self.torrent_info_repository.get_by_info_hash(info_hash).await?;
480+
481+
torrent_response
482+
.files
483+
.iter_mut()
484+
.for_each(|v| v.path = vec![torrent_info.name.to_string()]);
485+
}
486+
487+
// Add trackers
488+
489+
// code-review: duplicate logic. We have to check the same in the
490+
// download torrent file endpoint. Here he have only one list of tracker
491+
// like the `announce_list` in the torrent file.
492+
493+
torrent_response.trackers = self.torrent_announce_url_repository.get_by_torrent_id(&torrent_id).await?;
494+
495+
let tracker_url = self.get_tracker_url().await;
496+
let tracker_mode = self.get_tracker_mode().await;
497+
498+
if tracker_mode.is_open() {
499+
torrent_response.include_url_as_main_tracker(&tracker_url);
500+
} else {
501+
// Add main tracker URL
502+
match opt_user_id {
503+
Some(user_id) => {
504+
let personal_announce_url = self.tracker_service.get_personal_announce_url(user_id).await?;
505+
506+
torrent_response.include_url_as_main_tracker(&personal_announce_url);
507+
}
508+
None => {
509+
torrent_response.include_url_as_main_tracker(&tracker_url);
510+
}
511+
}
512+
}
513+
514+
// Add magnet link
515+
516+
// todo: extract a struct or function to build the magnet links
517+
let mut magnet = format!(
518+
"magnet:?xt=urn:btih:{}&dn={}",
519+
torrent_response.info_hash,
520+
urlencoding::encode(&torrent_response.title)
521+
);
522+
523+
// Add trackers from torrent file to magnet link
524+
for tracker in &torrent_response.trackers {
525+
magnet.push_str(&format!("&tr={}", urlencoding::encode(tracker)));
526+
}
527+
528+
torrent_response.magnet_link = magnet;
529+
530+
// Get realtime seeders and leechers
531+
if let Ok(torrent_info) = self
532+
.tracker_statistics_importer
533+
.import_torrent_statistics(torrent_response.torrent_id, &torrent_response.info_hash)
534+
.await
535+
{
536+
torrent_response.seeders = torrent_info.seeders;
537+
torrent_response.leechers = torrent_info.leechers;
538+
}
539+
540+
torrent_response.tags = self.torrent_tag_repository.get_tags_for_torrent(&torrent_id).await?;
541+
542+
Ok(torrent_response)
543+
}
519544
}
520545

521546
pub struct DbTorrentRepository {
@@ -579,6 +604,7 @@ pub struct DbTorrentInfoHash {
579604
/// This function returns the original infohashes of a canonical infohash.
580605
///
581606
/// The relationship is 1 canonical infohash -> N original infohashes.
607+
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
582608
pub struct CanonicalInfoHashGroup {
583609
pub canonical_info_hash: InfoHash,
584610
/// The list of original infohashes associated to the canonical one.

tests/common/contexts/torrent/responses.rs

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub struct TorrentDetails {
7272
pub creation_date: Option<i64>,
7373
pub created_by: Option<String>,
7474
pub encoding: Option<String>,
75+
pub canonical_info_hash_group: Vec<String>,
7576
}
7677

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

tests/e2e/web/api/v1/contexts/torrent/contract.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ mod for_guests {
197197
md5sum: None,
198198
}],
199199
// code-review: why is this duplicated? It seems that is adding the
200-
// same tracker twice because first ti adds all trackers and then
200+
// same tracker twice because first it adds all trackers and then
201201
// it adds the tracker with the personal announce url, if the user
202202
// is logged in. If the user is not logged in, it adds the default
203203
// tracker again, and it ends up with two trackers.
@@ -215,6 +215,7 @@ mod for_guests {
215215
creation_date: test_torrent.file_info.creation_date,
216216
created_by: test_torrent.file_info.created_by.clone(),
217217
encoding: test_torrent.file_info.encoding.clone(),
218+
canonical_info_hash_group: vec![test_torrent.file_info.info_hash.to_lowercase()],
218219
};
219220

220221
assert_expected_torrent_details(&torrent_details_response.data, &expected_torrent);

0 commit comments

Comments
 (0)