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 encryption: Arc<Mutex<EncryptionManager>>,
15}
16
17impl ObjectStore {
18 pub fn new(repo_path: &Path) -> Self {
20 let objects_dir = repo_path.join(".lit").join("objects");
21
22 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 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 let encryption_config = EncryptionConfig::load(repo_path)?;
38 let mut encryption_manager = EncryptionManager::new(encryption_config);
39
40 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 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 pub fn write(&self, object: &Object) -> Result<ObjectHash, String> {
63 let hash = object.hash();
64 let path = self.object_path(&hash);
65
66 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 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 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 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 let encrypted_data =
106 fs::read(&path).map_err(|e| format!("Failed to read object: {}", e))?;
107
108 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 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 pub fn exists(&self, hash: &ObjectHash) -> bool {
129 self.object_path(hash).exists()
130 }
131
132 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 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 fs::create_dir_all(repo_path.join(".lit").join("objects")).unwrap();
182
183 let store = ObjectStore::new(repo_path);
184
185 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 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}