atlas_cli/storage/
filesystem.rs

1use 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        // Parse the URL to extract the path
21        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            // Assume it's a direct path if not using file:// scheme
26            PathBuf::from(path_str.to_string())
27        };
28
29        // Create directory if it doesn't exist
30        if !path.exists() {
31            create_dir_all(&path)?;
32        }
33
34        Ok(Self { base_path: path })
35    }
36
37    // Helper to get path for a manifest
38    fn manifest_path(&self, id: &str) -> PathBuf {
39        // Create a hash of the ID to use as filename
40        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    // Helper to list all manifest files
47    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    // Helper to update index file for quick ID lookups
64    fn update_index(&self, id: &str, filename: &str) -> Result<()> {
65        let index_path = self.base_path.join("manifest_index.json");
66
67        // Read existing index or create new one
68        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        // Update index
78        index.insert(id.to_string(), filename.to_string());
79
80        // Write back to file
81        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        // Serialize to JSON
96        let json = serde_json::to_string_pretty(manifest)
97            .map_err(|e| Error::Serialization(e.to_string()))?;
98
99        // Write to file
100        let mut file = safe_create_file(&path, false)?;
101        file.write_all(json.as_bytes())?;
102
103        // Update index for quick lookups
104        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        // Read file
119        let mut file = safe_open_file(&path, false)?;
120        let mut content = String::new();
121        file.read_to_string(&mut content)?;
122
123        // Deserialize
124        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                    // Determine manifest type
139                    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                    // Log but don't fail on unparseable manifest
150                    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        // Update index
168        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            // Remove entry
178            index.remove(id);
179
180            // Write back to file
181            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
194// No conflict impl.
195impl FilesystemStorage {
196    // List manifests by type
197    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    // Export all manifests to a directory
210    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    // Import manifests from a directory
236    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    // Get manifest file size
275    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    // Get total storage size
287    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        // Include index file if it exists
296        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    // Check if a manifest exists
306    pub fn manifest_exists(&self, id: &str) -> bool {
307        self.manifest_path(id).exists()
308    }
309
310    // Backup all manifests to a directory
311    pub fn backup(&self, backup_path: PathBuf) -> Result<()> {
312        // Simplified version just copies files
313        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        // Copy index file if it exists
325        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}