@@ -4,14 +4,15 @@ use std::fmt;
44use std:: str:: FromStr ;
55use std:: sync:: Arc ;
66
7- use axum:: extract:: { Path , Query , State } ;
8- use axum:: response:: { IntoResponse , Json , Response } ;
7+ use axum:: extract:: { Path , State } ;
8+ use axum:: response:: { IntoResponse , Response } ;
9+ use axum_extra:: extract:: Query ;
910use log:: debug;
1011use serde:: { de, Deserialize , Deserializer } ;
12+ use thiserror:: Error ;
1113
12- use super :: resources:: torrent:: ListItem ;
1314use super :: responses:: { torrent_info_response, torrent_list_response, torrent_not_known_response} ;
14- use crate :: core:: services:: torrent:: { get_torrent_info, get_torrents, Pagination } ;
15+ use crate :: core:: services:: torrent:: { get_torrent_info, get_torrents, get_torrents_page , Pagination } ;
1516use crate :: core:: Tracker ;
1617use crate :: servers:: apis:: v1:: responses:: invalid_info_hash_param_response;
1718use crate :: servers:: apis:: InfoHashParam ;
@@ -36,16 +37,36 @@ pub async fn get_torrent_handler(State(tracker): State<Arc<Tracker>>, Path(info_
3637 }
3738}
3839
39- /// A container for the optional URL query pagination parameters:
40- /// `offset` and `limit`.
40+ /// A container for the URL query parameters.
41+ ///
42+ /// Pagination: `offset` and `limit`.
43+ /// Array of infohashes: `info_hash`.
44+ ///
45+ /// You can either get all torrents with pagination or get a list of torrents
46+ /// providing a list of infohashes. For example:
47+ ///
48+ /// First page of torrents:
49+ ///
50+ /// <http://127.0.0.1:1212/api/v1/torrents?token=MyAccessToken>
51+ ///
52+ ///
53+ /// Only two torrents:
54+ ///
55+ /// <http://127.0.0.1:1212/api/v1/torrents?token=MyAccessToken&info_hash=9c38422213e30bff212b30c360d26f9a02136422&info_hash=2b66980093bc11806fab50cb3cb41835b95a0362>
56+ ///
57+ ///
58+ /// NOTICE: Pagination is ignored if array of infohashes is provided.
4159#[ derive( Deserialize , Debug ) ]
42- pub struct PaginationParams {
60+ pub struct QueryParams {
4361 /// The offset of the first page to return. Starts at 0.
4462 #[ serde( default , deserialize_with = "empty_string_as_none" ) ]
4563 pub offset : Option < u32 > ,
46- /// The maximum number of items to return per page
64+ /// The maximum number of items to return per page.
4765 #[ serde( default , deserialize_with = "empty_string_as_none" ) ]
4866 pub limit : Option < u32 > ,
67+ /// A list of infohashes to retrieve.
68+ #[ serde( default , rename = "info_hash" ) ]
69+ pub info_hashes : Vec < String > ,
4970}
5071
5172/// It handles the request to get a list of torrents.
@@ -56,19 +77,49 @@ pub struct PaginationParams {
5677///
5778/// Refer to the [API endpoint documentation](crate::servers::apis::v1::context::torrent#list-torrents)
5879/// for more information about this endpoint.
59- pub async fn get_torrents_handler (
60- State ( tracker) : State < Arc < Tracker > > ,
61- pagination : Query < PaginationParams > ,
62- ) -> Json < Vec < ListItem > > {
80+ pub async fn get_torrents_handler ( State ( tracker) : State < Arc < Tracker > > , pagination : Query < QueryParams > ) -> Response {
6381 debug ! ( "pagination: {:?}" , pagination) ;
6482
65- torrent_list_response (
66- & get_torrents (
67- tracker. clone ( ) ,
68- & Pagination :: new_with_options ( pagination. 0 . offset , pagination. 0 . limit ) ,
83+ if pagination. 0 . info_hashes . is_empty ( ) {
84+ torrent_list_response (
85+ & get_torrents_page (
86+ tracker. clone ( ) ,
87+ & Pagination :: new_with_options ( pagination. 0 . offset , pagination. 0 . limit ) ,
88+ )
89+ . await ,
6990 )
70- . await ,
71- )
91+ . into_response ( )
92+ } else {
93+ match parse_info_hashes ( pagination. 0 . info_hashes ) {
94+ Ok ( info_hashes) => torrent_list_response ( & get_torrents ( tracker. clone ( ) , & info_hashes) . await ) . into_response ( ) ,
95+ Err ( err) => match err {
96+ QueryParamError :: InvalidInfoHash { info_hash } => invalid_info_hash_param_response ( & info_hash) ,
97+ } ,
98+ }
99+ }
100+ }
101+
102+ #[ derive( Error , Debug ) ]
103+ pub enum QueryParamError {
104+ #[ error( "invalid infohash {info_hash}" ) ]
105+ InvalidInfoHash { info_hash : String } ,
106+ }
107+
108+ fn parse_info_hashes ( info_hashes_str : Vec < String > ) -> Result < Vec < InfoHash > , QueryParamError > {
109+ let mut info_hashes: Vec < InfoHash > = Vec :: new ( ) ;
110+
111+ for info_hash_str in info_hashes_str {
112+ match InfoHash :: from_str ( & info_hash_str) {
113+ Ok ( info_hash) => info_hashes. push ( info_hash) ,
114+ Err ( _err) => {
115+ return Err ( QueryParamError :: InvalidInfoHash {
116+ info_hash : info_hash_str,
117+ } )
118+ }
119+ }
120+ }
121+
122+ Ok ( info_hashes)
72123}
73124
74125/// Serde deserialization decorator to map empty Strings to None,
0 commit comments