1- use std:: fmt;
1+ use std:: process:: { Command as ProcessCommand , Stdio } ;
2+ use std:: { fmt, fs} ;
23
3- use reqwest:: blocking:: multipart;
4+ use reqwest:: blocking:: multipart:: Form ;
5+ use reqwest:: blocking:: Client ;
6+ use reqwest:: Error ;
47use serde:: { Deserialize , Serialize } ;
58use serde_with:: { serde_as, DefaultOnNull } ;
69use tabled:: Tabled ;
710
11+ use crate :: put;
12+
813#[ derive( Debug , Serialize , Deserialize ) ]
914pub struct FileSize ( u64 ) ;
1015
@@ -33,18 +38,15 @@ pub struct FilesResponse {
3338}
3439
3540/// Returns the user's files.
36- pub fn list (
37- api_token : String ,
38- parent_id : u32 ,
39- ) -> Result < FilesResponse , Box < dyn std:: error:: Error > > {
40- let client = reqwest:: blocking:: Client :: new ( ) ;
41+ pub fn list ( client : & Client , api_token : & String , parent_id : u32 ) -> Result < FilesResponse , Error > {
4142 let response: FilesResponse = client
4243 . get ( format ! (
4344 "https://api.put.io/v2/files/list?parent_id={parent_id}"
4445 ) )
45- . header ( "authorization" , format ! ( "Bearer {}" , api_token ) )
46+ . header ( "authorization" , format ! ( "Bearer {api_token}" ) )
4647 . send ( ) ?
4748 . json ( ) ?;
49+
4850 Ok ( response)
4951}
5052
@@ -56,26 +58,27 @@ pub struct SearchResponse {
5658
5759/// Searches files for given keyword.
5860pub fn search (
59- api_token : String ,
60- query : String ,
61- ) -> Result < SearchResponse , Box < dyn std :: error :: Error > > {
62- let client = reqwest :: blocking :: Client :: new ( ) ;
61+ client : & Client ,
62+ api_token : & String ,
63+ query : & String ,
64+ ) -> Result < SearchResponse , Error > {
6365 let response: SearchResponse = client
6466 . get ( format ! ( "https://api.put.io/v2/files/search?query={query}" ) )
65- . header ( "authorization" , format ! ( "Bearer {}" , api_token ) )
67+ . header ( "authorization" , format ! ( "Bearer {api_token}" ) )
6668 . send ( ) ?
6769 . json ( ) ?;
70+
6871 Ok ( response)
6972}
7073
7174/// Delete file(s)
72- pub fn delete ( api_token : String , file_id : String ) -> Result < ( ) , Box < dyn std :: error :: Error > > {
73- let client = reqwest :: blocking :: Client :: new ( ) ;
74- let form = multipart :: Form :: new ( ) . text ( "file_ids" , file_id ) ;
75+ pub fn delete ( client : & Client , api_token : & String , file_id : & str ) -> Result < ( ) , Error > {
76+ let form : Form = Form :: new ( ) . text ( "file_ids" , file_id . to_owned ( ) ) ;
77+
7578 client
7679 . post ( "https://api.put.io/v2/files/delete" )
7780 . multipart ( form)
78- . header ( "authorization" , format ! ( "Bearer {}" , api_token ) )
81+ . header ( "authorization" , format ! ( "Bearer {api_token}" ) )
7982 . send ( ) ?;
8083
8184 Ok ( ( ) )
@@ -87,70 +90,72 @@ pub struct UrlResponse {
8790}
8891
8992/// Returns a download URL for a given file.
90- pub fn url ( api_token : String , file_id : u32 ) -> Result < UrlResponse , Box < dyn std:: error:: Error > > {
91- let client = reqwest:: blocking:: Client :: new ( ) ;
93+ pub fn url ( client : & Client , api_token : & String , file_id : u32 ) -> Result < UrlResponse , Error > {
9294 let response: UrlResponse = client
9395 . get ( format ! ( "https://api.put.io/v2/files/{file_id}/url" ) )
94- . header ( "authorization" , format ! ( "Bearer {}" , api_token ) )
96+ . header ( "authorization" , format ! ( "Bearer {api_token}" ) )
9597 . send ( ) ?
9698 . json ( ) ?;
99+
97100 Ok ( response)
98101}
99102
100103/// Moves a file to a different parent
101104pub fn mv (
102- api_token : String ,
103- file_id : String ,
105+ client : & Client ,
106+ api_token : & String ,
107+ file_id : u32 ,
104108 new_parent_id : u32 ,
105- ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
106- let client = reqwest:: blocking:: Client :: new ( ) ;
107- let form = multipart:: Form :: new ( )
108- . text ( "file_ids" , file_id)
109+ ) -> Result < ( ) , Error > {
110+ let form: Form = Form :: new ( )
111+ . text ( "file_ids" , file_id. to_string ( ) )
109112 . text ( "parent_id" , new_parent_id. to_string ( ) ) ;
113+
110114 client
111115 . post ( "https://api.put.io/v2/files/move" )
112116 . multipart ( form)
113- . header ( "authorization" , format ! ( "Bearer {}" , api_token ) )
117+ . header ( "authorization" , format ! ( "Bearer {api_token}" ) )
114118 . send ( ) ?;
115119
116120 Ok ( ( ) )
117121}
118122
119123/// Renames a file
120124pub fn rename (
121- api_token : String ,
125+ client : & Client ,
126+ api_token : & String ,
122127 file_id : u32 ,
123- new_name : String ,
124- ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
125- let client = reqwest:: blocking:: Client :: new ( ) ;
126- let form = multipart:: Form :: new ( )
128+ new_name : & String ,
129+ ) -> Result < ( ) , Error > {
130+ let form = Form :: new ( )
127131 . text ( "file_id" , file_id. to_string ( ) )
128- . text ( "name" , new_name) ;
132+ . text ( "name" , new_name. to_owned ( ) ) ;
133+
129134 client
130135 . post ( "https://api.put.io/v2/files/rename" )
131136 . multipart ( form)
132- . header ( "authorization" , format ! ( "Bearer {}" , api_token ) )
137+ . header ( "authorization" , format ! ( "Bearer {api_token}" ) )
133138 . send ( ) ?;
134139
135140 Ok ( ( ) )
136141}
137142
138143/// Extracts ZIP and RAR archives
139- pub fn extract ( api_token : String , file_id : String ) -> Result < ( ) , Box < dyn std :: error :: Error > > {
140- let client = reqwest :: blocking :: Client :: new ( ) ;
141- let form = multipart :: Form :: new ( ) . text ( "file_ids" , file_id ) ;
144+ pub fn extract ( client : & Client , api_token : & String , file_id : u32 ) -> Result < ( ) , Error > {
145+ let form : Form = Form :: new ( ) . text ( "file_ids" , file_id . to_string ( ) ) ;
146+
142147 client
143148 . post ( "https://api.put.io/v2/files/extract" )
144149 . multipart ( form)
145- . header ( "authorization" , format ! ( "Bearer {}" , api_token ) )
150+ . header ( "authorization" , format ! ( "Bearer {api_token}" ) )
146151 . send ( ) ?;
147152
148153 Ok ( ( ) )
149154}
150155
151156#[ derive( Debug , Serialize , Deserialize , Tabled ) ]
152157pub struct Extraction {
153- pub id : u32 ,
158+ pub id : String ,
154159 pub name : String ,
155160 pub status : String ,
156161 pub message : String ,
@@ -162,14 +167,108 @@ pub struct ExtractionResponse {
162167}
163168
164169/// Returns active extractions
165- pub fn get_extractions (
166- api_token : String ,
167- ) -> Result < ExtractionResponse , Box < dyn std:: error:: Error > > {
168- let client = reqwest:: blocking:: Client :: new ( ) ;
170+ pub fn get_extractions ( client : & Client , api_token : & String ) -> Result < ExtractionResponse , Error > {
169171 let response: ExtractionResponse = client
170172 . get ( "https://api.put.io/v2/files/extract" )
171- . header ( "authorization" , format ! ( "Bearer {}" , api_token ) )
173+ . header ( "authorization" , format ! ( "Bearer {api_token}" ) )
172174 . send ( ) ?
173175 . json ( ) ?;
176+
174177 Ok ( response)
175178}
179+
180+ // Downloads a file or folder
181+ pub fn download (
182+ client : & Client ,
183+ api_token : & String ,
184+ file_id : u32 ,
185+ recursive : bool ,
186+ path : Option < & String > ,
187+ ) -> Result < ( ) , Error > {
188+ let files: FilesResponse =
189+ put:: files:: list ( client, api_token, file_id) . expect ( "querying files" ) ;
190+
191+ match files. parent . file_type . as_str ( ) {
192+ "FOLDER" => {
193+ // ID is for a folder
194+ match recursive {
195+ true => {
196+ // Recursively download the folder
197+ let directory_path: String = match path {
198+ Some ( p) => format ! ( "{}/{}" , p, files. parent. name) , // Use the provided path if there is one
199+ None => format ! ( "./{}" , files. parent. name) ,
200+ } ;
201+
202+ fs:: create_dir_all ( directory_path. clone ( ) ) . expect ( "creating directory" ) ;
203+
204+ for file in files. files {
205+ download ( client, api_token, file. id , true , Some ( & directory_path) )
206+ . expect ( "downloading file recursively" ) ;
207+ }
208+ }
209+ false => {
210+ // Create a ZIP
211+ println ! ( "Creating ZIP..." ) ;
212+
213+ let zip_url: String = put:: zips:: create ( client, api_token, files. parent . id )
214+ . expect ( "creating zip job" ) ;
215+
216+ println ! ( "ZIP created!" ) ;
217+
218+ let output_path: String = match path {
219+ Some ( p) => format ! ( "{}/{}.zip" , p, files. parent. name) ,
220+ None => format ! ( "./{}.zip" , files. parent. name) ,
221+ } ;
222+
223+ println ! ( "Downloading: {}" , files. parent. name) ;
224+ println ! ( "Saving to: {}\n " , output_path) ;
225+
226+ // https://rust-lang-nursery.github.io/rust-cookbook/os/external.html#redirect-both-stdout-and-stderr-of-child-process-to-the-same-file
227+ ProcessCommand :: new ( "curl" )
228+ . arg ( "-C" )
229+ . arg ( "-" )
230+ . arg ( "-o" )
231+ . arg ( output_path)
232+ . arg ( zip_url)
233+ . stdout ( Stdio :: piped ( ) )
234+ . spawn ( )
235+ . expect ( "failed to run CURL command" )
236+ . wait_with_output ( )
237+ . expect ( "failed to run CURL command" ) ;
238+
239+ println ! ( "\n Download finished!\n " )
240+ }
241+ }
242+ }
243+ _ => {
244+ // ID is for a file
245+ let url_response: UrlResponse =
246+ put:: files:: url ( client, api_token, file_id) . expect ( "creating download URL" ) ;
247+
248+ let output_path: String = match path {
249+ Some ( p) => format ! ( "{}/{}" , p, files. parent. name) ,
250+ None => format ! ( "./{}" , files. parent. name) ,
251+ } ;
252+
253+ println ! ( "Downloading: {}" , files. parent. name) ;
254+ println ! ( "Saving to: {}\n " , output_path) ;
255+
256+ // https://rust-lang-nursery.github.io/rust-cookbook/os/external.html#redirect-both-stdout-and-stderr-of-child-process-to-the-same-file
257+ ProcessCommand :: new ( "curl" )
258+ . arg ( "-C" )
259+ . arg ( "-" )
260+ . arg ( "-o" )
261+ . arg ( output_path)
262+ . arg ( url_response. url )
263+ . stdout ( Stdio :: piped ( ) )
264+ . spawn ( )
265+ . expect ( "error while spawning curl" )
266+ . wait_with_output ( )
267+ . expect ( "running CURL command" ) ;
268+
269+ println ! ( "\n Download finished!\n " )
270+ }
271+ }
272+
273+ Ok ( ( ) )
274+ }
0 commit comments