1use crate::core::{Object, ObjectHash};
20use crate::crypto::encryption::EncryptionManager;
21use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
22use crc32fast::Hasher as Crc32Hasher;
23use flate2::read::ZlibDecoder;
24use flate2::write::ZlibEncoder;
25use flate2::Compression;
26use std::collections::HashMap;
27use std::fs;
28use std::io::{Cursor, Read, Write};
29use std::path::{Path, PathBuf};
30
31pub const PACK_MAGIC: &[u8; 4] = b"LITP";
33pub const PACK_VERSION: u32 = 1;
35
36pub const INDEX_MAGIC: &[u8; 4] = b"LITI";
38pub const INDEX_VERSION: u32 = 1;
40
41#[derive(Debug, Clone)]
43pub struct PackIndexEntry {
44 pub hash: ObjectHash,
45 pub offset: u64,
46 pub crc32: u32,
47}
48
49pub fn packs_dir(repo_root: &Path) -> PathBuf {
51 repo_root.join(".lit").join("packs")
52}
53
54pub fn write_pack(
62 objects: &[(ObjectHash, Object)],
63 pack_path: &Path,
64 encryption: &EncryptionManager,
65) -> Result<Vec<PackIndexEntry>, crate::errors::LitError> {
66 let mut buf: Vec<u8> = Vec::new();
67 let mut index_entries = Vec::new();
68
69 buf.extend_from_slice(PACK_MAGIC);
71 buf.write_u32::<BigEndian>(PACK_VERSION)
72 .map_err(|e| format!("Write error: {}", e))?;
73 buf.write_u32::<BigEndian>(objects.len() as u32)
74 .map_err(|e| format!("Write error: {}", e))?;
75
76 for (hash, obj) in objects {
77 let offset = buf.len() as u64;
78
79 let type_byte = match obj {
80 Object::Blob(_) => 1u8,
81 Object::Tree(_) => 2u8,
82 Object::Commit(_) => 3u8,
83 Object::Tag(_) => 4u8,
84 };
85
86 let raw = obj.to_bytes();
87 let uncompressed_size = raw.len() as u64;
88
89 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::best());
91 encoder
92 .write_all(&raw)
93 .map_err(|e| format!("Compress error: {}", e))?;
94 let compressed = encoder
95 .finish()
96 .map_err(|e| format!("Compress finish error: {}", e))?;
97
98 let stored = encryption.encrypt(&compressed)?;
100
101 let mut crc32 = Crc32Hasher::new();
103 crc32.update(&stored);
104 let crc_val = crc32.finalize();
105
106 buf.push(type_byte);
108 buf.write_u64::<BigEndian>(uncompressed_size)
109 .map_err(|e| format!("Write error: {}", e))?;
110 buf.write_u64::<BigEndian>(stored.len() as u64)
111 .map_err(|e| format!("Write error: {}", e))?;
112 buf.extend_from_slice(&stored);
113
114 index_entries.push(PackIndexEntry {
115 hash: hash.clone(),
116 offset,
117 crc32: crc_val,
118 });
119 }
120
121 fs::write(pack_path, &buf).map_err(|e| format!("Failed to write pack: {}", e))?;
122
123 Ok(index_entries)
124}
125
126pub fn write_pack_index(
128 entries: &[PackIndexEntry],
129 index_path: &Path,
130) -> Result<(), crate::errors::LitError> {
131 let mut buf: Vec<u8> = Vec::new();
132
133 buf.extend_from_slice(INDEX_MAGIC);
135 buf.write_u32::<BigEndian>(INDEX_VERSION)
136 .map_err(|e| format!("Write error: {}", e))?;
137 buf.write_u32::<BigEndian>(entries.len() as u32)
138 .map_err(|e| format!("Write error: {}", e))?;
139
140 let mut sorted = entries.to_vec();
142 sorted.sort_by(|a, b| a.hash.as_str().cmp(b.hash.as_str()));
143
144 for entry in &sorted {
145 let hash_bytes = entry.hash.as_str().as_bytes();
146 buf.write_u32::<BigEndian>(hash_bytes.len() as u32)
147 .map_err(|e| format!("Write error: {}", e))?;
148 buf.extend_from_slice(hash_bytes);
149 buf.write_u64::<BigEndian>(entry.offset)
150 .map_err(|e| format!("Write error: {}", e))?;
151 buf.write_u32::<BigEndian>(entry.crc32)
152 .map_err(|e| format!("Write error: {}", e))?;
153 }
154
155 fs::write(index_path, &buf).map_err(|e| format!("Failed to write index: {}", e))?;
156 Ok(())
157}
158
159pub fn read_pack_object(
161 pack_path: &Path,
162 offset: u64,
163 encryption: &EncryptionManager,
164) -> Result<Object, crate::errors::LitError> {
165 let pack_data = fs::read(pack_path).map_err(|e| format!("Failed to read pack: {}", e))?;
166 let mut cursor = Cursor::new(&pack_data);
167 cursor.set_position(offset);
168
169 let _type_byte = cursor.read_u8().map_err(|e| format!("Read error: {}", e))?;
171
172 let _uncompressed_size = cursor
174 .read_u64::<BigEndian>()
175 .map_err(|e| format!("Read error: {}", e))?;
176 let compressed_size = cursor
177 .read_u64::<BigEndian>()
178 .map_err(|e| format!("Read error: {}", e))?;
179
180 let pos = cursor.position() as usize;
182 let end = pos
183 .checked_add(compressed_size as usize)
184 .ok_or("Pack entry length overflows")?;
185 if end > pack_data.len() {
186 return Err("Pack data truncated".into());
187 }
188 let stored = &pack_data[pos..end];
189
190 let compressed = encryption.decrypt(stored)?;
192
193 let mut decoder = ZlibDecoder::new(&compressed[..]);
195 let mut raw = Vec::new();
196 decoder
197 .read_to_end(&mut raw)
198 .map_err(|e| format!("Decompress error: {}", e))?;
199
200 Object::from_bytes(&raw).map_err(Into::into)
201}
202
203pub fn load_pack_index(index_path: &Path) -> Result<HashMap<String, (PathBuf, u64)>, String> {
205 let data = fs::read(index_path).map_err(|e| format!("Failed to read index: {}", e))?;
206 let mut cursor = Cursor::new(&data);
207
208 let mut magic = [0u8; 4];
210 cursor
211 .read_exact(&mut magic)
212 .map_err(|e| format!("Read error: {}", e))?;
213 if &magic != INDEX_MAGIC {
214 return Err("Invalid pack index magic".into());
215 }
216
217 let _version = cursor
218 .read_u32::<BigEndian>()
219 .map_err(|e| format!("Read error: {}", e))?;
220 let count = cursor
221 .read_u32::<BigEndian>()
222 .map_err(|e| format!("Read error: {}", e))?;
223
224 let pack_path = index_path.with_extension("pack");
225 let mut map = HashMap::new();
226
227 for _ in 0..count {
228 let hash_len = cursor
229 .read_u32::<BigEndian>()
230 .map_err(|e| format!("Read error: {}", e))? as usize;
231
232 let pos = cursor.position() as usize;
233 if pos + hash_len > data.len() {
234 return Err("Index data truncated".into());
235 }
236 let hash_str = String::from_utf8(data[pos..pos + hash_len].to_vec())
237 .map_err(|e| format!("Invalid hash UTF-8: {}", e))?;
238 cursor.set_position((pos + hash_len) as u64);
239
240 let offset = cursor
241 .read_u64::<BigEndian>()
242 .map_err(|e| format!("Read error: {}", e))?;
243 let _crc32 = cursor
244 .read_u32::<BigEndian>()
245 .map_err(|e| format!("Read error: {}", e))?;
246
247 map.insert(hash_str, (pack_path.clone(), offset));
248 }
249
250 Ok(map)
251}
252
253pub fn load_all(packs_dir: &Path) -> HashMap<String, (PathBuf, u64)> {
259 let mut map = HashMap::new();
260
261 let entries = match fs::read_dir(packs_dir) {
262 Ok(entries) => entries,
263 Err(_) => return map,
264 };
265
266 for entry in entries.flatten() {
267 let path = entry.path();
268 if path.extension().and_then(|e| e.to_str()) != Some("idx") {
269 continue;
270 }
271 match load_pack_index(&path) {
272 Ok(entries) => map.extend(entries),
273 Err(e) => eprintln!("Warning: ignoring unreadable pack index {:?}: {}", path, e),
274 }
275 }
276
277 map
278}