@@ -4,14 +4,15 @@ use std::fmt;
4
4
use std:: str:: FromStr ;
5
5
use std:: sync:: Arc ;
6
6
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 ;
9
10
use log:: debug;
10
11
use serde:: { de, Deserialize , Deserializer } ;
12
+ use thiserror:: Error ;
11
13
12
- use super :: resources:: torrent:: ListItem ;
13
14
use 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 } ;
15
16
use crate :: core:: Tracker ;
16
17
use crate :: servers:: apis:: v1:: responses:: invalid_info_hash_param_response;
17
18
use crate :: servers:: apis:: InfoHashParam ;
@@ -36,16 +37,36 @@ pub async fn get_torrent_handler(State(tracker): State<Arc<Tracker>>, Path(info_
36
37
}
37
38
}
38
39
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.
41
59
#[ derive( Deserialize , Debug ) ]
42
- pub struct PaginationParams {
60
+ pub struct QueryParams {
43
61
/// The offset of the first page to return. Starts at 0.
44
62
#[ serde( default , deserialize_with = "empty_string_as_none" ) ]
45
63
pub offset : Option < u32 > ,
46
- /// The maximum number of items to return per page
64
+ /// The maximum number of items to return per page.
47
65
#[ serde( default , deserialize_with = "empty_string_as_none" ) ]
48
66
pub limit : Option < u32 > ,
67
+ /// A list of infohashes to retrieve.
68
+ #[ serde( default , rename = "info_hash" ) ]
69
+ pub info_hashes : Vec < String > ,
49
70
}
50
71
51
72
/// It handles the request to get a list of torrents.
@@ -56,19 +77,49 @@ pub struct PaginationParams {
56
77
///
57
78
/// Refer to the [API endpoint documentation](crate::servers::apis::v1::context::torrent#list-torrents)
58
79
/// 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 {
63
81
debug ! ( "pagination: {:?}" , pagination) ;
64
82
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 ,
69
90
)
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)
72
123
}
73
124
74
125
/// Serde deserialization decorator to map empty Strings to None,
0 commit comments