11use crate :: { Crate , Status , Tool , Version } ;
2- use aws_sdk_s3:: client:: fluent_builders:: PutObject ;
3- use aws_sdk_s3:: model:: Object ;
2+ use aws_sdk_s3:: model:: { CompletedMultipartUpload , CompletedPart , Object } ;
43use backoff:: Error ;
54use backoff:: ExponentialBackoff ;
65use color_eyre:: Result ;
@@ -16,6 +15,8 @@ pub struct Client {
1615 tool : Tool ,
1716}
1817
18+ const CHUNK_SIZE : usize = 5 * 1024 * 1024 ;
19+
1920impl Client {
2021 pub async fn new ( tool : Tool , bucket : & str ) -> Result < Self > {
2122 let config = aws_config:: load_from_env ( ) . await ;
@@ -31,22 +32,75 @@ impl Client {
3132 self . tool
3233 }
3334
34- pub fn put_object ( & self ) -> PutObject {
35- self . inner . put_object ( ) . bucket ( & self . bucket )
35+ pub async fn upload ( & self , key : & str , data : & [ u8 ] , content_type : & str ) -> Result < ( ) > {
36+ retry ( || self . _upload ( key , data , content_type ) ) . await
3637 }
3738
38- pub async fn upload_raw ( & self , krate : & Crate , data : Vec < u8 > ) -> Result < ( ) > {
39- retry ( || {
40- self . put_object ( )
41- . key ( self . tool . raw_crate_path ( krate) )
42- . body ( data. clone ( ) . into ( ) )
43- . content_type ( "text/plain" )
39+ async fn _upload ( & self , key : & str , data : & [ u8 ] , content_type : & str ) -> Result < ( ) > {
40+ // S3 has a minimum multipart upload size of 5 MB. If we are below that, we need to use
41+ // PutObject.
42+ if data. len ( ) < CHUNK_SIZE {
43+ self . inner
44+ . put_object ( )
45+ . bucket ( & self . bucket )
46+ . key ( key)
47+ . body ( data. to_vec ( ) . into ( ) )
48+ . content_type ( content_type)
4449 . send ( )
45- } )
46- . await ?;
50+ . await ?;
51+ return Ok ( ( ) ) ;
52+ }
53+
54+ let res = self
55+ . inner
56+ . create_multipart_upload ( )
57+ . bucket ( & self . bucket )
58+ . key ( key)
59+ . content_type ( content_type)
60+ . send ( )
61+ . await ?;
62+ let upload_id = res. upload_id ( ) . unwrap ( ) ;
63+ let mut parts = Vec :: new ( ) ;
64+ for ( part_number, chunk) in data. chunks ( CHUNK_SIZE ) . enumerate ( ) {
65+ // part numbers must start at 1
66+ let part_number = part_number as i32 + 1 ;
67+ let upload_part_res = self
68+ . inner
69+ . upload_part ( )
70+ . key ( key)
71+ . bucket ( & self . bucket )
72+ . upload_id ( upload_id)
73+ . body ( chunk. to_vec ( ) . into ( ) )
74+ . part_number ( part_number)
75+ . send ( )
76+ . await ?;
77+ parts. push (
78+ CompletedPart :: builder ( )
79+ . e_tag ( upload_part_res. e_tag . unwrap_or_default ( ) )
80+ . part_number ( part_number)
81+ . build ( ) ,
82+ )
83+ }
84+ let completed_multipart_upload = CompletedMultipartUpload :: builder ( )
85+ . set_parts ( Some ( parts) )
86+ . build ( ) ;
87+ self . inner
88+ . complete_multipart_upload ( )
89+ . bucket ( & self . bucket )
90+ . key ( key)
91+ . multipart_upload ( completed_multipart_upload)
92+ . upload_id ( upload_id)
93+ . send ( )
94+ . await ?;
95+
4796 Ok ( ( ) )
4897 }
4998
99+ pub async fn upload_raw ( & self , krate : & Crate , data : Vec < u8 > ) -> Result < ( ) > {
100+ self . upload ( & self . tool . raw_crate_path ( krate) , & data, "text/plain" )
101+ . await
102+ }
103+
50104 pub async fn download_raw ( & self , krate : & Crate ) -> Result < Vec < u8 > > {
51105 retry ( || self . _download_raw ( krate) ) . await
52106 }
@@ -69,17 +123,8 @@ impl Client {
69123 }
70124
71125 pub async fn upload_html ( & self , krate : & Crate , data : Vec < u8 > ) -> Result < ( ) > {
72- retry ( move || {
73- self . inner
74- . put_object ( )
75- . bucket ( & self . bucket )
76- . key ( self . tool . rendered_crate_path ( krate) )
77- . body ( data. clone ( ) . into ( ) )
78- . content_type ( "text/html" )
79- . send ( )
80- } )
81- . await ?;
82- Ok ( ( ) )
126+ let key = self . tool . rendered_crate_path ( krate) ;
127+ retry ( || self . upload ( & key, & data, "text/html" ) ) . await
83128 }
84129
85130 pub async fn get_crate_downloads ( & self ) -> Result < HashMap < String , Option < u64 > > > {
@@ -174,17 +219,8 @@ impl Client {
174219 }
175220
176221 pub async fn upload_landing_page ( & self , data : Vec < u8 > ) -> Result < ( ) > {
177- retry ( move || {
178- self . inner
179- . put_object ( )
180- . bucket ( & self . bucket )
181- . key ( self . tool . landing_page_path ( ) )
182- . body ( data. clone ( ) . into ( ) )
183- . content_type ( "text/html" )
184- . send ( )
185- } )
186- . await ?;
187- Ok ( ( ) )
222+ self . upload ( self . tool . landing_page_path ( ) , & data, "text/html" )
223+ . await
188224 }
189225
190226 pub async fn list_db ( & self ) -> Result < Option < Object > > {
0 commit comments