This is an automated email from the ASF dual-hosted git repository. yuchanns pushed a commit to branch feat-c-binding-writer in repository https://gitbox.apache.org/repos/asf/opendal.git
commit af2ce27a4344538f6711f0613b5c5d35b5b266e7 Author: Hanchin Hsieh <[email protected]> AuthorDate: Tue Sep 24 18:11:38 2024 +0800 feat(bindings/c): add writer operation Signed-off-by: Hanchin Hsieh <[email protected]> --- bindings/c/include/opendal.h | 106 ++++++++++++++++++++++++++++ bindings/c/src/lib.rs | 5 ++ bindings/c/src/operator.rs | 63 +++++++++++++++++ bindings/c/src/result.rs | 22 ++++++ bindings/c/src/writer.rs | 60 ++++++++++++++++ bindings/c/tests/bdd.cpp | 21 ++++-- core/src/types/blocking_write/std_writer.rs | 5 +- 7 files changed, 271 insertions(+), 11 deletions(-) diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index 585d0be023..7da64ec677 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -216,6 +216,18 @@ typedef struct OperatorInfo OperatorInfo; */ typedef struct StdReader StdReader; +/** + * StdWriter is the adapter of [`std::io::Write`] for [`BlockingWriter`]. + * + * Users can use this adapter in cases where they need to use [`std::io::Write`] related trait. + * + * # Notes + * + * Files are automatically closed when they go out of scope. Errors detected on closing are ignored + * by the implementation of Drop. Use the method `close` if these errors must be manually handled. + */ +typedef struct StdWriter StdWriter; + /** * \brief opendal_bytes carries raw-bytes with its length * @@ -430,6 +442,31 @@ typedef struct opendal_result_operator_reader { struct opendal_error *error; } opendal_result_operator_reader; +/** + * \brief The result type returned by opendal's writer operation. + * \note The opendal_writer actually owns a pointer to + * a opendal::BlockingWriter, which is inside the Rust core code. + */ +typedef struct opendal_writer { + struct StdWriter *inner; +} opendal_writer; + +/** + * \brief The result type returned by opendal_operator_writer(). + * The result type for opendal_operator_writer(), the field `writer` contains the writer + * of the path, which is an iterator of the objects under the path. the field `code` represents + */ +typedef struct opendal_result_operator_writer { + /** + * The pointer for opendal_writer + */ + struct opendal_writer *writer; + /** + * The error, if ok, it is null + */ + struct opendal_error *error; +} opendal_result_operator_writer; + /** * \brief The result type returned by opendal_operator_is_exist(). * @@ -680,6 +717,22 @@ typedef struct opendal_result_reader_read { struct opendal_error *error; } opendal_result_reader_read; +/** + * \brief The result type returned by opendal_writer_write(). + * The result type contains a size field, which is the size of the data written, + * which is zero on error. The error field is the error code and error message. + */ +typedef struct opendal_result_writer_write { + /** + * The write size if succeed. + */ + uintptr_t size; + /** + * The error, if ok, it is null + */ + struct opendal_error *error; +} opendal_result_writer_write; + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -970,6 +1023,47 @@ struct opendal_result_read opendal_operator_read(const struct opendal_operator * struct opendal_result_operator_reader opendal_operator_reader(const struct opendal_operator *op, const char *path); +/** + * \brief Blockingly create a writer for the specified path. + * + * This function prepares a writer that can be used to write data to the specified path + * using the provided operator. If successful, it returns a valid writer; otherwise, it + * returns an error. + * + * @param op The opendal_operator created previously + * @param path The designated path where the writer will be used + * @see opendal_operator + * @see opendal_result_operator_writer + * @see opendal_error + * @return Returns opendal_result_operator_writer, containing a writer and an opendal_error. + * If the operation succeeds, the `writer` field holds a valid writer and the `error` field + * is null. Otherwise, the `writer` will be null and the `error` will be set correspondingly. + * + * # Example + * + * Following is an example + * ```C + * //...prepare your opendal_operator, named op for example + * + * opendal_result_operator_writer result = opendal_operator_writer(op, "/testpath"); + * assert(result.error == NULL); + * opendal_writer *writer = result.writer; + * // Use the writer to write data... + * ``` + * + * # 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_operator_writer opendal_operator_writer(const struct opendal_operator *op, + const char *path); + /** * \brief Blockingly delete the object in `path`. * @@ -1419,6 +1513,18 @@ struct opendal_result_reader_read opendal_reader_read(const struct opendal_reade */ void opendal_reader_free(struct opendal_reader *ptr); +/** + * \brief Write data to the writer. + */ +struct opendal_result_writer_write opendal_writer_write(const struct opendal_writer *writer, + uint8_t *buf, + uintptr_t len); + +/** + * \brief Frees the heap memory used by the opendal_writer. + */ +void opendal_writer_free(struct opendal_writer *ptr); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs index cfd46f92ed..ac125b0d42 100644 --- a/bindings/c/src/lib.rs +++ b/bindings/c/src/lib.rs @@ -51,9 +51,11 @@ pub use result::opendal_result_list; pub use result::opendal_result_lister_next; pub use result::opendal_result_operator_new; pub use result::opendal_result_operator_reader; +pub use result::opendal_result_operator_writer; pub use result::opendal_result_read; pub use result::opendal_result_reader_read; pub use result::opendal_result_stat; +pub use result::opendal_result_writer_write; mod types; pub use types::opendal_bytes; @@ -64,3 +66,6 @@ pub use entry::opendal_entry; mod reader; pub use reader::opendal_reader; + +mod writer; +pub use writer::opendal_writer; diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs index 71a1e52e68..c1af1e5717 100644 --- a/bindings/c/src/operator.rs +++ b/bindings/c/src/operator.rs @@ -391,6 +391,69 @@ pub unsafe extern "C" fn opendal_operator_reader( } } +/// \brief Blockingly create a writer for the specified path. +/// +/// This function prepares a writer that can be used to write data to the specified path +/// using the provided operator. If successful, it returns a valid writer; otherwise, it +/// returns an error. +/// +/// @param op The opendal_operator created previously +/// @param path The designated path where the writer will be used +/// @see opendal_operator +/// @see opendal_result_operator_writer +/// @see opendal_error +/// @return Returns opendal_result_operator_writer, containing a writer and an opendal_error. +/// If the operation succeeds, the `writer` field holds a valid writer and the `error` field +/// is null. Otherwise, the `writer` will be null and the `error` will be set correspondingly. +/// +/// # Example +/// +/// Following is an example +/// ```C +/// //...prepare your opendal_operator, named op for example +/// +/// opendal_result_operator_writer result = opendal_operator_writer(op, "/testpath"); +/// assert(result.error == NULL); +/// opendal_writer *writer = result.writer; +/// // Use the writer to write data... +/// ``` +/// +/// # 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_writer( + op: *const opendal_operator, + path: *const c_char, +) -> opendal_result_operator_writer { + if path.is_null() { + panic!("The path given is pointing at NULL"); + } + let op = (*op).as_ref(); + + let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }; + let writer = match op.writer(path) { + Ok(writer) => writer, + Err(err) => { + return opendal_result_operator_writer { + writer: std::ptr::null_mut(), + error: opendal_error::new(err), + } + } + }; + + opendal_result_operator_writer { + writer: Box::into_raw(Box::new(opendal_writer::new(writer.into_std_write()))), + error: std::ptr::null_mut(), + } +} + /// \brief Blockingly delete the object in `path`. /// /// Delete the object in `path` blockingly by `op_ptr`. diff --git a/bindings/c/src/result.rs b/bindings/c/src/result.rs index 64b2789228..ba7e1c64a0 100644 --- a/bindings/c/src/result.rs +++ b/bindings/c/src/result.rs @@ -131,3 +131,25 @@ pub struct opendal_result_reader_read { /// The error, if ok, it is null pub error: *mut opendal_error, } + +/// \brief The result type returned by opendal_operator_writer(). +/// The result type for opendal_operator_writer(), the field `writer` contains the writer +/// of the path, which is an iterator of the objects under the path. the field `code` represents +#[repr(C)] +pub struct opendal_result_operator_writer { + /// The pointer for opendal_writer + pub writer: *mut opendal_writer, + /// The error, if ok, it is null + pub error: *mut opendal_error, +} + +/// \brief The result type returned by opendal_writer_write(). +/// The result type contains a size field, which is the size of the data written, +/// which is zero on error. The error field is the error code and error message. +#[repr(C)] +pub struct opendal_result_writer_write { + /// The write size if succeed. + pub size: usize, + /// The error, if ok, it is null + pub error: *mut opendal_error, +} diff --git a/bindings/c/src/writer.rs b/bindings/c/src/writer.rs new file mode 100644 index 0000000000..1c4fb33044 --- /dev/null +++ b/bindings/c/src/writer.rs @@ -0,0 +1,60 @@ +use std::io::Write; + +use ::opendal as core; + +use super::*; + +/// \brief The result type returned by opendal's writer operation. +/// \note The opendal_writer actually owns a pointer to +/// a opendal::BlockingWriter, which is inside the Rust core code. +#[repr(C)] +pub struct opendal_writer { + inner: *mut core::StdWriter, +} + +impl opendal_writer { + pub(crate) fn new(writer: core::StdWriter) -> Self { + Self { + inner: Box::into_raw(Box::new(writer)), + } + } + + /// \brief Write data to the writer. + #[no_mangle] + pub unsafe extern "C" fn opendal_writer_write( + writer: *const Self, + buf: *mut u8, + len: usize, + ) -> opendal_result_writer_write { + if buf.is_null() { + panic!("The buffer given is pointing at NULL"); + } + + let buf = unsafe { std::slice::from_raw_parts(buf, len) }; + + let inner = unsafe { &mut *(*writer).inner }; + let n = inner.write(buf); + match n { + Ok(n) => opendal_result_writer_write { + size: n, + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_writer_write { + size: 0, + error: opendal_error::new( + core::Error::new(core::ErrorKind::Unexpected, "write failed from writer") + .set_source(e), + ), + }, + } + } + + /// \brief Frees the heap memory used by the opendal_writer. + #[no_mangle] + pub unsafe extern "C" fn opendal_writer_free(ptr: *mut opendal_writer) { + if !ptr.is_null() { + let _ = unsafe { Box::from_raw((*ptr).inner) }; + let _ = unsafe { Box::from_raw(ptr) }; + } + } +} diff --git a/bindings/c/tests/bdd.cpp b/bindings/c/tests/bdd.cpp index 370c17fd5b..38f27df36b 100644 --- a/bindings/c/tests/bdd.cpp +++ b/bindings/c/tests/bdd.cpp @@ -90,6 +90,20 @@ TEST_F(OpendalBddTest, FeatureTest) EXPECT_EQ(this->content[i], (char)(r.data->data[i])); } + // The blocking file should be deleted + error = opendal_operator_delete(this->p, this->path.c_str()); + EXPECT_EQ(error, nullptr); + e = opendal_operator_is_exist(this->p, this->path.c_str()); + EXPECT_EQ(e.error, nullptr); + EXPECT_FALSE(e.is_exist); + + opendal_result_operator_writer writer = opendal_operator_writer(this->p, this->path.c_str()); + EXPECT_EQ(writer.error, nullptr); + opendal_result_writer_write w = opendal_writer_write(writer.writer, (unsigned char*)this->content.c_str(), this->content.length()); + EXPECT_EQ(w.error, nullptr); + EXPECT_EQ(w.size, this->content.length()); + opendal_writer_free(writer.writer); + // The blocking file "test" must have content "Hello, World!" and read into buffer int length = this->content.length(); unsigned char buffer[this->content.length()]; @@ -102,13 +116,6 @@ TEST_F(OpendalBddTest, FeatureTest) } opendal_reader_free(reader.reader); - // The blocking file should be deleted - error = opendal_operator_delete(this->p, this->path.c_str()); - EXPECT_EQ(error, nullptr); - e = opendal_operator_is_exist(this->p, this->path.c_str()); - EXPECT_EQ(e.error, nullptr); - EXPECT_FALSE(e.is_exist); - // The deletion operation should be idempotent error = opendal_operator_delete(this->p, this->path.c_str()); EXPECT_EQ(error, nullptr); diff --git a/core/src/types/blocking_write/std_writer.rs b/core/src/types/blocking_write/std_writer.rs index 91accbde53..56dfd8c1cf 100644 --- a/core/src/types/blocking_write/std_writer.rs +++ b/core/src/types/blocking_write/std_writer.rs @@ -112,9 +112,6 @@ impl Write for StdWriter { impl Drop for StdWriter { fn drop(&mut self) { - if let Some(mut w) = self.w.take() { - // Ignore error happens in close. - let _ = w.close(); - } + let _ = self.close(); } }
