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