Skip to content

Commit 6cc1380

Browse files
committed
refactor: extract tracker api client from tracker service
1 parent 44cde6a commit 6cc1380

File tree

3 files changed

+169
-102
lines changed

3 files changed

+169
-102
lines changed

src/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub async fn run(configuration: Configuration) -> Running {
4444

4545
let database = Arc::new(connect_database(&database_connect_url).await.expect("Database error."));
4646
let auth = Arc::new(AuthorizationService::new(cfg.clone(), database.clone()));
47-
let tracker_service = Arc::new(TrackerService::new(cfg.clone(), database.clone()));
47+
let tracker_service = Arc::new(TrackerService::new(cfg.clone(), database.clone()).await);
4848
let mailer_service = Arc::new(MailerService::new(cfg.clone()).await);
4949
let image_cache_service = Arc::new(ImageCacheService::new(cfg.clone()).await);
5050

src/console/commands/import_tracker_statistics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub async fn import(_args: &Arguments) {
7676
.expect("Database error."),
7777
);
7878

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

8181
tracker_service.update_torrents().await.unwrap();
8282
}

src/tracker.rs

Lines changed: 167 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::sync::Arc;
22

33
use log::{error, info};
4+
use reqwest::{Error, Response};
45
use serde::{Deserialize, Serialize};
56

67
use crate::config::Configuration;
@@ -35,148 +36,147 @@ pub struct PeerId {
3536
}
3637

3738
pub struct TrackerService {
38-
cfg: Arc<Configuration>,
3939
database: Arc<Box<dyn Database>>,
40+
api_client: ApiClient,
41+
token_valid_seconds: u64,
42+
tracker_url: String,
4043
}
4144

4245
impl TrackerService {
43-
pub fn new(cfg: Arc<Configuration>, database: Arc<Box<dyn Database>>) -> TrackerService {
44-
TrackerService { cfg, database }
46+
pub async fn new(cfg: Arc<Configuration>, database: Arc<Box<dyn Database>>) -> TrackerService {
47+
let settings = cfg.settings.read().await;
48+
let api_client = ApiClient::new(ApiConnectionInfo::new(
49+
settings.tracker.api_url.clone(),
50+
settings.tracker.token.clone(),
51+
));
52+
let token_valid_seconds = settings.tracker.token_valid_seconds;
53+
let tracker_url = settings.tracker.url.clone();
54+
drop(settings);
55+
TrackerService {
56+
database,
57+
api_client,
58+
token_valid_seconds,
59+
tracker_url,
60+
}
4561
}
4662

63+
/// Add a torrent to the tracker whitelist.
64+
///
65+
/// # Errors
66+
///
67+
/// Will return an error if the HTTP request failed (for example if the
68+
/// tracker API is offline) or if the tracker API returned an error.
4769
pub async fn whitelist_info_hash(&self, info_hash: String) -> Result<(), ServiceError> {
48-
let settings = self.cfg.settings.read().await;
49-
50-
let request_url = format!(
51-
"{}/api/v1/whitelist/{}?token={}",
52-
settings.tracker.api_url, info_hash, settings.tracker.token
53-
);
54-
55-
drop(settings);
56-
57-
let client = reqwest::Client::new();
58-
59-
let response = client
60-
.post(request_url)
61-
.send()
62-
.await
63-
.map_err(|_| ServiceError::TrackerOffline)?;
64-
65-
if response.status().is_success() {
66-
Ok(())
67-
} else {
68-
Err(ServiceError::WhitelistingError)
70+
let response = self.api_client.whitelist_info_hash(&info_hash).await;
71+
72+
match response {
73+
Ok(response) => {
74+
if response.status().is_success() {
75+
Ok(())
76+
} else {
77+
Err(ServiceError::WhitelistingError)
78+
}
79+
}
80+
Err(_) => Err(ServiceError::TrackerOffline),
6981
}
7082
}
7183

84+
/// Remove a torrent from the tracker whitelist.
85+
///
86+
/// # Errors
87+
///
88+
/// Will return an error if the HTTP request failed (for example if the
89+
/// tracker API is offline) or if the tracker API returned an error.
7290
pub async fn remove_info_hash_from_whitelist(&self, info_hash: String) -> Result<(), ServiceError> {
73-
let settings = self.cfg.settings.read().await;
74-
75-
let request_url = format!(
76-
"{}/api/v1/whitelist/{}?token={}",
77-
settings.tracker.api_url, info_hash, settings.tracker.token
78-
);
79-
80-
drop(settings);
81-
82-
let client = reqwest::Client::new();
83-
84-
let response = match client.delete(request_url).send().await {
85-
Ok(v) => Ok(v),
91+
let response = self.api_client.remove_info_hash_from_whitelist(&info_hash).await;
92+
93+
match response {
94+
Ok(response) => {
95+
if response.status().is_success() {
96+
Ok(())
97+
} else {
98+
Err(ServiceError::InternalServerError)
99+
}
100+
}
86101
Err(_) => Err(ServiceError::InternalServerError),
87-
}?;
88-
89-
if response.status().is_success() {
90-
return Ok(());
91102
}
92-
93-
Err(ServiceError::InternalServerError)
94103
}
95104

96-
// get personal tracker announce url of a user
97-
// Eg: https://tracker.torrust.com/announce/USER_TRACKER_KEY
105+
/// Get personal tracker announce url of a user.
106+
///
107+
/// Eg: <https://tracker:7070/USER_TRACKER_KEY>
108+
///
109+
/// If the user doesn't have a not expired tracker key, it will generate a
110+
/// new one and save it in the database.
111+
///
112+
/// # Errors
113+
///
114+
/// Will return an error if the HTTP request to get generated a new
115+
/// user tracker key failed.
98116
pub async fn get_personal_announce_url(&self, user_id: i64) -> Result<String, ServiceError> {
99-
let settings = self.cfg.settings.read().await;
100-
101-
// get a valid tracker key for this user from database
102117
let tracker_key = self.database.get_user_tracker_key(user_id).await;
103118

104119
match tracker_key {
105-
Some(v) => Ok(format!("{}/{}", settings.tracker.url, v.key)),
120+
Some(v) => Ok(self.announce_url_with_key(&v)),
106121
None => match self.retrieve_new_tracker_key(user_id).await {
107-
Ok(v) => Ok(format!("{}/{}", settings.tracker.url, v.key)),
122+
Ok(v) => Ok(self.announce_url_with_key(&v)),
108123
Err(_) => Err(ServiceError::TrackerOffline),
109124
},
110125
}
111126
}
112127

113-
// issue a new tracker key from tracker and save it in database, tied to a user
114-
async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result<TrackerKey, ServiceError> {
115-
let settings = self.cfg.settings.read().await;
116-
117-
let request_url = format!(
118-
"{}/api/v1/key/{}?token={}",
119-
settings.tracker.api_url, settings.tracker.token_valid_seconds, settings.tracker.token
120-
);
121-
122-
drop(settings);
123-
124-
let client = reqwest::Client::new();
128+
/// It builds the announce url appending the user tracker key.
129+
/// Eg: <https://tracker:7070/USER_TRACKER_KEY>
130+
fn announce_url_with_key(&self, tracker_key: &TrackerKey) -> String {
131+
format!("{}/{}", self.tracker_url, tracker_key.key)
132+
}
125133

126-
// issue new tracker key
127-
let response = client
128-
.post(request_url)
129-
.send()
134+
/// Issue a new tracker key from tracker and save it in database,
135+
/// tied to a user
136+
async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result<TrackerKey, ServiceError> {
137+
// Request new tracker key from tracker
138+
let response = self
139+
.api_client
140+
.retrieve_new_tracker_key(self.token_valid_seconds)
130141
.await
131142
.map_err(|_| ServiceError::InternalServerError)?;
132143

133-
// get tracker key from response
144+
// Parse tracker key from response
134145
let tracker_key = response
135146
.json::<TrackerKey>()
136147
.await
137148
.map_err(|_| ServiceError::InternalServerError)?;
138149

139-
// add tracker key to database (tied to a user)
150+
// Add tracker key to database (tied to a user)
140151
self.database.add_tracker_key(user_id, &tracker_key).await?;
141152

142153
// return tracker key
143154
Ok(tracker_key)
144155
}
145156

146-
// get torrent info from tracker api
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.
147163
pub async fn get_torrent_info(&self, torrent_id: i64, info_hash: &str) -> Result<TorrentInfo, ServiceError> {
148-
let settings = self.cfg.settings.read().await;
149-
150-
let tracker_url = settings.tracker.url.clone();
151-
152-
let request_url = format!(
153-
"{}/api/v1/torrent/{}?token={}",
154-
settings.tracker.api_url, info_hash, settings.tracker.token
155-
);
156-
157-
drop(settings);
158-
159-
let client = reqwest::Client::new();
160-
let response = match client.get(request_url).send().await {
161-
Ok(v) => Ok(v),
162-
Err(_) => Err(ServiceError::InternalServerError),
163-
}?;
164-
165-
let torrent_info = match response.json::<TorrentInfo>().await {
166-
Ok(torrent_info) => {
167-
let _ = self
168-
.database
169-
.update_tracker_info(torrent_id, &tracker_url, torrent_info.seeders, torrent_info.leechers)
170-
.await;
171-
Ok(torrent_info)
172-
}
173-
Err(_) => {
174-
let _ = self.database.update_tracker_info(torrent_id, &tracker_url, 0, 0).await;
175-
Err(ServiceError::TorrentNotFound)
176-
}
177-
}?;
164+
let response = self
165+
.api_client
166+
.get_torrent_info(info_hash)
167+
.await
168+
.map_err(|_| ServiceError::InternalServerError)?;
178169

179-
Ok(torrent_info)
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+
}
180180
}
181181

182182
pub async fn update_torrents(&self) -> Result<(), ServiceError> {
@@ -203,3 +203,70 @@ impl TrackerService {
203203
self.get_torrent_info(torrent_id, info_hash).await
204204
}
205205
}
206+
207+
struct ApiConnectionInfo {
208+
pub url: String,
209+
pub token: String,
210+
}
211+
212+
impl ApiConnectionInfo {
213+
pub fn new(url: String, token: String) -> Self {
214+
Self { url, token }
215+
}
216+
}
217+
218+
struct ApiClient {
219+
pub connection_info: ApiConnectionInfo,
220+
base_url: String,
221+
}
222+
223+
impl ApiClient {
224+
pub fn new(connection_info: ApiConnectionInfo) -> Self {
225+
let base_url = format!("{}/api/v1", connection_info.url);
226+
Self {
227+
connection_info,
228+
base_url,
229+
}
230+
}
231+
232+
pub async fn whitelist_info_hash(&self, info_hash: &str) -> Result<Response, Error> {
233+
let request_url = format!(
234+
"{}/whitelist/{}?token={}",
235+
self.base_url, info_hash, self.connection_info.token
236+
);
237+
238+
let client = reqwest::Client::new();
239+
240+
client.post(request_url).send().await
241+
}
242+
243+
pub async fn remove_info_hash_from_whitelist(&self, info_hash: &str) -> Result<Response, Error> {
244+
let request_url = format!(
245+
"{}/whitelist/{}?token={}",
246+
self.base_url, info_hash, self.connection_info.token
247+
);
248+
249+
let client = reqwest::Client::new();
250+
251+
client.delete(request_url).send().await
252+
}
253+
254+
async fn retrieve_new_tracker_key(&self, token_valid_seconds: u64) -> Result<Response, Error> {
255+
let request_url = format!(
256+
"{}/key/{}?token={}",
257+
self.base_url, token_valid_seconds, self.connection_info.token
258+
);
259+
260+
let client = reqwest::Client::new();
261+
262+
client.post(request_url).send().await
263+
}
264+
265+
pub async fn get_torrent_info(&self, info_hash: &str) -> Result<Response, Error> {
266+
let request_url = format!("{}/torrent/{}?token={}", self.base_url, info_hash, self.connection_info.token);
267+
268+
let client = reqwest::Client::new();
269+
270+
client.get(request_url).send().await
271+
}
272+
}

0 commit comments

Comments
 (0)