17
17
18
18
//! An object store implementation for S3
19
19
//!
20
- //! ## Multi-part uploads
20
+ //! ## Multipart uploads
21
21
//!
22
- //! Multi-part uploads can be initiated with the [ObjectStore::put_multipart] method.
23
- //! Data passed to the writer is automatically buffered to meet the minimum size
24
- //! requirements for a part. Multiple parts are uploaded concurrently.
22
+ //! Multipart uploads can be initiated with the [ObjectStore::put_multipart] method.
25
23
//!
26
24
//! If the writer fails for any reason, you may have parts uploaded to AWS but not
27
- //! used that you may be charged for. Use the [ObjectStore::abort_multipart] method
28
- //! to abort the upload and drop those unneeded parts. In addition, you may wish to
29
- //! consider implementing [automatic cleanup] of unused parts that are older than one
30
- //! week.
25
+ //! used that you will be charged for. [`MultipartUpload::abort`] may be invoked to drop
26
+ //! these unneeded parts, however, it is recommended that you consider implementing
27
+ //! [automatic cleanup] of unused parts that are older than some threshold.
31
28
//!
32
29
//! [automatic cleanup]: https://aws.amazon.com/blogs/aws/s3-lifecycle-management-update-support-for-multipart-uploads-and-delete-markers/
33
30
@@ -38,18 +35,17 @@ use futures::{StreamExt, TryStreamExt};
38
35
use reqwest:: header:: { HeaderName , IF_MATCH , IF_NONE_MATCH } ;
39
36
use reqwest:: { Method , StatusCode } ;
40
37
use std:: { sync:: Arc , time:: Duration } ;
41
- use tokio:: io:: AsyncWrite ;
42
38
use url:: Url ;
43
39
44
40
use crate :: aws:: client:: { RequestError , S3Client } ;
45
41
use crate :: client:: get:: GetClientExt ;
46
42
use crate :: client:: list:: ListClientExt ;
47
43
use crate :: client:: CredentialProvider ;
48
- use crate :: multipart:: { MultiPartStore , PartId , PutPart , WriteMultiPart } ;
44
+ use crate :: multipart:: { MultipartStore , PartId } ;
49
45
use crate :: signer:: Signer ;
50
46
use crate :: {
51
- Error , GetOptions , GetResult , ListResult , MultipartId , ObjectMeta , ObjectStore , Path , PutMode ,
52
- PutOptions , PutResult , Result ,
47
+ Error , GetOptions , GetResult , ListResult , MultipartId , MultipartUpload , ObjectMeta ,
48
+ ObjectStore , Path , PutMode , PutOptions , PutResult , Result , UploadPart ,
53
49
} ;
54
50
55
51
static TAGS_HEADER : HeaderName = HeaderName :: from_static ( "x-amz-tagging" ) ;
@@ -85,6 +81,7 @@ const STORE: &str = "S3";
85
81
86
82
/// [`CredentialProvider`] for [`AmazonS3`]
87
83
pub type AwsCredentialProvider = Arc < dyn CredentialProvider < Credential = AwsCredential > > ;
84
+ use crate :: client:: parts:: Parts ;
88
85
pub use credential:: { AwsAuthorizer , AwsCredential } ;
89
86
90
87
/// Interface for [Amazon S3](https://aws.amazon.com/s3/).
@@ -211,25 +208,18 @@ impl ObjectStore for AmazonS3 {
211
208
}
212
209
}
213
210
214
- async fn put_multipart (
215
- & self ,
216
- location : & Path ,
217
- ) -> Result < ( MultipartId , Box < dyn AsyncWrite + Unpin + Send > ) > {
218
- let id = self . client . create_multipart ( location) . await ?;
219
-
220
- let upload = S3MultiPartUpload {
221
- location : location. clone ( ) ,
222
- upload_id : id. clone ( ) ,
223
- client : Arc :: clone ( & self . client ) ,
224
- } ;
225
-
226
- Ok ( ( id, Box :: new ( WriteMultiPart :: new ( upload, 8 ) ) ) )
227
- }
228
-
229
- async fn abort_multipart ( & self , location : & Path , multipart_id : & MultipartId ) -> Result < ( ) > {
230
- self . client
231
- . delete_request ( location, & [ ( "uploadId" , multipart_id) ] )
232
- . await
211
+ async fn put_multipart ( & self , location : & Path ) -> Result < Box < dyn MultipartUpload > > {
212
+ let upload_id = self . client . create_multipart ( location) . await ?;
213
+
214
+ Ok ( Box :: new ( S3MultiPartUpload {
215
+ part_idx : 0 ,
216
+ state : Arc :: new ( UploadState {
217
+ client : Arc :: clone ( & self . client ) ,
218
+ location : location. clone ( ) ,
219
+ upload_id : upload_id. clone ( ) ,
220
+ parts : Default :: default ( ) ,
221
+ } ) ,
222
+ } ) )
233
223
}
234
224
235
225
async fn get_opts ( & self , location : & Path , options : GetOptions ) -> Result < GetResult > {
@@ -319,30 +309,55 @@ impl ObjectStore for AmazonS3 {
319
309
}
320
310
}
321
311
312
+ #[ derive( Debug ) ]
322
313
struct S3MultiPartUpload {
314
+ part_idx : usize ,
315
+ state : Arc < UploadState > ,
316
+ }
317
+
318
+ #[ derive( Debug ) ]
319
+ struct UploadState {
320
+ parts : Parts ,
323
321
location : Path ,
324
322
upload_id : String ,
325
323
client : Arc < S3Client > ,
326
324
}
327
325
328
326
#[ async_trait]
329
- impl PutPart for S3MultiPartUpload {
330
- async fn put_part ( & self , buf : Vec < u8 > , part_idx : usize ) -> Result < PartId > {
331
- self . client
332
- . put_part ( & self . location , & self . upload_id , part_idx, buf. into ( ) )
327
+ impl MultipartUpload for S3MultiPartUpload {
328
+ fn put_part ( & mut self , data : Bytes ) -> UploadPart {
329
+ let idx = self . part_idx ;
330
+ self . part_idx += 1 ;
331
+ let state = Arc :: clone ( & self . state ) ;
332
+ Box :: pin ( async move {
333
+ let part = state
334
+ . client
335
+ . put_part ( & state. location , & state. upload_id , idx, data)
336
+ . await ?;
337
+ state. parts . put ( idx, part) ;
338
+ Ok ( ( ) )
339
+ } )
340
+ }
341
+
342
+ async fn complete ( & mut self ) -> Result < PutResult > {
343
+ let parts = self . state . parts . finish ( self . part_idx ) ?;
344
+
345
+ self . state
346
+ . client
347
+ . complete_multipart ( & self . state . location , & self . state . upload_id , parts)
333
348
. await
334
349
}
335
350
336
- async fn complete ( & self , completed_parts : Vec < PartId > ) -> Result < ( ) > {
337
- self . client
338
- . complete_multipart ( & self . location , & self . upload_id , completed_parts )
339
- . await ? ;
340
- Ok ( ( ) )
351
+ async fn abort ( & mut self ) -> Result < ( ) > {
352
+ self . state
353
+ . client
354
+ . delete_request ( & self . state . location , & [ ( "uploadId" , & self . state . upload_id ) ] )
355
+ . await
341
356
}
342
357
}
343
358
344
359
#[ async_trait]
345
- impl MultiPartStore for AmazonS3 {
360
+ impl MultipartStore for AmazonS3 {
346
361
async fn create_multipart ( & self , path : & Path ) -> Result < MultipartId > {
347
362
self . client . create_multipart ( path) . await
348
363
}
@@ -377,7 +392,6 @@ mod tests {
377
392
use crate :: { client:: get:: GetClient , tests:: * } ;
378
393
use bytes:: Bytes ;
379
394
use hyper:: HeaderMap ;
380
- use tokio:: io:: AsyncWriteExt ;
381
395
382
396
const NON_EXISTENT_NAME : & str = "nonexistentname" ;
383
397
@@ -542,9 +556,9 @@ mod tests {
542
556
store. put ( & locations[ 0 ] , data. clone ( ) ) . await . unwrap ( ) ;
543
557
store. copy ( & locations[ 0 ] , & locations[ 1 ] ) . await . unwrap ( ) ;
544
558
545
- let ( _ , mut writer ) = store. put_multipart ( & locations[ 2 ] ) . await . unwrap ( ) ;
546
- writer . write_all ( & data) . await . unwrap ( ) ;
547
- writer . shutdown ( ) . await . unwrap ( ) ;
559
+ let mut upload = store. put_multipart ( & locations[ 2 ] ) . await . unwrap ( ) ;
560
+ upload . put_part ( data. clone ( ) ) . await . unwrap ( ) ;
561
+ upload . complete ( ) . await . unwrap ( ) ;
548
562
549
563
for location in & locations {
550
564
let res = store
0 commit comments