1- use std:: { path:: PathBuf , time:: Duration } ;
1+ use std:: {
2+ io:: { SeekFrom , Write } ,
3+ path:: PathBuf ,
4+ time:: { Duration , Instant } ,
5+ } ;
26
37use color_eyre:: { Result , eyre:: Context } ;
48use eyre:: eyre;
9+ use futures:: StreamExt ;
510use inquire:: { Password , Text } ;
611use itertools:: Itertools ;
712use reqwest:: Url ;
13+ use tokio:: {
14+ fs:: File ,
15+ io:: { AsyncReadExt , AsyncSeekExt , AsyncWriteExt , BufReader } ,
16+ } ;
817
918use crate :: {
1019 cscs:: {
@@ -14,7 +23,6 @@ use crate::{
1423 cscs_job_log, cscs_login, cscs_start_job, cscs_system_list, cscs_system_set,
1524 } ,
1625 } ,
17- trace_dbg,
1826 util:: types:: DockerImageUrl ,
1927} ;
2028
@@ -123,8 +131,8 @@ pub(crate) async fn cli_cscs_file_list(path: PathBuf) -> Result<()> {
123131 }
124132}
125133
126- pub ( crate ) async fn cli_cscs_file_download ( remote : PathBuf , local : PathBuf ) -> Result < ( ) > {
127- match cscs_file_download ( remote, local. clone ( ) ) . await {
134+ pub ( crate ) async fn cli_cscs_file_download ( remote : PathBuf , local : PathBuf , account : Option < String > ) -> Result < ( ) > {
135+ match cscs_file_download ( remote, local. clone ( ) , account ) . await {
128136 Ok ( None ) => {
129137 println ! ( "File successfully downloaded" ) ;
130138 Ok ( ( ) )
@@ -148,44 +156,91 @@ pub(crate) async fn cli_cscs_file_download(remote: PathBuf, local: PathBuf) -> R
148156
149157 // download from s3
150158 println ! ( "Downloading file from s3, this might take a while" ) ;
151- let credentials = s3:: creds:: Credentials :: default ( ) ?;
152- let url = Url :: parse ( & job_data. 1 ) ?;
153- let region = s3:: region:: Region :: Custom {
154- region : "cscs-zonegroup" . to_owned ( ) ,
155- endpoint : "https://rgw.cscs.ch" . to_owned ( ) ,
156- } ;
157- let mut segments = url. path_segments ( ) . unwrap ( ) ;
158- let bucket_name = segments. next ( ) . unwrap ( ) ;
159- let path = segments. join ( "/" ) ;
160- let bucket = s3:: bucket:: Bucket :: create_with_path_style (
161- bucket_name,
162- region,
163- credentials,
164- s3:: BucketConfiguration :: default ( ) ,
165- )
166- . await ?
167- . bucket ;
168- let mut async_output_file = tokio:: fs:: File :: create ( & local) . await . expect ( "Unable to create file" ) ;
169- let status = bucket. get_object_to_writer ( path, & mut async_output_file) . await ?;
170- println ! ( "download finished with status {}" , status) ;
159+
160+ let mut output = File :: create ( local) . await ?;
161+ let mut stream = reqwest:: get ( job_data. 1 ) . await ?. bytes_stream ( ) ;
162+ let mut progress = 0 ;
163+ let mut start_time = Instant :: now ( ) ;
164+ while let Some ( chunk_result) = stream. next ( ) . await {
165+ let chunk = chunk_result?;
166+ output. write_all ( & chunk) . await ?;
167+ progress += chunk. len ( ) ;
168+
169+ if start_time. elapsed ( ) >= Duration :: from_secs ( 1 ) {
170+ print ! ( "\r Downloaded {}/{}Mb" , progress / 1024 / 1024 , job_data. 2 / 1024 / 1024 ) ;
171+ std:: io:: stdout ( ) . flush ( ) ?;
172+ start_time = Instant :: now ( ) ;
173+ }
174+ }
175+ output. flush ( ) . await ?;
176+ println ! ( ) ; //force newline
177+ println ! ( "Download complete" ) ;
171178
172179 Ok ( ( ) )
173180 }
174181 Err ( e) => Err ( e) ,
175182 }
176183}
177184
178- pub ( crate ) async fn cli_cscs_file_upload ( local : PathBuf , remote : PathBuf ) -> Result < ( ) > {
179- match cscs_file_upload ( local, remote) . await {
185+ pub ( crate ) async fn cli_cscs_file_upload ( local : PathBuf , remote : PathBuf , account : Option < String > ) -> Result < ( ) > {
186+ match cscs_file_upload ( local. clone ( ) , remote, account ) . await {
180187 Ok ( None ) => {
181188 println ! ( "File successfully uploaded" ) ;
182189 Ok ( ( ) )
183190 }
184191 Ok ( Some ( transfer_data) ) => {
185192 println ! ( "starting file transfer, this might take a while" ) ;
186- let transfer_data = trace_dbg ! ( transfer_data) ;
187- Ok ( ( ) )
193+ let mut etags: Vec < String > = Vec :: new ( ) ;
194+ let client = reqwest:: Client :: new ( ) ;
195+ let num_parts = transfer_data. 1 . num_parts ;
196+ for ( chunk_id, transfer_url) in transfer_data. 1 . parts_upload_urls . into_iter ( ) . enumerate ( ) {
197+ println ! (
198+ "Uploading part {}/{} ({}Mb)" ,
199+ chunk_id + 1 ,
200+ num_parts,
201+ transfer_data. 1 . part_size / 1024 / 1024
202+ ) ;
203+ let etag = upload_chunk (
204+ local. clone ( ) ,
205+ ( chunk_id as u64 ) * transfer_data. 1 . part_size ,
206+ transfer_data. 1 . part_size ,
207+ transfer_url,
208+ )
209+ . await ?;
210+ etags. push ( etag) ;
211+ }
212+
213+ let body = etags
214+ . into_iter ( )
215+ . enumerate ( )
216+ . map ( |( i, etag) | ( i + 1 , etag) )
217+ . map ( |( i, etag) | format ! ( "<Part><PartNumber>{}</PartNumber><ETag>{}</ETag></Part>" , i, etag) )
218+ . join ( "" ) ;
219+ let body = format ! ( "<CompleteMultipartUpload>{}</CompleteMultipartUpload>" , body) ;
220+ let req = client. post ( transfer_data. 1 . complete_upload_url ) . body ( body) . build ( ) ?;
221+ let resp = client. execute ( req) . await ?;
222+ match resp. error_for_status ( ) {
223+ Ok ( _) => {
224+ println ! ( "done" ) ;
225+ Ok ( ( ) )
226+ }
227+ Err ( e) => Err ( e) . wrap_err ( "failed to complete upload" ) ,
228+ }
188229 }
189230 Err ( e) => Err ( e) ,
190231 }
191232}
233+
234+ async fn upload_chunk ( path : PathBuf , offset : u64 , size : u64 , url : Url ) -> Result < String > {
235+ let client = reqwest:: Client :: new ( ) ;
236+
237+ let source_file = File :: open ( path) . await ?;
238+ let mut buf = vec ! [ ] ;
239+ let mut reader = BufReader :: new ( source_file) ;
240+ reader. seek ( SeekFrom :: Start ( offset) ) . await ?;
241+ let mut chunk = reader. take ( size) ;
242+ chunk. read_to_end ( & mut buf) . await ?;
243+ let req = client. put ( url) . body ( buf) . build ( ) ?;
244+ let resp = client. execute ( req) . await ?;
245+ Ok ( resp. headers ( ) [ "etag" ] . to_str ( ) ?. to_owned ( ) )
246+ }
0 commit comments