qualia_core_db/
storage.rs1pub mod mmap;
6
7use crate::{NQuin, QualiaSuperBlock, BLOCK_MULTIPLIER_SIZE, QUINS_PER_BLOCK};
8use std::fs::{File, OpenOptions};
9use std::io::{self, Seek, SeekFrom};
10use std::path::Path;
11
12pub struct SuperBlockWriter {
14 file: File,
15 current_offset: u64,
16}
17
18impl SuperBlockWriter {
19 pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
20 let mut file = OpenOptions::new()
21 .create(true)
22 .write(true)
23 .read(true)
24 .open(path)?;
25
26 let current_offset = file.seek(SeekFrom::End(0))?;
27
28 Ok(Self {
29 file,
30 current_offset,
31 })
32 }
33
34 pub fn flush_block(
36 &mut self,
37 sequence_id: u64,
38 owner_did: u64,
39 quins: &[NQuin; QUINS_PER_BLOCK],
40 ) -> io::Result<()> {
41 let mut block = Box::new(unsafe { std::mem::zeroed::<QualiaSuperBlock>() });
42
43 block.block_sequence_id = sequence_id;
44 block.storage_owner_did = owner_did;
45 block.active_quin_count = QUINS_PER_BLOCK as u64;
46 block.hardware_profile_flags = 0x01; for i in 0..QUINS_PER_BLOCK {
50 block.quin_ledger[i] = quins[i];
51 }
52
53 block.validation_checksum = {
59 let ledger_bytes = unsafe {
60 std::slice::from_raw_parts(
61 block.quin_ledger.as_ptr() as *const u8,
62 QUINS_PER_BLOCK * core::mem::size_of::<NQuin>(),
63 )
64 };
65 crate::container_10d::crc32c::crc32c(ledger_bytes)
66 };
67
68 let bytes = unsafe {
70 std::slice::from_raw_parts(
71 (block.as_ref() as *const QualiaSuperBlock) as *const u8,
72 BLOCK_MULTIPLIER_SIZE,
73 )
74 };
75
76 #[cfg(target_family = "unix")]
77 {
78 use std::os::unix::fs::FileExt;
79 self.file.write_all_at(bytes, self.current_offset)?;
80 }
81
82 #[cfg(target_family = "windows")]
83 {
84 use std::os::windows::fs::FileExt;
85 let mut written = 0;
86 while written < BLOCK_MULTIPLIER_SIZE {
87 let n = self
88 .file
89 .seek_write(&bytes[written..], self.current_offset + written as u64)?;
90 if n == 0 {
91 return Err(io::Error::new(
92 io::ErrorKind::WriteZero,
93 "Failed to write whole block",
94 ));
95 }
96 written += n;
97 }
98 }
99
100 #[cfg(not(any(target_family = "unix", target_family = "windows")))]
101 {
102 use std::io::Write;
103 self.file.write_all(bytes)?;
104 }
105
106 self.file.sync_data()?;
108 self.current_offset += BLOCK_MULTIPLIER_SIZE as u64;
109
110 crate::telemetry::SUPERBLOCK_IO_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
111
112 Ok(())
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use tempfile::NamedTempFile;
120
121 #[test]
122 fn test_flush_superblock() {
123 let temp_file = NamedTempFile::new().unwrap();
124 let mut writer = SuperBlockWriter::open(temp_file.path()).unwrap();
125
126 let quins = [NQuin {
128 subject: 0,
129 predicate: 0,
130 object: 0,
131 context: 0,
132 metadata: 0,
133 parity: 0,
134 }; QUINS_PER_BLOCK];
135
136 let result = writer.flush_block(1, 42, &quins);
137 assert!(result.is_ok(), "Failed to flush SuperBlock");
138
139 let metadata = temp_file.as_file().metadata().unwrap();
141 assert_eq!(
142 metadata.len(),
143 BLOCK_MULTIPLIER_SIZE as u64,
144 "File size is not page aligned to 40,960 bytes"
145 );
146 }
147}
148
149use std::future::Future;
157use std::pin::Pin;
158
159pub trait VirtualFileSystem {
160 #[cfg(not(target_arch = "wasm32"))]
162 fn read_chunk(
163 &self,
164 path: &str,
165 ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send>>;
166 #[cfg(target_arch = "wasm32")]
167 fn read_chunk(&self, path: &str) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>>>>;
168
169 #[cfg(not(target_arch = "wasm32"))]
171 fn write_chunk(
172 &self,
173 path: &str,
174 data: &[u8],
175 ) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send>>;
176 #[cfg(target_arch = "wasm32")]
177 fn write_chunk(
178 &self,
179 path: &str,
180 data: &[u8],
181 ) -> Pin<Box<dyn Future<Output = Result<(), String>>>>;
182}
183
184#[cfg(not(target_arch = "wasm32"))]
185pub struct NativeVfs;
186
187#[cfg(not(target_arch = "wasm32"))]
188impl VirtualFileSystem for NativeVfs {
189 fn read_chunk(
190 &self,
191 path: &str,
192 ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send>> {
193 let path = path.to_string();
194 Box::pin(async move { std::fs::read(&path).map_err(|e| e.to_string()) })
195 }
196
197 fn write_chunk(
198 &self,
199 path: &str,
200 data: &[u8],
201 ) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send>> {
202 let path = path.to_string();
203 let data = data.to_vec();
204 Box::pin(async move { std::fs::write(&path, data).map_err(|e| e.to_string()) })
205 }
206}
207
208#[cfg(target_arch = "wasm32")]
209pub struct OpfsVfs;
210
211#[cfg(target_arch = "wasm32")]
212impl VirtualFileSystem for OpfsVfs {
213 fn read_chunk(&self, path: &str) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>>>> {
214 let path = path.to_string();
215 Box::pin(async move {
216 use wasm_bindgen::JsCast;
217 use wasm_bindgen_futures::JsFuture;
218
219 let window = web_sys::window().ok_or("No global window")?;
220 let navigator = window.navigator();
221 let storage = navigator.storage();
222
223 let dir_handle_val = JsFuture::from(storage.get_directory())
224 .await
225 .map_err(|e| format!("{:?}", e))?;
226 let dir_handle: web_sys::FileSystemDirectoryHandle = dir_handle_val.unchecked_into();
227
228 let file_handle_val = JsFuture::from(dir_handle.get_file_handle(&path))
229 .await
230 .map_err(|e| format!("{:?}", e))?;
231 let file_handle: web_sys::FileSystemFileHandle = file_handle_val.unchecked_into();
232
233 let file_val = JsFuture::from(file_handle.get_file())
234 .await
235 .map_err(|e| format!("{:?}", e))?;
236 let file: web_sys::File = file_val.unchecked_into();
237
238 let array_buffer_val = JsFuture::from(file.array_buffer())
239 .await
240 .map_err(|e| format!("{:?}", e))?;
241 let array_buffer: js_sys::ArrayBuffer = array_buffer_val.unchecked_into();
242
243 let uint8_array = js_sys::Uint8Array::new(&array_buffer);
244 Ok(uint8_array.to_vec())
245 })
246 }
247
248 fn write_chunk(
249 &self,
250 path: &str,
251 data: &[u8],
252 ) -> Pin<Box<dyn Future<Output = Result<(), String>>>> {
253 let path = path.to_string();
254 let data = data.to_vec();
255 Box::pin(async move {
256 use wasm_bindgen::JsCast;
257 use wasm_bindgen_futures::JsFuture;
258
259 let window = web_sys::window().ok_or("No global window")?;
260 let navigator = window.navigator();
261 let storage = navigator.storage();
262
263 let dir_handle_val = JsFuture::from(storage.get_directory())
264 .await
265 .map_err(|e| format!("{:?}", e))?;
266 let dir_handle: web_sys::FileSystemDirectoryHandle = dir_handle_val.unchecked_into();
267
268 let options = web_sys::FileSystemGetFileOptions::new();
269 options.set_create(true);
270 let file_handle_val =
271 JsFuture::from(dir_handle.get_file_handle_with_options(&path, &options))
272 .await
273 .map_err(|e| format!("{:?}", e))?;
274 let file_handle: web_sys::FileSystemFileHandle = file_handle_val.unchecked_into();
275
276 let writable_val = JsFuture::from(file_handle.create_writable())
277 .await
278 .map_err(|e| format!("{:?}", e))?;
279 let writable: web_sys::FileSystemWritableFileStream = writable_val.unchecked_into();
280
281 let uint8_array = js_sys::Uint8Array::from(data.as_slice());
282 JsFuture::from(
283 writable
284 .write_with_buffer_source(&uint8_array)
285 .map_err(|e| format!("{:?}", e))?,
286 )
287 .await
288 .map_err(|e| format!("{:?}", e))?;
289 JsFuture::from(writable.close())
290 .await
291 .map_err(|e| format!("{:?}", e))?;
292
293 Ok(())
294 })
295 }
296}