atlas_cli/storage/
filesystem.rs1use crate::error::{Error, Result};
2use crate::manifest::utils::determine_manifest_type;
3use crate::storage::traits::{ManifestMetadata, ManifestType, StorageBackend};
4use crate::utils::{safe_create_file, safe_open_file};
5use atlas_c2pa_lib::manifest::Manifest;
6use sha2::{Digest, Sha256};
7use std::collections::HashMap;
8use std::fs::{self, create_dir_all};
9use std::io::{Read, Write};
10use std::path::Path;
11use std::path::PathBuf;
12
13#[derive(Debug, Clone)]
14pub struct FilesystemStorage {
15 base_path: PathBuf,
16}
17
18impl FilesystemStorage {
19 pub fn new<P: AsRef<Path>>(url: P) -> Result<Self> {
20 let path_str = url.as_ref().to_string_lossy();
22 let path = if path_str.starts_with("file://") {
23 PathBuf::from(path_str.trim_start_matches("file://"))
24 } else {
25 PathBuf::from(path_str.to_string())
27 };
28
29 if !path.exists() {
31 create_dir_all(&path)?;
32 }
33
34 Ok(Self { base_path: path })
35 }
36
37 fn manifest_path(&self, id: &str) -> PathBuf {
39 let digest = Sha256::digest(id.as_bytes());
41 let filename = hex::encode(digest);
42
43 self.base_path.join(format!("{filename}.json"))
44 }
45
46 fn list_manifest_files(&self) -> Result<Vec<PathBuf>> {
48 let entries = fs::read_dir(&self.base_path)?
49 .filter_map(|entry| {
50 let entry = entry.ok()?;
51 let path = entry.path();
52 if path.is_file() && path.extension().is_some_and(|ext| ext == "json") {
53 Some(path)
54 } else {
55 None
56 }
57 })
58 .collect();
59
60 Ok(entries)
61 }
62
63 fn update_index(&self, id: &str, filename: &str) -> Result<()> {
65 let index_path = self.base_path.join("manifest_index.json");
66
67 let mut index: HashMap<String, String> = if index_path.exists() {
69 let mut file = safe_open_file(&index_path, false)?;
70 let mut content = String::new();
71 file.read_to_string(&mut content)?;
72 serde_json::from_str(&content).unwrap_or_default()
73 } else {
74 HashMap::new()
75 };
76
77 index.insert(id.to_string(), filename.to_string());
79
80 let json = serde_json::to_string_pretty(&index)
82 .map_err(|e| Error::Serialization(e.to_string()))?;
83 let mut file = safe_create_file(&index_path, false)?;
84 file.write_all(json.as_bytes())?;
85
86 Ok(())
87 }
88}
89
90impl StorageBackend for FilesystemStorage {
91 fn store_manifest(&self, manifest: &Manifest) -> Result<String> {
92 let manifest_id = manifest.instance_id.clone();
93 let path = self.manifest_path(&manifest_id);
94
95 let json = serde_json::to_string_pretty(manifest)
97 .map_err(|e| Error::Serialization(e.to_string()))?;
98
99 let mut file = safe_create_file(&path, false)?;
101 file.write_all(json.as_bytes())?;
102
103 if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
105 self.update_index(&manifest_id, filename)?;
106 }
107
108 Ok(manifest_id)
109 }
110
111 fn retrieve_manifest(&self, id: &str) -> Result<Manifest> {
112 let path = self.manifest_path(id);
113
114 if !path.exists() {
115 return Err(Error::Storage(format!("Manifest not found: {id}")));
116 }
117
118 let mut file = safe_open_file(&path, false)?;
120 let mut content = String::new();
121 file.read_to_string(&mut content)?;
122
123 serde_json::from_str(&content)
125 .map_err(|e| Error::Serialization(format!("Failed to parse manifest: {e}")))
126 }
127
128 fn list_manifests(&self) -> Result<Vec<ManifestMetadata>> {
129 let mut manifests = Vec::new();
130
131 for path in self.list_manifest_files()? {
132 let mut file = safe_open_file(&path, false)?;
133 let mut content = String::new();
134 file.read_to_string(&mut content)?;
135
136 match serde_json::from_str::<Manifest>(&content) {
137 Ok(manifest) => {
138 let manifest_type = determine_manifest_type(&manifest);
140
141 manifests.push(ManifestMetadata {
142 id: manifest.instance_id.clone(),
143 name: manifest.title.clone(),
144 manifest_type,
145 created_at: manifest.created_at.0.to_string(),
146 });
147 }
148 Err(e) => {
149 eprintln!("Error parsing manifest at {path:?}: {e}");
151 }
152 }
153 }
154
155 Ok(manifests)
156 }
157
158 fn delete_manifest(&self, id: &str) -> Result<()> {
159 let path = self.manifest_path(id);
160
161 if !path.exists() {
162 return Err(Error::Storage(format!("Manifest not found: {id}")));
163 }
164
165 fs::remove_file(&path)?;
166
167 let index_path = self.base_path.join("manifest_index.json");
169 if index_path.exists() {
170 let mut file = safe_open_file(&index_path, false)?;
171 let mut content = String::new();
172 file.read_to_string(&mut content)?;
173
174 let mut index: HashMap<String, String> =
175 serde_json::from_str(&content).unwrap_or_default();
176
177 index.remove(id);
179
180 let json = serde_json::to_string_pretty(&index)
182 .map_err(|e| Error::Serialization(e.to_string()))?;
183 let mut file = safe_create_file(&path, false)?;
184 file.write_all(json.as_bytes())?;
185 }
186
187 Ok(())
188 }
189 fn as_any(&self) -> &dyn std::any::Any {
190 self
191 }
192}
193
194impl FilesystemStorage {
196 pub fn list_manifests_by_type(
198 &self,
199 manifest_type: ManifestType,
200 ) -> Result<Vec<ManifestMetadata>> {
201 self.list_manifests().map(|all_manifests| {
202 all_manifests
203 .into_iter()
204 .filter(|m| m.manifest_type == manifest_type)
205 .collect()
206 })
207 }
208
209 pub fn export_all(&self, export_path: PathBuf) -> Result<usize> {
211 if !export_path.exists() {
212 create_dir_all(&export_path)?;
213 }
214
215 let manifests = self.list_manifests()?;
216 let mut exported_count = 0;
217
218 for metadata in manifests {
219 let manifest = self.retrieve_manifest(&metadata.id)?;
220 let json = serde_json::to_string_pretty(&manifest)
221 .map_err(|e| Error::Serialization(e.to_string()))?;
222
223 let filename = format!("{}.json", metadata.id.replace(":", "_"));
224 let export_file_path = export_path.join(filename);
225
226 let mut file = safe_create_file(&export_file_path, false)?;
227 file.write_all(json.as_bytes())?;
228
229 exported_count += 1;
230 }
231
232 Ok(exported_count)
233 }
234
235 pub fn import_from_directory(&self, import_path: PathBuf) -> Result<usize> {
237 if !import_path.exists() || !import_path.is_dir() {
238 return Err(Error::Storage(format!(
239 "Import path does not exist or is not a directory: {import_path:?}"
240 )));
241 }
242
243 let entries = fs::read_dir(import_path)?.filter_map(|entry| {
244 let entry = entry.ok()?;
245 let path = entry.path();
246 if path.is_file() && path.extension().is_some_and(|ext| ext == "json") {
247 Some(path)
248 } else {
249 None
250 }
251 });
252
253 let mut imported_count = 0;
254
255 for path in entries {
256 let mut file = safe_open_file(&path, false)?;
257 let mut content = String::new();
258 file.read_to_string(&mut content)?;
259
260 match serde_json::from_str::<Manifest>(&content) {
261 Ok(manifest) => {
262 self.store_manifest(&manifest)?;
263 imported_count += 1;
264 }
265 Err(e) => {
266 eprintln!("Error importing manifest from {path:?}: {e}");
267 }
268 }
269 }
270
271 Ok(imported_count)
272 }
273
274 pub fn get_manifest_size(&self, id: &str) -> Result<u64> {
276 let path = self.manifest_path(id);
277
278 if !path.exists() {
279 return Err(Error::Storage(format!("Manifest not found: {id}")));
280 }
281
282 let metadata = fs::metadata(path)?;
283 Ok(metadata.len())
284 }
285
286 pub fn get_total_storage_size(&self) -> Result<u64> {
288 let mut total_size = 0;
289
290 for path in self.list_manifest_files()? {
291 let metadata = fs::metadata(path)?;
292 total_size += metadata.len();
293 }
294
295 let index_path = self.base_path.join("manifest_index.json");
297 if index_path.exists() {
298 let metadata = fs::metadata(index_path)?;
299 total_size += metadata.len();
300 }
301
302 Ok(total_size)
303 }
304
305 pub fn manifest_exists(&self, id: &str) -> bool {
307 self.manifest_path(id).exists()
308 }
309
310 pub fn backup(&self, backup_path: PathBuf) -> Result<()> {
312 if !backup_path.exists() {
314 create_dir_all(&backup_path)?;
315 }
316
317 for path in self.list_manifest_files()? {
318 if let Some(filename) = path.file_name() {
319 let dest_path = backup_path.join(filename);
320 fs::copy(path, dest_path)?;
321 }
322 }
323
324 let index_path = self.base_path.join("manifest_index.json");
326 if index_path.exists() {
327 let dest_path = backup_path.join("manifest_index.json");
328 fs::copy(index_path, dest_path)?;
329 }
330
331 Ok(())
332 }
333}