| 506 |
506 |
|
/// public backend with the private bucket name as `src_bucket`. Required
|
| 507 |
507 |
|
/// (not defaulted) for the same reason as `copy_object`.
|
| 508 |
508 |
|
async fn copy_object_from(&self, src_bucket: &str, src_key: &S3Key, dst_key: &S3Key) -> Result<()>;
|
|
509 |
+ |
/// Server-side multipart copy (`UploadPartCopy`) for sources over the 5 GiB
|
|
510 |
+ |
/// single-part `CopyObject` limit — the >5 GiB half of scan-then-promote.
|
|
511 |
+ |
/// Always takes `src_bucket` explicitly, collapsing the
|
|
512 |
+ |
/// `copy_object`/`copy_object_from` pair into one method (pass this
|
|
513 |
+ |
/// backend's own bucket for a same-bucket promote). `content_type` sets the
|
|
514 |
+ |
/// destination's type, since a fresh multipart upload does not inherit the
|
|
515 |
+ |
/// source's metadata the way `CopyObject` does. Required (not defaulted) for
|
|
516 |
+ |
/// the same reason as `copy_object`.
|
|
517 |
+ |
async fn copy_object_multipart(&self, src_bucket: &str, src_key: &S3Key, dst_key: &S3Key, content_type: &str, src_size: u64, part_size: Option<usize>) -> Result<()>;
|
|
518 |
+ |
|
|
519 |
+ |
// -- Client-direct multipart sessions --
|
|
520 |
+ |
//
|
|
521 |
+ |
// The counterpart to `upload_multipart`, which drives a whole transfer
|
|
522 |
+ |
// server-side from a local file. Here the server only mints the session and
|
|
523 |
+ |
// the per-part presigned URLs; the client streams parts straight to S3, so
|
|
524 |
+ |
// no object bytes transit the server. This is the path large CLI/desktop
|
|
525 |
+ |
// uploads take (a browser stays on the single-PUT `presign_upload`).
|
|
526 |
+ |
//
|
|
527 |
+ |
// All four are required (not defaulted): a no-op default would mint a
|
|
528 |
+ |
// session no client could complete, or silently drop the cleanup that
|
|
529 |
+ |
// stops orphaned parts billing forever.
|
|
530 |
+ |
|
|
531 |
+ |
/// Begin a client-direct multipart upload, returning the `upload_id` the
|
|
532 |
+ |
/// part/complete/abort calls key on.
|
|
533 |
+ |
async fn create_multipart_upload(&self, s3_key: &S3Key, content_type: &str) -> Result<String>;
|
|
534 |
+ |
/// Presign an `UploadPart` request for one part (1-based `part_number`).
|
|
535 |
+ |
/// `max_bytes`, when set, is signed as `Content-Length`, the same
|
|
536 |
+ |
/// defense-in-depth as [`Self::presign_upload`] — the authoritative size
|
|
537 |
+ |
/// check still happens at confirm time.
|
|
538 |
+ |
async fn presign_upload_part(&self, s3_key: &S3Key, upload_id: &str, part_number: i32, expiry_secs: Option<u64>, max_bytes: Option<i64>) -> Result<String>;
|
|
539 |
+ |
/// Complete a multipart upload from the collected `(part_number, etag)`
|
|
540 |
+ |
/// pairs. Parts may be passed in any order; the backend sorts them.
|
|
541 |
+ |
async fn complete_multipart_upload(&self, s3_key: &S3Key, upload_id: &str, parts: &[(i32, String)]) -> Result<()>;
|
|
542 |
+ |
/// Abort a multipart upload, releasing its uploaded parts. The
|
|
543 |
+ |
/// pending-upload reaper calls this on sessions that were never confirmed —
|
|
544 |
+ |
/// incomplete multipart uploads bill for their parts indefinitely.
|
|
545 |
+ |
async fn abort_multipart_upload(&self, s3_key: &S3Key, upload_id: &str) -> Result<()>;
|
| 509 |
546 |
|
async fn check_connectivity(&self) -> std::result::Result<(), String>;
|
| 510 |
547 |
|
fn bucket(&self) -> &str;
|
| 511 |
548 |
|
}
|
| 918 |
955 |
|
.map_err(AppError::Storage)
|
| 919 |
956 |
|
}
|
| 920 |
957 |
|
|
|
958 |
+ |
/// Server-side multipart copy for sources over the 5 GiB single-part
|
|
959 |
+ |
/// `CopyObject` limit. See the [`StorageBackend::copy_object_multipart`]
|
|
960 |
+ |
/// trait method for the promote rationale. `part_size` of `None` lets the
|
|
961 |
+ |
/// storage layer auto-size parts for `src_size`.
|
|
962 |
+ |
pub async fn copy_object_multipart(&self, src_bucket: &str, src_key: &S3Key, dst_key: &S3Key, content_type: &str, src_size: u64, part_size: Option<usize>) -> Result<()> {
|
|
963 |
+ |
self.inner
|
|
964 |
+ |
.copy_object_multipart(src_bucket, src_key.as_str(), dst_key.as_str(), content_type, src_size, part_size)
|
|
965 |
+ |
.await
|
|
966 |
+ |
.map_err(AppError::Storage)
|
|
967 |
+ |
}
|
|
968 |
+ |
|
|
969 |
+ |
/// Begin a client-direct multipart upload. See the
|
|
970 |
+ |
/// [`StorageBackend::create_multipart_upload`] trait method.
|
|
971 |
+ |
pub async fn create_multipart_upload(&self, s3_key: &S3Key, content_type: &str) -> Result<String> {
|
|
972 |
+ |
self.inner
|
|
973 |
+ |
.create_multipart_upload(s3_key.as_str(), content_type)
|
|
974 |
+ |
.await
|
|
975 |
+ |
.map_err(AppError::Storage)
|
|
976 |
+ |
}
|
|
977 |
+ |
|
|
978 |
+ |
/// Presign one `UploadPart` request. See the
|
|
979 |
+ |
/// [`StorageBackend::presign_upload_part`] trait method.
|
|
980 |
+ |
pub async fn presign_upload_part(&self, s3_key: &S3Key, upload_id: &str, part_number: i32, expiry_secs: Option<u64>, max_bytes: Option<i64>) -> Result<String> {
|
|
981 |
+ |
self.inner
|
|
982 |
+ |
.presign_upload_part(s3_key.as_str(), upload_id, part_number, expiry_secs.unwrap_or(PRESIGN_EXPIRY_SECS), max_bytes)
|
|
983 |
+ |
.await
|
|
984 |
+ |
.map_err(AppError::Storage)
|
|
985 |
+ |
}
|
|
986 |
+ |
|
|
987 |
+ |
/// Complete a multipart upload. See the
|
|
988 |
+ |
/// [`StorageBackend::complete_multipart_upload`] trait method.
|
|
989 |
+ |
pub async fn complete_multipart_upload(&self, s3_key: &S3Key, upload_id: &str, parts: &[(i32, String)]) -> Result<()> {
|
|
990 |
+ |
self.inner
|
|
991 |
+ |
.complete_multipart_upload(s3_key.as_str(), upload_id, parts)
|
|
992 |
+ |
.await
|
|
993 |
+ |
.map_err(AppError::Storage)
|
|
994 |
+ |
}
|
|
995 |
+ |
|
|
996 |
+ |
/// Abort a multipart upload. See the
|
|
997 |
+ |
/// [`StorageBackend::abort_multipart_upload`] trait method.
|
|
998 |
+ |
pub async fn abort_multipart_upload(&self, s3_key: &S3Key, upload_id: &str) -> Result<()> {
|
|
999 |
+ |
self.inner
|
|
1000 |
+ |
.abort_multipart_upload(s3_key.as_str(), upload_id)
|
|
1001 |
+ |
.await
|
|
1002 |
+ |
.map_err(AppError::Storage)
|
|
1003 |
+ |
}
|
|
1004 |
+ |
|
| 921 |
1005 |
|
/// Lightweight connectivity check — issues a list with max_keys(0).
|
| 922 |
1006 |
|
pub async fn check_connectivity(&self) -> std::result::Result<(), String> {
|
| 923 |
1007 |
|
self.inner.check_connectivity().await
|
| 1145 |
1229 |
|
self.upload_multipart(s3_key, content_type, file_path).await
|
| 1146 |
1230 |
|
}
|
| 1147 |
1231 |
|
|
|
1232 |
+ |
async fn copy_object_multipart(&self, src_bucket: &str, src_key: &S3Key, dst_key: &S3Key, content_type: &str, src_size: u64, part_size: Option<usize>) -> Result<()> {
|
|
1233 |
+ |
// Inherent method; delegate.
|
|
1234 |
+ |
self.copy_object_multipart(src_bucket, src_key, dst_key, content_type, src_size, part_size).await
|
|
1235 |
+ |
}
|
|
1236 |
+ |
|
|
1237 |
+ |
async fn create_multipart_upload(&self, s3_key: &S3Key, content_type: &str) -> Result<String> {
|
|
1238 |
+ |
self.create_multipart_upload(s3_key, content_type).await
|
|
1239 |
+ |
}
|
|
1240 |
+ |
|
|
1241 |
+ |
async fn presign_upload_part(&self, s3_key: &S3Key, upload_id: &str, part_number: i32, expiry_secs: Option<u64>, max_bytes: Option<i64>) -> Result<String> {
|
|
1242 |
+ |
self.presign_upload_part(s3_key, upload_id, part_number, expiry_secs, max_bytes).await
|
|
1243 |
+ |
}
|
|
1244 |
+ |
|
|
1245 |
+ |
async fn complete_multipart_upload(&self, s3_key: &S3Key, upload_id: &str, parts: &[(i32, String)]) -> Result<()> {
|
|
1246 |
+ |
self.complete_multipart_upload(s3_key, upload_id, parts).await
|
|
1247 |
+ |
}
|
|
1248 |
+ |
|
|
1249 |
+ |
async fn abort_multipart_upload(&self, s3_key: &S3Key, upload_id: &str) -> Result<()> {
|
|
1250 |
+ |
self.abort_multipart_upload(s3_key, upload_id).await
|
|
1251 |
+ |
}
|
|
1252 |
+ |
|
| 1148 |
1253 |
|
async fn check_connectivity(&self) -> std::result::Result<(), String> {
|
| 1149 |
1254 |
|
self.check_connectivity().await
|
| 1150 |
1255 |
|
}
|