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
11pub struct ObjectStore {
13 objects_dir: PathBuf,
14 packs_dir: PathBuf,
18 encryption: Arc<Mutex<EncryptionManager>>,
19}
20
21impl ObjectStore {
22 pub fn new(repo_path: &Path) -> Self {
24 let objects_dir = repo_path.join(".lit").join("objects");
25
26 let encryption_config = EncryptionConfig::load(repo_path).unwrap_or_default();
28 let encryption = Arc::new(Mutex::new(EncryptionManager::new_auto(
29 encryption_config,
30 repo_path,
31 )));
32
33 ObjectStore {
34 objects_dir,
35 packs_dir: crate::storage::pack::packs_dir(repo_path),
36 encryption,
37 }
38 }
39
40 pub fn new_with_encryption(repo_path: &Path, passphrase: Option<&str>) -> Result<Self, String> {
42 let objects_dir = repo_path.join(".lit").join("objects");
43
44 let encryption_config = EncryptionConfig::load(repo_path)?;
46 let mut encryption_manager = EncryptionManager::new(encryption_config);
47
48 if let Some(pass) = passphrase {
50 encryption_manager.initialize(pass)?;
51 }
52
53 let encryption = Arc::new(Mutex::new(encryption_manager));
54
55 Ok(ObjectStore {
56 objects_dir,
57 packs_dir: crate::storage::pack::packs_dir(repo_path),
58 encryption,
59 })
60 }
61
62 pub fn with_encryption_manager(repo_path: &Path, manager: EncryptionManager) -> Self {
68 ObjectStore {
69 objects_dir: repo_path.join(".lit").join("objects"),
70 packs_dir: crate::storage::pack::packs_dir(repo_path),
71 encryption: Arc::new(Mutex::new(manager)),
72 }
73 }
74
75 fn object_path(&self, hash: &ObjectHash) -> PathBuf {
78 let hash_str = hash.as_str();
79 let (dir, file) = hash_str.split_at(4);
80 self.objects_dir.join(dir).join(file)
81 }
82
83 pub fn write(&self, object: &Object) -> Result<ObjectHash, String> {
85 let hash = object.hash();
86 let path = self.object_path(&hash);
87
88 if let Some(parent) = path.parent() {
90 fs::create_dir_all(parent)
91 .map_err(|e| format!("Failed to create object directory: {}", e))?;
92 }
93
94 let data = object.to_bytes();
96 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
97 encoder
98 .write_all(&data)
99 .map_err(|e| format!("Failed to compress object: {}", e))?;
100 let compressed = encoder
101 .finish()
102 .map_err(|e| format!("Failed to finish compression: {}", e))?;
103
104 let final_data = {
106 let encryption = self
107 .encryption
108 .lock()
109 .map_err(|e| format!("Failed to acquire encryption lock: {}", e))?;
110 encryption.encrypt(&compressed)?
111 };
112
113 fs::write(&path, final_data).map_err(|e| format!("Failed to write object: {}", e))?;
114
115 Ok(hash)
116 }
117
118 pub fn read(&self, hash: &ObjectHash) -> Result<Object, String> {
120 let path = self.object_path(hash);
121
122 if !path.exists() {
123 return self.read_packed(hash);
126 }
127
128 let encrypted_data =
130 fs::read(&path).map_err(|e| format!("Failed to read object: {}", e))?;
131
132 let compressed = {
134 let encryption = self
135 .encryption
136 .lock()
137 .map_err(|e| format!("Failed to acquire encryption lock: {}", e))?;
138 encryption.decrypt(&encrypted_data)?
139 };
140
141 let mut decoder = ZlibDecoder::new(&compressed[..]);
143 let mut data = Vec::new();
144 decoder
145 .read_to_end(&mut data)
146 .map_err(|e| format!("Failed to decompress object: {}", e))?;
147
148 Object::from_bytes(&data)
149 }
150
151 pub fn exists(&self, hash: &ObjectHash) -> bool {
153 self.object_path(hash).exists()
154 || crate::storage::pack::load_all(&self.packs_dir).contains_key(hash.as_str())
155 }
156
157 fn read_packed(&self, hash: &ObjectHash) -> Result<Object, String> {
159 let packed = crate::storage::pack::load_all(&self.packs_dir);
160 let Some((pack_path, offset)) = packed.get(hash.as_str()) else {
161 return Err(format!("Object {} not found", hash.short()));
162 };
163
164 let encryption = self
165 .encryption
166 .lock()
167 .map_err(|e| format!("Failed to acquire encryption lock: {}", e))?;
168
169 crate::storage::pack::read_pack_object(pack_path, *offset, &encryption)
170 .map_err(|e| format!("Failed to read {} from pack: {}", hash.short(), e))
171 }
172
173 pub fn encryption(&self) -> Arc<Mutex<EncryptionManager>> {
178 Arc::clone(&self.encryption)
179 }
180
181 pub fn list(&self) -> Result<Vec<ObjectHash>, String> {
183 let mut objects = Vec::new();
184
185 for hash in crate::storage::pack::load_all(&self.packs_dir).into_keys() {
189 objects.push(ObjectHash::from_hex(hash));
190 }
191
192 if !self.objects_dir.exists() {
193 return Ok(objects);
194 }
195
196 for entry in walkdir::WalkDir::new(&self.objects_dir)
197 .min_depth(2)
198 .max_depth(2)
199 {
200 let entry = entry.map_err(|e| format!("Failed to read objects: {}", e))?;
201
202 if entry.file_type().is_file() {
203 let path = entry.path();
204
205 if let Some(file_name) = path.file_name() {
207 if let Some(parent) = path.parent() {
208 if let Some(dir_name) = parent.file_name() {
209 let hash = format!(
210 "{}{}",
211 dir_name.to_string_lossy(),
212 file_name.to_string_lossy()
213 );
214 objects.push(ObjectHash::from_hex(hash));
215 }
216 }
217 }
218 }
219 }
220
221 objects.sort_by(|a, b| a.as_str().cmp(b.as_str()));
222 objects.dedup_by(|a, b| a.as_str() == b.as_str());
223 Ok(objects)
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::core::Blob;
231 use tempfile::TempDir;
232
233 #[test]
234 fn test_object_store() {
235 let temp_dir = TempDir::new().unwrap();
236 let repo_path = temp_dir.path();
237
238 fs::create_dir_all(repo_path.join(".lit").join("objects")).unwrap();
240
241 let store = ObjectStore::new(repo_path);
242
243 let content = b"Hello, world!".to_vec();
245 let blob = Blob::new(content.clone());
246 let object = Object::Blob(blob);
247
248 let hash = store.write(&object).unwrap();
249
250 let read_object = store.read(&hash).unwrap();
252
253 match read_object {
254 Object::Blob(blob) => assert_eq!(blob.content, content),
255 _ => panic!("Expected blob"),
256 }
257 }
258}