1use crate::config::AwsConfig;
2use anyhow::Result;
3
4pub mod object;
5
6pub struct S3Client {
7 config: AwsConfig,
8}
9
10impl S3Client {
11 pub fn new(config: AwsConfig) -> Self {
12 Self { config }
13 }
14
15 pub async fn list_buckets(&self) -> Result<Vec<(String, String, String)>> {
16 let client = self.config.s3_client();
17 let resp = client.list_buckets().send().await?;
18
19 let buckets: Vec<(String, String, String)> = resp
20 .buckets()
21 .iter()
22 .filter_map(|b| {
23 let name = b.name()?.to_string();
24 let date = b.creation_date().map(|d| d.to_string()).unwrap_or_default();
25 Some((name, String::new(), date))
26 })
27 .collect();
28
29 Ok(buckets)
30 }
31
32 pub async fn get_bucket_location(&self, bucket: &str) -> Result<String> {
33 let client = self.config.s3_client();
34 let region = match client.get_bucket_location().bucket(bucket).send().await {
35 Ok(resp) => match resp.location_constraint() {
36 Some(loc) => {
37 let loc_str = loc.as_str();
38 if loc_str.is_empty() || loc_str == "null" {
39 "us-east-1".to_string()
40 } else {
41 loc_str.to_string()
42 }
43 }
44 None => "us-east-1".to_string(),
45 },
46 Err(_) => "us-east-1".to_string(),
47 };
48
49 Ok(region)
50 }
51
52 pub async fn list_objects(
53 &self,
54 bucket: &str,
55 bucket_region: &str,
56 prefix: &str,
57 ) -> Result<Vec<(String, i64, String, bool, String)>> {
58 let client = self.config.s3_client_with_region(bucket_region).await;
59 let mut req = client.list_objects_v2().bucket(bucket).delimiter("/");
60
61 if !prefix.is_empty() {
62 req = req.prefix(prefix);
63 }
64
65 let resp = req.send().await?;
66
67 let mut objects = Vec::new();
68
69 for prefix in resp.common_prefixes() {
70 if let Some(p) = prefix.prefix() {
71 objects.push((p.to_string(), 0, String::new(), true, String::new()));
72 }
73 }
74
75 for obj in resp.contents() {
76 if let Some(key) = obj.key() {
77 let size = obj.size().unwrap_or(0);
78 let modified = obj
79 .last_modified()
80 .map(|d| d.to_string())
81 .unwrap_or_default();
82 let storage_class = obj
83 .storage_class()
84 .map(|s| s.as_str().to_string())
85 .unwrap_or_else(|| "STANDARD".to_string());
86 objects.push((key.to_string(), size, modified, false, storage_class));
87 }
88 }
89
90 Ok(objects)
91 }
92}