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 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        // Serialize to JSON
100        let json = serde_json::to_string_pretty(manifest)
101            .map_err(|e| Error::Serialization(e.to_string()))?;
102
103        // Write to file
104        let mut file = safe_create_file(&path, false)?;
105        file.write_all(json.as_bytes())?;
106
107        // Update index for quick lookups
108        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        // Read file
123        let mut file = safe_open_file(&path, false)?;
124        let mut content = String::new();
125        file.read_to_string(&mut content)?;
126
127        // Deserialize
128        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                    // Determine manifest type
143                    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                    // Log but don't fail on unparseable manifest
154                    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        // Update index
172        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            // Remove entry
182            index.remove(id);
183
184            // Write back to file
185            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
198// No conflict impl.
199impl FilesystemStorage {
200    // List manifests by type
201    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    // Export all manifests to a directory
214    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    // Import manifests from a directory
240    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    // Get manifest file size
279    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    // Get total storage size
291    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        // Include index file if it exists
300        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    // Check if a manifest exists
310    pub fn manifest_exists(&self, id: &str) -> bool {
311        self.manifest_path(id).exists()
312    }
313
314    // Backup all manifests to a directory
315    pub fn backup(&self, backup_path: PathBuf) -> Result<()> {
316        // Simplified version just copies files
317        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        // Copy index file if it exists
329        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}