1use rsqlite_vfs::{
36 check_import_db,
37 ffi::{
38 sqlite3_file, sqlite3_filename, sqlite3_vfs, sqlite3_vfs_register, sqlite3_vfs_unregister,
39 SQLITE_CANTOPEN, SQLITE_ERROR, SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN, SQLITE_IOERR,
40 SQLITE_IOERR_DELETE, SQLITE_OK, SQLITE_OPEN_DELETEONCLOSE, SQLITE_OPEN_MAIN_DB,
41 SQLITE_OPEN_MAIN_JOURNAL, SQLITE_OPEN_SUPER_JOURNAL, SQLITE_OPEN_WAL,
42 },
43 register_vfs, registered_vfs, ImportDbError, OsCallback, RegisterVfsError, SQLiteIoMethods,
44 SQLiteVfs, SQLiteVfsFile, VfsAppData, VfsError, VfsFile, VfsResult, VfsStore,
45};
46use std::collections::{HashMap, HashSet};
47use std::time::Duration;
48use std::{
49 cell::{Cell, RefCell},
50 marker::PhantomData,
51};
52
53use js_sys::{Array, DataView, IteratorNext, Reflect, Uint8Array};
54use wasm_bindgen::{JsCast, JsValue};
55use wasm_bindgen_futures::JsFuture;
56use web_sys::{
57 FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetDirectoryOptions,
58 FileSystemGetFileOptions, FileSystemReadWriteOptions, FileSystemSyncAccessHandle,
59 WorkerGlobalScope,
60};
61
62const SECTOR_SIZE: usize = 4096;
63const HEADER_MAX_FILENAME_SIZE: usize = 512;
64const HEADER_FLAGS_SIZE: usize = 4;
65const HEADER_CORPUS_SIZE: usize = HEADER_MAX_FILENAME_SIZE + HEADER_FLAGS_SIZE;
66const HEADER_OFFSET_FLAGS: usize = HEADER_MAX_FILENAME_SIZE;
67const HEADER_OFFSET_DATA: usize = SECTOR_SIZE;
68
69const PERSISTENT_FILE_TYPES: i32 =
70 SQLITE_OPEN_MAIN_DB | SQLITE_OPEN_MAIN_JOURNAL | SQLITE_OPEN_SUPER_JOURNAL | SQLITE_OPEN_WAL;
71
72type Result<T, E = OpfsSAHError> = std::result::Result<T, E>;
73
74fn read_write_options(at: f64) -> FileSystemReadWriteOptions {
75 let options = FileSystemReadWriteOptions::new();
76 options.set_at(at);
77 options
78}
79
80struct SyncAccessFile {
81 handle: FileSystemSyncAccessHandle,
82 opaque: String,
83}
84
85struct OpfsSAHPool {
86 dh_opaque: FileSystemDirectoryHandle,
89 header_buffer: Uint8Array,
91 header_buffer_view: DataView,
93 available_files: RefCell<Vec<SyncAccessFile>>,
95 map_filename_to_file: RefCell<HashMap<String, SyncAccessFile>>,
97 is_paused: Cell<bool>,
99 open_files: RefCell<HashSet<String>>,
101 vfs: Cell<(*mut sqlite3_vfs, bool)>,
103 random: fn(&mut [u8]),
104}
105
106impl OpfsSAHPool {
107 async fn new<C: OsCallback>(options: &OpfsSAHPoolCfg) -> Result<OpfsSAHPool> {
108 const OPAQUE_DIR_NAME: &str = ".opaque";
109
110 let vfs_dir = &options.directory;
111 let capacity = options.initial_capacity;
112 let clear_files = options.clear_on_init;
113
114 let create_option = FileSystemGetDirectoryOptions::new();
115 create_option.set_create(true);
116
117 let mut handle: FileSystemDirectoryHandle = JsFuture::from(
118 js_sys::global()
119 .dyn_into::<WorkerGlobalScope>()
120 .map_err(|_| OpfsSAHError::NotSupported)?
121 .navigator()
122 .storage()
123 .get_directory(),
124 )
125 .await
126 .map_err(OpfsSAHError::GetDirHandle)?
127 .into();
128
129 for dir in vfs_dir.split('/').filter(|x| !x.is_empty()) {
130 let next =
131 JsFuture::from(handle.get_directory_handle_with_options(dir, &create_option))
132 .await
133 .map_err(OpfsSAHError::GetDirHandle)?
134 .into();
135 handle = next;
136 }
137
138 let dh_opaque = JsFuture::from(
139 handle.get_directory_handle_with_options(OPAQUE_DIR_NAME, &create_option),
140 )
141 .await
142 .map_err(OpfsSAHError::GetDirHandle)?
143 .into();
144
145 let ap_body = Uint8Array::new_with_length(HEADER_CORPUS_SIZE as _);
146 let dv_body = DataView::new(
147 &ap_body.buffer(),
148 ap_body.byte_offset() as usize,
149 (ap_body.byte_length() - ap_body.byte_offset()) as usize,
150 );
151
152 let pool = Self {
153 dh_opaque,
154 header_buffer: ap_body,
155 header_buffer_view: dv_body,
156 map_filename_to_file: RefCell::new(HashMap::new()),
157 available_files: RefCell::new(Vec::new()),
158 is_paused: Cell::new(false),
159 open_files: RefCell::new(HashSet::new()),
160 vfs: Cell::new((std::ptr::null_mut(), false)),
161 random: C::random,
162 };
163
164 pool.acquire_access_handles(clear_files).await?;
165 pool.reserve_minimum_capacity(capacity).await?;
166
167 Ok(pool)
168 }
169
170 async fn add_capacity(&self, n: u32) -> Result<u32> {
171 for _ in 0..n {
172 let opaque = rsqlite_vfs::random_name(self.random);
173 let handle: FileSystemFileHandle =
174 JsFuture::from(self.dh_opaque.get_file_handle_with_options(&opaque, &{
175 let options = FileSystemGetFileOptions::new();
176 options.set_create(true);
177 options
178 }))
179 .await
180 .map_err(OpfsSAHError::GetFileHandle)?
181 .into();
182 let sah: FileSystemSyncAccessHandle =
183 JsFuture::from(handle.create_sync_access_handle())
184 .await
185 .map_err(OpfsSAHError::CreateSyncAccessHandle)?
186 .into();
187 let file = SyncAccessFile {
188 handle: sah,
189 opaque,
190 };
191 self.set_associated_filename(&file.handle, None, 0)?;
192 self.available_files.borrow_mut().push(file);
193 }
194 Ok(self.get_capacity())
195 }
196
197 async fn reserve_minimum_capacity(&self, min: u32) -> Result<()> {
198 self.add_capacity(min.saturating_sub(self.get_capacity()))
199 .await?;
200 Ok(())
201 }
202
203 #[allow(clippy::await_holding_refcell_ref)]
204 async fn reduce_capacity(&self, n: u32) -> Result<u32> {
205 let mut available_files = self.available_files.borrow_mut();
206 let available_length = available_files.len();
207 let max_reduce = available_length.min(n as usize);
208 let files = available_files.split_off(available_length - max_reduce);
209 drop(available_files);
211
212 for file in files {
213 file.handle.close();
214 JsFuture::from(self.dh_opaque.remove_entry(&file.opaque))
215 .await
216 .map_err(OpfsSAHError::RemoveEntity)?;
217 }
218
219 Ok(max_reduce as u32)
220 }
221
222 fn get_capacity(&self) -> u32 {
223 (self.map_filename_to_file.borrow().len() + self.available_files.borrow().len()) as u32
224 }
225
226 fn get_file_count(&self) -> u32 {
227 self.map_filename_to_file.borrow().len() as u32
228 }
229
230 fn get_filenames(&self) -> Vec<String> {
231 self.map_filename_to_file.borrow().keys().cloned().collect()
232 }
233
234 fn get_associated_filename(&self, sah: &FileSystemSyncAccessHandle) -> Result<Option<String>> {
235 sah.read_with_buffer_source_and_options(&self.header_buffer, &read_write_options(0.0))
236 .map_err(OpfsSAHError::Read)?;
237 let flags = self.header_buffer_view.get_uint32(HEADER_OFFSET_FLAGS);
238 if self.header_buffer.get_index(0) != 0
239 && ((flags & SQLITE_OPEN_DELETEONCLOSE as u32 != 0)
240 || (flags & PERSISTENT_FILE_TYPES as u32) == 0)
241 {
242 return Ok(None);
243 }
244
245 let name_length = self
246 .header_buffer
247 .to_vec()
248 .iter()
249 .position(|&x| x == 0)
250 .unwrap_or_default();
251 if name_length == 0 {
252 sah.truncate_with_u32(HEADER_OFFSET_DATA as u32)
253 .map_err(OpfsSAHError::Truncate)?;
254 return Ok(None);
255 }
256 let filename =
258 String::from_utf8(self.header_buffer.subarray(0, name_length as u32).to_vec()).unwrap();
259 Ok(Some(filename))
260 }
261
262 fn set_associated_filename(
263 &self,
264 sah: &FileSystemSyncAccessHandle,
265 filename: Option<&str>,
266 flags: i32,
267 ) -> Result<()> {
268 self.header_buffer_view
269 .set_uint32(HEADER_OFFSET_FLAGS, flags as u32);
270
271 if let Some(filename) = filename {
272 if filename.is_empty() {
273 return Err(OpfsSAHError::Generic("Filename is empty".into()));
274 }
275 if HEADER_MAX_FILENAME_SIZE <= filename.len() + 1 {
276 return Err(OpfsSAHError::Generic(format!(
277 "Filename too long: {filename}"
278 )));
279 }
280 self.header_buffer
281 .subarray(0, filename.len() as u32)
282 .copy_from(filename.as_bytes());
283 self.header_buffer
284 .fill(0, filename.len() as u32, HEADER_MAX_FILENAME_SIZE as u32);
285 } else {
286 self.header_buffer
287 .fill(0, 0, HEADER_MAX_FILENAME_SIZE as u32);
288 sah.truncate_with_u32(HEADER_OFFSET_DATA as u32)
289 .map_err(OpfsSAHError::Truncate)?;
290 }
291
292 sah.write_with_js_u8_array_and_options(&self.header_buffer, &read_write_options(0.0))
293 .map_err(OpfsSAHError::Write)?;
294
295 Ok(())
296 }
297
298 async fn acquire_access_handles(&self, clear_files: bool) -> Result<()> {
299 let iter = self.dh_opaque.entries();
300 while let Ok(future) = iter.next() {
301 let next: IteratorNext = JsFuture::from(future)
302 .await
303 .map_err(OpfsSAHError::IterHandle)?
304 .into();
305 if next.done() {
306 break;
307 }
308 let array: Array = next.value().into();
309 let opaque = array
310 .get(0)
311 .as_string()
312 .ok_or_else(|| OpfsSAHError::Generic("Failed to get file's opaque name".into()))?;
313 let value = array.get(1);
314 let kind = Reflect::get(&value, &JsValue::from("kind"))
315 .map_err(OpfsSAHError::Reflect)?
316 .as_string();
317 if kind.as_deref() == Some("file") {
318 let handle = FileSystemFileHandle::from(value);
319 let sah = JsFuture::from(handle.create_sync_access_handle())
320 .await
321 .map_err(OpfsSAHError::CreateSyncAccessHandle)?;
322 let sah = FileSystemSyncAccessHandle::from(sah);
323 let file = SyncAccessFile {
324 handle: sah,
325 opaque,
326 };
327 let clear_file = |file: SyncAccessFile| -> Result<()> {
328 self.set_associated_filename(&file.handle, None, 0)?;
329 self.available_files.borrow_mut().push(file);
330 Ok(())
331 };
332 if clear_files {
333 clear_file(file)?;
334 } else if let Some(filename) = self.get_associated_filename(&file.handle)? {
335 self.map_filename_to_file
336 .borrow_mut()
337 .insert(filename, file);
338 } else {
339 clear_file(file)?;
340 }
341 }
342 }
343
344 Ok(())
345 }
346
347 fn release_access_handles(&self) {
348 for file in std::mem::take(&mut *self.available_files.borrow_mut())
349 .into_iter()
350 .chain(std::mem::take(&mut *self.map_filename_to_file.borrow_mut()).into_values())
351 {
352 file.handle.close();
353 }
354 }
355
356 fn delete_file(&self, filename: &str) -> Result<bool> {
357 let mut map_filename_to_file = self.map_filename_to_file.borrow_mut();
358 let mut available_files = self.available_files.borrow_mut();
359
360 if let Some(file) = map_filename_to_file.remove(filename) {
361 available_files.push(file);
362 let Some(file) = available_files.last() else {
363 unreachable!();
364 };
365 self.set_associated_filename(&file.handle, None, 0)?;
366 Ok(true)
367 } else {
368 Ok(false)
369 }
370 }
371
372 fn has_filename(&self, filename: &str) -> bool {
373 self.map_filename_to_file.borrow().contains_key(filename)
374 }
375
376 fn with_file<E, R, F: Fn(&SyncAccessFile) -> Result<R, E>>(
377 &self,
378 filename: &str,
379 f: F,
380 ) -> Option<Result<R, E>> {
381 self.map_filename_to_file.borrow().get(filename).map(f)
382 }
383
384 fn with_file_mut<E, R, F: Fn(&mut SyncAccessFile) -> Result<R, E>>(
385 &self,
386 filename: &str,
387 f: F,
388 ) -> Option<Result<R, E>> {
389 self.map_filename_to_file
390 .borrow_mut()
391 .get_mut(filename)
392 .map(f)
393 }
394
395 fn with_new_file<E, F: Fn(&SyncAccessFile) -> Result<(), E>>(
396 &self,
397 filename: &str,
398 flags: i32,
399 f: F,
400 ) -> Result<Result<(), E>> {
401 let mut map_filename_to_file = self.map_filename_to_file.borrow_mut();
402 let mut available_files = self.available_files.borrow_mut();
403 if map_filename_to_file.contains_key(filename) {
404 return Err(OpfsSAHError::Generic(format!(
405 "{filename} file already exists"
406 )));
407 }
408 let file = available_files
409 .pop()
410 .ok_or_else(|| OpfsSAHError::Generic("No files available in the pool".into()))?;
411 map_filename_to_file.insert(filename.into(), file);
412
413 let Some(file) = map_filename_to_file.get(filename) else {
414 unreachable!();
415 };
416 self.set_associated_filename(&file.handle, Some(filename), flags)?;
417 Ok(f(file))
418 }
419
420 fn pause_vfs(&self) -> Result<()> {
421 if self.is_paused.get() {
422 return Ok(());
423 }
424
425 if !self.open_files.borrow().is_empty() {
426 return Err(OpfsSAHError::Generic(
427 "Cannot pause: files may be in use".to_string(),
428 ));
429 }
430
431 let (vfs, _) = self.vfs.get();
432 if !vfs.is_null() {
433 unsafe {
434 sqlite3_vfs_unregister(vfs);
435 }
436 }
437 self.release_access_handles();
438
439 self.is_paused.set(true);
440
441 Ok(())
442 }
443
444 async fn unpause_vfs(&self) -> Result<()> {
445 if !self.is_paused.get() {
446 return Ok(());
447 }
448
449 self.acquire_access_handles(false).await?;
450
451 let (vfs, make_default) = self.vfs.get();
452 if vfs.is_null() {
453 return Err(OpfsSAHError::Generic(
454 "VFS pointer is null. Did you forget to install?".to_string(),
455 ));
456 }
457
458 match unsafe { sqlite3_vfs_register(vfs, i32::from(make_default)) } {
459 SQLITE_OK => {
460 self.is_paused.set(false);
461 Ok(())
462 }
463 error_code => Err(OpfsSAHError::Generic(format!(
464 "Failed to register VFS (SQLite error code: {error_code})"
465 ))),
466 }
467 }
468
469 fn export_db(&self, filename: &str) -> Result<Vec<u8>> {
470 let files = self.map_filename_to_file.borrow();
471 let file = files
472 .get(filename)
473 .ok_or_else(|| OpfsSAHError::Generic(format!("File not found: {filename}")))?;
474
475 let sah = &file.handle;
476 let actual_size = (sah.get_size().map_err(OpfsSAHError::GetSize)?
477 - HEADER_OFFSET_DATA as f64)
478 .max(0.0) as usize;
479
480 let mut data = vec![0; actual_size];
481 if actual_size > 0 {
482 let read = sah
483 .read_with_u8_array_and_options(
484 &mut data,
485 &read_write_options(HEADER_OFFSET_DATA as f64),
486 )
487 .map_err(OpfsSAHError::Read)?;
488 if read != actual_size as f64 {
489 return Err(OpfsSAHError::Generic(format!(
490 "Expected to read {actual_size} bytes but read {read}.",
491 )));
492 }
493 }
494 Ok(data)
495 }
496
497 fn import_db(&self, filename: &str, bytes: &[u8]) -> Result<()> {
498 check_import_db(bytes)?;
499 self.import_db_unchecked(filename, bytes, true)
500 }
501
502 fn import_db_unchecked(&self, filename: &str, bytes: &[u8], clear_wal: bool) -> Result<()> {
503 self.with_new_file(filename, SQLITE_OPEN_MAIN_DB, |file| {
504 let sah = &file.handle;
505 let length = bytes.len() as f64;
506 let written = sah
507 .write_with_u8_array_and_options(
508 bytes,
509 &read_write_options(HEADER_OFFSET_DATA as f64),
510 )
511 .map_err(OpfsSAHError::Write)?;
512
513 if written != length {
514 return Err(OpfsSAHError::Generic(format!(
515 "Expected to write {length} bytes but wrote {written}.",
516 )));
517 }
518
519 if clear_wal {
520 sah.write_with_u8_array_and_options(
522 &[1, 1],
523 &read_write_options((HEADER_OFFSET_DATA + 18) as f64),
524 )
525 .map_err(OpfsSAHError::Write)?;
526 }
527
528 Ok(())
529 })?
530 }
531}
532
533impl VfsFile for SyncAccessFile {
534 fn read(&self, buf: &mut [u8], offset: usize) -> VfsResult<bool> {
535 let n_read = self
536 .handle
537 .read_with_u8_array_and_options(
538 buf,
539 &read_write_options((HEADER_OFFSET_DATA + offset) as f64),
540 )
541 .map_err(OpfsSAHError::Read)
542 .map_err(|err| err.vfs_err(SQLITE_IOERR))?;
543
544 if (n_read as usize) < buf.len() {
545 buf[n_read as usize..].fill(0);
546 return Ok(false);
547 }
548
549 Ok(true)
550 }
551
552 fn write(&mut self, buf: &[u8], offset: usize) -> VfsResult<()> {
553 let n_write = self
554 .handle
555 .write_with_u8_array_and_options(
556 buf,
557 &read_write_options((HEADER_OFFSET_DATA + offset) as f64),
558 )
559 .map_err(OpfsSAHError::Write)
560 .map_err(|err| err.vfs_err(SQLITE_IOERR))?;
561
562 if buf.len() != n_write as usize {
563 return Err(VfsError::new(SQLITE_ERROR, "failed to write file".into()));
564 }
565
566 Ok(())
567 }
568
569 fn truncate(&mut self, size: usize) -> VfsResult<()> {
570 self.handle
571 .truncate_with_f64((HEADER_OFFSET_DATA + size) as f64)
572 .map_err(OpfsSAHError::Truncate)
573 .map_err(|err| err.vfs_err(SQLITE_IOERR))
574 }
575
576 fn flush(&mut self) -> VfsResult<()> {
577 FileSystemSyncAccessHandle::flush(&self.handle)
578 .map_err(OpfsSAHError::Flush)
579 .map_err(|err| err.vfs_err(SQLITE_IOERR))
580 }
581
582 fn size(&self) -> VfsResult<usize> {
583 Ok(self
584 .handle
585 .get_size()
586 .map_err(OpfsSAHError::GetSize)
587 .map_err(|err| err.vfs_err(SQLITE_IOERR))? as usize
588 - HEADER_OFFSET_DATA)
589 }
590}
591
592type SyncAccessHandleAppData = OpfsSAHPool;
593
594struct SyncAccessHandleStore;
595
596impl VfsStore<SyncAccessFile, SyncAccessHandleAppData> for SyncAccessHandleStore {
597 fn add_file(vfs: *mut sqlite3_vfs, filename: &str, flags: i32) -> VfsResult<()> {
598 let pool = unsafe { Self::app_data(vfs) };
599
600 pool.with_new_file(filename, flags, |_| Ok(()))
601 .map_err(|err| err.vfs_err(SQLITE_CANTOPEN))?
602 }
603
604 fn contains_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<bool> {
605 let pool = unsafe { Self::app_data(vfs) };
606 Ok(pool.has_filename(file))
607 }
608
609 fn delete_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<()> {
610 let pool = unsafe { Self::app_data(vfs) };
611 pool.delete_file(file)
612 .map_err(|err| err.vfs_err(SQLITE_IOERR_DELETE))?;
613 Ok(())
614 }
615
616 fn with_file<F: Fn(&SyncAccessFile) -> VfsResult<i32>>(
617 vfs_file: &SQLiteVfsFile,
618 f: F,
619 ) -> VfsResult<i32> {
620 let name = unsafe { vfs_file.name() };
621 let pool = unsafe { Self::app_data(vfs_file.vfs) };
622 pool.with_file(name, f)
623 .ok_or_else(|| VfsError::new(SQLITE_IOERR, format!("{name} not found")))?
624 }
625
626 fn with_file_mut<F: Fn(&mut SyncAccessFile) -> VfsResult<i32>>(
627 vfs_file: &SQLiteVfsFile,
628 f: F,
629 ) -> VfsResult<i32> {
630 let name = unsafe { vfs_file.name() };
631 let pool = unsafe { Self::app_data(vfs_file.vfs) };
632 pool.with_file_mut(name, f)
633 .ok_or_else(|| VfsError::new(SQLITE_IOERR, format!("{name} not found")))?
634 }
635}
636
637struct SyncAccessHandleIoMethods;
638
639impl SQLiteIoMethods for SyncAccessHandleIoMethods {
640 type File = SyncAccessFile;
641 type AppData = SyncAccessHandleAppData;
642 type Store = SyncAccessHandleStore;
643
644 const VERSION: ::std::os::raw::c_int = 1;
645
646 unsafe extern "C" fn xSectorSize(_pFile: *mut sqlite3_file) -> ::std::os::raw::c_int {
647 SECTOR_SIZE as i32
648 }
649
650 unsafe extern "C" fn xCheckReservedLock(
651 _pFile: *mut sqlite3_file,
652 pResOut: *mut ::std::os::raw::c_int,
653 ) -> ::std::os::raw::c_int {
654 *pResOut = 1;
655 SQLITE_OK
656 }
657
658 unsafe extern "C" fn xDeviceCharacteristics(
659 _pFile: *mut sqlite3_file,
660 ) -> ::std::os::raw::c_int {
661 SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
662 }
663
664 unsafe extern "C" fn xClose(pFile: *mut sqlite3_file) -> ::std::os::raw::c_int {
665 let vfs_file = SQLiteVfsFile::from_file(pFile);
666 let file = vfs_file.name().to_string();
668 let app_data = SyncAccessHandleStore::app_data(vfs_file.vfs);
669 let ret = Self::xCloseImpl(pFile);
670 if ret == SQLITE_OK {
671 let exist = app_data.open_files.borrow_mut().remove(&file);
672 debug_assert!(exist, "DB closed without open");
673 }
674 ret
675 }
676}
677
678struct SyncAccessHandleVfs<C>(PhantomData<C>);
679
680impl<C> SQLiteVfs<SyncAccessHandleIoMethods> for SyncAccessHandleVfs<C>
681where
682 C: OsCallback,
683{
684 const VERSION: ::std::os::raw::c_int = 2;
685 const MAX_PATH_SIZE: ::std::os::raw::c_int = HEADER_MAX_FILENAME_SIZE as _;
686
687 unsafe extern "C" fn xOpen(
688 pVfs: *mut sqlite3_vfs,
689 zName: sqlite3_filename,
690 pFile: *mut sqlite3_file,
691 flags: ::std::os::raw::c_int,
692 pOutFlags: *mut ::std::os::raw::c_int,
693 ) -> ::std::os::raw::c_int {
694 let ret = Self::xOpenImpl(pVfs, zName, pFile, flags, pOutFlags);
695 if ret == SQLITE_OK {
696 let app_data = SyncAccessHandleStore::app_data(pVfs);
697
698 let vfs_file = SQLiteVfsFile::from_file(pFile);
700 app_data
701 .open_files
702 .borrow_mut()
703 .insert(vfs_file.name().into());
704 }
705 ret
706 }
707
708 fn sleep(dur: Duration) {
709 C::sleep(dur);
710 }
711
712 fn random(buf: &mut [u8]) {
713 C::random(buf);
714 }
715
716 fn epoch_timestamp_in_ms() -> i64 {
717 C::epoch_timestamp_in_ms()
718 }
719}
720
721pub struct OpfsSAHPoolCfgBuilder(OpfsSAHPoolCfg);
723
724impl OpfsSAHPoolCfgBuilder {
725 pub fn new() -> Self {
726 Self(OpfsSAHPoolCfg::default())
727 }
728
729 pub fn vfs_name(mut self, name: &str) -> Self {
731 self.0.vfs_name = name.into();
732 self
733 }
734
735 pub fn directory(mut self, directory: &str) -> Self {
737 self.0.directory = directory.into();
738 self
739 }
740
741 pub fn clear_on_init(mut self, set: bool) -> Self {
746 self.0.clear_on_init = set;
747 self
748 }
749
750 pub fn initial_capacity(mut self, cap: u32) -> Self {
753 self.0.initial_capacity = cap;
754 self
755 }
756
757 pub fn build(self) -> OpfsSAHPoolCfg {
759 self.0
760 }
761}
762
763impl Default for OpfsSAHPoolCfgBuilder {
764 fn default() -> Self {
765 Self::new()
766 }
767}
768
769pub struct OpfsSAHPoolCfg {
771 pub vfs_name: String,
773 pub directory: String,
775 pub clear_on_init: bool,
780 pub initial_capacity: u32,
783}
784
785impl Default for OpfsSAHPoolCfg {
786 fn default() -> Self {
787 Self {
788 vfs_name: "opfs-sahpool".into(),
789 directory: ".opfs-sahpool".into(),
790 clear_on_init: false,
791 initial_capacity: 6,
792 }
793 }
794}
795
796#[derive(thiserror::Error, Debug)]
797pub enum OpfsSAHError {
798 #[error(transparent)]
799 Vfs(#[from] RegisterVfsError),
800 #[error(transparent)]
801 ImportDb(#[from] ImportDbError),
802 #[error("This vfs is only available in dedicated worker")]
803 NotSupported,
804 #[error("An error occurred while getting the directory handle")]
805 GetDirHandle(JsValue),
806 #[error("An error occurred while getting the file handle")]
807 GetFileHandle(JsValue),
808 #[error("An error occurred while creating sync access handle")]
809 CreateSyncAccessHandle(JsValue),
810 #[error("An error occurred while iterating")]
811 IterHandle(JsValue),
812 #[error("An error occurred while getting filename")]
813 GetPath(JsValue),
814 #[error("An error occurred while removing entity")]
815 RemoveEntity(JsValue),
816 #[error("An error occurred while getting size")]
817 GetSize(JsValue),
818 #[error("An error occurred while reading data")]
819 Read(JsValue),
820 #[error("An error occurred while writing data")]
821 Write(JsValue),
822 #[error("An error occurred while flushing data")]
823 Flush(JsValue),
824 #[error("An error occurred while truncating data")]
825 Truncate(JsValue),
826 #[error("An error occurred while getting data using reflect")]
827 Reflect(JsValue),
828 #[error("Generic error: {0}")]
829 Generic(String),
830}
831
832impl OpfsSAHError {
833 fn vfs_err(&self, code: i32) -> VfsError {
834 VfsError::new(code, format!("{self}"))
835 }
836}
837
838pub struct OpfsSAHPoolUtil {
840 pool: &'static VfsAppData<SyncAccessHandleAppData>,
841}
842
843impl OpfsSAHPoolUtil {
844 pub fn get_capacity(&self) -> u32 {
846 self.pool.get_capacity()
847 }
848
849 pub async fn add_capacity(&self, n: u32) -> Result<u32> {
851 self.pool.add_capacity(n).await
852 }
853
854 pub async fn reduce_capacity(&self, n: u32) -> Result<u32> {
857 self.pool.reduce_capacity(n).await
858 }
859
860 pub async fn reserve_minimum_capacity(&self, min: u32) -> Result<()> {
863 self.pool.reserve_minimum_capacity(min).await
864 }
865}
866
867impl OpfsSAHPoolUtil {
868 pub fn import_db(&self, filename: &str, bytes: &[u8]) -> Result<()> {
877 self.pool.import_db(filename, bytes)
878 }
879
880 pub fn import_db_unchecked(&self, filename: &str, bytes: &[u8]) -> Result<()> {
882 self.pool.import_db_unchecked(filename, bytes, false)
883 }
884
885 pub fn export_db(&self, filename: &str) -> Result<Vec<u8>> {
887 self.pool.export_db(filename)
888 }
889
890 pub fn delete_db(&self, filename: &str) -> Result<bool> {
892 self.pool.delete_file(filename)
893 }
894
895 pub async fn clear_all(&self) -> Result<()> {
897 self.pool.release_access_handles();
898 self.pool.acquire_access_handles(true).await?;
899 Ok(())
900 }
901
902 pub fn exists(&self, filename: &str) -> Result<bool> {
904 Ok(self.pool.has_filename(filename))
905 }
906
907 pub fn list(&self) -> Vec<String> {
909 self.pool.get_filenames()
910 }
911
912 pub fn count(&self) -> u32 {
914 self.pool.get_file_count()
915 }
916
917 pub fn pause_vfs(&self) -> Result<()> {
933 self.pool.pause_vfs()
934 }
935
936 pub async fn unpause_vfs(&self) -> Result<()> {
943 self.pool.unpause_vfs().await
944 }
945
946 pub fn is_paused(&self) -> bool {
948 self.pool.is_paused.get()
949 }
950}
951
952pub async fn install<C: OsCallback>(
958 options: &OpfsSAHPoolCfg,
959 default_vfs: bool,
960) -> Result<OpfsSAHPoolUtil> {
961 static REGISTER_GUARD: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
962 let _guard = REGISTER_GUARD.lock().await;
963
964 let vfs = match registered_vfs(&options.vfs_name)? {
965 Some(vfs) => vfs,
966 None => register_vfs::<SyncAccessHandleIoMethods, SyncAccessHandleVfs<C>>(
967 &options.vfs_name,
968 OpfsSAHPool::new::<C>(options).await?,
969 default_vfs,
970 )?,
971 };
972
973 let pool = unsafe { SyncAccessHandleStore::app_data(vfs) };
974 pool.vfs.set((vfs, default_vfs));
975
976 Ok(OpfsSAHPoolUtil { pool })
977}
978
979#[cfg(test)]
980mod tests {
981 use super::{
982 OpfsSAHPool, OpfsSAHPoolCfgBuilder, SyncAccessFile, SyncAccessHandleAppData,
983 SyncAccessHandleStore,
984 };
985 use rsqlite_vfs::{test_suite::test_vfs_store, VfsAppData};
986 use wasm_bindgen_test::wasm_bindgen_test;
987
988 #[wasm_bindgen_test]
989 async fn test_opfs_vfs_store() {
990 let data = OpfsSAHPool::new::<sqlite_wasm_rs::WasmOsCallback>(
991 &OpfsSAHPoolCfgBuilder::new()
992 .directory("test_opfs_suite")
993 .build(),
994 )
995 .await
996 .unwrap();
997
998 test_vfs_store::<SyncAccessHandleAppData, SyncAccessFile, SyncAccessHandleStore>(
999 VfsAppData::new(data),
1000 )
1001 .unwrap();
1002 }
1003}