Skip to content

Commit

Permalink
refactor: use tracker::Service in StatisticsImporter
Browse files Browse the repository at this point in the history
Instead of using the API client directly. TrackerService is easier to
mock becuase you only need to build simple app structs instead of
reqwest responses.
  • Loading branch information
josecelano committed May 9, 2023
1 parent 63aefcf commit 404caee
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 48 deletions.
3 changes: 2 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ 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(Service::new(cfg.clone(), database.clone()).await);
let tracker_statistics_importer = Arc::new(StatisticsImporter::new(cfg.clone(), database.clone()).await);
let tracker_statistics_importer =
Arc::new(StatisticsImporter::new(cfg.clone(), tracker_service.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
5 changes: 4 additions & 1 deletion src/console/commands/import_tracker_statistics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use text_colorizer::*;
use crate::bootstrap::config::init_configuration;
use crate::bootstrap::logging;
use crate::databases::database::connect_database;
use crate::tracker::service::Service;
use crate::tracker::statistics_importer::StatisticsImporter;

const NUMBER_OF_ARGUMENTS: usize = 0;
Expand Down Expand Up @@ -76,7 +77,9 @@ pub async fn import(_args: &Arguments) {
.expect("Database error."),
);

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

tracker_statistics_importer.import_all_torrents_statistics().await.unwrap();
}
50 changes: 50 additions & 0 deletions src/tracker/service.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
use std::sync::Arc;

use log::error;
use serde::{Deserialize, Serialize};

use super::api::{Client, ConnectionInfo};
use crate::config::Configuration;
use crate::databases::database::Database;
use crate::errors::ServiceError;
use crate::models::tracker_key::TrackerKey;

#[derive(Debug, Serialize, Deserialize)]
pub struct TorrentInfo {
pub info_hash: String,
pub seeders: i64,
pub completed: i64,
pub leechers: i64,
pub peers: Vec<Peer>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Peer {
pub peer_id: Option<PeerId>,
pub peer_addr: Option<String>,
pub updated: Option<i64>,
pub uploaded: Option<i64>,
pub downloaded: Option<i64>,
pub left: Option<i64>,
pub event: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PeerId {
pub id: Option<String>,
pub client: Option<String>,
}

pub struct Service {
database: Arc<Box<dyn Database>>,
api_client: Client,
Expand Down Expand Up @@ -96,6 +125,27 @@ impl Service {
}
}

/// Get torrent info from tracker.
///
/// # Errors
///
/// Will return an error if the HTTP request to get torrent info fails or
/// if the response cannot be parsed.
pub async fn get_torrent_info(&self, info_hash: &str) -> Result<TorrentInfo, ServiceError> {
let response = self
.api_client
.get_torrent_info(info_hash)
.await
.map_err(|_| ServiceError::InternalServerError)?;

if let Ok(torrent_info) = response.json::<TorrentInfo>().await {
Ok(torrent_info)
} else {
error!("Failed to parse torrent info from tracker response");
Err(ServiceError::InternalServerError)
}
}

/// 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 {
Expand Down
51 changes: 5 additions & 46 deletions src/tracker/statistics_importer.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,26 @@
use std::sync::Arc;

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

use super::api::{Client, ConnectionInfo};
use super::service::{Service, TorrentInfo};
use crate::config::Configuration;
use crate::databases::database::{Database, DatabaseError};
use crate::errors::ServiceError;

// If `TorrentInfo` struct is used in the future for other purposes, it should
// be moved to a separate file. Maybe a `ClientWrapper` struct which returns
// `TorrentInfo` and `TrackerKey` structs instead of `Response` structs.

#[derive(Debug, Serialize, Deserialize)]
pub struct TorrentInfo {
pub info_hash: String,
pub seeders: i64,
pub completed: i64,
pub leechers: i64,
pub peers: Vec<Peer>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Peer {
pub peer_id: Option<PeerId>,
pub peer_addr: Option<String>,
pub updated: Option<i64>,
pub uploaded: Option<i64>,
pub downloaded: Option<i64>,
pub left: Option<i64>,
pub event: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PeerId {
pub id: Option<String>,
pub client: Option<String>,
}

pub struct StatisticsImporter {
database: Arc<Box<dyn Database>>,
api_client: Client,
tracker_service: Arc<Service>,
tracker_url: String,
}

impl StatisticsImporter {
pub async fn new(cfg: Arc<Configuration>, database: Arc<Box<dyn Database>>) -> Self {
pub async fn new(cfg: Arc<Configuration>, tracker_service: Arc<Service>, database: Arc<Box<dyn Database>>) -> Self {
let settings = cfg.settings.read().await;
let api_client = Client::new(ConnectionInfo::new(
settings.tracker.api_url.clone(),
settings.tracker.token.clone(),
));
let tracker_url = settings.tracker.url.clone();
drop(settings);
Self {
database,
api_client,
tracker_service,
tracker_url,
}
}
Expand Down Expand Up @@ -102,13 +67,7 @@ impl StatisticsImporter {
/// Will return an error if the HTTP request failed or the torrent is not
/// found.
pub async fn import_torrent_statistics(&self, torrent_id: i64, info_hash: &str) -> Result<TorrentInfo, ServiceError> {
let response = self
.api_client
.get_torrent_info(info_hash)
.await
.map_err(|_| ServiceError::InternalServerError)?;

if let Ok(torrent_info) = response.json::<TorrentInfo>().await {
if let Ok(torrent_info) = self.tracker_service.get_torrent_info(info_hash).await {
let _ = self
.database
.update_tracker_info(torrent_id, &self.tracker_url, torrent_info.seeders, torrent_info.leechers)
Expand Down

0 comments on commit 404caee

Please sign in to comment.