This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 58226a619 feat(bindings/C): add support for list in C binding (#2448)
58226a619 is described below
commit 58226a619da06f9b018955840ff976377a4a86b1
Author: xyJi <[email protected]>
AuthorDate: Fri Jul 14 16:43:50 2023 +0800
feat(bindings/C): add support for list in C binding (#2448)
* force documentaion, add basic type
* lister and some more func
* add skeleton file for list test
* impl
* test basics
* makefile
* add list tests, the free should also be added
* build and pass
* change to ptr: pt1
* ptr pt2
* some comments in test
* ptr
* works done, need commenting
* remove all box::leak, use box::into_raw for consistency
* comments for opendal_operator_blocking_list
* more comments, and modify free
* format
* crate doc
---
bindings/c/CONTRIBUTING.md | 50 ++++++++--------
bindings/c/Makefile | 2 +
bindings/c/README.md | 19 +++---
bindings/c/include/opendal.h | 135 +++++++++++++++++++++++++++++++++++++++++++
bindings/c/src/lib.rs | 83 +++++++++++++++++++++++++-
bindings/c/src/result.rs | 12 ++++
bindings/c/src/types.rs | 120 ++++++++++++++++++++++++++++++++------
bindings/c/tests/bdd.cpp | 6 +-
bindings/c/tests/common.hpp | 42 ++++++++++++++
bindings/c/tests/list.cpp | 119 ++++++++++++++++++++++++++++++++++++++
10 files changed, 532 insertions(+), 56 deletions(-)
diff --git a/bindings/c/CONTRIBUTING.md b/bindings/c/CONTRIBUTING.md
index 1108e46bc..0cf732a37 100644
--- a/bindings/c/CONTRIBUTING.md
+++ b/bindings/c/CONTRIBUTING.md
@@ -1,16 +1,17 @@
# Contributing
-- [Contributing](#contributing)
- - [Setup](#setup)
- - [Using a dev container environment](#using-a-dev-container-environment)
- - [Bring your own toolbox](#bring-your-own-toolbox)
- - [Build](#build)
- - [Test](#test)
- - [Docs](#docs)
- - [Misc](#misc)
+
+1. [Contributing](#contributing)
+ 1. [Setup](#setup)
+ 1. [Using a dev container
environment](#using-a-dev-container-environment)
+ 2. [Bring your own toolbox](#bring-your-own-toolbox)
+ 2. [Build](#build)
+ 3. [Test](#test)
+ 4. [Documentation](#documentation)
## Setup
### Using a dev container environment
+
OpenDAL provides a pre-configured [dev container](https://containers.dev/)
that could be used in [Github
Codespaces](https://github.com/features/codespaces),
[VSCode](https://code.visualstudio.com/),
[JetBrains](https://www.jetbrains.com/remote-development/gateway/),
[JuptyerLab](https://jupyterlab.readthedocs.io/en/stable/). Please pick up your
favourite runtime environment.
The fastest way is:
@@ -18,16 +19,20 @@ The fastest way is:
[](https://codespaces.new/apache/incubator-opendal?quickstart=1&machine=standardLinux32gb)
### Bring your own toolbox
+
To build OpenDAL C binding, the following is all you need:
-- **A C++ compiler** that supports **c++14**, *e.g.* clang++ and g++
+
+- **A C++ compiler** that supports **c++14**, _e.g._ clang++ and g++
- To format the code, you need to install **clang-format**
- - The `opendal.h` is not formatted by hands when you contribute, please do
not format the file. **Use `make format` only.**
- - If your contribution is related to the files under `./tests`, you may
format it before submitting your pull request. But notice that different
versions of `clang-format` may format the files differently.
+
+ - The `opendal.h` is not formatted by hands when you contribute, please do
not format the file. **Use `make format` only.**
+ - If your contribution is related to the files under `./tests`, you may
format it before submitting your pull request. But notice that different
versions of `clang-format` may format the files differently.
- **GTest(Google Test)** need to be installed to build the BDD (Behavior
Driven Development) tests. To see how to build, check
[here](https://github.com/google/googletest).
For Ubuntu and Debian:
+
```shell
# install C/C++ toolchain
sudo apt install -y build-essential
@@ -46,43 +51,36 @@ sudo ln -s /usr/lib/libgtest_main.a
/usr/local/lib/libgtest_main.a
```
## Build
+
To build the library and header file.
+
```shell
make build
```
-- The header file `opendal.h` is under `./include`
+- The header file `opendal.h` is under `./include`
- The library is under `../../target/debug` after building.
To clean the build results.
+
```shell
make clean
```
## Test
+
To build and run the tests. (Note that you need to install GTest)
+
```shell
make test
```
-```text
-[==========] Running 1 test from 1 test suite.
-[----------] Global test environment set-up.
-[----------] 1 test from OpendalBddTest
-[ RUN ] OpendalBddTest.FeatureTest
-[ OK ] OpendalBddTest.FeatureTest (0 ms)
-[----------] 1 test from OpendalBddTest (0 ms total)
-
-[----------] Global test environment tear-down
-[==========] 1 test from 1 test suite ran. (0 ms total)
-[ PASSED ] 1 test.
-```
-
## Documentation
+
The documentation index page source is under `./docs/doxygen/html/index.html`.
If you want to build the documentations yourself, you could use
+
```sh
# this requires you to install doxygen
make doc
```
-
diff --git a/bindings/c/Makefile b/bindings/c/Makefile
index 0d62b6e94..85cb33306 100644
--- a/bindings/c/Makefile
+++ b/bindings/c/Makefile
@@ -41,7 +41,9 @@ build:
.PHONY: test
test:
$(CXX) tests/bdd.cpp -o $(OBJ_DIR)/bdd $(CXXFLAGS) $(LDFLAGS) $(LIBS)
+ $(CXX) tests/list.cpp -o $(OBJ_DIR)/list $(CXXFLAGS) $(LDFLAGS) $(LIBS)
$(OBJ_DIR)/bdd
+ $(OBJ_DIR)/list
.PHONY: doc
doc:
diff --git a/bindings/c/README.md b/bindings/c/README.md
index 2fe1fe72f..711361296 100644
--- a/bindings/c/README.md
+++ b/bindings/c/README.md
@@ -3,7 +3,9 @@

## Example
+
A simple read and write example
+
```C
#include "assert.h"
#include "opendal.h"
@@ -44,21 +46,25 @@ int main()
opendal_operator_free(&op);
}
```
+
For more examples, please refer to `./examples`
## Prerequisites
To build OpenDAL C binding, the following is all you need:
-- **A C++ compiler** that supports **c++14**, *e.g.* clang++ and g++
+
+- **A C++ compiler** that supports **c++14**, _e.g._ clang++ and g++
- To format the code, you need to install **clang-format**
- - The `opendal.h` is not formatted by hands when you contribute, please do
not format the file. **Use `make format` only.**
- - If your contribution is related to the files under `./tests`, you may
format it before submitting your pull request. But notice that different
versions of `clang-format` may format the files differently.
+
+ - The `opendal.h` is not formatted by hands when you contribute, please do
not format the file. **Use `make format` only.**
+ - If your contribution is related to the files under `./tests`, you may
format it before submitting your pull request. But notice that different
versions of `clang-format` may format the files differently.
- **GTest(Google Test)** need to be installed to build the BDD (Behavior
Driven Development) tests. To see how to build, check
[here](https://github.com/google/googletest).
- (optional) **Doxygen** need to be installed to generate documentations.
For Ubuntu and Debian:
+
```shell
# install C/C++ toolchain
sudo apt install -y build-essential
@@ -77,6 +83,7 @@ sudo ln -s /usr/lib/libgtest_main.a
/usr/local/lib/libgtest_main.a
```
## Makefile
+
- To **build the library and header file**.
```sh
@@ -87,7 +94,6 @@ sudo ln -s /usr/lib/libgtest_main.a
/usr/local/lib/libgtest_main.a
- The library is under `../../target/debug` after building.
-
- To **clean** the build results.
```sh
@@ -106,17 +112,16 @@ sudo ln -s /usr/lib/libgtest_main.a
/usr/local/lib/libgtest_main.a
make examples
```
-
-
## Documentation
+
The documentation index page source is under `./docs/doxygen/html/index.html`.
If you want to build the documentations yourself, you could use
+
```sh
# this requires you to install doxygen
make doc
```
-
## License
[Apache v2.0](https://www.apache.org/licenses/LICENSE-2.0)
diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h
index 73e1cdf7b..c2aa31721 100644
--- a/bindings/c/include/opendal.h
+++ b/bindings/c/include/opendal.h
@@ -81,6 +81,14 @@ typedef enum opendal_code {
OPENDAL_IS_SAME_FILE,
} opendal_code;
+/**
+ * BlockingLister is designed to list entries at given path in a blocking
+ * manner.
+ *
+ * Users can construct Lister by `blocking_list` or `blocking_scan`.
+ */
+typedef struct BlockingLister BlockingLister;
+
/**
* BlockingOperator is the entry for all public blocking APIs.
*
@@ -113,6 +121,11 @@ typedef enum opendal_code {
*/
typedef struct BlockingOperator BlockingOperator;
+/**
+ * Entry is the file/dir entry returned by `Lister`.
+ */
+typedef struct Entry Entry;
+
typedef struct HashMap_String__String HashMap_String__String;
/**
@@ -261,6 +274,43 @@ typedef struct opendal_result_stat {
enum opendal_code code;
} opendal_result_stat;
+/**
+ * \brief BlockingLister is designed to list entries at given path in a
blocking
+ * manner.
+ *
+ * Users can construct Lister by `blocking_list` or `blocking_scan`(currently
not supported in C binding)
+ *
+ * For examples, please see the comment section of
opendal_operator_blocking_list()
+ * @see opendal_operator_blocking_list()
+ */
+typedef struct opendal_blocking_lister {
+ struct BlockingLister *inner;
+} opendal_blocking_lister;
+
+/**
+ * \brief The result type returned by opendal_operator_blocking_list().
+ *
+ * The result type for opendal_operator_blocking_list(), the field `lister`
contains the lister
+ * of the path, which is an iterator of the objects under the path. the field
`code` represents
+ * whether the stat operation is successful.
+ */
+typedef struct opendal_result_list {
+ struct opendal_blocking_lister *lister;
+ enum opendal_code code;
+} opendal_result_list;
+
+/**
+ * \brief opendal_list_entry is the entry under a path, which is listed from
the opendal_blocking_lister
+ *
+ * For examples, please see the comment section of
opendal_operator_blocking_list()
+ * @see opendal_operator_blocking_list()
+ * @see opendal_list_entry_path()
+ * @see opendal_list_entry_name()
+ */
+typedef struct opendal_list_entry {
+ struct Entry *inner;
+} opendal_list_entry;
+
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
@@ -531,6 +581,56 @@ struct opendal_result_is_exist
opendal_operator_is_exist(const struct opendal_op
struct opendal_result_stat opendal_operator_stat(const struct
opendal_operator_ptr *ptr,
const char *path);
+/**
+ * \brief Blockingly list the objects in `path`.
+ *
+ * List the object in `path` blockingly by `op_ptr`, return a result with a
+ * opendal_blocking_lister. Users should call opendal_lister_next() on the
+ * lister.
+ *
+ * @param ptr The opendal_operator_ptr created previously
+ * @param path The designated path you want to delete
+ * @see opendal_blocking_lister
+ * @return
+ *
+ * # Example
+ *
+ * Following is an example
+ * ```C
+ * // You have written some data into some files path "root/dir1"
+ * // Your opendal_operator_ptr was called ptr
+ * opendal_result_list l = opendal_operator_blocking_list(ptr, "root/dir1");
+ * assert(l.code == OPENDAL_OK);
+ *
+ * opendal_blocking_lister *lister = l.lister;
+ * opendal_list_entry *entry;
+ *
+ * while ((entry = opendal_lister_next(lister)) != NULL) {
+ * const char* de_path = opendal_list_entry_path(entry);
+ * const char* de_name = opendal_list_entry_name(entry);
+ * // ...... your operations
+ *
+ * // remember to free the entry after you are done using it
+ * opendal_list_entry_free(entry);
+ * }
+ *
+ * // and remember to free the lister
+ * opendal_lister_free(lister);
+ * ```
+ *
+ * # Safety
+ *
+ * It is **safe** under the cases below
+ * * The memory pointed to by `path` must contain a valid nul terminator at
the end of
+ * the string.
+ *
+ * # Panic
+ *
+ * * If the `path` points to NULL, this function panics, i.e. exits with
information
+ */
+struct opendal_result_list opendal_operator_blocking_list(const struct
opendal_operator_ptr *ptr,
+ const char *path);
+
/**
* \brief Free the heap-allocated operator pointed by opendal_operator_ptr.
*
@@ -648,6 +748,41 @@ void opendal_operator_options_set(struct
opendal_operator_options *self,
*/
void opendal_operator_options_free(const struct opendal_operator_options
*options);
+/**
+ * \brief Return the next object to be listed
+ *
+ * Lister is an iterator of the objects under its path, this method is the
same as
+ * calling next() on the iterator
+ *
+ * For examples, please see the comment section of
opendal_operator_blocking_list()
+ * @see opendal_operator_blocking_list()
+ */
+struct opendal_list_entry *opendal_lister_next(const struct
opendal_blocking_lister *self);
+
+/**
+ * \brief Free the heap-allocated metadata used by opendal_blocking_lister
+ */
+void opendal_lister_free(const struct opendal_blocking_lister *p);
+
+/**
+ * Path of entry. Path is relative to operator's root.
+ * Only valid in current operator.
+ */
+const char *opendal_list_entry_path(const struct opendal_list_entry *self);
+
+/**
+ * Name of entry. Name is the last segment of path.
+ *
+ * If this entry is a dir, `Name` MUST endswith `/`
+ * Otherwise, `Name` MUST NOT endswith `/`.
+ */
+const char *opendal_list_entry_name(const struct opendal_list_entry *self);
+
+/**
+ * \brief Frees the heap memory used by the opendal_list_entry
+ */
+void opendal_list_entry_free(const struct opendal_list_entry *p);
+
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs
index 7c1c88f43..b749a5d6b 100644
--- a/bindings/c/src/lib.rs
+++ b/bindings/c/src/lib.rs
@@ -15,8 +15,16 @@
// specific language governing permissions and limitations
// under the License.
+#![warn(missing_docs)]
#![allow(non_camel_case_types)]
+//! The OpenDAL C binding.
+//!
+//! The OpenDAL C binding allows users to utilize the OpenDAL's amazing
storage accessing capability
+//! in the C programming language.
+//!
+//! For examples, you may see the examples subdirectory
+
mod error;
mod result;
mod types;
@@ -26,6 +34,8 @@ use std::os::raw::c_char;
use std::str::FromStr;
use ::opendal as od;
+use result::opendal_result_list;
+use types::opendal_blocking_lister;
use crate::error::opendal_code;
use crate::result::opendal_result_is_exist;
@@ -108,9 +118,9 @@ pub unsafe extern "C" fn opendal_operator_new(
};
// this prevents the operator memory from being dropped by the Box
- let op = opendal_operator_ptr::from(Box::leak(Box::new(op)));
+ let op = opendal_operator_ptr::from(Box::into_raw(Box::new(op)));
- Box::leak(Box::new(op))
+ Box::into_raw(Box::new(op))
}
/// \brief Blockingly write raw bytes to `path`.
@@ -415,3 +425,72 @@ pub unsafe extern "C" fn opendal_operator_stat(
},
}
}
+
+/// \brief Blockingly list the objects in `path`.
+///
+/// List the object in `path` blockingly by `op_ptr`, return a result with a
+/// opendal_blocking_lister. Users should call opendal_lister_next() on the
+/// lister.
+///
+/// @param ptr The opendal_operator_ptr created previously
+/// @param path The designated path you want to delete
+/// @see opendal_blocking_lister
+/// @return
+///
+/// # Example
+///
+/// Following is an example
+/// ```C
+/// // You have written some data into some files path "root/dir1"
+/// // Your opendal_operator_ptr was called ptr
+/// opendal_result_list l = opendal_operator_blocking_list(ptr, "root/dir1");
+/// assert(l.code == OPENDAL_OK);
+///
+/// opendal_blocking_lister *lister = l.lister;
+/// opendal_list_entry *entry;
+///
+/// while ((entry = opendal_lister_next(lister)) != NULL) {
+/// const char* de_path = opendal_list_entry_path(entry);
+/// const char* de_name = opendal_list_entry_name(entry);
+/// // ...... your operations
+///
+/// // remember to free the entry after you are done using it
+/// opendal_list_entry_free(entry);
+/// }
+///
+/// // and remember to free the lister
+/// opendal_lister_free(lister);
+/// ```
+///
+/// # Safety
+///
+/// It is **safe** under the cases below
+/// * The memory pointed to by `path` must contain a valid nul terminator at
the end of
+/// the string.
+///
+/// # Panic
+///
+/// * If the `path` points to NULL, this function panics, i.e. exits with
information
+#[no_mangle]
+pub unsafe extern "C" fn opendal_operator_blocking_list(
+ ptr: *const opendal_operator_ptr,
+ path: *const c_char,
+) -> opendal_result_list {
+ if path.is_null() {
+ panic!("The path given is pointing at NULL");
+ }
+
+ let op = (*ptr).as_ref();
+ let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() };
+ match op.list(path) {
+ Ok(lister) => opendal_result_list {
+ lister:
Box::into_raw(Box::new(opendal_blocking_lister::new(lister))),
+ code: opendal_code::OPENDAL_OK,
+ },
+
+ Err(e) => opendal_result_list {
+ lister: std::ptr::null_mut(),
+ code: opendal_code::from_opendal_error(e),
+ },
+ }
+}
diff --git a/bindings/c/src/result.rs b/bindings/c/src/result.rs
index bb338645f..c0d113718 100644
--- a/bindings/c/src/result.rs
+++ b/bindings/c/src/result.rs
@@ -21,6 +21,7 @@
//! we are defining all Result types here
use crate::error::opendal_code;
+use crate::types::opendal_blocking_lister;
use crate::types::opendal_bytes;
use crate::types::opendal_metadata;
@@ -65,3 +66,14 @@ pub struct opendal_result_stat {
/// The error code, should be OPENDAL_OK if succeeds
pub code: opendal_code,
}
+
+/// \brief The result type returned by opendal_operator_blocking_list().
+///
+/// The result type for opendal_operator_blocking_list(), the field `lister`
contains the lister
+/// of the path, which is an iterator of the objects under the path. the field
`code` represents
+/// whether the stat operation is successful.
+#[repr(C)]
+pub struct opendal_result_list {
+ pub lister: *mut opendal_blocking_lister,
+ pub code: opendal_code,
+}
diff --git a/bindings/c/src/types.rs b/bindings/c/src/types.rs
index f84f18190..e324b508e 100644
--- a/bindings/c/src/types.rs
+++ b/bindings/c/src/types.rs
@@ -57,9 +57,6 @@ impl opendal_operator_ptr {
/// ```
#[no_mangle]
pub unsafe extern "C" fn opendal_operator_free(op: *const
opendal_operator_ptr) {
- if op.is_null() || unsafe { (*op).ptr.is_null() } {
- return;
- }
let _ = unsafe { Box::from_raw((*op).ptr as *mut od::BlockingOperator)
};
let _ = unsafe { Box::from_raw(op as *mut opendal_operator_ptr) };
}
@@ -73,15 +70,15 @@ impl opendal_operator_ptr {
}
#[allow(clippy::from_over_into)]
-impl From<&od::BlockingOperator> for opendal_operator_ptr {
- fn from(value: &od::BlockingOperator) -> Self {
+impl From<*const od::BlockingOperator> for opendal_operator_ptr {
+ fn from(value: *const od::BlockingOperator) -> Self {
Self { ptr: value }
}
}
#[allow(clippy::from_over_into)]
-impl From<&mut od::BlockingOperator> for opendal_operator_ptr {
- fn from(value: &mut od::BlockingOperator) -> Self {
+impl From<*mut od::BlockingOperator> for opendal_operator_ptr {
+ fn from(value: *mut od::BlockingOperator) -> Self {
Self { ptr: value }
}
}
@@ -159,13 +156,10 @@ impl opendal_metadata {
/// \brief Free the heap-allocated metadata used by opendal_metadata
#[no_mangle]
pub extern "C" fn opendal_metadata_free(&mut self) {
- if !self.inner.is_null() {
- unsafe {
- mem::drop(Box::from_raw(self.inner));
- }
+ unsafe {
+ mem::drop(Box::from_raw(self.inner));
+ mem::drop(Box::from_raw(self as *mut Self));
}
-
- unsafe { mem::drop(Box::from_raw(self as *mut Self)) }
}
/// \brief Return the content_length of the metadata
@@ -256,10 +250,10 @@ impl opendal_operator_options {
pub extern "C" fn opendal_operator_options_new() -> *mut Self {
let map: HashMap<String, String> = HashMap::default();
let options = Self {
- inner: Box::leak(Box::new(map)),
+ inner: Box::into_raw(Box::new(map)),
};
- Box::leak(Box::new(options))
+ Box::into_raw(Box::new(options))
}
/// \brief Set a Key-Value pair inside opendal_operator_options
@@ -306,10 +300,100 @@ impl opendal_operator_options {
pub unsafe extern "C" fn opendal_operator_options_free(
options: *const opendal_operator_options,
) {
- if options.is_null() || unsafe { (*options).inner.is_null() } {
- return;
- }
let _ = unsafe { Box::from_raw((*options).inner as *mut
HashMap<String, String>) };
let _ = unsafe { Box::from_raw(options as *mut
opendal_operator_options) };
}
}
+
+/// \brief BlockingLister is designed to list entries at given path in a
blocking
+/// manner.
+///
+/// Users can construct Lister by `blocking_list` or `blocking_scan`(currently
not supported in C binding)
+///
+/// For examples, please see the comment section of
opendal_operator_blocking_list()
+/// @see opendal_operator_blocking_list()
+#[repr(C)]
+pub struct opendal_blocking_lister {
+ inner: *mut od::BlockingLister,
+}
+
+impl opendal_blocking_lister {
+ pub(crate) fn new(lister: od::BlockingLister) -> Self {
+ Self {
+ inner: Box::into_raw(Box::new(lister)),
+ }
+ }
+
+ /// \brief Return the next object to be listed
+ ///
+ /// Lister is an iterator of the objects under its path, this method is
the same as
+ /// calling next() on the iterator
+ ///
+ /// For examples, please see the comment section of
opendal_operator_blocking_list()
+ /// @see opendal_operator_blocking_list()
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_lister_next(&self) -> *mut
opendal_list_entry {
+ let e = (*self.inner).next();
+ if e.is_none() {
+ return std::ptr::null_mut();
+ }
+
+ match e.unwrap() {
+ Ok(e) => Box::into_raw(Box::new(opendal_list_entry::new(e))),
+ Err(_) => std::ptr::null_mut(),
+ }
+ }
+
+ /// \brief Free the heap-allocated metadata used by opendal_blocking_lister
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_lister_free(p: *const
opendal_blocking_lister) {
+ unsafe {
+ let _ = Box::from_raw((*p).inner as *mut od::BlockingLister);
+ let _ = Box::from_raw(p as *mut opendal_blocking_lister);
+ }
+ }
+}
+
+/// \brief opendal_list_entry is the entry under a path, which is listed from
the opendal_blocking_lister
+///
+/// For examples, please see the comment section of
opendal_operator_blocking_list()
+/// @see opendal_operator_blocking_list()
+/// @see opendal_list_entry_path()
+/// @see opendal_list_entry_name()
+#[repr(C)]
+pub struct opendal_list_entry {
+ inner: *mut od::Entry,
+}
+
+impl opendal_list_entry {
+ /// Used to convert the Rust type into C type
+ pub(crate) fn new(entry: od::Entry) -> Self {
+ Self {
+ inner: Box::into_raw(Box::new(entry)),
+ }
+ }
+
+ /// Path of entry. Path is relative to operator's root.
+ /// Only valid in current operator.
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_list_entry_path(&self) -> *const c_char {
+ (*self.inner).path().as_ptr() as *const c_char
+ }
+
+ /// Name of entry. Name is the last segment of path.
+ ///
+ /// If this entry is a dir, `Name` MUST endswith `/`
+ /// Otherwise, `Name` MUST NOT endswith `/`.
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_list_entry_name(&self) -> *const c_char {
+ (*self.inner).name().as_ptr() as *const c_char
+ }
+
+ /// \brief Frees the heap memory used by the opendal_list_entry
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_list_entry_free(p: *const
opendal_list_entry) {
+ unsafe {
+ let _ = Box::from_raw(p as *mut opendal_list_entry);
+ }
+ }
+}
diff --git a/bindings/c/tests/bdd.cpp b/bindings/c/tests/bdd.cpp
index cac9a11a0..c7d797172 100644
--- a/bindings/c/tests/bdd.cpp
+++ b/bindings/c/tests/bdd.cpp
@@ -25,7 +25,7 @@ extern "C" {
class OpendalBddTest : public ::testing::Test {
protected:
- const opendal_operator_ptr *p;
+ const opendal_operator_ptr* p;
std::string scheme;
std::string path;
@@ -37,7 +37,7 @@ protected:
this->path = std::string("test");
this->content = std::string("Hello, World!");
- opendal_operator_options *options = opendal_operator_options_new();
+ opendal_operator_options* options = opendal_operator_options_new();
opendal_operator_options_set(options, "root", "/myroot");
// Given A new OpenDAL Blocking Operator
@@ -69,7 +69,7 @@ TEST_F(OpendalBddTest, FeatureTest)
// The blocking file "test" entry mode must be file
opendal_result_stat s = opendal_operator_stat(this->p, this->path.c_str());
EXPECT_EQ(s.code, OPENDAL_OK);
- opendal_metadata *meta = s.meta;
+ opendal_metadata* meta = s.meta;
EXPECT_TRUE(opendal_metadata_is_file(meta));
// The blocking file "test" content length must be 13
diff --git a/bindings/c/tests/common.hpp b/bindings/c/tests/common.hpp
new file mode 100644
index 000000000..b8bf2e9b3
--- /dev/null
+++ b/bindings/c/tests/common.hpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#include <vector>
+#include <random>
+
+std::vector<unsigned char> generateRandomBytes(std::size_t size) {
+ // Create a random device to generate random numbers
+ std::random_device rd;
+
+ // Create a random engine and seed it with the random device
+ std::mt19937_64 gen(rd());
+
+ // Create a distribution to produce random bytes
+ std::uniform_int_distribution<unsigned char> dist(0, 255);
+
+ // Create a vector to hold the random bytes
+ std::vector<unsigned char> randomBytes(size);
+
+ // Generate random bytes and store them in the vector
+ for (std::size_t i = 0; i < size; ++i) {
+ randomBytes[i] = dist(gen);
+ }
+
+ return randomBytes;
+}
diff --git a/bindings/c/tests/list.cpp b/bindings/c/tests/list.cpp
new file mode 100644
index 000000000..de905f17d
--- /dev/null
+++ b/bindings/c/tests/list.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#include "common.hpp"
+#include "gtest/gtest.h"
+#include <cstring>
+
+extern "C" {
+#include "opendal.h"
+}
+
+class OpendalListTest : public ::testing::Test {
+protected:
+ const opendal_operator_ptr* p;
+
+ // set up a brand new operator
+ void SetUp() override
+ {
+ opendal_operator_options* options = opendal_operator_options_new();
+ opendal_operator_options_set(options, "root", "/myroot");
+
+ this->p = opendal_operator_new("memory", options);
+ EXPECT_TRUE(this->p->ptr);
+
+ opendal_operator_options_free(options);
+ }
+
+ void TearDown() override { opendal_operator_free(this->p); }
+};
+
+// Basic usecase of list
+TEST_F(OpendalListTest, ListDirTest)
+{
+ std::string dname = "some_random_dir_name_152312dbfas";
+ std::string fname = "some_random_file_name_2138912rbf";
+
+ // 4 MiB of random bytes
+ uintptr_t nbytes = 4 * 1024 * 1024;
+ auto rand = generateRandomBytes(4 * 1024 * 1024);
+ auto random_bytes = reinterpret_cast<uint8_t*>(rand.data());
+
+ std::string path = dname + "/" + fname;
+ opendal_bytes data = {
+ .data = random_bytes,
+ .len = nbytes,
+ };
+
+ // write must succeed
+ EXPECT_EQ(opendal_operator_blocking_write(this->p, path.c_str(), data),
+ OPENDAL_OK);
+
+ // list must succeed since the write succeeded
+ opendal_result_list l = opendal_operator_blocking_list(this->p, (dname +
"/").c_str());
+ EXPECT_EQ(l.code, OPENDAL_OK);
+
+ opendal_blocking_lister* lister = l.lister;
+
+ // start checking the lister's result
+ bool found = false;
+
+ opendal_list_entry* entry = opendal_lister_next(lister);
+ while (entry) {
+ const char* de_path = opendal_list_entry_path(entry);
+
+ // stat must succeed
+ opendal_result_stat s = opendal_operator_stat(this->p, de_path);
+ EXPECT_EQ(s.code, OPENDAL_OK);
+
+ opendal_metadata* meta = s.meta;
+
+ if (!strcmp(de_path, path.c_str())) {
+ found = true;
+
+ // the path we found has to be a file, and the length must be
coherent
+ EXPECT_TRUE(opendal_metadata_is_file(meta));
+ EXPECT_EQ(opendal_metadata_content_length(meta), nbytes);
+ }
+
+ opendal_list_entry_free(entry);
+ entry = opendal_lister_next(lister);
+ }
+
+ // we must have found the file we wrote
+ EXPECT_TRUE(found);
+
+ // delete
+ EXPECT_EQ(opendal_operator_blocking_delete(this->p, path.c_str()),
+ OPENDAL_OK);
+
+ opendal_lister_free(lister);
+}
+
+// todo: Try list an empty directory
+TEST_F(OpendalListTest, ListEmptyDirTest) { }
+
+// todo: Try list a directory that does not exist
+TEST_F(OpendalListTest, ListNotExistDirTest) { }
+
+int main(int argc, char** argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}