| // 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. |
| |
| #[macro_use] |
| extern crate napi_derive; |
| |
| use std::collections::HashMap; |
| use std::fmt::Display; |
| use std::io::Read; |
| use std::time::Duration; |
| |
| use futures::AsyncReadExt; |
| use futures::TryStreamExt; |
| use napi::bindgen_prelude::*; |
| use opendal::options::{ |
| DeleteOptions, ListOptions, ReadOptions, ReaderOptions, StatOptions, WriteOptions, |
| }; |
| |
| use crate::layer::Layer; |
| |
| mod capability; |
| mod layer; |
| mod options; |
| |
| #[napi] |
| pub struct Operator { |
| async_op: opendal::Operator, |
| blocking_op: opendal::blocking::Operator, |
| } |
| |
| #[napi] |
| impl Operator { |
| /// @see For the full list of scheme, see https://docs.rs/opendal/latest/opendal/services/index.html |
| /// And the options, |
| /// please refer to the documentation of the corresponding service for the corresponding parameters. |
| /// Note that the current options key is snake_case. |
| #[napi(constructor, async_runtime)] |
| pub fn new(scheme: String, options: Option<HashMap<String, String>>) -> Result<Self> { |
| let options = options.unwrap_or_default(); |
| |
| let async_op = opendal::Operator::via_iter(scheme, options).map_err(format_napi_error)?; |
| |
| let blocking_op = |
| opendal::blocking::Operator::new(async_op.clone()).map_err(format_napi_error)?; |
| |
| Ok(Operator { |
| async_op, |
| blocking_op, |
| }) |
| } |
| |
| /// Get current operator(service)'s full capability. |
| #[napi] |
| pub fn capability(&self) -> Result<capability::Capability> { |
| Ok(capability::Capability::new( |
| self.async_op.info().full_capability(), |
| )) |
| } |
| |
| /// Get current path's metadata **without cache** directly. |
| /// |
| /// ### Notes |
| /// Use stat if you: |
| /// |
| /// - Want to detect the outside changes of a path. |
| /// - Don’t want to read from cached metadata. |
| /// |
| /// You may want to use `metadata` if you are working with entries returned by `Lister`. It’s highly possible that metadata you want has already been cached. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// const meta = await op.stat("test"); |
| /// if (meta.isDir) { |
| /// // do something |
| /// } |
| /// ``` |
| #[napi] |
| pub async fn stat( |
| &self, |
| path: String, |
| options: Option<options::StatOptions>, |
| ) -> Result<Metadata> { |
| let options = options.map_or_else(StatOptions::default, StatOptions::from); |
| let meta = self |
| .async_op |
| .stat_options(&path, options) |
| .await |
| .map_err(format_napi_error)?; |
| |
| Ok(Metadata(meta)) |
| } |
| |
| /// Get current path's metadata **without cache** directly and synchronously. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// const meta = op.statSync("test"); |
| /// if (meta.isDir) { |
| /// // do something |
| /// } |
| /// ``` |
| #[napi] |
| pub fn stat_sync( |
| &self, |
| path: String, |
| options: Option<options::StatOptions>, |
| ) -> Result<Metadata> { |
| let options = options.map_or_else(StatOptions::default, StatOptions::from); |
| let meta = self |
| .blocking_op |
| .stat_options(&path, options) |
| .map_err(format_napi_error)?; |
| |
| Ok(Metadata(meta)) |
| } |
| |
| /// Check if this operator can work correctly. |
| /// |
| /// We will send a `list` request to the given path and return any errors we met. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// await op.check(); |
| /// ``` |
| #[napi] |
| pub async fn check(&self) -> Result<()> { |
| self.async_op.check().await.map_err(format_napi_error) |
| } |
| |
| /// Check the op synchronously. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// op.checkSync(); |
| /// ``` |
| #[napi] |
| pub fn check_sync(&self) -> Result<()> { |
| self.blocking_op.check().map_err(format_napi_error) |
| } |
| |
| /// Check if this path exists or not. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// await op.isExist("test"); |
| /// ``` |
| #[napi] |
| pub async fn exists(&self, path: String) -> Result<bool> { |
| self.async_op.exists(&path).await.map_err(format_napi_error) |
| } |
| |
| /// Check if this path exists or not synchronously. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// op.isExistSync("test"); |
| /// ``` |
| #[napi] |
| pub fn exists_sync(&self, path: String) -> Result<bool> { |
| self.blocking_op.exists(&path).map_err(format_napi_error) |
| } |
| |
| /// Create dir with a given path. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// await op.createDir("path/to/dir/"); |
| /// ``` |
| #[napi] |
| pub async fn create_dir(&self, path: String) -> Result<()> { |
| self.async_op |
| .create_dir(&path) |
| .await |
| .map_err(format_napi_error) |
| } |
| |
| /// Create dir with a given path synchronously. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// op.createDirSync("path/to/dir/"); |
| /// ``` |
| #[napi] |
| pub fn create_dir_sync(&self, path: String) -> Result<()> { |
| self.blocking_op |
| .create_dir(&path) |
| .map_err(format_napi_error) |
| } |
| |
| /// Read the whole path into a buffer. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// const buf = await op.read("path/to/file"); |
| /// ``` |
| #[napi] |
| pub async fn read( |
| &self, |
| path: String, |
| options: Option<options::ReadOptions>, |
| ) -> Result<Buffer> { |
| let options = options.map_or_else(ReadOptions::default, ReadOptions::from); |
| let res = self |
| .async_op |
| .read_options(&path, options) |
| .await |
| .map_err(format_napi_error)? |
| .to_vec(); |
| Ok(res.into()) |
| } |
| |
| /// Create a reader to read the given path. |
| /// |
| /// It could be used to read large file in a streaming way. |
| #[napi] |
| pub async fn reader( |
| &self, |
| path: String, |
| options: Option<options::ReaderOptions>, |
| ) -> Result<Reader> { |
| let options = options.map_or_else(ReaderOptions::default, ReaderOptions::from); |
| let r = self |
| .async_op |
| .reader_options(&path, options) |
| .await |
| .map_err(format_napi_error)?; |
| Ok(Reader { |
| inner: r |
| .into_futures_async_read(std::ops::RangeFull) |
| .await |
| .map_err(format_napi_error)?, |
| }) |
| } |
| |
| /// Read the whole path into a buffer synchronously. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// const buf = op.readSync("path/to/file"); |
| /// ``` |
| #[napi] |
| pub fn read_sync(&self, path: String, options: Option<options::ReadOptions>) -> Result<Buffer> { |
| let options = options.map_or_else(ReadOptions::default, ReadOptions::from); |
| let res = self |
| .blocking_op |
| .read_options(&path, options) |
| .map_err(format_napi_error)? |
| .to_vec(); |
| Ok(res.into()) |
| } |
| |
| /// Create a reader to read the given path synchronously. |
| /// |
| /// It could be used to read large file in a streaming way. |
| #[napi] |
| pub fn reader_sync( |
| &self, |
| path: String, |
| options: Option<options::ReaderOptions>, |
| ) -> Result<BlockingReader> { |
| let options = options.map_or_else(ReaderOptions::default, ReaderOptions::from); |
| let r = self |
| .blocking_op |
| .reader_options(&path, options) |
| .map_err(format_napi_error)?; |
| Ok(BlockingReader { |
| inner: r.into_std_read(..).map_err(format_napi_error)?, |
| }) |
| } |
| |
| //noinspection DuplicatedCode |
| /// Write bytes into a path. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// await op.write("path/to/file", Buffer.from("hello world")); |
| /// // or |
| /// await op.write("path/to/file", "hello world"); |
| /// // or |
| /// await op.write("path/to/file", Buffer.from("hello world"), { contentType: "text/plain" }); |
| /// ``` |
| #[napi] |
| pub async fn write( |
| &self, |
| path: String, |
| content: Either<Buffer, String>, |
| options: Option<options::WriteOptions>, |
| ) -> Result<Metadata> { |
| let c = match content { |
| Either::A(buf) => buf.as_ref().to_owned(), |
| Either::B(s) => s.into_bytes(), |
| }; |
| let options = options.map_or_else(WriteOptions::default, WriteOptions::from); |
| let metadata = self |
| .async_op |
| .write_options(&path, c, options) |
| .await |
| .map_err(format_napi_error)?; |
| Ok(Metadata(metadata)) |
| } |
| |
| //noinspection DuplicatedCode |
| /// Write multiple bytes into a path. |
| /// |
| /// It could be used to write large file in a streaming way. |
| #[napi] |
| pub async fn writer( |
| &self, |
| path: String, |
| options: Option<options::WriteOptions>, |
| ) -> Result<Writer> { |
| let options = options.unwrap_or_default(); |
| let writer = self |
| .async_op |
| .writer_options(&path, options.into()) |
| .await |
| .map_err(format_napi_error)?; |
| Ok(Writer(writer)) |
| } |
| |
| /// Write multiple bytes into a path synchronously. |
| /// |
| /// It could be used to write large file in a streaming way. |
| #[napi] |
| pub fn writer_sync( |
| &self, |
| path: String, |
| options: Option<options::WriteOptions>, |
| ) -> Result<BlockingWriter> { |
| let options = options.unwrap_or_default(); |
| let writer = self |
| .blocking_op |
| .writer_options(&path, options.into()) |
| .map_err(format_napi_error)?; |
| Ok(BlockingWriter(writer)) |
| } |
| |
| //noinspection DuplicatedCode |
| /// Write bytes into a path synchronously. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// op.writeSync("path/to/file", Buffer.from("hello world")); |
| /// // or |
| /// op.writeSync("path/to/file", "hello world"); |
| /// // or |
| /// op.writeSync("path/to/file", Buffer.from("hello world"), { contentType: "text/plain" }); |
| /// ``` |
| #[napi] |
| pub fn write_sync( |
| &self, |
| path: String, |
| content: Either<Buffer, String>, |
| options: Option<options::WriteOptions>, |
| ) -> Result<Metadata> { |
| let c = match content { |
| Either::A(buf) => buf.as_ref().to_owned(), |
| Either::B(s) => s.into_bytes(), |
| }; |
| let options = options.map_or_else(WriteOptions::default, WriteOptions::from); |
| let metadata = self |
| .blocking_op |
| .write_options(&path, c, options) |
| .map_err(format_napi_error)?; |
| Ok(Metadata(metadata)) |
| } |
| |
| /// Copy file according to given `from` and `to` path. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// await op.copy("path/to/file", "path/to/dest"); |
| /// ``` |
| #[napi] |
| pub async fn copy(&self, from: String, to: String) -> Result<()> { |
| self.async_op |
| .copy(&from, &to) |
| .await |
| .map_err(format_napi_error) |
| } |
| |
| /// Copy file according to given `from` and `to` path synchronously. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// op.copySync("path/to/file", "path/to/dest"); |
| /// ``` |
| #[napi] |
| pub fn copy_sync(&self, from: String, to: String) -> Result<()> { |
| self.blocking_op.copy(&from, &to).map_err(format_napi_error) |
| } |
| |
| /// Rename file according to given `from` and `to` path. |
| /// |
| /// It's similar to `mv` command. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// await op.rename("path/to/file", "path/to/dest"); |
| /// ``` |
| #[napi] |
| pub async fn rename(&self, from: String, to: String) -> Result<()> { |
| self.async_op |
| .rename(&from, &to) |
| .await |
| .map_err(format_napi_error) |
| } |
| |
| /// Rename file according to given `from` and `to` path synchronously. |
| /// |
| /// It's similar to `mv` command. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// op.renameSync("path/to/file", "path/to/dest"); |
| /// ``` |
| #[napi] |
| pub fn rename_sync(&self, from: String, to: String) -> Result<()> { |
| self.blocking_op |
| .rename(&from, &to) |
| .map_err(format_napi_error) |
| } |
| |
| /// Delete the given path. |
| /// |
| /// ### Notes |
| /// Delete not existing error won’t return errors. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// await op.delete("test"); |
| /// ``` |
| #[napi] |
| pub async fn delete( |
| &self, |
| path: String, |
| options: Option<options::DeleteOptions>, |
| ) -> Result<()> { |
| let options = options.map_or_else(DeleteOptions::default, DeleteOptions::from); |
| self.async_op |
| .delete_options(&path, options) |
| .await |
| .map_err(format_napi_error) |
| } |
| |
| /// Delete the given path synchronously. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// op.deleteSync("test"); |
| /// ``` |
| #[napi] |
| pub fn delete_sync(&self, path: String, options: Option<options::DeleteOptions>) -> Result<()> { |
| let options = options.map_or_else(DeleteOptions::default, DeleteOptions::from); |
| self.blocking_op |
| .delete_options(&path, options) |
| .map_err(format_napi_error) |
| } |
| |
| /// Remove given paths. |
| /// |
| /// ### Notes |
| /// If underlying services support delete in batch, we will use batch delete instead. |
| /// |
| /// ### Examples |
| /// ```javascript |
| /// await op.remove(["abc", "def"]); |
| /// ``` |
| #[napi] |
| pub async fn remove(&self, paths: Vec<String>) -> Result<()> { |
| self.async_op |
| .delete_iter(paths) |
| .await |
| .map_err(format_napi_error) |
| } |
| |
| /// Remove given paths. |
| /// |
| /// ### Notes |
| /// If underlying services support delete in batch, we will use batch delete instead. |
| /// |
| /// ### Examples |
| /// ```javascript |
| /// op.removeSync(["abc", "def"]); |
| /// ``` |
| #[napi] |
| pub fn remove_sync(&self, paths: Vec<String>) -> Result<()> { |
| self.blocking_op |
| .delete_iter(paths) |
| .map_err(format_napi_error) |
| } |
| |
| /// Remove the path and all nested dirs and files recursively. |
| /// |
| /// ### Notes |
| /// If underlying services support delete in batch, we will use batch delete instead. |
| /// |
| /// ### Examples |
| /// ```javascript |
| /// await op.removeAll("path/to/dir/"); |
| /// ``` |
| #[napi] |
| pub async fn remove_all(&self, path: String) -> Result<()> { |
| self.async_op |
| .delete_with(&path) |
| .recursive(true) |
| .await |
| .map_err(format_napi_error) |
| } |
| |
| /// Remove the path and all nested dirs and files recursively. |
| /// |
| /// ### Notes |
| /// If underlying services support delete in batch, we will use batch delete instead. |
| /// |
| /// ### Examples |
| /// ```javascript |
| /// op.removeAllSync("path/to/dir/"); |
| /// ``` |
| #[napi] |
| pub fn remove_all_sync(&self, path: String) -> Result<()> { |
| use opendal::options::DeleteOptions; |
| self.blocking_op |
| .delete_options( |
| &path, |
| DeleteOptions { |
| recursive: true, |
| ..Default::default() |
| }, |
| ) |
| .map_err(format_napi_error) |
| } |
| |
| /// List the given path. |
| /// |
| /// This function will return an array of entries. |
| /// |
| /// An error will be returned if given path doesn't end with `/`. |
| /// |
| /// ### Example |
| /// |
| /// ```javascript |
| /// const list = await op.list("path/to/dir/"); |
| /// for (let entry of list) { |
| /// let meta = await op.stat(entry.path); |
| /// if (meta.isFile) { |
| /// // do something |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// #### List recursively |
| /// |
| /// With `recursive` option, you can list recursively. |
| /// |
| /// ```javascript |
| /// const list = await op.list("path/to/dir/", { recursive: true }); |
| /// for (let entry of list) { |
| /// let meta = await op.stat(entry.path); |
| /// if (meta.isFile) { |
| /// // do something |
| /// } |
| /// } |
| /// ``` |
| #[napi] |
| pub async fn list( |
| &self, |
| path: String, |
| options: Option<options::ListOptions>, |
| ) -> Result<Vec<Entry>> { |
| let options = options.map_or(ListOptions::default(), ListOptions::from); |
| let l = self |
| .async_op |
| .list_options(&path, options) |
| .await |
| .map_err(format_napi_error)?; |
| |
| Ok(l.into_iter().map(Entry).collect()) |
| } |
| |
| /// List the given path synchronously. |
| /// |
| /// This function will return an array of entries. |
| /// |
| /// An error will be returned if given path doesn't end with `/`. |
| /// |
| /// ### Example |
| /// |
| /// ```javascript |
| /// const list = op.listSync("path/to/dir/"); |
| /// for (let entry of list) { |
| /// let meta = op.statSync(entry.path); |
| /// if (meta.isFile) { |
| /// // do something |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// #### List recursively |
| /// |
| /// With `recursive` option, you can list recursively. |
| /// |
| /// ```javascript |
| /// const list = op.listSync("path/to/dir/", { recursive: true }); |
| /// for (let entry of list) { |
| /// let path = entry.path(); |
| /// let meta = entry.metadata(); |
| /// if (meta.isFile) { |
| /// // do something |
| /// } |
| /// } |
| /// ``` |
| #[napi] |
| pub fn list_sync( |
| &self, |
| path: String, |
| options: Option<options::ListOptions>, |
| ) -> Result<Vec<Entry>> { |
| let options = options.map_or(ListOptions::default(), ListOptions::from); |
| let l = self |
| .blocking_op |
| .list_options(&path, options) |
| .map_err(format_napi_error)?; |
| |
| Ok(l.into_iter().map(Entry).collect()) |
| } |
| |
| /// Create a lister to list entries at given path. |
| /// |
| /// This function returns a Lister that can be used to iterate over entries |
| /// in a streaming manner, which is more memory-efficient for large directories. |
| /// |
| /// An error will be returned if given path doesn't end with `/`. |
| /// |
| /// ### Example |
| /// |
| /// ```javascript |
| /// const lister = await op.lister("path/to/dir/"); |
| /// let entry; |
| /// while ((entry = await lister.next()) !== null) { |
| /// console.log(entry.path()); |
| /// } |
| /// ``` |
| /// |
| /// #### List recursively |
| /// |
| /// With `recursive` option, you can list recursively. |
| /// |
| /// ```javascript |
| /// const lister = await op.lister("path/to/dir/", { recursive: true }); |
| /// let entry; |
| /// while ((entry = await lister.next()) !== null) { |
| /// console.log(entry.path()); |
| /// } |
| /// ``` |
| #[napi] |
| pub async fn lister( |
| &self, |
| path: String, |
| options: Option<options::ListOptions>, |
| ) -> Result<Lister> { |
| let options = options.map_or(ListOptions::default(), ListOptions::from); |
| let l = self |
| .async_op |
| .lister_options(&path, options) |
| .await |
| .map_err(format_napi_error)?; |
| |
| Ok(Lister(l)) |
| } |
| |
| /// Create a lister to list entries at given path synchronously. |
| /// |
| /// This function returns a BlockingLister that can be used to iterate over entries |
| /// in a streaming manner, which is more memory-efficient for large directories. |
| /// |
| /// An error will be returned if given path doesn't end with `/`. |
| /// |
| /// ### Example |
| /// |
| /// ```javascript |
| /// const lister = op.listerSync("path/to/dir/"); |
| /// let entry; |
| /// while ((entry = lister.next()) !== null) { |
| /// console.log(entry.path()); |
| /// } |
| /// ``` |
| /// |
| /// #### List recursively |
| /// |
| /// With `recursive` option, you can list recursively. |
| /// |
| /// ```javascript |
| /// const lister = op.listerSync("path/to/dir/", { recursive: true }); |
| /// let entry; |
| /// while ((entry = lister.next()) !== null) { |
| /// console.log(entry.path()); |
| /// } |
| /// ``` |
| #[napi] |
| pub fn lister_sync( |
| &self, |
| path: String, |
| options: Option<options::ListOptions>, |
| ) -> Result<BlockingLister> { |
| let options = options.map_or(ListOptions::default(), ListOptions::from); |
| let l = self |
| .blocking_op |
| .lister_options(&path, options) |
| .map_err(format_napi_error)?; |
| |
| Ok(BlockingLister(l)) |
| } |
| |
| /// Get a presigned request for read. |
| /// |
| /// Unit of `expires` is seconds. |
| /// |
| /// ### Example |
| /// |
| /// ```javascript |
| /// const req = await op.presignRead(path, parseInt(expires)); |
| /// |
| /// console.log("method: ", req.method); |
| /// console.log("url: ", req.url); |
| /// console.log("headers: ", req.headers); |
| /// ``` |
| #[napi] |
| pub async fn presign_read(&self, path: String, expires: u32) -> Result<PresignedRequest> { |
| let res = self |
| .async_op |
| .presign_read(&path, Duration::from_secs(expires as u64)) |
| .await |
| .map_err(format_napi_error)?; |
| Ok(PresignedRequest::new(res)) |
| } |
| |
| /// Get a presigned request for `write`. |
| /// |
| /// Unit of `expires` is seconds. |
| /// |
| /// ### Example |
| /// |
| /// ```javascript |
| /// const req = await op.presignWrite(path, parseInt(expires)); |
| /// |
| /// console.log("method: ", req.method); |
| /// console.log("url: ", req.url); |
| /// console.log("headers: ", req.headers); |
| /// ``` |
| #[napi] |
| pub async fn presign_write(&self, path: String, expires: u32) -> Result<PresignedRequest> { |
| let res = self |
| .async_op |
| .presign_write(&path, Duration::from_secs(expires as u64)) |
| .await |
| .map_err(format_napi_error)?; |
| Ok(PresignedRequest::new(res)) |
| } |
| |
| /// Get a presigned request for stat. |
| /// |
| /// Unit of `expires` is seconds. |
| /// |
| /// ### Example |
| /// |
| /// ```javascript |
| /// const req = await op.presignStat(path, parseInt(expires)); |
| /// |
| /// console.log("method: ", req.method); |
| /// console.log("url: ", req.url); |
| /// console.log("headers: ", req.headers); |
| /// ``` |
| #[napi] |
| pub async fn presign_stat(&self, path: String, expires: u32) -> Result<PresignedRequest> { |
| let res = self |
| .async_op |
| .presign_stat(&path, Duration::from_secs(expires as u64)) |
| .await |
| .map_err(format_napi_error)?; |
| Ok(PresignedRequest::new(res)) |
| } |
| } |
| |
| /// Entry returned by Lister or BlockingLister to represent a path, and it's a relative metadata. |
| #[napi] |
| pub struct Entry(opendal::Entry); |
| |
| #[napi] |
| impl Entry { |
| /// Return the path of this entry. |
| #[napi] |
| pub fn path(&self) -> String { |
| self.0.path().to_string() |
| } |
| |
| /// Return the metadata of this entry. |
| #[napi] |
| pub fn metadata(&self) -> Metadata { |
| Metadata(self.0.metadata().clone()) |
| } |
| } |
| |
| #[napi] |
| pub enum EntryMode { |
| /// FILE means the path has data to read. |
| FILE, |
| /// DIR means the path can be listed. |
| DIR, |
| /// Unknown means we don't know what we can do on this path. |
| Unknown, |
| } |
| |
| impl From<opendal::EntryMode> for EntryMode { |
| fn from(mode: opendal::EntryMode) -> Self { |
| match mode { |
| opendal::EntryMode::FILE => EntryMode::FILE, |
| opendal::EntryMode::DIR => EntryMode::DIR, |
| opendal::EntryMode::Unknown => EntryMode::Unknown, |
| } |
| } |
| } |
| |
| /// Metadata carries all metadata associated with a path. |
| #[napi] |
| pub struct Metadata(opendal::Metadata); |
| |
| #[napi] |
| impl Metadata { |
| /// Returns true if the <op.stat> object describes a file system directory. |
| #[napi] |
| pub fn is_directory(&self) -> bool { |
| self.0.is_dir() |
| } |
| |
| /// Returns true if the <op.stat> object describes a regular file. |
| #[napi] |
| pub fn is_file(&self) -> bool { |
| self.0.is_file() |
| } |
| |
| /// This function returns `true` if the file represented by this metadata has been marked for |
| /// deletion or has been permanently deleted. |
| #[napi] |
| pub fn is_deleted(&self) -> bool { |
| self.0.is_deleted() |
| } |
| |
| /// Cache-Control of this object. |
| #[napi(getter)] |
| pub fn cache_control(&self) -> Option<String> { |
| self.0.cache_control().map(|s| s.to_string()) |
| } |
| |
| /// Content-Disposition of this object |
| #[napi(getter)] |
| pub fn content_disposition(&self) -> Option<String> { |
| self.0.content_disposition().map(|s| s.to_string()) |
| } |
| |
| /// Content Length of this object |
| #[napi(getter)] |
| pub fn content_length(&self) -> Option<u64> { |
| self.0.content_length().into() |
| } |
| |
| /// Content Encoding of this object |
| #[napi(getter)] |
| pub fn content_encoding(&self) -> Option<String> { |
| self.0.content_encoding().map(|s| s.to_string()) |
| } |
| |
| /// Content MD5 of this object. |
| #[napi(getter)] |
| pub fn content_md5(&self) -> Option<String> { |
| self.0.content_md5().map(|s| s.to_string()) |
| } |
| |
| /// Content Type of this object. |
| #[napi(getter)] |
| pub fn content_type(&self) -> Option<String> { |
| self.0.content_type().map(|s| s.to_string()) |
| } |
| |
| /// User Metadata of this object. |
| #[napi(getter)] |
| pub fn user_metadata(&self) -> Option<HashMap<String, String>> { |
| self.0.user_metadata().cloned() |
| } |
| |
| /// ETag of this object. |
| #[napi(getter)] |
| pub fn etag(&self) -> Option<String> { |
| self.0.etag().map(|s| s.to_string()) |
| } |
| |
| /// Last Modified of this object. |
| /// |
| /// We will output this time in RFC3339 format like `1996-12-19T16:39:57+08:00`. |
| #[napi(getter)] |
| pub fn last_modified(&self) -> Option<String> { |
| self.0.last_modified().map(|ta| ta.to_string()) |
| } |
| |
| /// mode represent this entry's mode. |
| #[napi(getter)] |
| pub fn mode(&self) -> Option<EntryMode> { |
| Some(self.0.mode().into()) |
| } |
| |
| /// Retrieves the `version` of the file, if available. |
| #[napi(getter)] |
| pub fn version(&self) -> Option<String> { |
| self.0.version().map(|v| v.to_string()) |
| } |
| } |
| |
| /// BlockingReader is designed to read data from a given path in a blocking |
| /// manner. |
| #[napi] |
| pub struct BlockingReader { |
| inner: opendal::blocking::StdReader, |
| } |
| |
| #[napi] |
| impl BlockingReader { |
| #[napi] |
| pub fn read(&mut self, mut buf: Buffer) -> Result<usize> { |
| let buf = buf.as_mut(); |
| let n = self.inner.read(buf).map_err(format_napi_error)?; |
| Ok(n) |
| } |
| } |
| |
| /// Reader is designed to read data from a given path in an asynchronous |
| /// manner. |
| #[napi] |
| pub struct Reader { |
| inner: opendal::FuturesAsyncReader, |
| } |
| |
| #[napi] |
| impl Reader { |
| /// # Safety |
| /// |
| /// > &mut self in async napi methods should be marked as unsafe |
| /// |
| /// Read bytes from this reader into given buffer. |
| #[napi] |
| pub async unsafe fn read(&mut self, mut buf: Buffer) -> Result<usize> { |
| let buf = buf.as_mut(); |
| let n = self.inner.read(buf).await.map_err(format_napi_error)?; |
| Ok(n) |
| } |
| } |
| |
| /// BlockingWriter is designed to write data into a given path in a blocking |
| /// manner. |
| #[napi] |
| pub struct BlockingWriter(opendal::blocking::Writer); |
| |
| #[napi] |
| impl BlockingWriter { |
| /// # Safety |
| /// |
| /// > &mut self in async napi methods should be marked as unsafe |
| /// |
| /// Write bytes into this writer. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// const writer = await op.writer("path/to/file"); |
| /// await writer.write(Buffer.from("hello world")); |
| /// await writer.close(); |
| /// ``` |
| #[napi] |
| pub unsafe fn write(&mut self, content: Either<Buffer, String>) -> Result<()> { |
| let c = match content { |
| Either::A(buf) => buf.as_ref().to_owned(), |
| Either::B(s) => s.into_bytes(), |
| }; |
| self.0.write(c).map_err(format_napi_error) |
| } |
| |
| /// # Safety |
| /// |
| /// > &mut self in async napi methods should be marked as unsafe |
| /// |
| /// Close this writer. |
| /// |
| /// ### Example |
| /// |
| /// ```javascript |
| /// const writer = op.writerSync("path/to/file"); |
| /// writer.write(Buffer.from("hello world")); |
| /// writer.close(); |
| /// ``` |
| #[napi] |
| pub unsafe fn close(&mut self) -> Result<()> { |
| self.0.close().map(|_| ()).map_err(format_napi_error) |
| } |
| } |
| |
| /// Writer is designed to write data into a given path in an asynchronous |
| /// manner. |
| #[napi] |
| pub struct Writer(opendal::Writer); |
| |
| #[napi] |
| impl Writer { |
| /// # Safety |
| /// |
| /// > &mut self in async napi methods should be marked as unsafe |
| /// |
| /// Write bytes into this writer. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// const writer = await op.writer("path/to/file"); |
| /// await writer.write(Buffer.from("hello world")); |
| /// await writer.close(); |
| /// ``` |
| #[napi] |
| pub async unsafe fn write(&mut self, content: Either<Buffer, String>) -> Result<()> { |
| let c = match content { |
| Either::A(buf) => buf.as_ref().to_owned(), |
| Either::B(s) => s.into_bytes(), |
| }; |
| self.0.write(c).await.map_err(format_napi_error) |
| } |
| |
| /// # Safety |
| /// |
| /// > &mut self in async napi methods should be marked as unsafe |
| /// |
| /// Close this writer. |
| /// |
| /// ### Example |
| /// ```javascript |
| /// const writer = await op.writer("path/to/file"); |
| /// await writer.write(Buffer.from("hello world")); |
| /// await writer.close(); |
| /// ``` |
| #[napi] |
| pub async unsafe fn close(&mut self) -> Result<()> { |
| self.0.close().await.map(|_| ()).map_err(format_napi_error) |
| } |
| } |
| |
| /// Lister is designed to list entries at a given path in an asynchronous |
| /// manner. |
| #[napi] |
| pub struct Lister(opendal::Lister); |
| |
| #[napi] |
| impl Lister { |
| /// # Safety |
| /// |
| /// > &mut self in async napi methods should be marked as unsafe |
| /// |
| /// napi will make sure the function is safe, and we didn't do unsafe |
| /// things internally. |
| #[napi] |
| pub async unsafe fn next(&mut self) -> Result<Option<Entry>> { |
| Ok(self |
| .0 |
| .try_next() |
| .await |
| .map_err(format_napi_error)? |
| .map(Entry)) |
| } |
| } |
| |
| /// BlockingLister is designed to list entries at a given path in a blocking |
| /// manner. |
| #[napi] |
| pub struct BlockingLister(opendal::blocking::Lister); |
| |
| /// Method `next` can be confused for the standard trait method `std::iter::Iterator::next`. |
| /// But in JavaScript, it is also customary to use the next method directly to get the next element. |
| /// Therefore, disable this clippy. |
| /// It can be removed after a complete implementation of `Generator`. |
| /// FYI: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator> |
| #[napi] |
| #[allow(clippy::should_implement_trait)] |
| impl BlockingLister { |
| #[napi] |
| pub fn next(&mut self) -> Result<Option<Entry>> { |
| match self.0.next() { |
| Some(Ok(entry)) => Ok(Some(Entry(entry))), |
| Some(Err(e)) => Err(format_napi_error(e)), |
| None => Ok(None), |
| } |
| } |
| } |
| |
| /// PresignedRequest is a presigned request return by `presign`. |
| #[napi(object)] |
| pub struct PresignedRequest { |
| /// HTTP method of this request. |
| pub method: String, |
| /// URL of this request. |
| pub url: String, |
| /// HTTP headers of this request. |
| pub headers: HashMap<String, String>, |
| } |
| |
| impl PresignedRequest { |
| pub fn new(req: opendal::raw::PresignedRequest) -> Self { |
| let method = req.method().to_string(); |
| let url = req.uri().to_string(); |
| let headers = req |
| .header() |
| .iter() |
| .map(|(k, v)| { |
| ( |
| k.as_str().to_string(), |
| v.to_str() |
| .expect("header value contains non visible ascii characters") |
| .to_string(), |
| ) |
| }) |
| .collect(); |
| Self { |
| method, |
| url, |
| headers, |
| } |
| } |
| } |
| |
| #[napi] |
| impl Operator { |
| /// Add a layer to this operator. |
| #[napi(async_runtime)] |
| pub fn layer(&self, layer: &External<Layer>) -> Result<Self> { |
| let async_op = layer.inner.layer(self.async_op.clone()); |
| |
| let blocking_op = |
| opendal::blocking::Operator::new(async_op.clone()).map_err(format_napi_error)?; |
| |
| Ok(Self { |
| async_op, |
| blocking_op, |
| }) |
| } |
| } |
| |
| /// Format opendal error to napi error. |
| /// |
| /// FIXME: handle error correctly. |
| fn format_napi_error(err: impl Display) -> Error { |
| Error::from_reason(format!("{err}")) |
| } |