1use crate::error::{InnerErrorCode, MeowError};
7use crate::transfer_task::TransferTask;
8use async_trait::async_trait;
9
10pub use crate::download_trait::{BreakpointDownload, DownloadHeadCtx, DownloadRangeGetCtx};
11pub use crate::upload_trait::{BreakpointUpload, UploadChunkCtx, UploadPrepareCtx};
12use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
13use reqwest::multipart;
14use reqwest::Method;
15
16#[derive(Debug, Clone, Default)]
17pub struct UploadResumeInfo {
18 pub completed_file_id: Option<String>,
20 pub next_byte: Option<u64>,
24 pub provider_upload_id: Option<String>,
39}
40
41#[derive(Debug)]
43pub enum UploadBody {
44 Multipart(multipart::Form),
45 Binary(Vec<u8>),
46}
47
48#[derive(Debug)]
50pub struct UploadRequest {
51 pub method: Method,
53 pub url: String,
55 pub headers: HeaderMap,
57 pub body: UploadBody,
59}
60
61impl UploadRequest {
62 pub fn from_task(task: &TransferTask, body: UploadBody) -> Self {
75 Self {
76 method: task.method(),
77 url: task.url().to_string(),
78 headers: task.headers().clone(),
79 body,
80 }
81 }
82}
83
84fn parse_default_upload_response(body: &str) -> Result<UploadResumeInfo, MeowError> {
86 if body.trim().is_empty() {
87 crate::meow_trace_log!(
88 "upload_protocol",
89 "empty upload response body, fallback default"
90 );
91 return Ok(UploadResumeInfo::default());
92 }
93 let v: DefaultUploadResp = serde_json::from_str(body).map_err(|e| {
94 crate::meow_error_log!(
95 "upload_protocol",
96 "upload response parse failed: body_len={} err={}",
97 body.len(),
98 e
99 );
100 MeowError::from_code(
101 InnerErrorCode::ResponseParseError,
102 format!("upload response json: {e}, body: {body}"),
103 )
104 })?;
105 crate::meow_trace_log!(
106 "upload_protocol",
107 "upload response parsed: file_id_present={} next_byte={:?}",
108 v.file_id.is_some(),
109 v.next_byte
110 );
111 Ok(UploadResumeInfo {
112 completed_file_id: v.file_id,
113 next_byte: v.next_byte.map(|n| if n < 0 { 0u64 } else { n as u64 }),
114 provider_upload_id: None,
116 })
117}
118
119async fn send_upload_request(
121 client: &reqwest::Client,
122 req: UploadRequest,
123) -> Result<String, MeowError> {
124 let log_url = req.url.clone();
127 let mut builder = client.request(req.method, req.url).headers(req.headers);
128 builder = match req.body {
129 UploadBody::Multipart(form) => builder.multipart(form),
130 UploadBody::Binary(bytes) => builder.body(bytes),
131 };
132 let resp = builder.send().await.map_err(|e| {
133 crate::log::emit_lazy(|| {
134 crate::log::Log::error(
135 "upload_send",
136 format!(
137 "upload request send() transport error: {}",
138 crate::log::redact_secrets(&e.to_string())
139 ),
140 )
141 .with_url(log_url.as_str())
142 });
143 map_reqwest(e)
144 })?;
145 let status = resp.status();
146 let body = resp.text().await.map_err(|e| {
147 crate::log::emit_lazy(|| {
148 crate::log::Log::error(
149 "upload_send",
150 format!(
151 "upload response body read error: {}",
152 crate::log::redact_secrets(&e.to_string())
153 ),
154 )
155 .with_http_status(status.as_u16())
156 .with_url(log_url.as_str())
157 });
158 map_reqwest(e)
159 })?;
160 if !status.is_success() {
161 crate::log::emit_lazy(|| {
162 crate::log::Log::error(
163 "upload_send",
164 format!(
165 "upload HTTP non-2xx status={} body={}",
166 status,
167 crate::log::redact_secrets(&body)
168 ),
169 )
170 .with_http_status(status.as_u16())
171 .with_url(log_url.as_str())
172 });
173 return Err(MeowError::from_code(
174 InnerErrorCode::ResponseStatusError,
175 format!("upload HTTP {status}: {body}"),
176 )
177 .with_http_status(status.as_u16()));
178 }
179 Ok(body)
180}
181
182fn map_reqwest(e: reqwest::Error) -> MeowError {
184 MeowError::from_source(InnerErrorCode::HttpError, e.to_string(), e)
185}
186
187#[derive(Debug, Clone)]
188pub struct DefaultStyleUpload {
189 pub category: String,
191}
192
193impl Default for DefaultStyleUpload {
194 fn default() -> Self {
195 Self {
196 category: String::new(),
197 }
198 }
199}
200
201const KEY_FILE_MD5: &str = "fileMd5";
203const KEY_FILE_NAME: &str = "fileName";
205const KEY_CATEGORY: &str = "category";
207const KEY_TOTAL_SIZE: &str = "totalSize";
209const KEY_OFFSET: &str = "offset";
211const KEY_PART_SIZE: &str = "partSize";
213const KEY_FILE: &str = "file";
215const KEY_UPLOAD_CHUNK_DATA: &str = "upload_chunk_data";
217
218#[derive(serde::Deserialize)]
219struct DefaultUploadResp {
220 #[serde(rename = "fileId")]
221 file_id: Option<String>,
222 #[serde(rename = "nextByte")]
223 next_byte: Option<i64>,
224}
225
226#[async_trait]
227impl BreakpointUpload for DefaultStyleUpload {
228 async fn prepare(&self, ctx: UploadPrepareCtx<'_>) -> Result<UploadResumeInfo, MeowError> {
230 let form = multipart::Form::new()
231 .text(KEY_FILE_MD5, ctx.task.file_sign().to_string())
232 .text(KEY_FILE_NAME, ctx.task.file_name().to_string())
233 .text(KEY_CATEGORY, self.category.clone())
234 .text(KEY_TOTAL_SIZE, ctx.task.total_size().to_string());
235 let req = UploadRequest::from_task(ctx.task, UploadBody::Multipart(form));
236 let body = send_upload_request(ctx.client, req).await?;
237 parse_default_upload_response(&body)
238 }
239
240 async fn upload_chunk(&self, ctx: UploadChunkCtx<'_>) -> Result<UploadResumeInfo, MeowError> {
247 let chunk_len = ctx.chunk.len();
248 let body = reqwest::Body::from(ctx.chunk.clone());
249 let part = multipart::Part::stream_with_length(body, chunk_len as u64)
250 .file_name(KEY_UPLOAD_CHUNK_DATA)
251 .mime_str("application/octet-stream")
252 .map_err(|e| {
253 let err_text = e.to_string();
254 let offset = ctx.offset;
255 let byte_len = chunk_len as u64;
256 let url = ctx.task.url().to_string();
257 crate::log::emit_lazy(move || {
258 crate::log::Log::error(
259 "upload_chunk",
260 format!(
261 "multipart chunk Part build failed: {}",
262 crate::log::redact_secrets(&err_text)
263 ),
264 )
265 .with_offset(offset)
266 .with_byte_len(byte_len)
267 .with_url(url.as_str())
268 });
269 MeowError::from_code(InnerErrorCode::HttpError, e.to_string())
270 })?;
271
272 let form = multipart::Form::new()
273 .part(KEY_FILE, part)
274 .text(KEY_FILE_MD5, ctx.task.file_sign().to_string())
275 .text(KEY_FILE_NAME, ctx.task.file_name().to_string())
276 .text(KEY_CATEGORY, self.category.clone())
277 .text(KEY_OFFSET, ctx.offset.to_string())
278 .text(KEY_PART_SIZE, chunk_len.to_string())
279 .text(KEY_TOTAL_SIZE, ctx.task.total_size().to_string());
280 let req = UploadRequest::from_task(ctx.task, UploadBody::Multipart(form));
281 let body = send_upload_request(ctx.client, req).await?;
282 parse_default_upload_response(&body)
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Eq)]
291pub struct BreakpointDownloadHttpConfig {
292 pub range_accept: String,
296}
297
298impl Default for BreakpointDownloadHttpConfig {
299 fn default() -> Self {
300 Self {
301 range_accept: DEFAULT_RANGE_ACCEPT.to_string(),
302 }
303 }
304}
305
306pub(crate) const DEFAULT_RANGE_ACCEPT: &str = "application/octet-stream";
307
308pub(crate) fn insert_header(map: &mut HeaderMap, name: &str, value: &str) {
310 if let (Ok(n), Ok(v)) = (
311 HeaderName::from_bytes(name.as_bytes()),
312 HeaderValue::from_str(value),
313 ) {
314 map.insert(n, v);
315 }
316}
317
318#[derive(Debug, Clone, Default)]
323pub struct StandardRangeDownload;
324
325impl BreakpointDownload for StandardRangeDownload {
326 fn supports_parallel_parts(&self) -> bool {
327 true
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::{BreakpointUpload, DefaultStyleUpload, StandardRangeDownload};
334
335 #[test]
336 fn default_style_upload_is_serial_only() {
337 assert!(!DefaultStyleUpload::default().supports_parallel_parts());
340 }
341
342 #[test]
343 fn standard_range_download_supports_parallel_parts() {
344 use crate::download_trait::BreakpointDownload;
345 assert!(StandardRangeDownload.supports_parallel_parts());
346 }
347}