blob: 71dd514d4ebb890aa3cf72c1460f24304bd513ca [file]
// 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.
use crate::{
buffer::{OpendalReadBuffer, WriteBufferSlot, take_payload},
entry::into_entry_list_ptr,
error::OpenDALError,
executor::executor_or_default,
metadata::OpendalMetadata,
operator_info::{OpendalOperatorInfo, into_operator_info},
options::{
parse_delete_options, parse_list_options, parse_read_options, parse_stat_options,
parse_write_options,
},
presign::into_presigned_request_ptr,
result::{
OpendalEntryListResult, OpendalMetadataResult, OpendalOperatorInfoResult,
OpendalOperatorResult, OpendalOptionsResult, OpendalPresignedRequestResult,
OpendalReadResult, OpendalResult,
},
utils::{collect_options, require_callback, require_cstr, require_data_ptr, require_op_handle},
validators::prelude::{
validate_concurrent_limit_options, validate_retry_options, validate_throttle_options,
validate_timeout_options,
},
};
use std::collections::HashMap;
use std::ffi::c_void;
use std::os::raw::c_char;
use std::sync::Arc;
use std::time::Duration;
use futures::StreamExt;
use crate::executor::Executor;
/// The operator handle handed to .NET: the opendal operator plus the executor
/// it was bound to at construction. Every operation runs on that executor, and
/// the owned `Arc` keeps the runtime alive for as long as the operator lives.
pub(crate) struct OperatorHandle {
op: opendal::Operator,
executor: Arc<Executor>,
}
impl OperatorHandle {
/// Build a handle around a derived operator (layered or duplicated), keeping the same executor.
fn with_operator(&self, op: opendal::Operator) -> OperatorHandle {
OperatorHandle {
op,
executor: self.executor.clone(),
}
}
/// Clone the inner opendal operator into an owned value, e.g. to move it into a spawned task.
fn operator(&self) -> opendal::Operator {
self.op.clone()
}
}
impl std::ops::Deref for OperatorHandle {
type Target = opendal::Operator;
fn deref(&self) -> &opendal::Operator {
&self.op
}
}
/// Callback signature for async write completion.
///
/// The callback is provided by the .NET side and must remain valid until
/// invoked by Rust.
type WriteCallback = extern "C" fn(context: i64, result: OpendalResult);
type ReadCallback = extern "C" fn(context: i64, result: OpendalReadResult);
type StatCallback = extern "C" fn(context: i64, result: OpendalMetadataResult);
type ListCallback = extern "C" fn(context: i64, result: OpendalEntryListResult);
type PresignCallback = extern "C" fn(context: i64, result: OpendalPresignedRequestResult);
/// Build constructor options from raw C string key/value arrays.
///
/// On success, the returned pointer must be released by
/// `constructor_option_free`.
/// # Safety
///
/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays
/// containing at least `len` C-string pointers.
/// - Each key/value entry must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn constructor_option_build(
keys: *const *const c_char,
values: *const *const c_char,
len: usize,
) -> OpendalOptionsResult {
match unsafe { collect_options(keys, values, len) } {
Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void),
Err(error) => OpendalOptionsResult::from_error(error),
}
}
/// # Safety
///
/// - `options` must be null or a pointer returned by
/// `constructor_option_build`.
/// - This function must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub extern "C" fn constructor_option_free(options: *mut HashMap<String, String>) {
if options.is_null() {
return;
}
unsafe {
drop(Box::from_raw(options));
}
}
/// Build read options from raw C string key/value arrays.
///
/// On success, the returned pointer must be released by `read_option_free`.
/// # Safety
///
/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays
/// containing at least `len` C-string pointers.
/// - Each key/value entry must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn read_option_build(
keys: *const *const c_char,
values: *const *const c_char,
len: usize,
) -> OpendalOptionsResult {
match unsafe { collect_options(keys, values, len) }
.and_then(|values| parse_read_options(&values))
{
Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void),
Err(error) => OpendalOptionsResult::from_error(error),
}
}
/// # Safety
///
/// - `options` must be null or a pointer returned by `read_option_build`.
/// - This function must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn read_option_free(options: *mut opendal::options::ReadOptions) {
if options.is_null() {
return;
}
unsafe {
drop(Box::from_raw(options));
}
}
/// Build write options from raw C string key/value arrays.
///
/// On success, the returned pointer must be released by `write_option_free`.
/// # Safety
///
/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays
/// containing at least `len` C-string pointers.
/// - Each key/value entry must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn write_option_build(
keys: *const *const c_char,
values: *const *const c_char,
len: usize,
) -> OpendalOptionsResult {
match unsafe { collect_options(keys, values, len) }
.and_then(|values| parse_write_options(&values))
{
Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void),
Err(error) => OpendalOptionsResult::from_error(error),
}
}
/// # Safety
///
/// - `options` must be null or a pointer returned by `write_option_build`.
/// - This function must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn write_option_free(options: *mut opendal::options::WriteOptions) {
if options.is_null() {
return;
}
unsafe {
drop(Box::from_raw(options));
}
}
/// Build stat options from raw C string key/value arrays.
///
/// On success, the returned pointer must be released by `stat_option_free`.
/// # Safety
///
/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays
/// containing at least `len` C-string pointers.
/// - Each key/value entry must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn stat_option_build(
keys: *const *const c_char,
values: *const *const c_char,
len: usize,
) -> OpendalOptionsResult {
match unsafe { collect_options(keys, values, len) }
.and_then(|values| parse_stat_options(&values))
{
Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void),
Err(error) => OpendalOptionsResult::from_error(error),
}
}
/// # Safety
///
/// - `options` must be null or a pointer returned by `stat_option_build`.
/// - This function must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn stat_option_free(options: *mut opendal::options::StatOptions) {
if options.is_null() {
return;
}
unsafe {
drop(Box::from_raw(options));
}
}
/// Build list options from raw C string key/value arrays.
///
/// On success, the returned pointer must be released by `list_option_free`.
/// # Safety
///
/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays
/// containing at least `len` C-string pointers.
/// - Each key/value entry must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn list_option_build(
keys: *const *const c_char,
values: *const *const c_char,
len: usize,
) -> OpendalOptionsResult {
match unsafe { collect_options(keys, values, len) }
.and_then(|values| parse_list_options(&values))
{
Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void),
Err(error) => OpendalOptionsResult::from_error(error),
}
}
/// # Safety
///
/// - `options` must be null or a pointer returned by `list_option_build`.
/// - This function must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn list_option_free(options: *mut opendal::options::ListOptions) {
if options.is_null() {
return;
}
unsafe {
drop(Box::from_raw(options));
}
}
/// Build delete options from raw C string key/value arrays.
///
/// On success, the returned pointer must be released by `delete_option_free`.
/// # Safety
///
/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays
/// containing at least `len` C-string pointers.
/// - Each key/value entry must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn delete_option_build(
keys: *const *const c_char,
values: *const *const c_char,
len: usize,
) -> OpendalOptionsResult {
match unsafe { collect_options(keys, values, len) }
.and_then(|values| parse_delete_options(&values))
{
Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void),
Err(error) => OpendalOptionsResult::from_error(error),
}
}
/// # Safety
///
/// - `options` must be null or a pointer returned by `delete_option_build`.
/// - This function must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn delete_option_free(options: *mut opendal::options::DeleteOptions) {
if options.is_null() {
return;
}
unsafe {
drop(Box::from_raw(options));
}
}
/// Construct an OpenDAL operator instance from a scheme and key/value options.
///
/// Returns a pointer that must be released with `operator_free`.
/// # Safety
///
/// - `scheme` must be a valid null-terminated UTF-8 string.
/// - When `len > 0`, `keys` and `values` must be non-null and point to arrays
/// of at least `len` entries.
/// - Every key/value entry in those arrays must be a valid null-terminated
/// UTF-8 string.
/// - `executor` must be either null or a live pointer returned by
/// `executor_create`. The operator binds it for its whole lifetime.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_construct(
scheme: *const c_char,
options: *const HashMap<String, String>,
executor: *const c_void,
) -> OpendalOperatorResult {
match operator_construct_inner(scheme, options, executor) {
Ok(op) => OpendalOperatorResult::ok(op),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
fn operator_construct_inner(
scheme: *const c_char,
options: *const HashMap<String, String>,
executor: *const c_void,
) -> Result<*mut c_void, OpenDALError> {
let scheme = require_cstr(scheme, "scheme")?;
let options = if options.is_null() {
HashMap::default()
} else {
unsafe { (&*options).clone() }
};
// SAFETY: the caller keeps the executor handle alive across this call per
// `operator_construct`'s contract; the clone owns the runtime afterwards.
let executor = unsafe { executor_or_default(executor)? };
let op =
opendal::Operator::via_iter(scheme, options).map_err(OpenDALError::from_opendal_error)?;
Ok(Box::into_raw(Box::new(OperatorHandle { op, executor })) as *mut c_void)
}
/// # Safety
///
/// - `op_handle` must be either null or a pointer returned by `operator_construct`.
/// - The pointer must not be used after this call.
/// - This function must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_free(op_handle: *mut OperatorHandle) {
if op_handle.is_null() {
return;
}
unsafe {
drop(Box::from_raw(op_handle));
}
}
/// Get operator info payload.
///
/// On success, payload must be released by `opendal_operator_info_result_release`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_info_get(
op_handle: *const OperatorHandle,
) -> OpendalOperatorInfoResult {
match operator_info_get_inner(op_handle) {
Ok(value) => OpendalOperatorInfoResult::ok(value),
Err(error) => OpendalOperatorInfoResult::from_error(error),
}
}
fn operator_info_get_inner(op_handle: *const OperatorHandle) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
let info = into_operator_info(handle.info());
Ok(Box::into_raw(Box::new(info)) as *mut c_void)
}
/// # Safety
///
/// - `info` must be either null or a pointer returned by `operator_info_get`.
/// - The pointer must not be used after this call.
/// - This function must be called at most once for the same pointer.
pub(crate) unsafe fn operator_info_free(info: *mut OpendalOperatorInfo) {
if info.is_null() {
return;
}
unsafe {
let info = Box::from_raw(info);
if !info.scheme.is_null() {
drop(std::ffi::CString::from_raw(info.scheme));
}
if !info.root.is_null() {
drop(std::ffi::CString::from_raw(info.root));
}
if !info.name.is_null() {
drop(std::ffi::CString::from_raw(info.name));
}
}
}
/// Create a new operator layered with retry behavior.
///
/// The current operator is not modified. Returned pointer must be released with
/// `operator_free`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_layer_retry(
op_handle: *const OperatorHandle,
jitter: bool,
factor: f32,
min_delay_nanos: u64,
max_delay_nanos: u64,
max_times: usize,
) -> OpendalOperatorResult {
match operator_layer_retry_inner(
op_handle,
jitter,
factor,
min_delay_nanos,
max_delay_nanos,
max_times,
) {
Ok(value) => OpendalOperatorResult::ok(value),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
fn operator_layer_retry_inner(
op_handle: *const OperatorHandle,
jitter: bool,
factor: f32,
min_delay_nanos: u64,
max_delay_nanos: u64,
max_times: usize,
) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
validate_retry_options(factor, min_delay_nanos, max_delay_nanos)?;
let mut retry = opendal::layers::RetryLayer::new();
retry = retry.with_factor(factor);
retry = retry.with_min_delay(Duration::from_nanos(min_delay_nanos));
retry = retry.with_max_delay(Duration::from_nanos(max_delay_nanos));
retry = retry.with_max_times(max_times);
if jitter {
retry = retry.with_jitter();
}
Ok(Box::into_raw(Box::new(
handle.with_operator(handle.operator().layer(retry)),
)) as *mut c_void)
}
/// Create a new operator layered with concurrent-limit behavior.
///
/// The current operator is not modified. Returned pointer must be released with
/// `operator_free`.
///
/// `http_permits` limits concurrent HTTP requests independently of `permits`,
/// which limits concurrent operations. It is read only when `has_http_permits`
/// is true; otherwise the HTTP limit is left unset.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_layer_concurrent_limit(
op_handle: *const OperatorHandle,
permits: usize,
http_permits: usize,
has_http_permits: bool,
) -> OpendalOperatorResult {
let http_permits = has_http_permits.then_some(http_permits);
match operator_layer_concurrent_limit_inner(op_handle, permits, http_permits) {
Ok(value) => OpendalOperatorResult::ok(value),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
fn operator_layer_concurrent_limit_inner(
op_handle: *const OperatorHandle,
permits: usize,
http_permits: Option<usize>,
) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
validate_concurrent_limit_options(permits, http_permits)?;
let mut concurrent_limit = opendal::layers::ConcurrentLimitLayer::new(permits);
if let Some(http_permits) = http_permits {
concurrent_limit = concurrent_limit.with_http_concurrent_limit(http_permits);
}
Ok(Box::into_raw(Box::new(
handle.with_operator(handle.operator().layer(concurrent_limit)),
)) as *mut c_void)
}
/// Create a new operator layered with capability override behavior.
///
/// The current operator is not modified. Returned pointer must be released with
/// `operator_free`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `overrides` must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub extern "C" fn operator_layer_capability_override(
op_handle: *const OperatorHandle,
overrides: *const c_char,
) -> OpendalOperatorResult {
match operator_layer_capability_override_inner(op_handle, overrides) {
Ok(value) => OpendalOperatorResult::ok(value),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
fn operator_layer_capability_override_inner(
op_handle: *const OperatorHandle,
overrides: *const c_char,
) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
let overrides = require_cstr(overrides, "capability overrides")?;
let layer = opendal::layers::CapabilityOverrideLayer::from_overrides(overrides)
.map_err(OpenDALError::from_opendal_error)?;
Ok(Box::into_raw(Box::new(
handle.with_operator(handle.operator().layer(layer)),
)) as *mut c_void)
}
/// Create a new operator layered with timeout behavior.
///
/// The current operator is not modified. Returned pointer must be released with
/// `operator_free`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_layer_timeout(
op_handle: *const OperatorHandle,
timeout_nanos: u64,
io_timeout_nanos: u64,
) -> OpendalOperatorResult {
match operator_layer_timeout_inner(op_handle, timeout_nanos, io_timeout_nanos) {
Ok(value) => OpendalOperatorResult::ok(value),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
fn operator_layer_timeout_inner(
op_handle: *const OperatorHandle,
timeout_nanos: u64,
io_timeout_nanos: u64,
) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
validate_timeout_options(timeout_nanos, io_timeout_nanos)?;
let timeout = opendal::layers::TimeoutLayer::new()
.with_timeout(Duration::from_nanos(timeout_nanos))
.with_io_timeout(Duration::from_nanos(io_timeout_nanos));
Ok(Box::into_raw(Box::new(
handle.with_operator(handle.operator().layer(timeout)),
)) as *mut c_void)
}
/// Create a new operator layered with throttle behavior.
///
/// The current operator is not modified. Returned pointer must be released with
/// `operator_free`.
///
/// `bandwidth` is the maximum number of bytes allowed through per second, and
/// `burst` is the maximum number of bytes allowed through at once. `burst` must
/// exceed the largest possible operation size, otherwise that operation can
/// never acquire enough quota to proceed.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_layer_throttle(
op_handle: *const OperatorHandle,
bandwidth: u32,
burst: u32,
) -> OpendalOperatorResult {
match operator_layer_throttle_inner(op_handle, bandwidth, burst) {
Ok(value) => OpendalOperatorResult::ok(value),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
fn operator_layer_throttle_inner(
op_handle: *const OperatorHandle,
bandwidth: u32,
burst: u32,
) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
validate_throttle_options(bandwidth, burst)?;
let throttle = opendal::layers::ThrottleLayer::new(bandwidth, burst);
Ok(Box::into_raw(Box::new(
handle.with_operator(handle.operator().layer(throttle)),
)) as *mut c_void)
}
/// Create a new operator layered with MIME-guess behavior.
///
/// The current operator is not modified. Returned pointer must be released with
/// `operator_free`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_layer_mime_guess(
op_handle: *const OperatorHandle,
) -> OpendalOperatorResult {
match operator_layer_mime_guess_inner(op_handle) {
Ok(value) => OpendalOperatorResult::ok(value),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
fn operator_layer_mime_guess_inner(
op_handle: *const OperatorHandle,
) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
let mime_guess = opendal::layers::MimeGuessLayer::default();
Ok(Box::into_raw(Box::new(
handle.with_operator(handle.operator().layer(mime_guess)),
)) as *mut c_void)
}
/// Duplicate an operator instance.
///
/// Returned pointer must be released with `operator_free`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_duplicate(op_handle: *const OperatorHandle) -> OpendalOperatorResult {
match operator_duplicate_inner(op_handle) {
Ok(value) => OpendalOperatorResult::ok(value),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
fn operator_duplicate_inner(op_handle: *const OperatorHandle) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
Ok(Box::into_raw(Box::new(handle.with_operator(handle.operator()))) as *mut c_void)
}
/// Delete `path` synchronously with options.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - `options` must be null or a pointer returned by `delete_option_build`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_delete_with_options(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::DeleteOptions,
) -> OpendalResult {
match operator_delete_with_options_inner(op_handle, path, options) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_delete_with_options_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::DeleteOptions,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?;
let options = if options.is_null() {
opendal::options::DeleteOptions::default()
} else {
unsafe { (&*options).clone() }
};
executor
.block_on(handle.delete_options(path, options))
.map_err(OpenDALError::from_opendal_error)
}
/// Delete `path` asynchronously with options.
///
/// The callback is invoked exactly once with the final result.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - `options` must be null or a pointer returned by `delete_option_build`.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_delete_with_options_async(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::DeleteOptions,
callback: Option<WriteCallback>,
context: i64,
) -> OpendalResult {
match operator_delete_with_options_async_inner(op_handle, path, options, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_delete_with_options_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::DeleteOptions,
callback: Option<WriteCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
let options = if options.is_null() {
opendal::options::DeleteOptions::default()
} else {
unsafe { (&*options).clone() }
};
let op = handle.operator();
executor.spawn(async move {
let result = op
.delete_options(&path, options)
.await
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
},
);
});
Ok(())
}
/// Create directory at `path` synchronously.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub extern "C" fn operator_create_dir(
op_handle: *const OperatorHandle,
path: *const c_char,
) -> OpendalResult {
match operator_create_dir_inner(op_handle, path) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_create_dir_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?;
executor
.block_on(handle.create_dir(path))
.map_err(OpenDALError::from_opendal_error)
}
/// Create directory at `path` asynchronously.
///
/// The callback is invoked exactly once with the final result.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_create_dir_async(
op_handle: *const OperatorHandle,
path: *const c_char,
callback: Option<WriteCallback>,
context: i64,
) -> OpendalResult {
match operator_create_dir_async_inner(op_handle, path, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_create_dir_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
callback: Option<WriteCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
let op = handle.operator();
executor.spawn(async move {
let result = op
.create_dir(&path)
.await
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
},
);
});
Ok(())
}
/// Copy from `source_path` to `target_path` synchronously.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `source_path` and `target_path` must be valid null-terminated UTF-8 strings.
#[unsafe(no_mangle)]
pub extern "C" fn operator_copy(
op_handle: *const OperatorHandle,
source_path: *const c_char,
target_path: *const c_char,
) -> OpendalResult {
match operator_copy_inner(op_handle, source_path, target_path) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_copy_inner(
op_handle: *const OperatorHandle,
source_path: *const c_char,
target_path: *const c_char,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let source_path = require_cstr(source_path, "source_path")?;
let target_path = require_cstr(target_path, "target_path")?;
executor
.block_on(handle.copy(source_path, target_path))
.map(|_| ())
.map_err(OpenDALError::from_opendal_error)
}
/// Copy from `source_path` to `target_path` asynchronously.
///
/// The callback is invoked exactly once with the final result.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `source_path` and `target_path` must be valid null-terminated UTF-8 strings.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_copy_async(
op_handle: *const OperatorHandle,
source_path: *const c_char,
target_path: *const c_char,
callback: Option<WriteCallback>,
context: i64,
) -> OpendalResult {
match operator_copy_async_inner(op_handle, source_path, target_path, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_copy_async_inner(
op_handle: *const OperatorHandle,
source_path: *const c_char,
target_path: *const c_char,
callback: Option<WriteCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let source_path = require_cstr(source_path, "source_path")?.to_string();
let target_path = require_cstr(target_path, "target_path")?.to_string();
let callback = require_callback(callback)?;
let op = handle.operator();
executor.spawn(async move {
let result = op
.copy(&source_path, &target_path)
.await
.map(|_| ())
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
},
);
});
Ok(())
}
/// Rename from `source_path` to `target_path` synchronously.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `source_path` and `target_path` must be valid null-terminated UTF-8 strings.
#[unsafe(no_mangle)]
pub extern "C" fn operator_rename(
op_handle: *const OperatorHandle,
source_path: *const c_char,
target_path: *const c_char,
) -> OpendalResult {
match operator_rename_inner(op_handle, source_path, target_path) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_rename_inner(
op_handle: *const OperatorHandle,
source_path: *const c_char,
target_path: *const c_char,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let source_path = require_cstr(source_path, "source_path")?;
let target_path = require_cstr(target_path, "target_path")?;
executor
.block_on(handle.rename(source_path, target_path))
.map_err(OpenDALError::from_opendal_error)
}
/// Rename from `source_path` to `target_path` asynchronously.
///
/// The callback is invoked exactly once with the final result.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `source_path` and `target_path` must be valid null-terminated UTF-8 strings.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_rename_async(
op_handle: *const OperatorHandle,
source_path: *const c_char,
target_path: *const c_char,
callback: Option<WriteCallback>,
context: i64,
) -> OpendalResult {
match operator_rename_async_inner(op_handle, source_path, target_path, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_rename_async_inner(
op_handle: *const OperatorHandle,
source_path: *const c_char,
target_path: *const c_char,
callback: Option<WriteCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let source_path = require_cstr(source_path, "source_path")?.to_string();
let target_path = require_cstr(target_path, "target_path")?.to_string();
let callback = require_callback(callback)?;
let op = handle.operator();
executor.spawn(async move {
let result = op
.rename(&source_path, &target_path)
.await
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
},
);
});
Ok(())
}
/// Presign read asynchronously.
///
/// The callback is invoked exactly once with the final result.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_presign_read_async(
op_handle: *const OperatorHandle,
path: *const c_char,
expire_nanos: u64,
callback: Option<PresignCallback>,
context: i64,
) -> OpendalResult {
match operator_presign_read_async_inner(op_handle, path, expire_nanos, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_presign_read_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
expire_nanos: u64,
callback: Option<PresignCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
let expire = Duration::from_nanos(expire_nanos);
let op = handle.operator();
executor.spawn(async move {
let result = op
.presign_read(&path, expire)
.await
.map_err(OpenDALError::from_opendal_error)
.and_then(into_presigned_request_ptr);
callback(
context,
match result {
Ok(value) => OpendalPresignedRequestResult::ok(value),
Err(error) => OpendalPresignedRequestResult::from_error(error),
},
);
});
Ok(())
}
/// Presign write asynchronously.
///
/// The callback is invoked exactly once with the final result.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_presign_write_async(
op_handle: *const OperatorHandle,
path: *const c_char,
expire_nanos: u64,
callback: Option<PresignCallback>,
context: i64,
) -> OpendalResult {
match operator_presign_write_async_inner(op_handle, path, expire_nanos, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_presign_write_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
expire_nanos: u64,
callback: Option<PresignCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
let expire = Duration::from_nanos(expire_nanos);
let op = handle.operator();
executor.spawn(async move {
let result = op
.presign_write(&path, expire)
.await
.map_err(OpenDALError::from_opendal_error)
.and_then(into_presigned_request_ptr);
callback(
context,
match result {
Ok(value) => OpendalPresignedRequestResult::ok(value),
Err(error) => OpendalPresignedRequestResult::from_error(error),
},
);
});
Ok(())
}
/// Presign stat asynchronously.
///
/// The callback is invoked exactly once with the final result.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_presign_stat_async(
op_handle: *const OperatorHandle,
path: *const c_char,
expire_nanos: u64,
callback: Option<PresignCallback>,
context: i64,
) -> OpendalResult {
match operator_presign_stat_async_inner(op_handle, path, expire_nanos, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_presign_stat_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
expire_nanos: u64,
callback: Option<PresignCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
let expire = Duration::from_nanos(expire_nanos);
let op = handle.operator();
executor.spawn(async move {
let result = op
.presign_stat(&path, expire)
.await
.map_err(OpenDALError::from_opendal_error)
.and_then(into_presigned_request_ptr);
callback(
context,
match result {
Ok(value) => OpendalPresignedRequestResult::ok(value),
Err(error) => OpendalPresignedRequestResult::from_error(error),
},
);
});
Ok(())
}
/// Presign delete asynchronously.
///
/// The callback is invoked exactly once with the final result.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_presign_delete_async(
op_handle: *const OperatorHandle,
path: *const c_char,
expire_nanos: u64,
callback: Option<PresignCallback>,
context: i64,
) -> OpendalResult {
match operator_presign_delete_async_inner(op_handle, path, expire_nanos, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_presign_delete_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
expire_nanos: u64,
callback: Option<PresignCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
let expire = Duration::from_nanos(expire_nanos);
let op = handle.operator();
executor.spawn(async move {
let result = op
.presign_delete(&path, expire)
.await
.map_err(OpenDALError::from_opendal_error)
.and_then(into_presigned_request_ptr);
callback(
context,
match result {
Ok(value) => OpendalPresignedRequestResult::ok(value),
Err(error) => OpendalPresignedRequestResult::from_error(error),
},
);
});
Ok(())
}
/// Create an input stream for `path` with read options.
///
/// Returned pointer must be released by `operator_input_stream_free`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub extern "C" fn operator_input_stream_create(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ReadOptions,
) -> OpendalOperatorResult {
match operator_input_stream_create_inner(op_handle, path, options) {
Ok(value) => OpendalOperatorResult::ok(value),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
/// Streaming reader backing `OperatorInputStream`.
///
/// The underlying byte stream is async; the sync FFI path blocks on it
/// through the owning executor while the async path spawns onto it. The
/// mutex is defensive — the C# stream contract already forbids concurrent
/// reads — and the `Arc` lets in-flight tasks outlive an early free safely.
struct InputStream {
executor: Arc<Executor>,
inner: Arc<tokio::sync::Mutex<opendal::FuturesBytesStream>>,
}
/// Convert one polled chunk into an FFI read payload.
///
/// EOF becomes an empty buffer, matching the one-shot read contract.
fn next_chunk_to_buffer(
value: Option<std::io::Result<bytes::Bytes>>,
) -> Result<OpendalReadBuffer, OpenDALError> {
value
.transpose()
.map_err(|err| match err.downcast::<opendal::Error>() {
Ok(error) => OpenDALError::from_opendal_error(error),
Err(err) => OpenDALError::from_opendal_error(opendal::Error::new(
opendal::ErrorKind::Unexpected,
err.to_string(),
)),
})
.map(|chunk| {
chunk
.map(|bytes| OpendalReadBuffer::from_buffer(bytes.into()))
.unwrap_or_else(OpendalReadBuffer::empty)
})
}
fn operator_input_stream_create_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ReadOptions,
) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let options = if options.is_null() {
opendal::options::ReadOptions::default()
} else {
unsafe { (&*options).clone() }
};
let range = options.range;
let reader_options = opendal::options::ReaderOptions {
version: options.version,
if_match: options.if_match,
if_none_match: options.if_none_match,
if_modified_since: options.if_modified_since,
if_unmodified_since: options.if_unmodified_since,
content_length_hint: options.content_length_hint,
concurrent: options.concurrent,
chunk: options.chunk,
gap: options.gap,
..Default::default()
};
let stream_op = handle.operator();
let stream = executor
.block_on(async move {
let reader = stream_op.reader_options(&path, reader_options).await?;
reader.into_bytes_stream(range).await
})
.map_err(OpenDALError::from_opendal_error)?;
Ok(Box::into_raw(Box::new(InputStream {
executor,
inner: Arc::new(tokio::sync::Mutex::new(stream)),
})) as *mut c_void)
}
/// Read next bytes chunk from input stream.
///
/// Returns an empty buffer when EOF is reached.
/// # Safety
///
/// - `stream` must be a valid pointer returned by `operator_input_stream_create`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_input_stream_read_next(stream: *mut c_void) -> OpendalReadResult {
match operator_input_stream_read_next_inner(stream) {
Ok(buffer) => OpendalReadResult::ok(buffer),
Err(error) => OpendalReadResult::from_error(error),
}
}
fn operator_input_stream_read_next_inner(
stream: *mut c_void,
) -> Result<OpendalReadBuffer, OpenDALError> {
if stream.is_null() {
return Err(crate::utils::config_invalid_error(
"input stream pointer is null",
));
}
let stream = unsafe { &*(stream as *const InputStream) };
let inner = stream.inner.clone();
let value = stream
.executor
.block_on(async move { inner.lock().await.next().await });
next_chunk_to_buffer(value)
}
/// Read next bytes chunk from input stream asynchronously.
///
/// The callback is invoked exactly once; EOF is reported as an empty buffer.
/// On successful reads, the callback result must be released with
/// `opendal_read_result_release`. The stream must not be read again, on
/// either path, until the callback fires.
/// # Safety
///
/// - `stream` must be a valid pointer returned by `operator_input_stream_create`.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_input_stream_read_next_async(
stream: *mut c_void,
callback: Option<ReadCallback>,
context: i64,
) -> OpendalResult {
match operator_input_stream_read_next_async_inner(stream, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_input_stream_read_next_async_inner(
stream: *mut c_void,
callback: Option<ReadCallback>,
context: i64,
) -> Result<(), OpenDALError> {
if stream.is_null() {
return Err(crate::utils::config_invalid_error(
"input stream pointer is null",
));
}
let callback = require_callback(callback)?;
let stream = unsafe { &*(stream as *const InputStream) };
let inner = stream.inner.clone();
stream.executor.spawn(async move {
let value = inner.lock().await.next().await;
callback(
context,
match next_chunk_to_buffer(value) {
Ok(buffer) => OpendalReadResult::ok(buffer),
Err(error) => OpendalReadResult::from_error(error),
},
);
});
Ok(())
}
/// # Safety
///
/// - `stream` must be null or a pointer returned by `operator_input_stream_create`.
/// - Must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_input_stream_free(stream: *mut c_void) {
if stream.is_null() {
return;
}
unsafe {
drop(Box::from_raw(stream as *mut InputStream));
}
}
/// Create an output stream for `path` with write options.
///
/// Returned pointer must be released by `operator_output_stream_free`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub extern "C" fn operator_output_stream_create(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::WriteOptions,
) -> OpendalOperatorResult {
match operator_output_stream_create_inner(op_handle, path, options) {
Ok(value) => OpendalOperatorResult::ok(value),
Err(error) => OpendalOperatorResult::from_error(error),
}
}
/// Streaming writer backing `OperatorOutputStream`.
///
/// Same split as [`InputStream`]: async writer underneath, sync FFI blocks on
/// it, async FFI spawns onto it. The C# stream contract forbids concurrent
/// writes; the mutex is defensive and the `Arc` keeps in-flight tasks safe
/// against an early free.
struct OutputStream {
executor: Arc<Executor>,
inner: Arc<tokio::sync::Mutex<opendal::Writer>>,
}
fn operator_output_stream_create_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::WriteOptions,
) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let options = if options.is_null() {
opendal::options::WriteOptions::default()
} else {
unsafe { (&*options).clone() }
};
let writer_op = handle.operator();
let writer = executor
.block_on(async move { writer_op.writer_options(&path, options).await })
.map_err(OpenDALError::from_opendal_error)?;
Ok(Box::into_raw(Box::new(OutputStream {
executor,
inner: Arc::new(tokio::sync::Mutex::new(writer)),
})) as *mut c_void)
}
/// Write bytes to output stream.
/// # Safety
///
/// - `stream` must be a valid pointer returned by `operator_output_stream_create`.
/// - When `len > 0`, `data` must be non-null and readable for `len` bytes.
#[unsafe(no_mangle)]
pub extern "C" fn operator_output_stream_write(
stream: *mut c_void,
data: *const u8,
len: usize,
) -> OpendalResult {
match operator_output_stream_write_inner(stream, data, len) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_output_stream_write_inner(
stream: *mut c_void,
data: *const u8,
len: usize,
) -> Result<(), OpenDALError> {
if stream.is_null() {
return Err(crate::utils::config_invalid_error(
"output stream pointer is null",
));
}
require_data_ptr(data, len)?;
let stream = unsafe { &*(stream as *const OutputStream) };
let payload = if len == 0 {
bytes::Bytes::new()
} else {
bytes::Bytes::copy_from_slice(unsafe { std::slice::from_raw_parts(data, len) })
};
let inner = stream.inner.clone();
stream
.executor
.block_on(async move { inner.lock().await.write(payload).await })
.map(|_| ())
.map_err(OpenDALError::from_opendal_error)
}
/// Write bytes to output stream asynchronously.
///
/// The payload is copied before this function returns, so the caller only has
/// to keep `data` alive for the duration of this call. The callback is
/// invoked exactly once. The stream must not be written to again, on either
/// path, until the callback fires.
/// # Safety
///
/// - `stream` must be a valid pointer returned by `operator_output_stream_create`.
/// - When `len > 0`, `data` must be non-null and readable for `len` bytes.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_output_stream_write_async(
stream: *mut c_void,
data: *const u8,
len: usize,
callback: Option<WriteCallback>,
context: i64,
) -> OpendalResult {
match operator_output_stream_write_async_inner(stream, data, len, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_output_stream_write_async_inner(
stream: *mut c_void,
data: *const u8,
len: usize,
callback: Option<WriteCallback>,
context: i64,
) -> Result<(), OpenDALError> {
if stream.is_null() {
return Err(crate::utils::config_invalid_error(
"output stream pointer is null",
));
}
require_data_ptr(data, len)?;
let callback = require_callback(callback)?;
let stream = unsafe { &*(stream as *const OutputStream) };
let payload = if len == 0 {
bytes::Bytes::new()
} else {
bytes::Bytes::copy_from_slice(unsafe { std::slice::from_raw_parts(data, len) })
};
let inner = stream.inner.clone();
stream.executor.spawn(async move {
let result = inner
.lock()
.await
.write(payload)
.await
.map(|_| ())
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
},
);
});
Ok(())
}
/// Flush output stream.
///
/// The underlying writer only persists data on close, so this is a no-op that
/// exists to keep the FFI surface symmetric.
/// # Safety
///
/// - `stream` must be a valid pointer returned by `operator_output_stream_create`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_output_stream_flush(stream: *mut c_void) -> OpendalResult {
match operator_output_stream_flush_inner(stream) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_output_stream_flush_inner(stream: *mut c_void) -> Result<(), OpenDALError> {
if stream.is_null() {
return Err(crate::utils::config_invalid_error(
"output stream pointer is null",
));
}
Ok(())
}
/// Close output stream.
/// # Safety
///
/// - `stream` must be a valid pointer returned by `operator_output_stream_create`.
#[unsafe(no_mangle)]
pub extern "C" fn operator_output_stream_close(stream: *mut c_void) -> OpendalResult {
match operator_output_stream_close_inner(stream) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_output_stream_close_inner(stream: *mut c_void) -> Result<(), OpenDALError> {
if stream.is_null() {
return Err(crate::utils::config_invalid_error(
"output stream pointer is null",
));
}
let stream = unsafe { &*(stream as *const OutputStream) };
let inner = stream.inner.clone();
stream
.executor
.block_on(async move { inner.lock().await.close().await })
.map(|_| ())
.map_err(OpenDALError::from_opendal_error)
}
/// Close output stream asynchronously.
///
/// The callback is invoked exactly once. After a successful close the handle
/// only needs `operator_output_stream_free`.
/// # Safety
///
/// - `stream` must be a valid pointer returned by `operator_output_stream_create`.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_output_stream_close_async(
stream: *mut c_void,
callback: Option<WriteCallback>,
context: i64,
) -> OpendalResult {
match operator_output_stream_close_async_inner(stream, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_output_stream_close_async_inner(
stream: *mut c_void,
callback: Option<WriteCallback>,
context: i64,
) -> Result<(), OpenDALError> {
if stream.is_null() {
return Err(crate::utils::config_invalid_error(
"output stream pointer is null",
));
}
let callback = require_callback(callback)?;
let stream = unsafe { &*(stream as *const OutputStream) };
let inner = stream.inner.clone();
stream.executor.spawn(async move {
let result = inner
.lock()
.await
.close()
.await
.map(|_| ())
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
},
);
});
Ok(())
}
/// # Safety
///
/// - `stream` must be null or a pointer returned by `operator_output_stream_create`.
/// - Must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_output_stream_free(stream: *mut c_void) {
if stream.is_null() {
return;
}
unsafe {
drop(Box::from_raw(stream as *mut OutputStream));
}
}
/// Write the committed bytes of a write buffer to `path` synchronously.
///
/// On a non-error return the contents belong to the write and the caller
/// must not write through the buffer's pointers again; the handle itself
/// still needs `write_buffer_free`. An error raised before the payload is
/// taken leaves the slot untouched, while a backend failure after it has
/// already consumed the contents.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - `buffer` must be a handle from `write_buffer_create` whose contents
/// have not been consumed, and must not have been freed.
/// - Calls taking the same buffer handle must not run concurrently.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_write_with_options(
op_handle: *const OperatorHandle,
path: *const c_char,
buffer: *mut c_void,
committed_in_current: usize,
options: *const opendal::options::WriteOptions,
) -> OpendalResult {
match operator_write_with_options_inner(op_handle, path, buffer, committed_in_current, options)
{
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_write_with_options_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
buffer: *mut c_void,
committed_in_current: usize,
options: *const opendal::options::WriteOptions,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?;
if buffer.is_null() {
return Err(crate::utils::config_invalid_error(
"write buffer handle is null",
));
}
let options = if options.is_null() {
opendal::options::WriteOptions::default()
} else {
unsafe { (&*options).clone() }
};
// Taken only after every fallible check above, so an error result means
// ownership never transferred and the buffer stays usable.
let slot = unsafe { &mut *(buffer as *mut WriteBufferSlot) };
let payload = take_payload(slot, committed_in_current)?;
executor
.block_on(handle.write_options(path, payload, options))
.map(|_| ())
.map_err(OpenDALError::from_opendal_error)
}
/// Write the committed bytes of a write buffer to `path` asynchronously.
///
/// The callback is invoked exactly once. Ownership behaves as in the sync
/// variant: consumed on a non-error return, untouched on error.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - `buffer` must be a handle from `write_buffer_create` whose contents
/// have not been consumed, and must not have been freed.
/// - Calls taking the same buffer handle must not run concurrently.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_write_with_options_async(
op_handle: *const OperatorHandle,
path: *const c_char,
buffer: *mut c_void,
committed_in_current: usize,
options: *const opendal::options::WriteOptions,
callback: Option<WriteCallback>,
context: i64,
) -> OpendalResult {
match operator_write_with_options_async_inner(
op_handle,
path,
buffer,
committed_in_current,
options,
callback,
context,
) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
// Mirrors the `extern "C"` signature above, which clippy exempts because a C ABI
// is not a design choice. This helper exists only to give that function a body it
// can write with `?`.
#[allow(clippy::too_many_arguments)]
fn operator_write_with_options_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
buffer: *mut c_void,
committed_in_current: usize,
options: *const opendal::options::WriteOptions,
callback: Option<WriteCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
if buffer.is_null() {
return Err(crate::utils::config_invalid_error(
"write buffer handle is null",
));
}
let options = if options.is_null() {
opendal::options::WriteOptions::default()
} else {
unsafe { (&*options).clone() }
};
// Taken only after every fallible check above, so an error result means
// ownership never transferred and the buffer stays usable.
let slot = unsafe { &mut *(buffer as *mut WriteBufferSlot) };
let payload = take_payload(slot, committed_in_current)?;
let op = handle.operator();
executor.spawn(async move {
let result = op
.write_options(&path, payload, options)
.await
.map(|_| ())
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
},
);
});
Ok(())
}
/// Write a caller-owned byte range to `path` synchronously.
///
/// The bytes are copied before the write, so `data` only has to stay valid
/// for the duration of this call.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - When `len > 0`, `data` must be non-null and readable for `len` bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_write_bytes_with_options(
op_handle: *const OperatorHandle,
path: *const c_char,
data: *const u8,
len: usize,
options: *const opendal::options::WriteOptions,
) -> OpendalResult {
match operator_write_bytes_with_options_inner(op_handle, path, data, len, options) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_write_bytes_with_options_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
data: *const u8,
len: usize,
options: *const opendal::options::WriteOptions,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?;
require_data_ptr(data, len)?;
let options = if options.is_null() {
opendal::options::WriteOptions::default()
} else {
unsafe { (&*options).clone() }
};
// Copied explicitly: the only `Into<Buffer>` impl for slices is
// `From<&'static [u8]>`, which is zero-copy, so passing the raw slice
// would launder the caller's pointer into `'static` and let backends
// retain memory that is only pinned for the duration of this call.
let payload = if len == 0 {
bytes::Bytes::new()
} else {
bytes::Bytes::copy_from_slice(unsafe { std::slice::from_raw_parts(data, len) })
};
executor
.block_on(handle.write_options(path, payload, options))
.map(|_| ())
.map_err(OpenDALError::from_opendal_error)
}
/// Write a caller-owned byte range to `path` asynchronously.
///
/// The bytes are copied before the task is spawned, so `data` only has to
/// stay valid for the duration of this call. The callback is invoked exactly
/// once with the final result.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - When `len > 0`, `data` must be non-null and readable for `len` bytes.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_write_bytes_with_options_async(
op_handle: *const OperatorHandle,
path: *const c_char,
data: *const u8,
len: usize,
options: *const opendal::options::WriteOptions,
callback: Option<WriteCallback>,
context: i64,
) -> OpendalResult {
match operator_write_bytes_with_options_async_inner(
op_handle, path, data, len, options, callback, context,
) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
// Mirrors the `extern "C"` signature above, which clippy exempts because a C ABI
// is not a design choice. This helper exists only to give that function a body it
// can write with `?`.
#[allow(clippy::too_many_arguments)]
fn operator_write_bytes_with_options_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
data: *const u8,
len: usize,
options: *const opendal::options::WriteOptions,
callback: Option<WriteCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
require_data_ptr(data, len)?;
let callback = require_callback(callback)?;
let options = if options.is_null() {
opendal::options::WriteOptions::default()
} else {
unsafe { (&*options).clone() }
};
// Copied before the task is spawned, so the caller only has to keep its
// array alive for the duration of this call.
let payload = if len == 0 {
bytes::Bytes::new()
} else {
bytes::Bytes::copy_from_slice(unsafe { std::slice::from_raw_parts(data, len) })
};
let op = handle.operator();
executor.spawn(async move {
let result = op
.write_options(&path, payload, options)
.await
.map(|_| ())
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
},
);
});
Ok(())
}
/// Read bytes from `path` synchronously with options.
///
/// On success, the returned buffer must be released with `opendal_read_result_release`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn operator_read_with_options(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ReadOptions,
) -> OpendalReadResult {
match operator_read_with_options_inner(op_handle, path, options) {
Ok(value) => OpendalReadResult::ok(value),
Err(error) => OpendalReadResult::from_error(error),
}
}
fn operator_read_with_options_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ReadOptions,
) -> Result<OpendalReadBuffer, OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?;
let options = if options.is_null() {
opendal::options::ReadOptions::default()
} else {
unsafe { (&*options).clone() }
};
let value = executor
.block_on(handle.read_options(path, options))
.map_err(OpenDALError::from_opendal_error)?;
Ok(OpendalReadBuffer::from_buffer(value))
}
/// Read bytes from `path` asynchronously with options.
///
/// The callback is invoked exactly once. On successful reads, the callback
/// result must be released with `opendal_read_result_release`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_read_with_options_async(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ReadOptions,
callback: Option<ReadCallback>,
context: i64,
) -> OpendalResult {
match operator_read_with_options_async_inner(op_handle, path, options, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_read_with_options_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ReadOptions,
callback: Option<ReadCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
let options = if options.is_null() {
opendal::options::ReadOptions::default()
} else {
unsafe { (&*options).clone() }
};
let op = handle.operator();
executor.spawn(async move {
let result = op
.read_options(&path, options)
.await
.map(OpendalReadBuffer::from_buffer)
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(value) => OpendalReadResult::ok(value),
Err(error) => OpendalReadResult::from_error(error),
},
);
});
Ok(())
}
/// Stat `path` synchronously with options.
///
/// On success, returned payload must be released with `opendal_metadata_result_release`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays.
#[unsafe(no_mangle)]
pub extern "C" fn operator_stat_with_options(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::StatOptions,
) -> OpendalMetadataResult {
match operator_stat_with_options_inner(op_handle, path, options) {
Ok(value) => OpendalMetadataResult::ok(value as *mut c_void),
Err(error) => OpendalMetadataResult::from_error(error),
}
}
fn operator_stat_with_options_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::StatOptions,
) -> Result<*mut OpendalMetadata, OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?;
let options = if options.is_null() {
opendal::options::StatOptions::default()
} else {
unsafe { (&*options).clone() }
};
let metadata = executor
.block_on(handle.stat_options(path, options))
.map_err(OpenDALError::from_opendal_error)?;
Ok(Box::into_raw(Box::new(OpendalMetadata::from_metadata(
metadata,
))))
}
/// Stat `path` asynchronously with options.
///
/// The callback is invoked exactly once. On success, callback result must be
/// released with `opendal_metadata_result_release`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_stat_with_options_async(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::StatOptions,
callback: Option<StatCallback>,
context: i64,
) -> OpendalResult {
match operator_stat_with_options_async_inner(op_handle, path, options, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_stat_with_options_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::StatOptions,
callback: Option<StatCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
let options = if options.is_null() {
opendal::options::StatOptions::default()
} else {
unsafe { (&*options).clone() }
};
let op = handle.operator();
executor.spawn(async move {
let result = op
.stat_options(&path, options)
.await
.map(OpendalMetadata::from_metadata)
.map(|v| Box::into_raw(Box::new(v)))
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(value) => OpendalMetadataResult::ok(value as *mut c_void),
Err(error) => OpendalMetadataResult::from_error(error),
},
);
});
Ok(())
}
/// List entries from `path` synchronously with options.
///
/// On success, returned payload must be released with `opendal_entry_list_result_release`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays.
#[unsafe(no_mangle)]
pub extern "C" fn operator_list_with_options(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ListOptions,
) -> OpendalEntryListResult {
match operator_list_with_options_inner(op_handle, path, options) {
Ok(value) => OpendalEntryListResult::ok(value),
Err(error) => OpendalEntryListResult::from_error(error),
}
}
fn operator_list_with_options_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ListOptions,
) -> Result<*mut c_void, OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?;
let options = if options.is_null() {
opendal::options::ListOptions::default()
} else {
unsafe { (&*options).clone() }
};
let entries = executor
.block_on(handle.list_options(path, options))
.map_err(OpenDALError::from_opendal_error)?;
Ok(into_entry_list_ptr(entries))
}
/// List entries from `path` asynchronously with options.
///
/// The callback is invoked exactly once. On success, callback result must be
/// released with `opendal_entry_list_result_release`.
/// # Safety
///
/// - `op_handle` must be a valid operator pointer from `operator_construct`.
/// - `path` must be a valid null-terminated UTF-8 string.
/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays.
/// - `callback` must be a valid function pointer and remain callable until invoked.
#[unsafe(no_mangle)]
pub extern "C" fn operator_list_with_options_async(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ListOptions,
callback: Option<ListCallback>,
context: i64,
) -> OpendalResult {
match operator_list_with_options_async_inner(op_handle, path, options, callback, context) {
Ok(()) => OpendalResult::ok(),
Err(error) => OpendalResult::from_error(error),
}
}
fn operator_list_with_options_async_inner(
op_handle: *const OperatorHandle,
path: *const c_char,
options: *const opendal::options::ListOptions,
callback: Option<ListCallback>,
context: i64,
) -> Result<(), OpenDALError> {
let handle = require_op_handle(op_handle)?;
let executor = handle.executor.clone();
let path = require_cstr(path, "path")?.to_string();
let callback = require_callback(callback)?;
let options = if options.is_null() {
opendal::options::ListOptions::default()
} else {
unsafe { (&*options).clone() }
};
let op = handle.operator();
executor.spawn(async move {
let result = op
.list_options(&path, options)
.await
.map(into_entry_list_ptr)
.map_err(OpenDALError::from_opendal_error);
callback(
context,
match result {
Ok(value) => OpendalEntryListResult::ok(value),
Err(error) => OpendalEntryListResult::from_error(error),
},
);
});
Ok(())
}