Skip to main content

ufile_rus3/api/
multipart_file.rs

1use std::collections::HashMap;
2
3use crate::{
4    AuthorizationService,
5    api::{ObjectOptAuthParamBuilder, traits::ApiOperation},
6    define_api_request,
7};
8use anyhow::Error;
9use bytes::Bytes;
10use chrono::Local;
11use reqwest::{Method, header::HeaderMap};
12
13use crate::{
14    api::object::{InitMultipartState, MultipartUploadState},
15    define_operation_struct,
16};
17
18define_operation_struct!(MultipartFileOperation);
19define_api_request!(
20    MultipartFileRequest,
21    MultipartFileOperationBuilder,
22    MultipartUploadState,
23    {
24        /// Required: Slice initial state
25        pub state: InitMultipartState,
26
27        /// Required: Slice data
28        pub buffer: Bytes,
29
30        /// Required: Slice data size
31        pub buffer_size: u64,
32
33        /// Required: Index of slices
34        pub part_index: usize,
35
36        /// Optional: Content-MD5
37        #[builder(setter(into, strip_option), default)]
38        pub content_md5: Option<String>,
39
40        ///  Optional: temporary `STS` token
41        #[builder(setter(into, strip_option), default)]
42        pub security_token: Option<String>,
43    }
44);
45
46#[async_trait::async_trait]
47impl ApiOperation for MultipartFileOperation {
48    type Request = MultipartFileRequest;
49    type Response = MultipartUploadState;
50    type Error = Error;
51
52    async fn execute(&self, request: Self::Request) -> Result<MultipartUploadState, Error> {
53        let MultipartFileRequest {
54            state,
55            buffer,
56            part_index,
57            content_md5,
58            security_token,
59            ..
60        } = request;
61        let date = Local::now().format("%Y%m%d%H%M%S").to_string();
62        let mime_type = state
63            .mime_type
64            .clone()
65            .ok_or(Error::msg("mime type is unset."))?;
66        let auth_object = ObjectOptAuthParamBuilder::default()
67            .method(Method::PUT)
68            .bucket(state.bucket.as_str())
69            .key_name(state.key_name.as_str())
70            .content_type(mime_type.as_str())
71            .date(date.as_str())
72            .content_md5(content_md5.clone().unwrap_or_default())
73            .build()?;
74        let authorization =
75            AuthorizationService.authorization(auth_object, self.object_config.clone())?;
76        let mut headers = HeaderMap::new();
77        headers.insert("Content-Type", mime_type.parse().unwrap());
78        headers.insert("Accept", "*/*".parse().unwrap());
79        headers.insert("Date", date.parse().unwrap());
80        headers.insert("Authorization", authorization.parse().unwrap());
81        headers.insert("Content-Length", buffer.len().to_string().parse().unwrap());
82        if let Some(content_md5) = content_md5 {
83            headers.insert("Content-MD5", content_md5.parse().unwrap());
84        }
85
86        if let Some(ref security_token) = security_token
87            && !security_token.is_empty()
88        {
89            headers.insert("SecurityToken", security_token.parse().unwrap());
90        }
91        // We must add metadata to headers if metadata is not empty.
92        let url = self
93            .object_config
94            .generate_final_host(state.bucket.as_str(), state.key_name.as_str());
95        let url = format!(
96            "{url}?uploadId={}&partNumber={}",
97            state.upload_id, part_index
98        );
99        let resp = self
100            .client
101            .get_client()
102            .put(url)
103            .headers(headers)
104            .body(buffer.to_vec())
105            .send()
106            .await?;
107        tracing::debug!("Upload part file response: {resp:?}");
108        if resp.status().is_success() {
109            let headers: HashMap<String, String> = resp
110                .headers()
111                .iter()
112                .map(|(k, v)| {
113                    (
114                        k.to_string(),
115                        String::from_utf8_lossy(v.as_bytes()).to_string(),
116                    )
117                })
118                .collect();
119            let mut body: MultipartUploadState = resp.json().await?;
120            body.headers.extend(headers);
121            if let Some(etag) = body.headers.get("etag") {
122                // get etag and set back to response.
123                body.etag = remove_quotes(etag).to_string();
124            }
125            return Ok(body);
126        }
127        Err(Error::msg("Failed to upload part file"))
128    }
129}
130
131fn remove_quotes(s: &str) -> String {
132    s.trim_matches(|c| c == '\"' || c == '\'').to_string()
133}