cos_rust_sdk/
object.rs

1//! 对象存储操作模块
2//!
3//! 提供对象的上传、下载、删除等核心功能
4
5use crate::client::CosClient;
6use crate::error::{CosError, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::path::Path;
10use tokio::fs::File;
11use tokio::io::{AsyncReadExt, AsyncWriteExt};
12
13/// 对象操作客户端
14#[derive(Debug, Clone)]
15pub struct ObjectClient {
16    client: CosClient,
17}
18
19impl ObjectClient {
20    /// 创建新的对象操作客户端
21    pub fn new(client: CosClient) -> Self {
22        Self { client }
23    }
24
25    /// 上传对象
26    pub async fn put_object(
27        &self,
28        key: &str,
29        data: Vec<u8>,
30        content_type: Option<&str>,
31    ) -> Result<PutObjectResponse> {
32        let params = HashMap::new();
33        
34        let mut headers = HashMap::new();
35        if let Some(ct) = content_type {
36            headers.insert("Content-Type".to_string(), ct.to_string());
37        }
38        headers.insert("Content-Length".to_string(), data.len().to_string());
39        
40        let response = self.client.put(&format!("/{}", key), params, Some(data)).await?;
41        
42        Ok(PutObjectResponse {
43            etag: response
44                .headers()
45                .get("etag")
46                .and_then(|v| v.to_str().ok())
47                .unwrap_or("")
48                .to_string(),
49            version_id: response
50                .headers()
51                .get("x-cos-version-id")
52                .and_then(|v| v.to_str().ok())
53                .map(|s| s.to_string()),
54        })
55    }
56
57    /// 从文件上传对象
58    pub async fn put_object_from_file(
59        &self,
60        key: &str,
61        file_path: &Path,
62        content_type: Option<&str>,
63    ) -> Result<PutObjectResponse> {
64        let mut file = File::open(file_path)
65            .await
66            .map_err(|e| CosError::other(format!("Failed to open file: {}", e)))?;
67        
68        let mut data = Vec::new();
69        file.read_to_end(&mut data)
70            .await
71            .map_err(|e| CosError::other(format!("Failed to read file: {}", e)))?;
72        
73        let content_type = content_type.or_else(|| {
74            file_path
75                .extension()
76                .and_then(|ext| ext.to_str())
77                .and_then(|ext| match ext.to_lowercase().as_str() {
78                    // 文本文件
79                    "txt" => Some("text/plain"),
80                    "html" | "htm" => Some("text/html"),
81                    "css" => Some("text/css"),
82                    "js" => Some("application/javascript"),
83                    "json" => Some("application/json"),
84                    "xml" => Some("application/xml"),
85                    "csv" => Some("text/csv"),
86                    "md" => Some("text/markdown"),
87                    
88                    // 图片格式
89                    "jpg" | "jpeg" => Some("image/jpeg"),
90                    "png" => Some("image/png"),
91                    "gif" => Some("image/gif"),
92                    "webp" => Some("image/webp"),
93                    "bmp" => Some("image/bmp"),
94                    "tiff" | "tif" => Some("image/tiff"),
95                    "svg" => Some("image/svg+xml"),
96                    "ico" => Some("image/x-icon"),
97                    "heic" => Some("image/heic"),
98                    "heif" => Some("image/heif"),
99                    "avif" => Some("image/avif"),
100                    "jxl" => Some("image/jxl"),
101                    
102                    // 视频格式
103                    "mp4" => Some("video/mp4"),
104                    "avi" => Some("video/x-msvideo"),
105                    "mov" => Some("video/quicktime"),
106                    "wmv" => Some("video/x-ms-wmv"),
107                    "flv" => Some("video/x-flv"),
108                    "webm" => Some("video/webm"),
109                    "mkv" => Some("video/x-matroska"),
110                    "m4v" => Some("video/x-m4v"),
111                    "3gp" => Some("video/3gpp"),
112                    "3g2" => Some("video/3gpp2"),
113                    "ts" => Some("video/mp2t"),
114                    "mts" => Some("video/mp2t"),
115                    "m2ts" => Some("video/mp2t"),
116                    "ogv" => Some("video/ogg"),
117                    
118                    // 音频格式
119                    "mp3" => Some("audio/mpeg"),
120                    "wav" => Some("audio/wav"),
121                    "flac" => Some("audio/flac"),
122                    "aac" => Some("audio/aac"),
123                    "ogg" => Some("audio/ogg"),
124                    "wma" => Some("audio/x-ms-wma"),
125                    "m4a" => Some("audio/mp4"),
126                    "opus" => Some("audio/opus"),
127                    
128                    // 文档格式
129                    "pdf" => Some("application/pdf"),
130                    "doc" => Some("application/msword"),
131                    "docx" => Some("application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
132                    "xls" => Some("application/vnd.ms-excel"),
133                    "xlsx" => Some("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
134                    "ppt" => Some("application/vnd.ms-powerpoint"),
135                    "pptx" => Some("application/vnd.openxmlformats-officedocument.presentationml.presentation"),
136                    "rtf" => Some("application/rtf"),
137                    
138                    // 压缩文件
139                    "zip" => Some("application/zip"),
140                    "rar" => Some("application/vnd.rar"),
141                    "7z" => Some("application/x-7z-compressed"),
142                    "tar" => Some("application/x-tar"),
143                    "gz" => Some("application/gzip"),
144                    "bz2" => Some("application/x-bzip2"),
145                    
146                    // 其他常见格式
147                    "bin" => Some("application/octet-stream"),
148                    "exe" => Some("application/octet-stream"),
149                    "dmg" => Some("application/x-apple-diskimage"),
150                    "iso" => Some("application/x-iso9660-image"),
151                    
152                    _ => None,
153                })
154        });
155        
156        self.put_object(key, data, content_type).await
157    }
158
159    /// 获取对象
160    pub async fn get_object(&self, key: &str) -> Result<GetObjectResponse> {
161        let params = HashMap::new();
162        let response = self.client.get(&format!("/{}", key), params).await?;
163        
164        let content_length = response
165            .headers()
166            .get("content-length")
167            .and_then(|v| v.to_str().ok())
168            .and_then(|s| s.parse().ok())
169            .unwrap_or(0);
170        
171        let content_type = response
172            .headers()
173            .get("content-type")
174            .and_then(|v| v.to_str().ok())
175            .unwrap_or("application/octet-stream")
176            .to_string();
177        
178        let etag = response
179            .headers()
180            .get("etag")
181            .and_then(|v| v.to_str().ok())
182            .unwrap_or("")
183            .to_string();
184        
185        let last_modified = response
186            .headers()
187            .get("last-modified")
188            .and_then(|v| v.to_str().ok())
189            .map(|s| s.to_string());
190        
191        let data = response
192            .bytes()
193            .await
194            .map_err(|e| CosError::other(format!("Failed to read response body: {}", e)))?
195            .to_vec();
196        
197        Ok(GetObjectResponse {
198            data,
199            content_length,
200            content_type,
201            etag,
202            last_modified,
203        })
204    }
205
206    /// 下载对象到文件
207    pub async fn get_object_to_file(&self, key: &str, file_path: &Path) -> Result<()> {
208        let response = self.get_object(key).await?;
209        
210        let mut file = File::create(file_path)
211            .await
212            .map_err(|e| CosError::other(format!("Failed to create file: {}", e)))?;
213        
214        file.write_all(&response.data)
215            .await
216            .map_err(|e| CosError::other(format!("Failed to write file: {}", e)))?;
217        
218        Ok(())
219    }
220
221    /// 删除对象
222    pub async fn delete_object(&self, key: &str) -> Result<DeleteObjectResponse> {
223        let params = HashMap::new();
224        let response = self.client.delete(&format!("/{}", key), params).await?;
225        
226        Ok(DeleteObjectResponse {
227            version_id: response
228                .headers()
229                .get("x-cos-version-id")
230                .and_then(|v| v.to_str().ok())
231                .map(|s| s.to_string()),
232            delete_marker: response
233                .headers()
234                .get("x-cos-delete-marker")
235                .and_then(|v| v.to_str().ok())
236                .and_then(|s| s.parse().ok())
237                .unwrap_or(false),
238        })
239    }
240
241    /// 批量删除对象
242    pub async fn delete_objects(&self, keys: &[String]) -> Result<DeleteObjectsResponse> {
243        let delete_request = DeleteRequest {
244            objects: keys.iter().map(|key| DeleteObject {
245                key: key.clone(),
246                version_id: None,
247            }).collect(),
248            quiet: false,
249        };
250        
251        let xml_body = quick_xml::se::to_string(&delete_request)
252            .map_err(|e| CosError::other(format!("Failed to serialize delete request: {}", e)))?;
253        
254        let mut params = HashMap::new();
255        params.insert("delete".to_string(), "".to_string());
256        
257        let response = self.client.post("/", params, Some(xml_body)).await?;
258        
259        let response_text = response
260            .text()
261            .await
262            .map_err(|e| CosError::other(format!("Failed to read response: {}", e)))?;
263        
264        let delete_response: DeleteObjectsResponse = quick_xml::de::from_str(&response_text)
265            .map_err(|e| CosError::other(format!("Failed to parse delete response: {}", e)))?;
266        
267        Ok(delete_response)
268    }
269
270    /// 获取对象元数据
271    pub async fn head_object(&self, key: &str) -> Result<HeadObjectResponse> {
272        let params = HashMap::new();
273        let response = self.client.head(&format!("/{}", key), params).await?;
274        
275        let content_length = response
276            .headers()
277            .get("content-length")
278            .and_then(|v| v.to_str().ok())
279            .and_then(|s| s.parse().ok())
280            .unwrap_or(0);
281        
282        let content_type = response
283            .headers()
284            .get("content-type")
285            .and_then(|v| v.to_str().ok())
286            .unwrap_or("application/octet-stream")
287            .to_string();
288        
289        let etag = response
290            .headers()
291            .get("etag")
292            .and_then(|v| v.to_str().ok())
293            .unwrap_or("")
294            .to_string();
295        
296        let last_modified = response
297            .headers()
298            .get("last-modified")
299            .and_then(|v| v.to_str().ok())
300            .map(|s| s.to_string());
301        
302        Ok(HeadObjectResponse {
303            content_length,
304            content_type,
305            etag,
306            last_modified,
307        })
308    }
309
310    /// 检查对象是否存在
311    pub async fn object_exists(&self, key: &str) -> Result<bool> {
312        match self.head_object(key).await {
313            Ok(_) => Ok(true),
314            Err(CosError::Server { .. }) => Ok(false),
315            Err(e) => Err(e),
316        }
317    }
318}
319
320/// 上传对象响应
321#[derive(Debug, Clone)]
322pub struct PutObjectResponse {
323    pub etag: String,
324    pub version_id: Option<String>,
325}
326
327/// 获取对象响应
328#[derive(Debug, Clone)]
329pub struct GetObjectResponse {
330    pub data: Vec<u8>,
331    pub content_length: u64,
332    pub content_type: String,
333    pub etag: String,
334    pub last_modified: Option<String>,
335}
336
337/// 删除对象响应
338#[derive(Debug, Clone)]
339pub struct DeleteObjectResponse {
340    pub version_id: Option<String>,
341    pub delete_marker: bool,
342}
343
344/// 获取对象元数据响应
345#[derive(Debug, Clone)]
346pub struct HeadObjectResponse {
347    pub content_length: u64,
348    pub content_type: String,
349    pub etag: String,
350    pub last_modified: Option<String>,
351}
352
353/// 批量删除请求
354#[derive(Debug, Serialize)]
355#[serde(rename = "Delete")]
356struct DeleteRequest {
357    #[serde(rename = "Object")]
358    objects: Vec<DeleteObject>,
359    #[serde(rename = "Quiet")]
360    quiet: bool,
361}
362
363/// 删除对象项
364#[derive(Debug, Serialize)]
365struct DeleteObject {
366    #[serde(rename = "Key")]
367    key: String,
368    #[serde(rename = "VersionId", skip_serializing_if = "Option::is_none")]
369    version_id: Option<String>,
370}
371
372/// 批量删除响应
373#[derive(Debug, Deserialize)]
374#[serde(rename = "DeleteResult")]
375pub struct DeleteObjectsResponse {
376    #[serde(rename = "Deleted", default)]
377    pub deleted: Vec<DeletedObject>,
378    #[serde(rename = "Error", default)]
379    pub errors: Vec<DeleteError>,
380}
381
382/// 已删除对象
383#[derive(Debug, Deserialize)]
384pub struct DeletedObject {
385    #[serde(rename = "Key")]
386    pub key: String,
387    #[serde(rename = "VersionId")]
388    pub version_id: Option<String>,
389    #[serde(rename = "DeleteMarker")]
390    pub delete_marker: Option<bool>,
391}
392
393/// 删除错误
394#[derive(Debug, Deserialize)]
395pub struct DeleteError {
396    #[serde(rename = "Key")]
397    pub key: String,
398    #[serde(rename = "Code")]
399    pub code: String,
400    #[serde(rename = "Message")]
401    pub message: String,
402}
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407    use crate::config::Config;
408    use std::time::Duration;
409
410    #[tokio::test]
411    async fn test_object_operations() {
412        let config = Config::new("test_id", "test_key", "ap-beijing", "test-bucket-123")
413            .with_timeout(Duration::from_secs(60));
414        
415        let cos_client = CosClient::new(config).unwrap();
416        let object_client = ObjectClient::new(cos_client);
417        
418        // 测试对象存在性检查
419        let exists = object_client.object_exists("test-key").await;
420        // 在实际测试中,这里会根据具体情况返回结果
421    }
422}