ibmcloud_cos/
multipartupload.rs

1// Copyright 2022 Mathew Odden <mathewrodden@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use quick_xml::{de::from_str, se::to_string};
16use reqwest::blocking::Body;
17use serde::{Deserialize, Serialize};
18
19use crate::cos::{check_response, Client, Error};
20
21#[derive(Deserialize, Debug)]
22pub struct InitiateMultipartUploadResult {
23    #[serde(rename = "$unflatten=Bucket")]
24    pub bucket: String,
25    #[serde(rename = "$unflatten=Key")]
26    pub key: String,
27    #[serde(rename = "$unflatten=UploadId")]
28    pub upload_id: String,
29}
30
31#[derive(Deserialize, Serialize, Debug, Clone)]
32pub struct Part {
33    #[serde(rename = "$unflatten=ETag")]
34    pub etag: String,
35    #[serde(rename = "$unflatten=PartNumber")]
36    pub part_number: usize,
37}
38
39#[derive(Deserialize, Serialize, Debug)]
40pub struct CompleteMultipartUpload {
41    #[serde(rename = "Part", default)]
42    pub parts: Vec<Part>,
43}
44
45pub type UploadId = String;
46
47impl Client {
48    pub fn create_multipart_upload(&self, bucket: &str, key: &str) -> Result<UploadId, Error> {
49        let c = &self.client;
50
51        let url = format!("https://{}.{}/{}?uploads", bucket, self.endpoint, key);
52        let response = c
53            .post(url)
54            .header(
55                "Authorization",
56                format!("Bearer {}", self.tm.token()?.access_token),
57            )
58            .send()?;
59
60        let text: String = check_response(response)?.text()?;
61        let mpu_resp: InitiateMultipartUploadResult = from_str(&text)?;
62
63        Ok(mpu_resp.upload_id)
64    }
65
66    pub fn upload_part<T: Into<Body>>(
67        &self,
68        bucket: &str,
69        key: &str,
70        upload_id: &str,
71        sequence_number: usize,
72        chunk: T,
73    ) -> Result<Part, Error> {
74        let c = &self.client;
75
76        let url = format!(
77            "https://{}.{}/{}?partNumber={}&uploadId={}",
78            bucket, self.endpoint, key, sequence_number, upload_id,
79        );
80
81        let resp = c
82            .put(url)
83            .header(
84                "Authorization",
85                format!("Bearer {}", self.tm.token()?.access_token),
86            )
87            .body(chunk)
88            .send()?;
89
90        let resp = check_response(resp)?;
91        let etag = resp.headers()[reqwest::header::ETAG].to_str().unwrap();
92
93        let part = Part {
94            etag: etag.to_string(),
95            part_number: sequence_number,
96        };
97
98        Ok(part)
99    }
100
101    pub fn complete_multipart_upload(
102        &self,
103        bucket: &str,
104        key: &str,
105        upload_id: &str,
106        cmpu: CompleteMultipartUpload,
107    ) -> Result<(), Error> {
108        let c = &self.client;
109
110        let url = format!(
111            "https://{}.{}/{}?uploadId={}",
112            bucket, self.endpoint, key, upload_id
113        );
114
115        let payload = to_string(&cmpu).unwrap();
116
117        let resp = c
118            .post(url)
119            .header(
120                "Authorization",
121                format!("Bearer {}", self.tm.token()?.access_token),
122            )
123            .body(payload)
124            .send()?;
125
126        let _ = check_response(resp)?;
127
128        Ok(())
129    }
130
131    pub fn abort_multipart_upload(
132        &self,
133        bucket: &str,
134        key: &str,
135        upload_id: &str,
136    ) -> Result<(), Error> {
137        let c = &self.client;
138
139        let url = format!(
140            "https://{}.{}/{}?uploadId={}",
141            bucket, self.endpoint, key, upload_id
142        );
143
144        let resp = c
145            .delete(url)
146            .header(
147                "Authorization",
148                format!("Bearer {}", self.tm.token()?.access_token),
149            )
150            .send()?;
151
152        let _ = check_response(resp)?;
153
154        Ok(())
155    }
156}