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