Skip to content

Commit 63aefcf

Browse files
committed
refactor: decouple tracker::StatisticsImporter from tracker::Service
1 parent 0198361 commit 63aefcf

File tree

7 files changed

+149
-97
lines changed

7 files changed

+149
-97
lines changed

src/app.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::databases::database::connect_database;
1515
use crate::mailer::MailerService;
1616
use crate::routes;
1717
use crate::tracker::service::Service;
18+
use crate::tracker::statistics_importer::StatisticsImporter;
1819

1920
pub struct Running {
2021
pub api_server: Server,
@@ -45,6 +46,7 @@ pub async fn run(configuration: Configuration) -> Running {
4546
let database = Arc::new(connect_database(&database_connect_url).await.expect("Database error."));
4647
let auth = Arc::new(AuthorizationService::new(cfg.clone(), database.clone()));
4748
let tracker_service = Arc::new(Service::new(cfg.clone(), database.clone()).await);
49+
let tracker_statistics_importer = Arc::new(StatisticsImporter::new(cfg.clone(), database.clone()).await);
4850
let mailer_service = Arc::new(MailerService::new(cfg.clone()).await);
4951
let image_cache_service = Arc::new(ImageCacheService::new(cfg.clone()).await);
5052

@@ -55,23 +57,24 @@ pub async fn run(configuration: Configuration) -> Running {
5557
database.clone(),
5658
auth.clone(),
5759
tracker_service.clone(),
60+
tracker_statistics_importer.clone(),
5861
mailer_service,
5962
image_cache_service,
6063
));
6164

6265
// Start repeating task to import tracker torrent data and updating
6366
// seeders and leechers info.
6467

65-
let weak_tracker_service = Arc::downgrade(&tracker_service);
68+
let weak_tracker_statistics_importer = Arc::downgrade(&tracker_statistics_importer);
6669

67-
let tracker_data_importer_handle = tokio::spawn(async move {
70+
let tracker_statistics_importer_handle = tokio::spawn(async move {
6871
let interval = std::time::Duration::from_secs(database_torrent_info_update_interval);
6972
let mut interval = tokio::time::interval(interval);
7073
interval.tick().await; // first tick is immediate...
7174
loop {
7275
interval.tick().await;
73-
if let Some(tracker) = weak_tracker_service.upgrade() {
74-
let _ = tracker.update_torrents().await;
76+
if let Some(tracker) = weak_tracker_statistics_importer.upgrade() {
77+
let _ = tracker.import_all_torrents_statistics().await;
7578
} else {
7679
break;
7780
}
@@ -105,6 +108,6 @@ pub async fn run(configuration: Configuration) -> Running {
105108
Running {
106109
api_server: running_server,
107110
socket_address,
108-
tracker_data_importer_handle,
111+
tracker_data_importer_handle: tracker_statistics_importer_handle,
109112
}
110113
}

src/common.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::config::Configuration;
66
use crate::databases::database::Database;
77
use crate::mailer::MailerService;
88
use crate::tracker::service::Service;
9+
use crate::tracker::statistics_importer::StatisticsImporter;
910

1011
pub type Username = String;
1112

@@ -15,7 +16,8 @@ pub struct AppData {
1516
pub cfg: Arc<Configuration>,
1617
pub database: Arc<Box<dyn Database>>,
1718
pub auth: Arc<AuthorizationService>,
18-
pub tracker: Arc<Service>,
19+
pub tracker_service: Arc<Service>,
20+
pub tracker_statistics_importer: Arc<StatisticsImporter>,
1921
pub mailer: Arc<MailerService>,
2022
pub image_cache_manager: Arc<ImageCacheService>,
2123
}
@@ -25,15 +27,17 @@ impl AppData {
2527
cfg: Arc<Configuration>,
2628
database: Arc<Box<dyn Database>>,
2729
auth: Arc<AuthorizationService>,
28-
tracker: Arc<Service>,
30+
tracker_service: Arc<Service>,
31+
tracker_statistics_importer: Arc<StatisticsImporter>,
2932
mailer: Arc<MailerService>,
3033
image_cache_manager: Arc<ImageCacheService>,
3134
) -> AppData {
3235
AppData {
3336
cfg,
3437
database,
3538
auth,
36-
tracker,
39+
tracker_service,
40+
tracker_statistics_importer,
3741
mailer,
3842
image_cache_manager,
3943
}

src/console/commands/import_tracker_statistics.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use text_colorizer::*;
99
use crate::bootstrap::config::init_configuration;
1010
use crate::bootstrap::logging;
1111
use crate::databases::database::connect_database;
12-
use crate::tracker::service::Service;
12+
use crate::tracker::statistics_importer::StatisticsImporter;
1313

1414
const NUMBER_OF_ARGUMENTS: usize = 0;
1515

@@ -76,7 +76,7 @@ pub async fn import(_args: &Arguments) {
7676
.expect("Database error."),
7777
);
7878

79-
let tracker_service = Arc::new(Service::new(cfg.clone(), database.clone()).await);
79+
let tracker_statistics_importer = Arc::new(StatisticsImporter::new(cfg.clone(), database.clone()).await);
8080

81-
tracker_service.update_torrents().await.unwrap();
81+
tracker_statistics_importer.import_all_torrents_statistics().await.unwrap();
8282
}

src/routes/torrent.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,15 @@ pub async fn upload_torrent(req: HttpRequest, payload: Multipart, app_data: WebA
100100

101101
// update torrent tracker stats
102102
let _ = app_data
103-
.tracker
104-
.update_torrent_tracker_stats(torrent_id, &torrent_request.torrent.info_hash())
103+
.tracker_statistics_importer
104+
.import_torrent_statistics(torrent_id, &torrent_request.torrent.info_hash())
105105
.await;
106106

107107
// whitelist info hash on tracker
108108
// code-review: why do we always try to whitelist the torrent on the tracker?
109109
// shouldn't we only do this if the torrent is in "Listed" mode?
110110
if let Err(e) = app_data
111-
.tracker
111+
.tracker_service
112112
.whitelist_info_hash(torrent_request.torrent.info_hash())
113113
.await
114114
{
@@ -146,7 +146,7 @@ pub async fn download_torrent_handler(req: HttpRequest, app_data: WebAppData) ->
146146
match user {
147147
Ok(user) => {
148148
let personal_announce_url = app_data
149-
.tracker
149+
.tracker_service
150150
.get_personal_announce_url(user.user_id)
151151
.await
152152
.unwrap_or(tracker_url);
@@ -210,7 +210,7 @@ pub async fn get_torrent_handler(req: HttpRequest, app_data: WebAppData) -> Serv
210210
Ok(user) => {
211211
// if no user owned tracker key can be found, use default tracker url
212212
let personal_announce_url = app_data
213-
.tracker
213+
.tracker_service
214214
.get_personal_announce_url(user.user_id)
215215
.await
216216
.unwrap_or(tracker_url);
@@ -240,8 +240,8 @@ pub async fn get_torrent_handler(req: HttpRequest, app_data: WebAppData) -> Serv
240240

241241
// get realtime seeders and leechers
242242
if let Ok(torrent_info) = app_data
243-
.tracker
244-
.get_torrent_info(torrent_response.torrent_id, &torrent_response.info_hash)
243+
.tracker_statistics_importer
244+
.import_torrent_statistics(torrent_response.torrent_id, &torrent_response.info_hash)
245245
.await
246246
{
247247
torrent_response.seeders = torrent_info.seeders;
@@ -310,7 +310,7 @@ pub async fn delete_torrent_handler(req: HttpRequest, app_data: WebAppData) -> S
310310

311311
// remove info_hash from tracker whitelist
312312
let _ = app_data
313-
.tracker
313+
.tracker_service
314314
.remove_info_hash_from_whitelist(torrent_listing.info_hash)
315315
.await;
316316

src/tracker/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod api;
22
pub mod service;
3+
pub mod statistics_importer;

src/tracker/service.rs

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,11 @@
11
use std::sync::Arc;
22

3-
use log::{error, info};
4-
use serde::{Deserialize, Serialize};
5-
63
use super::api::{Client, ConnectionInfo};
74
use crate::config::Configuration;
85
use crate::databases::database::Database;
96
use crate::errors::ServiceError;
107
use crate::models::tracker_key::TrackerKey;
118

12-
#[derive(Debug, Serialize, Deserialize)]
13-
pub struct TorrentInfo {
14-
pub info_hash: String,
15-
pub seeders: i64,
16-
pub completed: i64,
17-
pub leechers: i64,
18-
pub peers: Vec<Peer>,
19-
}
20-
21-
#[derive(Debug, Serialize, Deserialize)]
22-
pub struct Peer {
23-
pub peer_id: Option<PeerId>,
24-
pub peer_addr: Option<String>,
25-
pub updated: Option<i64>,
26-
pub uploaded: Option<i64>,
27-
pub downloaded: Option<i64>,
28-
pub left: Option<i64>,
29-
pub event: Option<String>,
30-
}
31-
32-
#[derive(Debug, Serialize, Deserialize)]
33-
pub struct PeerId {
34-
pub id: Option<String>,
35-
pub client: Option<String>,
36-
}
37-
389
pub struct Service {
3910
database: Arc<Box<dyn Database>>,
4011
api_client: Client,
@@ -153,53 +124,4 @@ impl Service {
153124
// return tracker key
154125
Ok(tracker_key)
155126
}
156-
157-
/// Get torrent info from tracker API
158-
///
159-
/// # Errors
160-
///
161-
/// Will return an error if the HTTP request failed or the torrent is not
162-
/// found.
163-
pub async fn get_torrent_info(&self, torrent_id: i64, info_hash: &str) -> Result<TorrentInfo, ServiceError> {
164-
let response = self
165-
.api_client
166-
.get_torrent_info(info_hash)
167-
.await
168-
.map_err(|_| ServiceError::InternalServerError)?;
169-
170-
if let Ok(torrent_info) = response.json::<TorrentInfo>().await {
171-
let _ = self
172-
.database
173-
.update_tracker_info(torrent_id, &self.tracker_url, torrent_info.seeders, torrent_info.leechers)
174-
.await;
175-
Ok(torrent_info)
176-
} else {
177-
let _ = self.database.update_tracker_info(torrent_id, &self.tracker_url, 0, 0).await;
178-
Err(ServiceError::TorrentNotFound)
179-
}
180-
}
181-
182-
pub async fn update_torrents(&self) -> Result<(), ServiceError> {
183-
info!("Updating torrents ...");
184-
let torrents = self.database.get_all_torrents_compact().await?;
185-
186-
for torrent in torrents {
187-
info!("Updating torrent {} ...", torrent.torrent_id);
188-
let ret = self
189-
.update_torrent_tracker_stats(torrent.torrent_id, &torrent.info_hash)
190-
.await;
191-
if let Some(err) = ret.err() {
192-
error!(
193-
"Error updating torrent tracker stats for torrent {}: {:?}",
194-
torrent.torrent_id, err
195-
);
196-
}
197-
}
198-
199-
Ok(())
200-
}
201-
202-
pub async fn update_torrent_tracker_stats(&self, torrent_id: i64, info_hash: &str) -> Result<TorrentInfo, ServiceError> {
203-
self.get_torrent_info(torrent_id, info_hash).await
204-
}
205127
}

src/tracker/statistics_importer.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use std::sync::Arc;
2+
3+
use log::{error, info};
4+
use serde::{Deserialize, Serialize};
5+
6+
use super::api::{Client, ConnectionInfo};
7+
use crate::config::Configuration;
8+
use crate::databases::database::{Database, DatabaseError};
9+
use crate::errors::ServiceError;
10+
11+
// If `TorrentInfo` struct is used in the future for other purposes, it should
12+
// be moved to a separate file. Maybe a `ClientWrapper` struct which returns
13+
// `TorrentInfo` and `TrackerKey` structs instead of `Response` structs.
14+
15+
#[derive(Debug, Serialize, Deserialize)]
16+
pub struct TorrentInfo {
17+
pub info_hash: String,
18+
pub seeders: i64,
19+
pub completed: i64,
20+
pub leechers: i64,
21+
pub peers: Vec<Peer>,
22+
}
23+
24+
#[derive(Debug, Serialize, Deserialize)]
25+
pub struct Peer {
26+
pub peer_id: Option<PeerId>,
27+
pub peer_addr: Option<String>,
28+
pub updated: Option<i64>,
29+
pub uploaded: Option<i64>,
30+
pub downloaded: Option<i64>,
31+
pub left: Option<i64>,
32+
pub event: Option<String>,
33+
}
34+
35+
#[derive(Debug, Serialize, Deserialize)]
36+
pub struct PeerId {
37+
pub id: Option<String>,
38+
pub client: Option<String>,
39+
}
40+
41+
pub struct StatisticsImporter {
42+
database: Arc<Box<dyn Database>>,
43+
api_client: Client,
44+
tracker_url: String,
45+
}
46+
47+
impl StatisticsImporter {
48+
pub async fn new(cfg: Arc<Configuration>, database: Arc<Box<dyn Database>>) -> Self {
49+
let settings = cfg.settings.read().await;
50+
let api_client = Client::new(ConnectionInfo::new(
51+
settings.tracker.api_url.clone(),
52+
settings.tracker.token.clone(),
53+
));
54+
let tracker_url = settings.tracker.url.clone();
55+
drop(settings);
56+
Self {
57+
database,
58+
api_client,
59+
tracker_url,
60+
}
61+
}
62+
63+
/// Import torrents statistics from tracker and update them in database.
64+
///
65+
/// # Errors
66+
///
67+
/// Will return an error if the database query failed.
68+
pub async fn import_all_torrents_statistics(&self) -> Result<(), DatabaseError> {
69+
info!("Importing torrents statistics from tracker ...");
70+
let torrents = self.database.get_all_torrents_compact().await?;
71+
72+
for torrent in torrents {
73+
info!("Updating torrent {} ...", torrent.torrent_id);
74+
75+
let ret = self.import_torrent_statistics(torrent.torrent_id, &torrent.info_hash).await;
76+
77+
// code-review: should we treat differently for each case?. The
78+
// tracker API could be temporarily offline, or there could be a
79+
// tracker misconfiguration.
80+
//
81+
// This is the log when the torrent is not found in the tracker:
82+
//
83+
// ```
84+
// 2023-05-09T13:31:24.497465723+00:00 [torrust_index_backend::tracker::statistics_importer][ERROR] Error updating torrent tracker stats for torrent with id 140: TorrentNotFound
85+
// ```
86+
87+
if let Some(err) = ret.err() {
88+
error!(
89+
"Error updating torrent tracker stats for torrent with id {}: {:?}",
90+
torrent.torrent_id, err
91+
);
92+
}
93+
}
94+
95+
Ok(())
96+
}
97+
98+
/// Import torrent statistics from tracker and update them in database.
99+
///
100+
/// # Errors
101+
///
102+
/// Will return an error if the HTTP request failed or the torrent is not
103+
/// found.
104+
pub async fn import_torrent_statistics(&self, torrent_id: i64, info_hash: &str) -> Result<TorrentInfo, ServiceError> {
105+
let response = self
106+
.api_client
107+
.get_torrent_info(info_hash)
108+
.await
109+
.map_err(|_| ServiceError::InternalServerError)?;
110+
111+
if let Ok(torrent_info) = response.json::<TorrentInfo>().await {
112+
let _ = self
113+
.database
114+
.update_tracker_info(torrent_id, &self.tracker_url, torrent_info.seeders, torrent_info.leechers)
115+
.await;
116+
Ok(torrent_info)
117+
} else {
118+
let _ = self.database.update_tracker_info(torrent_id, &self.tracker_url, 0, 0).await;
119+
Err(ServiceError::TorrentNotFound)
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)