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 get_base_uri(&self) -> String {
92 "file:///".to_string()
93 }
94
95 fn store_manifest(&self, manifest: &Manifest) -> Result<String> {
96 let manifest_id = manifest.instance_id.clone();
97 let path = self.manifest_path(&manifest_id);
98
99 let json = serde_json::to_string_pretty(manifest)
101 .map_err(|e| Error::Serialization(e.to_string()))?;
102
103 let mut file = safe_create_file(&path, false)?;
105 file.write_all(json.as_bytes())?;
106
107 if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
109 self.update_index(&manifest_id, filename)?;
110 }
111
112 Ok(manifest_id)
113 }
114
115 fn retrieve_manifest(&self, id: &str) -> Result<Manifest> {
116 let path = self.manifest_path(id);
117
118 if !path.exists() {
119 return Err(Error::Storage(format!("Manifest not found: {id}")));
120 }
121
122 let mut file = safe_open_file(&path, false)?;
124 let mut content = String::new();
125 file.read_to_string(&mut content)?;
126
127 serde_json::from_str(&content)
129 .map_err(|e| Error::Serialization(format!("Failed to parse manifest: {e}")))
130 }
131
132 fn list_manifests(&self) -> Result<Vec<ManifestMetadata>> {
133 let mut manifests = Vec::new();
134
135 for path in self.list_manifest_files()? {
136 let mut file = safe_open_file(&path, false)?;
137 let mut content = String::new();
138 file.read_to_string(&mut content)?;
139
140 match serde_json::from_str::<Manifest>(&content) {
141 Ok(manifest) => {
142 let manifest_type = determine_manifest_type(&manifest);
144
145 manifests.push(ManifestMetadata {
146 id: manifest.instance_id.clone(),
147 name: manifest.title.clone(),
148 manifest_type,
149 created_at: manifest.created_at.0.to_string(),
150 });
151 }
152 Err(e) => {
153 eprintln!("Error parsing manifest at {path:?}: {e}");
155 }
156 }
157 }
158
159 Ok(manifests)
160 }
161
162 fn delete_manifest(&self, id: &str) -> Result<()> {
163 let path = self.manifest_path(id);
164
165 if !path.exists() {
166 return Err(Error::Storage(format!("Manifest not found: {id}")));
167 }
168
169 fs::remove_file(&path)?;
170
171 let index_path = self.base_path.join("manifest_index.json");
173 if index_path.exists() {
174 let mut file = safe_open_file(&index_path, false)?;
175 let mut content = String::new();
176 file.read_to_string(&mut content)?;
177
178 let mut index: HashMap<String, String> =
179 serde_json::from_str(&content).unwrap_or_default();
180
181 index.remove(id);
183
184 let json = serde_json::to_string_pretty(&index)
186 .map_err(|e| Error::Serialization(e.to_string()))?;
187 let mut file = safe_create_file(&path, false)?;
188 file.write_all(json.as_bytes())?;
189 }
190
191 Ok(())
192 }
193 fn as_any(&self) -> &dyn std::any::Any {
194 self
195 }
196}
197
198impl FilesystemStorage {
200 pub fn list_manifests_by_type(
202 &self,
203 manifest_type: ManifestType,
204 ) -> Result<Vec<ManifestMetadata>> {
205 self.list_manifests().map(|all_manifests| {
206 all_manifests
207 .into_iter()
208 .filter(|m| m.manifest_type == manifest_type)
209 .collect()
210 })
211 }
212
213 pub fn export_all(&self, export_path: PathBuf) -> Result<usize> {
215 if !export_path.exists() {
216 create_dir_all(&export_path)?;
217 }
218
219 let manifests = self.list_manifests()?;
220 let mut exported_count = 0;
221
222 for metadata in manifests {
223 let manifest = self.retrieve_manifest(&metadata.id)?;
224 let json = serde_json::to_string_pretty(&manifest)
225 .map_err(|e| Error::Serialization(e.to_string()))?;
226
227 let filename = format!("{}.json", metadata.id.replace(":", "_"));
228 let export_file_path = export_path.join(filename);
229
230 let mut file = safe_create_file(&export_file_path, false)?;
231 file.write_all(json.as_bytes())?;
232
233 exported_count += 1;
234 }
235
236 Ok(exported_count)
237 }
238
239 pub fn import_from_directory(&self, import_path: PathBuf) -> Result<usize> {
241 if !import_path.exists() || !import_path.is_dir() {
242 return Err(Error::Storage(format!(
243 "Import path does not exist or is not a directory: {import_path:?}"
244 )));
245 }
246
247 let entries = fs::read_dir(import_path)?.filter_map(|entry| {
248 let entry = entry.ok()?;
249 let path = entry.path();
250 if path.is_file() && path.extension().is_some_and(|ext| ext == "json") {
251 Some(path)
252 } else {
253 None
254 }
255 });
256
257 let mut imported_count = 0;
258
259 for path in entries {
260 let mut file = safe_open_file(&path, false)?;
261 let mut content = String::new();
262 file.read_to_string(&mut content)?;
263
264 match serde_json::from_str::<Manifest>(&content) {
265 Ok(manifest) => {
266 self.store_manifest(&manifest)?;
267 imported_count += 1;
268 }
269 Err(e) => {
270 eprintln!("Error importing manifest from {path:?}: {e}");
271 }
272 }
273 }
274
275 Ok(imported_count)
276 }
277
278 pub fn get_manifest_size(&self, id: &str) -> Result<u64> {
280 let path = self.manifest_path(id);
281
282 if !path.exists() {
283 return Err(Error::Storage(format!("Manifest not found: {id}")));
284 }
285
286 let metadata = fs::metadata(path)?;
287 Ok(metadata.len())
288 }
289
290 pub fn get_total_storage_size(&self) -> Result<u64> {
292 let mut total_size = 0;
293
294 for path in self.list_manifest_files()? {
295 let metadata = fs::metadata(path)?;
296 total_size += metadata.len();
297 }
298
299 let index_path = self.base_path.join("manifest_index.json");
301 if index_path.exists() {
302 let metadata = fs::metadata(index_path)?;
303 total_size += metadata.len();
304 }
305
306 Ok(total_size)
307 }
308
309 pub fn manifest_exists(&self, id: &str) -> bool {
311 self.manifest_path(id).exists()
312 }
313
314 pub fn backup(&self, backup_path: PathBuf) -> Result<()> {
316 if !backup_path.exists() {
318 create_dir_all(&backup_path)?;
319 }
320
321 for path in self.list_manifest_files()? {
322 if let Some(filename) = path.file_name() {
323 let dest_path = backup_path.join(filename);
324 fs::copy(path, dest_path)?;
325 }
326 }
327
328 let index_path = self.base_path.join("manifest_index.json");
330 if index_path.exists() {
331 let dest_path = backup_path.join("manifest_index.json");
332 fs::copy(index_path, dest_path)?;
333 }
334
335 Ok(())
336 }
337}