1use bytes::Bytes;
2use chrono::{DateTime, Utc};
3use fakecloud_persistence::cache::{BodyCache, BodyKey};
4use fakecloud_persistence::BodyRef;
5use parking_lot::RwLock;
6use std::collections::{BTreeMap, HashMap};
7use std::io::{self, Read, Seek, SeekFrom};
8use std::sync::Arc;
9
10#[derive(Debug, Clone)]
12pub struct AclGrant {
13 pub grantee_type: String, pub grantee_id: Option<String>,
15 pub grantee_display_name: Option<String>,
16 pub grantee_uri: Option<String>,
17 pub permission: String, }
19
20#[derive(Debug, Clone, Default)]
21pub struct S3Object {
22 pub key: String,
23 pub body: BodyRef,
24 pub content_type: String,
25 pub etag: String,
26 pub size: u64,
27 pub last_modified: DateTime<Utc>,
28 pub metadata: HashMap<String, String>,
29 pub storage_class: String,
30 pub tags: HashMap<String, String>,
31 pub acl_grants: Vec<AclGrant>,
32 pub acl_owner_id: Option<String>,
33 pub parts_count: Option<u32>,
35 pub part_sizes: Option<Vec<(u32, u64)>>,
37 pub sse_algorithm: Option<String>,
39 pub sse_kms_key_id: Option<String>,
41 pub bucket_key_enabled: Option<bool>,
43 pub version_id: Option<String>,
44 pub is_delete_marker: bool,
45 pub content_encoding: Option<String>,
46 pub website_redirect_location: Option<String>,
47 pub restore_ongoing: Option<bool>,
49 pub restore_expiry: Option<String>,
51 pub checksum_algorithm: Option<String>,
53 pub checksum_value: Option<String>,
55 pub lock_mode: Option<String>,
57 pub lock_retain_until: Option<DateTime<Utc>>,
59 pub lock_legal_hold: Option<String>,
61}
62
63#[derive(Debug, Clone)]
65pub struct UploadPart {
66 pub part_number: u32,
67 pub body: BodyRef,
68 pub etag: String,
69 pub size: u64,
70 pub last_modified: DateTime<Utc>,
71}
72
73#[derive(Debug, Clone)]
75pub struct MultipartUpload {
76 pub upload_id: String,
77 pub key: String,
78 pub initiated: DateTime<Utc>,
79 pub parts: BTreeMap<u32, UploadPart>,
81 pub metadata: HashMap<String, String>,
83 pub content_type: String,
84 pub storage_class: String,
85 pub sse_algorithm: Option<String>,
86 pub sse_kms_key_id: Option<String>,
87 pub tagging: Option<String>,
88 pub acl_grants: Vec<AclGrant>,
89 pub checksum_algorithm: Option<String>,
90}
91
92#[derive(Debug, Clone)]
93pub struct S3Bucket {
94 pub name: String,
95 pub creation_date: DateTime<Utc>,
96 pub region: String,
97 pub objects: BTreeMap<String, S3Object>,
99 pub tags: HashMap<String, String>,
100 pub acl_grants: Vec<AclGrant>,
101 pub acl_owner_id: String,
102 pub multipart_uploads: HashMap<String, MultipartUpload>,
104 pub versioning: Option<String>,
106 pub object_versions: HashMap<String, Vec<S3Object>>,
108 pub acl: Option<String>,
110 pub encryption_config: Option<String>,
111 pub lifecycle_config: Option<String>,
112 pub policy: Option<String>,
113 pub cors_config: Option<String>,
114 pub notification_config: Option<String>,
115 pub logging_config: Option<String>,
116 pub website_config: Option<String>,
117 pub accelerate_status: Option<String>,
118 pub public_access_block: Option<String>,
119 pub object_lock_config: Option<String>,
120 pub replication_config: Option<String>,
121 pub ownership_controls: Option<String>,
122 pub inventory_configs: HashMap<String, String>,
123 pub eventbridge_enabled: bool,
125}
126
127impl S3Bucket {
128 pub fn new(name: &str, region: &str, owner_id: &str) -> Self {
129 Self {
130 name: name.to_string(),
131 creation_date: Utc::now(),
132 region: region.to_string(),
133 objects: BTreeMap::new(),
134 tags: HashMap::new(),
135 acl_grants: vec![AclGrant {
136 grantee_type: "CanonicalUser".to_string(),
137 grantee_id: Some(owner_id.to_string()),
138 grantee_display_name: Some(owner_id.to_string()),
139 grantee_uri: None,
140 permission: "FULL_CONTROL".to_string(),
141 }],
142 acl_owner_id: owner_id.to_string(),
143 multipart_uploads: HashMap::new(),
144 versioning: None,
145 object_versions: HashMap::new(),
146 acl: None,
147 encryption_config: None,
148 lifecycle_config: None,
149 policy: None,
150 cors_config: None,
151 notification_config: None,
152 logging_config: None,
153 website_config: None,
154 accelerate_status: None,
155 public_access_block: None,
156 object_lock_config: None,
157 replication_config: None,
158 ownership_controls: None,
159 inventory_configs: HashMap::new(),
160 eventbridge_enabled: false,
161 }
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct S3NotificationEvent {
168 pub bucket: String,
169 pub key: String,
170 pub event_type: String,
171 pub timestamp: DateTime<Utc>,
172}
173
174pub struct S3State {
175 pub account_id: String,
176 pub region: String,
177 pub buckets: HashMap<String, S3Bucket>,
178 pub notification_events: Vec<S3NotificationEvent>,
179 pub body_cache: Option<Arc<BodyCache>>,
180}
181
182impl S3State {
183 pub fn new(account_id: &str, region: &str) -> Self {
184 Self {
185 account_id: account_id.to_string(),
186 region: region.to_string(),
187 buckets: HashMap::new(),
188 notification_events: Vec::new(),
189 body_cache: None,
190 }
191 }
192
193 pub fn set_body_cache(&mut self, cache: Arc<BodyCache>) {
194 self.body_cache = Some(cache);
195 }
196
197 pub fn reset(&mut self) {
198 self.buckets.clear();
199 self.notification_events.clear();
200 }
201
202 pub fn read_body(&self, body: &BodyRef) -> io::Result<Bytes> {
205 match body {
206 BodyRef::Memory(b) => Ok(b.clone()),
207 BodyRef::Disk {
208 bucket,
209 key,
210 version,
211 path,
212 ..
213 } => {
214 let cache_key = BodyKey::new(bucket.clone(), key.clone(), version.clone());
215 if let Some(cache) = &self.body_cache {
216 if let Some(hit) = cache.get(&cache_key) {
217 return Ok(hit);
218 }
219 }
220 let data = std::fs::read(path)?;
221 let bytes = Bytes::from(data);
222 if let Some(cache) = &self.body_cache {
223 cache.insert(cache_key, bytes.clone());
224 }
225 Ok(bytes)
226 }
227 }
228 }
229
230 pub fn read_body_range(&self, body: &BodyRef, offset: u64, len: u64) -> io::Result<Bytes> {
234 match body {
235 BodyRef::Memory(b) => {
236 let start = offset as usize;
237 let end = start.saturating_add(len as usize).min(b.len());
238 if start > b.len() {
239 return Ok(Bytes::new());
240 }
241 Ok(b.slice(start..end))
242 }
243 BodyRef::Disk { path, .. } => {
244 let mut f = std::fs::File::open(path)?;
245 f.seek(SeekFrom::Start(offset))?;
246 let mut buf = vec![0u8; len as usize];
247 f.read_exact(&mut buf)?;
248 Ok(Bytes::from(buf))
249 }
250 }
251 }
252}
253
254pub type SharedS3State = Arc<RwLock<S3State>>;
255
256pub fn memory_body(bytes: Bytes) -> BodyRef {
258 BodyRef::Memory(bytes)
259}