use std::path::Path; use thiserror::Error; use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; use web_sys::{ window, File, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetDirectoryOptions, FileSystemGetFileOptions, FileSystemWritableFileStream, Url }; use crate::FileStore; #[derive(Clone)] pub struct FilesOPFS { directory: String, } impl FilesOPFS { pub async fn new(directory: impl AsRef) -> Result { let directory = directory.as_ref(); let directory_string = directory.to_string(); let promise = window().unwrap().navigator().storage().get_directory(); let opfs_root: FileSystemDirectoryHandle = JsFuture::from(promise).await?.into(); let options = FileSystemGetDirectoryOptions::new(); options.set_create(true); let directory: FileSystemDirectoryHandle = JsFuture::from(opfs_root.get_directory_handle_with_options(directory, &options)) .await? .into(); Ok(Self { directory: directory_string }) } pub async fn get_src(&self, file_name: impl AsRef) -> Result { let promise = window().unwrap().navigator().storage().get_directory(); let opfs_root: FileSystemDirectoryHandle = JsFuture::from(promise).await?.into(); let directory: FileSystemDirectoryHandle = JsFuture::from(opfs_root.get_directory_handle(&self.directory)) .await? .into(); let handle: FileSystemFileHandle = JsFuture::from(directory.get_file_handle(file_name.as_ref())) .await? .into(); let file: File = JsFuture::from(handle.get_file()).await?.into(); let src = Url::create_object_url_with_blob(&file)?; Ok(src) } } impl FileStore for FilesOPFS { type Err = OPFSError; async fn is_stored(&self, name: &str) -> Result { let promise = window().unwrap().navigator().storage().get_directory(); let opfs_root: FileSystemDirectoryHandle = JsFuture::from(promise).await?.into(); let directory: FileSystemDirectoryHandle = JsFuture::from(opfs_root.get_directory_handle(&self.directory)) .await? .into(); let stored = JsFuture::from(directory.get_file_handle(name)) .await .map(|_| true) // TODO: distinguish between other errors and notfound .unwrap_or(false); Ok(stored) } async fn store(&self, name: &str, data: &[u8]) -> Result<(), Self::Err> { let promise = window().unwrap().navigator().storage().get_directory(); let opfs_root: FileSystemDirectoryHandle = JsFuture::from(promise).await?.into(); let directory: FileSystemDirectoryHandle = JsFuture::from(opfs_root.get_directory_handle(&self.directory)) .await? .into(); let options = FileSystemGetFileOptions::new(); options.set_create(true); let handle: FileSystemFileHandle = JsFuture::from(directory.get_file_handle_with_options(name, &options)) .await? .into(); let write_handle: FileSystemWritableFileStream = JsFuture::from(handle.create_writable()).await?.into(); let write_promise = write_handle.write_with_u8_array(data)?; let _ = JsFuture::from(write_promise).await?; let _ = JsFuture::from(write_handle.close()).await?; Ok(()) } async fn delete(&self, name: &str) -> Result<(), Self::Err> { let promise = window().unwrap().navigator().storage().get_directory(); let opfs_root: FileSystemDirectoryHandle = JsFuture::from(promise).await?.into(); let directory: FileSystemDirectoryHandle = JsFuture::from(opfs_root.get_directory_handle(&self.directory)) .await? .into(); let _ = JsFuture::from(directory.remove_entry(name)).await?; Ok(()) } } #[derive(Error, Clone, Debug)] pub enum OPFSError { #[error("not found")] NotFound, } // TODO: better errors impl From for OPFSError { fn from(value: JsValue) -> Self { Self::NotFound } }