use std::io::{Error, ErrorKind, Result};
use js_sys::Promise;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{
DomException, Exception, FileSystemDirectoryHandle, FileSystemFileHandle, StorageManager,
WorkerGlobalScope,
};
mod directory;
mod external;
mod file;
mod metadata;
mod open_options;
mod read;
mod seek;
mod write;
pub use directory::*;
pub use file::*;
pub use metadata::*;
pub use open_options::*;
fn from_js_error(value: JsValue) -> Error {
if value.is_instance_of::<DomException>() {
let handle = value.unchecked_into::<DomException>();
return match handle.name().as_str() {
"NotAllowedError" => Error::new(ErrorKind::PermissionDenied, handle.message()),
"NotFoundError" => Error::new(ErrorKind::NotFound, handle.message()),
"TypeMismatchError" => Error::new(ErrorKind::Other, handle.message()),
"InvalidModificationError" => {
Error::new(ErrorKind::DirectoryNotEmpty, handle.message())
}
_ => Error::other(handle.name()),
};
}
if value.is_instance_of::<Exception>() {
let handle = value.unchecked_into::<Exception>();
return Error::other(handle.name());
}
if let Some(err) = value.dyn_ref::<web_sys::js_sys::TypeError>() {
let message: String = err.message().into();
return Error::new(ErrorKind::InvalidInput, message);
}
Error::other("unknown error")
}
fn from_js<V: JsCast>(value: JsValue) -> Result<V> {
value.dyn_into::<V>().map_err(from_js_error)
}
async fn resolve<V: JsCast>(promise: Promise) -> Result<V> {
from_js(JsFuture::from(promise).await.map_err(from_js_error)?)
}
async fn resolve_undefined(promise: Promise) -> Result<()> {
JsFuture::from(promise).await.map_err(from_js_error)?;
Ok(())
}
async fn storage() -> Result<StorageManager> {
if js_sys::global().is_instance_of::<WorkerGlobalScope>() {
let global = js_sys::global().unchecked_into::<WorkerGlobalScope>();
return Ok(global.navigator().storage());
}
Err(Error::new(
ErrorKind::Unsupported,
"storage manage not accessible",
))
}
async fn root_directory() -> Result<FileSystemDirectoryHandle> {
let storage = storage().await?;
let res = JsFuture::from(storage.get_directory())
.await
.map_err(|_| Error::new(ErrorKind::Unsupported, "unable to access root directory"))?;
res.dyn_into::<FileSystemDirectoryHandle>()
.map_err(|_| Error::new(ErrorKind::Unsupported, "unable to cast root directory"))
}
#[derive(Clone, Copy, Debug)]
pub enum FileType {
File,
Directory,
}
impl FileType {
pub const fn is_dir(&self) -> bool {
matches!(self, Self::Directory)
}
pub const fn is_file(&self) -> bool {
matches!(self, Self::File)
}
pub const fn is_symlink(&self) -> bool {
false
}
}
#[derive(Debug)]
enum Entry {
Directory(FileSystemDirectoryHandle),
File(FileSystemFileHandle),
}
impl Entry {
fn name(&self) -> String {
match self {
Self::Directory(inner) => inner.name(),
Self::File(inner) => inner.name(),
}
}
fn try_from_js_value(value: JsValue) -> Result<Self> {
if value.is_instance_of::<FileSystemFileHandle>() {
Ok(Self::File(value.unchecked_into()))
} else if value.is_instance_of::<FileSystemDirectoryHandle>() {
Ok(Self::Directory(value.unchecked_into()))
} else {
Err(Error::new(ErrorKind::InvalidData, "unable to caste handle"))
}
}
fn file_type(&self) -> FileType {
match self {
Self::Directory(_) => FileType::Directory,
Self::File(_) => FileType::File,
}
}
}
impl Entry {
async fn from_directory(parent: &FileSystemDirectoryHandle, name: &str) -> Result<Self> {
let dir_promise = parent.get_directory_handle(name);
let dir_res = crate::resolve::<FileSystemDirectoryHandle>(dir_promise).await;
let file_promise = parent.get_file_handle(name);
let file_res = crate::resolve::<FileSystemFileHandle>(file_promise).await;
dir_res.map(Self::Directory).or(file_res.map(Self::File))
}
}