torrust_index_backend/tracker/
service.rs1use 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 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 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 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 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 fn announce_url_with_key(&self, tracker_key: &TrackerKey) -> String {
167 format!("{}/{}", self.tracker_url, tracker_key.key)
168 }
169
170 async fn retrieve_new_tracker_key(&self, user_id: i64) -> Result<TrackerKey, ServiceError> {
173 let response = self
175 .api_client
176 .retrieve_new_tracker_key(self.token_valid_seconds)
177 .await
178 .map_err(|_| ServiceError::InternalServerError)?;
179
180 let tracker_key = response
182 .json::<TrackerKey>()
183 .await
184 .map_err(|_| ServiceError::InternalServerError)?;
185
186 self.database.add_tracker_key(user_id, &tracker_key).await?;
188
189 Ok(tracker_key)
191 }
192}