-
Notifications
You must be signed in to change notification settings - Fork 507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OPFS support for OpenDAL (draft PR) #5269
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
use serde::Deserialize; | ||
use std::sync::Arc; | ||
|
||
use crate::{ | ||
raw::{ | ||
Access, AccessorInfo, OpCopy, OpCreateDir, OpDelete, OpList, OpRead, OpRename, OpStat, | ||
OpWrite, RpCopy, RpCreateDir, RpDelete, RpList, RpRead, RpRename, RpStat, RpWrite, | ||
}, | ||
types, Builder, Capability, Error, Result, Scheme, | ||
}; | ||
use std::fmt::Debug; | ||
|
||
use super::{core::OpfsCore, lister::OpfsLister, reader::OpfsReader, writer::OpfsWriter}; | ||
|
||
/// Origin private file system (OPFS) configuration | ||
#[derive(Default, Deserialize)] | ||
#[serde(default)] | ||
#[non_exhaustive] | ||
pub struct OpfsConfig {} | ||
|
||
impl Debug for OpfsConfig { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
panic!() | ||
} | ||
} | ||
|
||
/// Origin private file system (OPFS) support | ||
#[doc = include_str!("docs.md")] | ||
#[derive(Default)] | ||
pub struct OpfsBuilder { | ||
config: OpfsConfig, | ||
} | ||
|
||
impl OpfsBuilder {} | ||
|
||
impl Debug for OpfsBuilder { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
panic!() | ||
} | ||
} | ||
|
||
impl Builder for OpfsBuilder { | ||
const SCHEME: Scheme = Scheme::Opfs; | ||
|
||
type Config = (); | ||
|
||
fn build(self) -> Result<impl Access> { | ||
Ok(OpfsBackend {}) | ||
} | ||
} | ||
|
||
/// OPFS Service backend | ||
#[derive(Debug, Clone)] | ||
pub struct OpfsBackend {} | ||
|
||
impl Access for OpfsBackend { | ||
type Reader = OpfsReader; | ||
|
||
type Writer = OpfsWriter; | ||
|
||
type Lister = OpfsLister; | ||
|
||
type BlockingLister = OpfsLister; | ||
|
||
type BlockingReader = OpfsReader; | ||
|
||
type BlockingWriter = OpfsWriter; | ||
|
||
fn info(&self) -> Arc<AccessorInfo> { | ||
let mut access_info = AccessorInfo::default(); | ||
access_info | ||
.set_scheme(Scheme::Opfs) | ||
.set_native_capability(Capability { | ||
stat: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please leave them as |
||
read: true, | ||
write: true, | ||
write_can_empty: true, | ||
write_can_append: true, | ||
write_can_multi: true, | ||
create_dir: true, | ||
delete: true, | ||
list: true, | ||
copy: true, | ||
rename: true, | ||
blocking: true, | ||
..Default::default() | ||
}); | ||
Arc::new(access_info) | ||
} | ||
|
||
async fn create_dir(&self, path: &str, _: OpCreateDir) -> Result<RpCreateDir> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have default implementations for those functions, so we can remove unsupported operations. |
||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> { | ||
let path = path.to_owned(); | ||
|
||
// OpfsHelper::read_file_with_local_set(path).await; | ||
let out_buf = OpfsCore::read_file(path.as_str()).await?; | ||
Ok::<(RpRead, Self::Reader), Error>((RpRead::default(), Self::Reader {})) | ||
} | ||
|
||
async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> { | ||
// Access the OPFS | ||
// let path = path.to_owned(); | ||
|
||
// spawn_local(async move { | ||
// OpfsCore::store_file(path.as_str(), &[1, 2, 3, 4]).await?; | ||
// Ok::<(), Error>(()) | ||
// }) | ||
// .await | ||
// .unwrap()?; | ||
|
||
Ok((RpWrite::default(), Self::Writer {})) | ||
} | ||
|
||
async fn delete(&self, path: &str, _: OpDelete) -> Result<RpDelete> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
async fn copy(&self, from: &str, to: &str, args: OpCopy) -> Result<RpCopy> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
async fn rename(&self, from: &str, to: &str, _args: OpRename) -> Result<RpRename> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
fn blocking_create_dir(&self, path: &str, args: OpCreateDir) -> Result<RpCreateDir> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
fn blocking_stat(&self, path: &str, args: OpStat) -> Result<RpStat> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
fn blocking_read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::BlockingReader)> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
fn blocking_write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::BlockingWriter)> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
fn blocking_delete(&self, path: &str, args: OpDelete) -> Result<RpDelete> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
fn blocking_list(&self, path: &str, args: OpList) -> Result<(RpList, Self::BlockingLister)> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
fn blocking_copy(&self, from: &str, to: &str, args: OpCopy) -> Result<RpCopy> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
|
||
fn blocking_rename(&self, from: &str, to: &str, args: OpRename) -> Result<RpRename> { | ||
Err(Error::new( | ||
types::ErrorKind::Unsupported, | ||
"Operation not supported yet", | ||
)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
#[cfg(target_arch = "wasm32")] | ||
mod opfs_tests { | ||
use wasm_bindgen::prelude::*; | ||
use wasm_bindgen_test::*; | ||
|
||
use std::collections::HashMap; | ||
|
||
use crate::Operator; | ||
|
||
use super::*; | ||
|
||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); | ||
|
||
#[wasm_bindgen] | ||
pub async fn test_opfs() -> String { | ||
let map = HashMap::new(); | ||
let op = Operator::via_map(Scheme::Opfs, map).unwrap(); | ||
let bs = op.read("path/to/file").await.unwrap(); | ||
"ok".to_string() | ||
} | ||
|
||
#[wasm_bindgen_test] | ||
async fn basic_test() -> Result<()> { | ||
let s = test_opfs().await; | ||
assert_eq!(s, "ok".to_string()); | ||
Ok(()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
use std::fmt::Debug; | ||
|
||
use crate::Result; | ||
|
||
use web_sys::{ | ||
window, File, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetFileOptions, | ||
FileSystemWritableFileStream, | ||
}; | ||
|
||
use wasm_bindgen::{JsCast, JsValue}; | ||
use wasm_bindgen_futures::JsFuture; | ||
|
||
pub struct OpfsCore {} | ||
|
||
impl Debug for OpfsCore { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
panic!() | ||
} | ||
} | ||
|
||
impl OpfsCore { | ||
pub async fn store_file(file_name: &str, content: &[u8]) -> Result<(), JsValue> { | ||
// Access the OPFS | ||
let navigator = window().unwrap().navigator(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please try your best to avoid using |
||
let storage_manager = navigator.storage(); | ||
let root: FileSystemDirectoryHandle = JsFuture::from(storage_manager.get_directory()) | ||
.await? | ||
.dyn_into()?; | ||
|
||
let opt = FileSystemGetFileOptions::new(); | ||
opt.set_create(true); | ||
|
||
// Create or get the file in the OPFS | ||
let file_handle: FileSystemFileHandle = | ||
JsFuture::from(root.get_file_handle_with_options(file_name, &opt)) | ||
.await? | ||
.dyn_into()?; | ||
|
||
// Create a writable stream | ||
let writable: FileSystemWritableFileStream = JsFuture::from(file_handle.create_writable()) | ||
.await? | ||
.dyn_into()?; | ||
|
||
// Write the content to the file | ||
JsFuture::from( | ||
writable | ||
.write_with_u8_array(content) | ||
.expect("failed to write file"), | ||
) | ||
.await?; | ||
|
||
// Close the writable stream | ||
JsFuture::from(writable.close()).await?; | ||
|
||
Ok(()) | ||
} | ||
|
||
pub async fn read_file(file_name: &str) -> Result<Vec<u8>, JsValue> { | ||
// Access the OPFS | ||
let navigator = window().unwrap().navigator(); | ||
let storage_manager = navigator.storage(); | ||
let root: FileSystemDirectoryHandle = JsFuture::from(storage_manager.get_directory()) | ||
.await? | ||
.dyn_into()?; | ||
|
||
// Get the file handle | ||
let file_handle: FileSystemFileHandle = JsFuture::from(root.get_file_handle(file_name)) | ||
.await? | ||
.dyn_into()?; | ||
|
||
// Get the file from the handle | ||
let file: File = JsFuture::from(file_handle.get_file()).await?.dyn_into()?; | ||
let array_buffer = JsFuture::from(file.array_buffer()).await?; | ||
|
||
// Convert the ArrayBuffer to a Vec<u8> | ||
let u8_array = js_sys::Uint8Array::new(&array_buffer); | ||
let mut vec = vec![0; u8_array.length() as usize]; | ||
u8_array.copy_to(&mut vec[..]); | ||
|
||
Ok(vec) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
use wasm_bindgen::JsValue; | ||
|
||
use crate::Error; | ||
use crate::ErrorKind; | ||
|
||
impl From<JsValue> for Error { | ||
fn from(value: JsValue) -> Self { | ||
Error::new(ErrorKind::Unexpected, "Error") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, please mark those dependencies as
optional
and hide them under theservices-opfs
feature to ensure other builds aren't affected.