ufile_rus3/api/
multipart_file.rs1use 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 pub state: InitMultipartState,
26
27 pub buffer: Bytes,
29
30 pub buffer_size: u64,
32
33 pub part_index: usize,
35
36 #[builder(setter(into, strip_option), default)]
38 pub content_md5: Option<String>,
39
40 #[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 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 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}