use std::path::Path; use thiserror::Error; use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen_futures::JsFuture; use web_sys::{ File, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetDirectoryOptions, FileSystemGetFileOptions, FileSystemWritableFileStream, Url, js_sys, window, }; 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 buffer = js_sys::ArrayBuffer::new(data.len() as u32); let u8arr = js_sys::Uint8Array::new(&buffer); for (idx, v) in data.iter().enumerate() { u8arr.set_index(idx as u32, *v); } let write_promise = write_handle.write_with_js_u8_array(&u8arr).unwrap(); // 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("js opfs error: {0}")] Error(String), } // TODO: better errors impl From for OPFSError { fn from(value: JsValue) -> Self { Self::Error( value .dyn_into::() .ok() .and_then(|err| err.message().as_string()) .unwrap_or(String::from("")), ) } }