diff --git a/core/Cargo.lock b/core/Cargo.lock index 28417ce9e28b..8eea78708322 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -5021,6 +5021,7 @@ dependencies = [ "hmac", "hrana-client-proto", "http 1.1.0", + "js-sys", "libtest-mimic", "log", "md-5", @@ -5068,6 +5069,9 @@ dependencies = [ "tracing-opentelemetry", "tracing-subscriber", "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] diff --git a/core/Cargo.toml b/core/Cargo.toml index d404845dd3e2..ad382b1637b5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -383,6 +383,20 @@ prometheus-client = { version = "0.22.2", optional = true } tracing = { version = "0.1", optional = true } # for layers-dtrace probe = { version = "0.5.1", optional = true } +wasm-bindgen = "0.2.95" +wasm-bindgen-futures = "0.4.45" +web-sys = { version = "0.3.72", features = [ + "Window", + "File", + "FileSystemDirectoryHandle", + "FileSystemFileHandle", + "FileSystemGetFileOptions", + "FileSystemWritableFileStream", + "Navigator", + "StorageManager", + "FileSystemGetFileOptions", +] } +js-sys = "0.3.72" [target.'cfg(target_arch = "wasm32")'.dependencies] backon = { version = "1.2", features = ["gloo-timers-sleep"] } diff --git a/core/src/services/mod.rs b/core/src/services/mod.rs index 0437dff4a759..a603efd45425 100644 --- a/core/src/services/mod.rs +++ b/core/src/services/mod.rs @@ -154,6 +154,9 @@ pub use obs::*; mod onedrive; pub use onedrive::*; +mod opfs; +pub use opfs::*; + mod oss; pub use oss::*; diff --git a/core/src/services/opfs/backend.rs b/core/src/services/opfs/backend.rs new file mode 100644 index 000000000000..4776e904712c --- /dev/null +++ b/core/src/services/opfs/backend.rs @@ -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 { + 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 { + let mut access_info = AccessorInfo::default(); + access_info + .set_scheme(Scheme::Opfs) + .set_native_capability(Capability { + stat: true, + 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 { + Err(Error::new( + types::ErrorKind::Unsupported, + "Operation not supported yet", + )) + } + + async fn stat(&self, path: &str, _: OpStat) -> Result { + 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 { + 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 { + Err(Error::new( + types::ErrorKind::Unsupported, + "Operation not supported yet", + )) + } + + async fn rename(&self, from: &str, to: &str, _args: OpRename) -> Result { + Err(Error::new( + types::ErrorKind::Unsupported, + "Operation not supported yet", + )) + } + + fn blocking_create_dir(&self, path: &str, args: OpCreateDir) -> Result { + Err(Error::new( + types::ErrorKind::Unsupported, + "Operation not supported yet", + )) + } + + fn blocking_stat(&self, path: &str, args: OpStat) -> Result { + 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 { + 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 { + Err(Error::new( + types::ErrorKind::Unsupported, + "Operation not supported yet", + )) + } + + fn blocking_rename(&self, from: &str, to: &str, args: OpRename) -> Result { + 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(()) + } +} diff --git a/core/src/services/opfs/core.rs b/core/src/services/opfs/core.rs new file mode 100644 index 000000000000..43a598f84947 --- /dev/null +++ b/core/src/services/opfs/core.rs @@ -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(); + 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, 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 + 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) + } +} diff --git a/core/src/services/opfs/docs.md b/core/src/services/opfs/docs.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/core/src/services/opfs/error.rs b/core/src/services/opfs/error.rs new file mode 100644 index 000000000000..a313b602b50a --- /dev/null +++ b/core/src/services/opfs/error.rs @@ -0,0 +1,10 @@ +use wasm_bindgen::JsValue; + +use crate::Error; +use crate::ErrorKind; + +impl From for Error { + fn from(value: JsValue) -> Self { + Error::new(ErrorKind::Unexpected, "Error") + } +} diff --git a/core/src/services/opfs/helper.rs b/core/src/services/opfs/helper.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/core/src/services/opfs/helper.rs @@ -0,0 +1 @@ + diff --git a/core/src/services/opfs/lister.rs b/core/src/services/opfs/lister.rs new file mode 100644 index 000000000000..c2835f616f3d --- /dev/null +++ b/core/src/services/opfs/lister.rs @@ -0,0 +1,15 @@ +use crate::{raw::oio, Result}; + +pub struct OpfsLister {} + +impl oio::List for OpfsLister { + async fn next(&mut self) -> Result> { + panic!() + } +} + +impl oio::BlockingList for OpfsLister { + fn next(&mut self) -> Result> { + panic!() + } +} diff --git a/core/src/services/opfs/mod.rs b/core/src/services/opfs/mod.rs new file mode 100644 index 000000000000..7bb46f38af4a --- /dev/null +++ b/core/src/services/opfs/mod.rs @@ -0,0 +1,9 @@ +mod backend; +mod core; +mod error; +mod helper; +mod lister; +mod reader; +mod writer; + +pub use backend::OpfsBuilder as Opfs; diff --git a/core/src/services/opfs/reader.rs b/core/src/services/opfs/reader.rs new file mode 100644 index 000000000000..7647024cae05 --- /dev/null +++ b/core/src/services/opfs/reader.rs @@ -0,0 +1,21 @@ +use crate::{raw::oio, Buffer, Result}; + +pub struct OpfsReader {} + +// impl oio::Read for OpfsReader +impl oio::Read for OpfsReader { + async fn read(&mut self) -> Result { + panic!() + } + + async fn read_all(&mut self) -> Result { + panic!() + } +} + +// impl oio::BlockingRead for OpfsReader +impl oio::BlockingRead for OpfsReader { + fn read(&mut self) -> Result { + panic!() + } +} diff --git a/core/src/services/opfs/writer.rs b/core/src/services/opfs/writer.rs new file mode 100644 index 000000000000..a6a2e2e26c3f --- /dev/null +++ b/core/src/services/opfs/writer.rs @@ -0,0 +1,27 @@ +use crate::{raw::oio, Buffer, Result}; + +pub struct OpfsWriter {} + +impl oio::Write for OpfsWriter { + async fn abort(&mut self) -> Result<()> { + panic!() + } + + async fn close(&mut self) -> Result<()> { + panic!() + } + + async fn write(&mut self, bs: Buffer) -> Result<()> { + panic!() + } +} + +impl oio::BlockingWrite for OpfsWriter { + fn close(&mut self) -> Result<()> { + panic!() + } + + fn write(&mut self, bs: Buffer) -> Result<()> { + panic!() + } +} diff --git a/core/src/types/scheme.rs b/core/src/types/scheme.rs index c0da5219b829..a5cc9a44e113 100644 --- a/core/src/types/scheme.rs +++ b/core/src/types/scheme.rs @@ -111,6 +111,8 @@ pub enum Scheme { Obs, /// [onedrive][crate::services::Onedrive]: Microsoft OneDrive services. Onedrive, + /// [opfs][crate::services::opfs]: Original private file system. + Opfs, /// [gdrive][crate::services::Gdrive]: GoogleDrive services. Gdrive, /// [dropbox][crate::services::Dropbox]: Dropbox services. @@ -385,6 +387,7 @@ impl FromStr for Scheme { "monoiofs" => Ok(Scheme::Monoiofs), "obs" => Ok(Scheme::Obs), "onedrive" => Ok(Scheme::Onedrive), + "opfs" => Ok(Scheme::Opfs), "persy" => Ok(Scheme::Persy), "postgresql" => Ok(Scheme::Postgresql), "redb" => Ok(Scheme::Redb), @@ -454,6 +457,7 @@ impl From for &'static str { Scheme::Monoiofs => "monoiofs", Scheme::Obs => "obs", Scheme::Onedrive => "onedrive", + Scheme::Opfs => "opfs", Scheme::Persy => "persy", Scheme::Postgresql => "postgresql", Scheme::Mysql => "mysql",