torrust_index_backend/tracker/
service.rs

1use std::sync::Arc;
2
3use hyper::StatusCode;
4use log::error;
5use serde::{Deserialize, Serialize};
6
7use super::api::{Client, ConnectionInfo};
8use crate::config::Configuration;
9use crate::databases::database::Database;
10use crate::errors::ServiceError;
11use crate::models::tracker_key::TrackerKey;
12use crate::models::user::UserId;
13
14#[derive(Debug, Serialize, Deserialize)]
15pub struct TorrentInfo {
16    pub info_hash: String,
17    pub seeders: i64,
18    pub completed: i64,
19    pub leechers: i64,
20    pub peers: Vec<Peer>,
21}
22
23#[derive(Debug, Serialize, Deserialize)]
24pub struct Peer {
25    pub peer_id: Option<PeerId>,
26    pub peer_addr: Option<String>,
27    pub updated: Option<i64>,
28    pub uploaded: Option<i64>,
29    pub downloaded: Option<i64>,
30    pub left: Option<i64>,
31    pub event: Option<String>,
32}
33
34#[derive(Debug, Serialize, Deserialize)]
35pub struct PeerId {
36    pub id: Option<String>,
37    pub client: Option<String>,
38}
39
40pub struct Service {
41    database: Arc<Box<dyn Database>>,
42    api_client: Client,
43    token_valid_seconds: u64,
44    tracker_url: String,
45}
46
47impl Service {
48    pub async fn new(cfg: Arc<Configuration>, database: Arc<Box<dyn Database>>) -> Service {
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 token_valid_seconds = settings.tracker.token_valid_seconds;
55        let tracker_url = settings.tracker.url.clone();
56        drop(settings);
57        Service {
58            database,
59            api_client,
60            token_valid_seconds,
61            tracker_url,
62        }
63    }
64
65    /// Add a torrent to the tracker whitelist.
66    ///
67    /// # Errors
68    ///
69    /// Will return an error if the HTTP request failed (for example if the
70    /// tracker API is offline) or if the tracker API returned an error.
71    pub async fn whitelist_info_hash(&self, info_hash: String) -> Result<(), ServiceError> {
72        let response = self.api_client.whitelist_torrent(&info_hash).await;
73
74        match response {
75            Ok(response) => {
76                if response.status().is_success() {
77                    Ok(())
78                } else {
79                    Err(ServiceError::WhitelistingError)
80                }
81            }
82            Err(_) => Err(ServiceError::TrackerOffline),
83        }
84    }
85
86    /// Remove a torrent from the tracker whitelist.
87    ///
88    /// # Errors
89    ///
90    /// Will return an error if the HTTP request failed (for example if the
91    /// tracker API is offline) or if the tracker API returned an error.
92    pub async fn remove_info_hash_from_whitelist(&self, info_hash: String) -> Result<(), ServiceError> {
93        let response = self.api_client.remove_torrent_from_whitelist(&info_hash).await;
94
95        match response {
96            Ok(response) => {
97                if response.status().is_success() {
98                    Ok(())
99                } else {
100                    Err(ServiceError::InternalServerError)
101                }
102            }
103            Err(_) => Err(ServiceError::InternalServerError),
104        }
105    }
106
107    /// Get personal tracker announce url of a user.
108    ///
109    /// Eg: <https://tracker:7070/USER_TRACKER_KEY>
110    ///
111    /// If the user doesn't have a not expired tracker key, it will generate a
112    /// new one and save it in the database.
113    ///
114    /// # Errors
115    ///
116    /// Will return an error if the HTTP request to get generated a new
117    /// user tracker key failed.
118    pub async fn get_personal_announce_url(&self, user_id: UserId) -> Result<String, ServiceError> {
119        let tracker_key = self.database.get_user_tracker_key(user_id).await;
120
121        match tracker_key {
122            Some(v) => Ok(self.announce_url_with_key(&v)),
123            None => match self.retrieve_new_tracker_key(user_id).await {
124                Ok(v) => Ok(self.announce_url_with_key(&v)),
125                Err(_) => Err(ServiceError::TrackerOffline),
126            },
127        }
128    }
129
130    /// Get torrent info from tracker.
131    ///
132    /// # Errors
133    ///
134    /// Will return an error if the HTTP request to get torrent info fails or
135    /// if the response cannot be parsed.
136    pub async fn get_torrent_info(&self, info_hash: &str) -> Result<TorrentInfo, ServiceError> {
137        let response = self
138            .api_client
139            .get_torrent_info(info_hash)
140            .await
141            .map_err(|_| ServiceError::InternalServerError)?;
142
143        if response.status() == StatusCode::NOT_FOUND {
144            return Err(ServiceError::TorrentNotFound);
145        }
146
147        let body = response.text().await;
148
149        if let Ok(body) = body {
150            let torrent_info = serde_json::from_str(&body);
151
152            if let Ok(torrent_info) = torrent_info {
153                Ok(torrent_info)
154            } else {
155                error!("Failed to parse torrent info from tracker response");
156                Err(ServiceError::InternalServerError)
157            }
158        } else {
159            error!("Tracker API response without body");
160            Err(ServiceError::InternalServerError)
161        }
162    }
163
164    /// It builds the announce url appending the user tracker key.
165    /// Eg: <https://tracker:7070/USER_TRACKER_KEY>
166    fn announce_url_with_key(&self, tracker_key: &TrackerKey) -> String {
167        format!("{}/{}", self.tracker_url, tracker_key.key)
168    }
169
170    /// Issue a new tracker key from tracker and save it in database,
171    /// tied to a user
172    async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result<TrackerKey, ServiceError> {
173        // Request new tracker key from tracker
174        let response = self
175            .api_client
176            .retrieve_new_tracker_key(self.token_valid_seconds)
177            .await
178            .map_err(|_| ServiceError::InternalServerError)?;
179
180        // Parse tracker key from response
181        let tracker_key = response
182            .json::<TrackerKey>()
183            .await
184            .map_err(|_| ServiceError::InternalServerError)?;
185
186        // Add tracker key to database (tied to a user)
187        self.database.add_tracker_key(user_id, &tracker_key).await?;
188
189        // return tracker key
190        Ok(tracker_key)
191    }
192}