blob: 326d26c2de089cfd5412485184723f285328b2d2 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use super::OPFS_SCHEME;
use opendal_core::raw::*;
use opendal_core::*;
#[derive(Debug, Clone)]
pub struct OpfsCore {
pub root: String,
pub info: ServiceInfo,
pub capability: Capability,
}
impl OpfsCore {
pub fn new(root: String) -> Self {
let info = ServiceInfo::new(OPFS_SCHEME, &root, "opfs");
let capability = Capability {
stat: true,
read: true,
list: true,
create_dir: true,
write: true,
write_can_empty: true,
write_can_multi: true,
delete: true,
..Default::default()
};
Self {
root,
info,
capability,
}
}
}
mod error {
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::DomException;
use opendal_core::Error;
use opendal_core::ErrorKind;
pub(crate) fn parse_js_error(value: JsValue) -> Error {
if let Some(exc) = value.dyn_ref::<DomException>() {
let kind = match exc.name().as_str() {
"NotFoundError" => ErrorKind::NotFound,
"TypeMismatchError" => ErrorKind::NotFound,
"NotAllowedError" => ErrorKind::PermissionDenied,
"QuotaExceededError" => ErrorKind::Unexpected,
e => {
console_debug!("Got unhandled DOM exception {e:?}");
ErrorKind::Unexpected
}
};
return Error::new(kind, exc.message());
}
// FIXME: how to map `TypeError`` from `getDirectoryHandle`:
// "name specified is not a valid string or contains characters that
// would interfere with the native file system"
if let Some(err) = value.dyn_ref::<js_sys::Error>() {
return Error::new(ErrorKind::Unexpected, String::from(err.message()));
}
Error::new(
ErrorKind::Unexpected,
value
.as_string()
.unwrap_or_else(|| "unknown JS error".to_string()),
)
}
}
pub(super) use error::*;
mod utils {
use opendal_core::Result;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::FileSystemDirectoryHandle;
use web_sys::FileSystemFileHandle;
use web_sys::FileSystemGetDirectoryOptions;
use web_sys::FileSystemGetFileOptions;
use web_sys::window;
use crate::core::*;
/// Get the OPFS root directory handle.
pub(crate) async fn get_root_directory_handle() -> Result<FileSystemDirectoryHandle> {
let navigator = window().unwrap().navigator();
let storage_manager = navigator.storage();
// This may fail if not secure (not: HTTPS or localhost)
JsFuture::from(storage_manager.get_directory())
.await
.and_then(JsCast::dyn_into)
.map_err(parse_js_error)
}
/// Navigate to a directory handle by path.
///
/// When `create` is true, intermediate directories are created as needed.
pub(crate) async fn get_directory_handle(
path: &str,
create: bool,
) -> Result<FileSystemDirectoryHandle> {
let opt = FileSystemGetDirectoryOptions::new();
opt.set_create(create);
let trimmed = path.trim_matches('/');
let mut handle = get_root_directory_handle().await?;
if trimmed.is_empty() {
return Ok(handle);
}
for dir in trimmed.split('/') {
handle = JsFuture::from(handle.get_directory_handle_with_options(dir, &opt))
.await
.and_then(JsCast::dyn_into)
.map_err(parse_js_error)?;
}
Ok(handle)
}
/// Split a file path into its parent directory handle and filename.
///
/// When `create` is true, intermediate directories are created as needed.
pub(crate) async fn get_parent_dir_and_name<'a>(
path: &'a str,
create: bool,
) -> Result<(FileSystemDirectoryHandle, &'a str)> {
let trimmed = path.trim_matches('/');
match trimmed.rsplit_once('/') {
Some((parent, name)) => {
let dir = get_directory_handle(parent, create).await?;
Ok((dir, name))
}
None => {
let root = get_root_directory_handle().await?;
Ok((root, trimmed))
}
}
}
/// Get a file handle by its full path.
///
/// When `create` is true, intermediate directories and the file itself are created as needed.
pub(crate) async fn get_file_handle(path: &str, create: bool) -> Result<FileSystemFileHandle> {
let (dir, name) = get_parent_dir_and_name(path, create).await?;
let opt = FileSystemGetFileOptions::new();
opt.set_create(create);
JsFuture::from(dir.get_file_handle_with_options(name, &opt))
.await
.and_then(JsCast::dyn_into)
.map_err(parse_js_error)
}
}
pub(super) use utils::*;