11use std:: sync:: Arc ;
22
33use log:: { error, info} ;
4+ use reqwest:: { Error , Response } ;
45use serde:: { Deserialize , Serialize } ;
56
67use crate :: config:: Configuration ;
@@ -35,148 +36,147 @@ pub struct PeerId {
3536}
3637
3738pub 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
4245impl 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