Skip to main content

lit/storage/
objects.rs

1use crate::core::{Object, ObjectHash};
2use crate::crypto::encryption::{EncryptionConfig, EncryptionManager};
3use flate2::read::ZlibDecoder;
4use flate2::write::ZlibEncoder;
5use flate2::Compression;
6use std::fs;
7use std::io::{Read, Write};
8use std::path::{Path, PathBuf};
9use std::sync::{Arc, Mutex};
10
11/// Object storage - handles reading/writing objects to disk with optional encryption
12pub struct ObjectStore {
13    objects_dir: PathBuf,
14    encryption: Arc<Mutex<EncryptionManager>>,
15}
16
17impl ObjectStore {
18    /// Create a new object store
19    pub fn new(repo_path: &Path) -> Self {
20        let objects_dir = repo_path.join(".lit").join("objects");
21
22        // Load encryption configuration
23        let encryption_config = EncryptionConfig::load(repo_path).unwrap_or_default();
24        let encryption = Arc::new(Mutex::new(EncryptionManager::new(encryption_config)));
25
26        ObjectStore {
27            objects_dir,
28            encryption,
29        }
30    }
31
32    /// Create object store with encryption passphrase
33    pub fn new_with_encryption(repo_path: &Path, passphrase: Option<&str>) -> Result<Self, String> {
34        let objects_dir = repo_path.join(".lit").join("objects");
35
36        // Load encryption configuration
37        let encryption_config = EncryptionConfig::load(repo_path)?;
38        let mut encryption_manager = EncryptionManager::new(encryption_config);
39
40        // Initialize encryption if passphrase provided
41        if let Some(pass) = passphrase {
42            encryption_manager.initialize(pass)?;
43        }
44
45        let encryption = Arc::new(Mutex::new(encryption_manager));
46
47        Ok(ObjectStore {
48            objects_dir,
49            encryption,
50        })
51    }
52
53    /// Get the path for an object by its hash
54    /// Uses first 4 chars for directory sharding (65,536 shards for better distribution)
55    fn object_path(&self, hash: &ObjectHash) -> PathBuf {
56        let hash_str = hash.as_str();
57        let (dir, file) = hash_str.split_at(4);
58        self.objects_dir.join(dir).join(file)
59    }
60
61    /// Write an object to storage
62    pub fn write(&self, object: &Object) -> Result<ObjectHash, String> {
63        let hash = object.hash();
64        let path = self.object_path(&hash);
65
66        // Create parent directory
67        if let Some(parent) = path.parent() {
68            fs::create_dir_all(parent)
69                .map_err(|e| format!("Failed to create object directory: {}", e))?;
70        }
71
72        // Serialize and compress
73        let data = object.to_bytes();
74        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
75        encoder
76            .write_all(&data)
77            .map_err(|e| format!("Failed to compress object: {}", e))?;
78        let compressed = encoder
79            .finish()
80            .map_err(|e| format!("Failed to finish compression: {}", e))?;
81
82        // Encrypt if enabled
83        let final_data = {
84            let encryption = self
85                .encryption
86                .lock()
87                .map_err(|e| format!("Failed to acquire encryption lock: {}", e))?;
88            encryption.encrypt(&compressed)?
89        };
90
91        fs::write(&path, final_data).map_err(|e| format!("Failed to write object: {}", e))?;
92
93        Ok(hash)
94    }
95
96    /// Read an object from storage
97    pub fn read(&self, hash: &ObjectHash) -> Result<Object, String> {
98        let path = self.object_path(hash);
99
100        if !path.exists() {
101            return Err(format!("Object {} not found", hash.short()));
102        }
103
104        // Read encrypted/compressed data
105        let encrypted_data =
106            fs::read(&path).map_err(|e| format!("Failed to read object: {}", e))?;
107
108        // Decrypt if enabled
109        let compressed = {
110            let encryption = self
111                .encryption
112                .lock()
113                .map_err(|e| format!("Failed to acquire encryption lock: {}", e))?;
114            encryption.decrypt(&encrypted_data)?
115        };
116
117        // Decompress
118        let mut decoder = ZlibDecoder::new(&compressed[..]);
119        let mut data = Vec::new();
120        decoder
121            .read_to_end(&mut data)
122            .map_err(|e| format!("Failed to decompress object: {}", e))?;
123
124        Object::from_bytes(&data)
125    }
126
127    /// Check if an object exists
128    pub fn exists(&self, hash: &ObjectHash) -> bool {
129        self.object_path(hash).exists()
130    }
131
132    /// List all objects
133    pub fn list(&self) -> Result<Vec<ObjectHash>, String> {
134        let mut objects = Vec::new();
135
136        if !self.objects_dir.exists() {
137            return Ok(objects);
138        }
139
140        for entry in walkdir::WalkDir::new(&self.objects_dir)
141            .min_depth(2)
142            .max_depth(2)
143        {
144            let entry = entry.map_err(|e| format!("Failed to read objects: {}", e))?;
145
146            if entry.file_type().is_file() {
147                let path = entry.path();
148
149                // Reconstruct hash from path
150                if let Some(file_name) = path.file_name() {
151                    if let Some(parent) = path.parent() {
152                        if let Some(dir_name) = parent.file_name() {
153                            let hash = format!(
154                                "{}{}",
155                                dir_name.to_string_lossy(),
156                                file_name.to_string_lossy()
157                            );
158                            objects.push(ObjectHash::from_hex(hash));
159                        }
160                    }
161                }
162            }
163        }
164
165        Ok(objects)
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use crate::core::Blob;
173    use tempfile::TempDir;
174
175    #[test]
176    fn test_object_store() {
177        let temp_dir = TempDir::new().unwrap();
178        let repo_path = temp_dir.path();
179
180        // Create .lit/objects directory
181        fs::create_dir_all(repo_path.join(".lit").join("objects")).unwrap();
182
183        let store = ObjectStore::new(repo_path);
184
185        // Create and write a blob
186        let content = b"Hello, world!".to_vec();
187        let blob = Blob::new(content.clone());
188        let object = Object::Blob(blob);
189
190        let hash = store.write(&object).unwrap();
191
192        // Read it back
193        let read_object = store.read(&hash).unwrap();
194
195        match read_object {
196            Object::Blob(blob) => assert_eq!(blob.content, content),
197            _ => panic!("Expected blob"),
198        }
199    }
200}