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