Skip to content

Commit

Permalink
refactor: extract tracker api client from tracker service
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed May 9, 2023
1 parent 44cde6a commit 6cc1380
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 102 deletions.
2 changes: 1 addition & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub async fn run(configuration: Configuration) -> Running {

let database = Arc::new(connect_database(&database_connect_url).await.expect("Database error."));
let auth = Arc::new(AuthorizationService::new(cfg.clone(), database.clone()));
let tracker_service = Arc::new(TrackerService::new(cfg.clone(), database.clone()));
let tracker_service = Arc::new(TrackerService::new(cfg.clone(), database.clone()).await);
let mailer_service = Arc::new(MailerService::new(cfg.clone()).await);
let image_cache_service = Arc::new(ImageCacheService::new(cfg.clone()).await);

Expand Down
2 changes: 1 addition & 1 deletion src/console/commands/import_tracker_statistics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub async fn import(_args: &Arguments) {
.expect("Database error."),
);

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

tracker_service.update_torrents().await.unwrap();
}
267 changes: 167 additions & 100 deletions src/tracker.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::sync::Arc;

use log::{error, info};
use reqwest::{Error, Response};
use serde::{Deserialize, Serialize};

use crate::config::Configuration;
Expand Down Expand Up @@ -35,148 +36,147 @@ pub struct PeerId {
}

pub struct TrackerService {
cfg: Arc<Configuration>,
database: Arc<Box<dyn Database>>,
api_client: ApiClient,
token_valid_seconds: u64,
tracker_url: String,
}

impl TrackerService {
pub fn new(cfg: Arc<Configuration>, database: Arc<Box<dyn Database>>) -> TrackerService {
TrackerService { cfg, database }
pub async fn new(cfg: Arc<Configuration>, database: Arc<Box<dyn Database>>) -> TrackerService {
let settings = cfg.settings.read().await;
let api_client = ApiClient::new(ApiConnectionInfo::new(
settings.tracker.api_url.clone(),
settings.tracker.token.clone(),
));
let token_valid_seconds = settings.tracker.token_valid_seconds;
let tracker_url = settings.tracker.url.clone();
drop(settings);
TrackerService {
database,
api_client,
token_valid_seconds,
tracker_url,
}
}

/// Add a torrent to the tracker whitelist.
///
/// # Errors
///
/// Will return an error if the HTTP request failed (for example if the
/// tracker API is offline) or if the tracker API returned an error.
pub async fn whitelist_info_hash(&self, info_hash: String) -> Result<(), ServiceError> {
let settings = self.cfg.settings.read().await;

let request_url = format!(
"{}/api/v1/whitelist/{}?token={}",
settings.tracker.api_url, info_hash, settings.tracker.token
);

drop(settings);

let client = reqwest::Client::new();

let response = client
.post(request_url)
.send()
.await
.map_err(|_| ServiceError::TrackerOffline)?;

if response.status().is_success() {
Ok(())
} else {
Err(ServiceError::WhitelistingError)
let response = self.api_client.whitelist_info_hash(&info_hash).await;

match response {
Ok(response) => {
if response.status().is_success() {
Ok(())
} else {
Err(ServiceError::WhitelistingError)
}
}
Err(_) => Err(ServiceError::TrackerOffline),
}
}

/// Remove a torrent from the tracker whitelist.
///
/// # Errors
///
/// Will return an error if the HTTP request failed (for example if the
/// tracker API is offline) or if the tracker API returned an error.
pub async fn remove_info_hash_from_whitelist(&self, info_hash: String) -> Result<(), ServiceError> {
let settings = self.cfg.settings.read().await;

let request_url = format!(
"{}/api/v1/whitelist/{}?token={}",
settings.tracker.api_url, info_hash, settings.tracker.token
);

drop(settings);

let client = reqwest::Client::new();

let response = match client.delete(request_url).send().await {
Ok(v) => Ok(v),
let response = self.api_client.remove_info_hash_from_whitelist(&info_hash).await;

match response {
Ok(response) => {
if response.status().is_success() {
Ok(())
} else {
Err(ServiceError::InternalServerError)
}
}
Err(_) => Err(ServiceError::InternalServerError),
}?;

if response.status().is_success() {
return Ok(());
}

Err(ServiceError::InternalServerError)
}

// get personal tracker announce url of a user
// Eg: https://tracker.torrust.com/announce/USER_TRACKER_KEY
/// Get personal tracker announce url of a user.
///
/// Eg: <https://tracker:7070/USER_TRACKER_KEY>
///
/// If the user doesn't have a not expired tracker key, it will generate a
/// new one and save it in the database.
///
/// # Errors
///
/// Will return an error if the HTTP request to get generated a new
/// user tracker key failed.
pub async fn get_personal_announce_url(&self, user_id: i64) -> Result<String, ServiceError> {
let settings = self.cfg.settings.read().await;

// get a valid tracker key for this user from database
let tracker_key = self.database.get_user_tracker_key(user_id).await;

match tracker_key {
Some(v) => Ok(format!("{}/{}", settings.tracker.url, v.key)),
Some(v) => Ok(self.announce_url_with_key(&v)),
None => match self.retrieve_new_tracker_key(user_id).await {
Ok(v) => Ok(format!("{}/{}", settings.tracker.url, v.key)),
Ok(v) => Ok(self.announce_url_with_key(&v)),
Err(_) => Err(ServiceError::TrackerOffline),
},
}
}

// issue a new tracker key from tracker and save it in database, tied to a user
async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result<TrackerKey, ServiceError> {
let settings = self.cfg.settings.read().await;

let request_url = format!(
"{}/api/v1/key/{}?token={}",
settings.tracker.api_url, settings.tracker.token_valid_seconds, settings.tracker.token
);

drop(settings);

let client = reqwest::Client::new();
/// It builds the announce url appending the user tracker key.
/// Eg: <https://tracker:7070/USER_TRACKER_KEY>
fn announce_url_with_key(&self, tracker_key: &TrackerKey) -> String {
format!("{}/{}", self.tracker_url, tracker_key.key)
}

// issue new tracker key
let response = client
.post(request_url)
.send()
/// Issue a new tracker key from tracker and save it in database,
/// tied to a user
async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result<TrackerKey, ServiceError> {
// Request new tracker key from tracker
let response = self
.api_client
.retrieve_new_tracker_key(self.token_valid_seconds)
.await
.map_err(|_| ServiceError::InternalServerError)?;

// get tracker key from response
// Parse tracker key from response
let tracker_key = response
.json::<TrackerKey>()
.await
.map_err(|_| ServiceError::InternalServerError)?;

// add tracker key to database (tied to a user)
// Add tracker key to database (tied to a user)
self.database.add_tracker_key(user_id, &tracker_key).await?;

// return tracker key
Ok(tracker_key)
}

// get torrent info from tracker api
/// Get torrent info from tracker API
///
/// # Errors
///
/// Will return an error if the HTTP request failed or the torrent is not
/// found.
pub async fn get_torrent_info(&self, torrent_id: i64, info_hash: &str) -> Result<TorrentInfo, ServiceError> {
let settings = self.cfg.settings.read().await;

let tracker_url = settings.tracker.url.clone();

let request_url = format!(
"{}/api/v1/torrent/{}?token={}",
settings.tracker.api_url, info_hash, settings.tracker.token
);

drop(settings);

let client = reqwest::Client::new();
let response = match client.get(request_url).send().await {
Ok(v) => Ok(v),
Err(_) => Err(ServiceError::InternalServerError),
}?;

let torrent_info = match response.json::<TorrentInfo>().await {
Ok(torrent_info) => {
let _ = self
.database
.update_tracker_info(torrent_id, &tracker_url, torrent_info.seeders, torrent_info.leechers)
.await;
Ok(torrent_info)
}
Err(_) => {
let _ = self.database.update_tracker_info(torrent_id, &tracker_url, 0, 0).await;
Err(ServiceError::TorrentNotFound)
}
}?;
let response = self
.api_client
.get_torrent_info(info_hash)
.await
.map_err(|_| ServiceError::InternalServerError)?;

Ok(torrent_info)
if let Ok(torrent_info) = response.json::<TorrentInfo>().await {
let _ = self
.database
.update_tracker_info(torrent_id, &self.tracker_url, torrent_info.seeders, torrent_info.leechers)
.await;
Ok(torrent_info)
} else {
let _ = self.database.update_tracker_info(torrent_id, &self.tracker_url, 0, 0).await;
Err(ServiceError::TorrentNotFound)
}
}

pub async fn update_torrents(&self) -> Result<(), ServiceError> {
Expand All @@ -203,3 +203,70 @@ impl TrackerService {
self.get_torrent_info(torrent_id, info_hash).await
}
}

struct ApiConnectionInfo {
pub url: String,
pub token: String,
}

impl ApiConnectionInfo {
pub fn new(url: String, token: String) -> Self {
Self { url, token }
}
}

struct ApiClient {
pub connection_info: ApiConnectionInfo,
base_url: String,
}

impl ApiClient {
pub fn new(connection_info: ApiConnectionInfo) -> Self {
let base_url = format!("{}/api/v1", connection_info.url);
Self {
connection_info,
base_url,
}
}

pub async fn whitelist_info_hash(&self, info_hash: &str) -> Result<Response, Error> {
let request_url = format!(
"{}/whitelist/{}?token={}",
self.base_url, info_hash, self.connection_info.token
);

let client = reqwest::Client::new();

client.post(request_url).send().await
}

pub async fn remove_info_hash_from_whitelist(&self, info_hash: &str) -> Result<Response, Error> {
let request_url = format!(
"{}/whitelist/{}?token={}",
self.base_url, info_hash, self.connection_info.token
);

let client = reqwest::Client::new();

client.delete(request_url).send().await
}

async fn retrieve_new_tracker_key(&self, token_valid_seconds: u64) -> Result<Response, Error> {
let request_url = format!(
"{}/key/{}?token={}",
self.base_url, token_valid_seconds, self.connection_info.token
);

let client = reqwest::Client::new();

client.post(request_url).send().await
}

pub async fn get_torrent_info(&self, info_hash: &str) -> Result<Response, Error> {
let request_url = format!("{}/torrent/{}?token={}", self.base_url, info_hash, self.connection_info.token);

let client = reqwest::Client::new();

client.get(request_url).send().await
}
}

0 comments on commit 6cc1380

Please sign in to comment.