blob: e0651b20f860b02fcc5f2bf6b067dfb239a14d4c [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.
#[cfg(feature = "async")]
mod r#async;
mod layer;
mod lister;
mod reader;
mod types;
mod writer;
#[cfg(feature = "testing")]
mod test_support;
pub use layer::LayerBuilder;
use std::collections::HashMap;
use std::sync::LazyLock;
use anyhow::Result;
use lister::Lister;
use od::raw::Timestamp;
use opendal as od;
use reader::Reader;
use writer::Writer;
static RUNTIME: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
});
#[cxx::bridge(namespace = "opendal::ffi")]
mod ffi {
struct HashMapValue {
key: String,
value: String,
}
struct OptionalU64 {
has_value: bool,
value: u64,
}
struct OptionalUsize {
has_value: bool,
value: usize,
}
struct OptionalTimestamp {
has_value: bool,
seconds: i64,
nanoseconds: u32,
}
struct FfiBytesRange {
kind: u8,
start: u64,
end: u64,
}
struct FfiStatOptions {
version: OptionalString,
if_match: OptionalString,
if_none_match: OptionalString,
if_modified_since: OptionalTimestamp,
if_unmodified_since: OptionalTimestamp,
override_content_type: OptionalString,
override_cache_control: OptionalString,
override_content_disposition: OptionalString,
}
struct FfiReadOptions {
range: FfiBytesRange,
version: OptionalString,
if_match: OptionalString,
if_none_match: OptionalString,
if_modified_since: OptionalTimestamp,
if_unmodified_since: OptionalTimestamp,
content_length_hint: OptionalU64,
concurrent: usize,
chunk: OptionalUsize,
gap: OptionalUsize,
override_content_type: OptionalString,
override_cache_control: OptionalString,
override_content_disposition: OptionalString,
}
struct FfiReaderOptions {
version: OptionalString,
if_match: OptionalString,
if_none_match: OptionalString,
if_modified_since: OptionalTimestamp,
if_unmodified_since: OptionalTimestamp,
content_length_hint: OptionalU64,
concurrent: usize,
chunk: OptionalUsize,
gap: OptionalUsize,
prefetch: usize,
}
struct FfiWriteOptions {
append: bool,
cache_control: OptionalString,
content_type: OptionalString,
content_disposition: OptionalString,
content_encoding: OptionalString,
has_user_metadata: bool,
user_metadata: Vec<HashMapValue>,
if_match: OptionalString,
if_none_match: OptionalString,
if_not_exists: bool,
concurrent: usize,
chunk: OptionalUsize,
}
struct FfiCopyOptions {
if_not_exists: bool,
if_match: OptionalString,
source_version: OptionalString,
source_content_length_hint: OptionalU64,
concurrent: usize,
chunk: OptionalUsize,
}
struct FfiRenameOptions {
if_not_exists: bool,
}
struct FfiDeleteOptions {
version: OptionalString,
recursive: bool,
}
struct FfiListOptions {
limit: OptionalUsize,
start_after: OptionalString,
recursive: bool,
versions: bool,
deleted: bool,
}
#[cxx_name = "SeekDir"]
enum SeekFrom {
Start = 0,
Current = 1,
End = 2,
}
enum EntryMode {
File = 1,
Dir = 2,
Unknown = 0,
}
struct OptionalString {
has_value: bool,
value: String,
}
struct OptionalBool {
has_value: bool,
value: bool,
}
struct OptionalEntry {
has_value: bool,
value: Entry,
}
struct Capability {
stat: bool,
stat_with_if_match: bool,
stat_with_if_none_match: bool,
read: bool,
read_with_if_match: bool,
read_with_if_none_match: bool,
read_with_override_cache_control: bool,
read_with_override_content_disposition: bool,
read_with_override_content_type: bool,
write: bool,
write_can_multi: bool,
write_can_empty: bool,
write_can_append: bool,
write_with_content_type: bool,
write_with_content_disposition: bool,
write_with_cache_control: bool,
write_multi_max_size: usize,
write_multi_min_size: usize,
write_total_max_size: usize,
create_dir: bool,
delete_feature: bool,
copy: bool,
rename: bool,
list: bool,
list_with_limit: bool,
list_with_start_after: bool,
list_with_recursive: bool,
presign: bool,
presign_read: bool,
presign_stat: bool,
presign_write: bool,
shared: bool,
}
struct Metadata {
mode: EntryMode,
content_length: u64,
cache_control: OptionalString,
content_disposition: OptionalString,
content_md5: OptionalString,
content_type: OptionalString,
content_encoding: OptionalString,
etag: OptionalString,
last_modified: OptionalString,
version: OptionalString,
is_current: OptionalBool,
is_deleted: bool,
// Note: content_range and user_metadata are complex types that need special handling
}
struct Entry {
path: String,
}
extern "Rust" {
type Operator;
type Reader;
type Lister;
type Writer;
type LayerBuilder;
fn layer_builder_new() -> Box<LayerBuilder>;
fn add_timeout(self: &mut LayerBuilder, timeout_ns: u64, io_timeout_ns: u64);
fn add_retry(
self: &mut LayerBuilder,
jitter: bool,
factor: f32,
min_delay_ns: u64,
max_delay_ns: u64,
max_times: u64,
);
// cxx::UniquePtr isn't yet supported in stable Rust. We'll adopt it once support becomes available
fn new_operator(
scheme: &str,
configs: Vec<HashMapValue>,
layers: &LayerBuilder,
) -> Result<*mut Operator>;
unsafe fn delete_operator(op: *mut Operator);
fn read(self: &Operator, path: &str) -> Result<Vec<u8>>;
fn read_options(self: &Operator, path: &str, opts: FfiReadOptions) -> Result<Vec<u8>>;
fn write(self: &Operator, path: &str, bs: Vec<u8>) -> Result<()>;
fn write_options(
self: &Operator,
path: &str,
bs: Vec<u8>,
opts: FfiWriteOptions,
) -> Result<()>;
fn exists(self: &Operator, path: &str) -> Result<bool>;
fn create_dir(self: &Operator, path: &str) -> Result<()>;
fn copy(self: &Operator, src: &str, dst: &str) -> Result<()>;
fn copy_options(self: &Operator, src: &str, dst: &str, opts: FfiCopyOptions) -> Result<()>;
fn rename(self: &Operator, src: &str, dst: &str) -> Result<()>;
fn rename_options(
self: &Operator,
src: &str,
dst: &str,
opts: FfiRenameOptions,
) -> Result<()>;
fn remove(self: &Operator, path: &str) -> Result<()>;
fn remove_options(self: &Operator, path: &str, opts: FfiDeleteOptions) -> Result<()>;
fn remove_all(self: &Operator, paths: Vec<String>) -> Result<()>;
fn stat(self: &Operator, path: &str) -> Result<Metadata>;
fn stat_options(self: &Operator, path: &str, opts: FfiStatOptions) -> Result<Metadata>;
fn list(self: &Operator, path: &str) -> Result<Vec<Entry>>;
fn list_options(self: &Operator, path: &str, opts: FfiListOptions) -> Result<Vec<Entry>>;
fn reader(self: &Operator, path: &str) -> Result<*mut Reader>;
fn reader_options(
self: &Operator,
path: &str,
opts: FfiReaderOptions,
) -> Result<*mut Reader>;
fn lister(self: &Operator, path: &str) -> Result<*mut Lister>;
fn lister_options(self: &Operator, path: &str, opts: FfiListOptions)
-> Result<*mut Lister>;
fn writer(self: &Operator, path: &str) -> Result<*mut Writer>;
fn writer_options(
self: &Operator,
path: &str,
opts: FfiWriteOptions,
) -> Result<*mut Writer>;
fn info(self: &Operator) -> Result<Capability>;
unsafe fn delete_reader(reader: *mut Reader);
fn read(self: &mut Reader, buf: &mut [u8]) -> Result<usize>;
fn read_at(self: &Reader, buf: &mut [u8], offset: u64) -> Result<usize>;
fn seek(self: &mut Reader, offset: i64, dir: SeekFrom) -> Result<u64>;
unsafe fn delete_writer(writer: *mut Writer);
fn write(self: &mut Writer, bs: Vec<u8>) -> Result<()>;
fn flush(self: &mut Writer) -> Result<()>;
fn close(self: &mut Writer) -> Result<()>;
unsafe fn delete_lister(lister: *mut Lister);
fn next(self: &mut Lister) -> Result<OptionalEntry>;
#[cfg(feature = "testing")]
fn new_test_hanging_operator(layers: &LayerBuilder) -> Result<*mut Operator>;
#[cfg(feature = "testing")]
fn new_test_retryable_operator(layers: &LayerBuilder) -> Result<*mut Operator>;
#[cfg(feature = "testing")]
fn test_retryable_attempt_count() -> u64;
}
}
pub struct Operator(od::blocking::Operator);
fn into_blocking_operator(op: od::Operator) -> Result<Operator> {
let runtime =
tokio::runtime::Handle::try_current().unwrap_or_else(|_| RUNTIME.handle().clone());
let _guard = runtime.enter();
Ok(Operator(od::blocking::Operator::new(op)?))
}
fn layer_builder_new() -> Box<LayerBuilder> {
Box::new(LayerBuilder::new())
}
fn new_operator(
scheme: &str,
configs: Vec<ffi::HashMapValue>,
layers: &LayerBuilder,
) -> Result<*mut Operator> {
let map: HashMap<String, String> = configs
.into_iter()
.map(|value| (value.key, value.value))
.collect();
od::init_default_registry();
let op = od::Operator::via_iter(scheme, map)?;
let op = layers.apply(op);
let op = into_blocking_operator(op)?;
let op = Box::into_raw(Box::new(op));
Ok(op)
}
#[cfg(feature = "testing")]
fn new_test_hanging_operator(layers: &LayerBuilder) -> Result<*mut Operator> {
test_support::new_hanging_operator(layers)
}
#[cfg(feature = "testing")]
fn new_test_retryable_operator(layers: &LayerBuilder) -> Result<*mut Operator> {
test_support::new_retryable_operator(layers)
}
#[cfg(feature = "testing")]
fn test_retryable_attempt_count() -> u64 {
test_support::retryable_attempt_count() as u64
}
unsafe fn delete_operator(op: *mut Operator) {
if !op.is_null() {
unsafe {
drop(Box::from_raw(op));
}
}
}
unsafe fn delete_reader(reader: *mut Reader) {
if !reader.is_null() {
unsafe {
drop(Box::from_raw(reader));
}
}
}
unsafe fn delete_writer(writer: *mut Writer) {
if !writer.is_null() {
unsafe {
drop(Box::from_raw(writer));
}
}
}
unsafe fn delete_lister(lister: *mut Lister) {
if !lister.is_null() {
unsafe {
drop(Box::from_raw(lister));
}
}
}
fn optional_string(v: ffi::OptionalString) -> Option<String> {
v.has_value.then_some(v.value)
}
fn optional_u64(v: ffi::OptionalU64) -> Option<u64> {
v.has_value.then_some(v.value)
}
fn optional_usize(v: ffi::OptionalUsize) -> Option<usize> {
v.has_value.then_some(v.value)
}
fn optional_timestamp(v: ffi::OptionalTimestamp) -> Result<Option<Timestamp>> {
if v.has_value {
Ok(Some(Timestamp::new(v.seconds, v.nanoseconds as i32)?))
} else {
Ok(None)
}
}
fn bytes_range(v: ffi::FfiBytesRange) -> od::BytesRange {
match v.kind {
1 => od::BytesRange::new(v.start, None),
2 => od::BytesRange::new(v.start, Some(v.end.saturating_sub(v.start))),
3 => od::BytesRange::suffix(v.end),
_ => od::BytesRange::default(),
}
}
fn user_metadata(
has_value: bool,
values: Vec<ffi::HashMapValue>,
) -> Option<HashMap<String, String>> {
has_value.then(|| {
values
.into_iter()
.map(|value| (value.key, value.value))
.collect()
})
}
fn stat_options(opts: ffi::FfiStatOptions) -> Result<od::options::StatOptions> {
Ok(od::options::StatOptions {
version: optional_string(opts.version),
if_match: optional_string(opts.if_match),
if_none_match: optional_string(opts.if_none_match),
if_modified_since: optional_timestamp(opts.if_modified_since)?,
if_unmodified_since: optional_timestamp(opts.if_unmodified_since)?,
override_content_type: optional_string(opts.override_content_type),
override_cache_control: optional_string(opts.override_cache_control),
override_content_disposition: optional_string(opts.override_content_disposition),
})
}
fn read_options(opts: ffi::FfiReadOptions) -> Result<od::options::ReadOptions> {
Ok(od::options::ReadOptions {
range: bytes_range(opts.range),
version: optional_string(opts.version),
if_match: optional_string(opts.if_match),
if_none_match: optional_string(opts.if_none_match),
if_modified_since: optional_timestamp(opts.if_modified_since)?,
if_unmodified_since: optional_timestamp(opts.if_unmodified_since)?,
content_length_hint: optional_u64(opts.content_length_hint),
concurrent: opts.concurrent,
chunk: optional_usize(opts.chunk),
gap: optional_usize(opts.gap),
override_content_type: optional_string(opts.override_content_type),
override_cache_control: optional_string(opts.override_cache_control),
override_content_disposition: optional_string(opts.override_content_disposition),
})
}
fn reader_options(opts: ffi::FfiReaderOptions) -> Result<od::options::ReaderOptions> {
Ok(od::options::ReaderOptions {
version: optional_string(opts.version),
if_match: optional_string(opts.if_match),
if_none_match: optional_string(opts.if_none_match),
if_modified_since: optional_timestamp(opts.if_modified_since)?,
if_unmodified_since: optional_timestamp(opts.if_unmodified_since)?,
content_length_hint: optional_u64(opts.content_length_hint),
concurrent: opts.concurrent,
chunk: optional_usize(opts.chunk),
gap: optional_usize(opts.gap),
prefetch: opts.prefetch,
})
}
fn write_options(opts: ffi::FfiWriteOptions) -> od::options::WriteOptions {
od::options::WriteOptions {
append: opts.append,
cache_control: optional_string(opts.cache_control),
content_type: optional_string(opts.content_type),
content_disposition: optional_string(opts.content_disposition),
content_encoding: optional_string(opts.content_encoding),
user_metadata: user_metadata(opts.has_user_metadata, opts.user_metadata),
if_match: optional_string(opts.if_match),
if_none_match: optional_string(opts.if_none_match),
if_not_exists: opts.if_not_exists,
concurrent: opts.concurrent,
chunk: optional_usize(opts.chunk),
}
}
fn copy_options(opts: ffi::FfiCopyOptions) -> od::options::CopyOptions {
od::options::CopyOptions {
if_not_exists: opts.if_not_exists,
if_match: optional_string(opts.if_match),
source_version: optional_string(opts.source_version),
source_content_length_hint: optional_u64(opts.source_content_length_hint),
concurrent: opts.concurrent,
chunk: optional_usize(opts.chunk),
}
}
fn rename_options(opts: ffi::FfiRenameOptions) -> od::options::RenameOptions {
od::options::RenameOptions {
if_not_exists: opts.if_not_exists,
}
}
fn delete_options(opts: ffi::FfiDeleteOptions) -> od::options::DeleteOptions {
od::options::DeleteOptions {
version: optional_string(opts.version),
recursive: opts.recursive,
}
}
fn list_options(opts: ffi::FfiListOptions) -> od::options::ListOptions {
od::options::ListOptions {
limit: optional_usize(opts.limit),
start_after: optional_string(opts.start_after),
recursive: opts.recursive,
versions: opts.versions,
deleted: opts.deleted,
}
}
impl Operator {
fn read(&self, path: &str) -> Result<Vec<u8>> {
Ok(self.0.read(path)?.to_vec())
}
fn read_options(&self, path: &str, opts: ffi::FfiReadOptions) -> Result<Vec<u8>> {
Ok(self.0.read_options(path, read_options(opts)?)?.to_vec())
}
fn write(&self, path: &str, bs: Vec<u8>) -> Result<()> {
Ok(self.0.write(path, bs).map(|_| ())?)
}
fn write_options(&self, path: &str, bs: Vec<u8>, opts: ffi::FfiWriteOptions) -> Result<()> {
Ok(self
.0
.write_options(path, bs, write_options(opts))
.map(|_| ())?)
}
fn exists(&self, path: &str) -> Result<bool> {
Ok(self.0.exists(path)?)
}
fn create_dir(&self, path: &str) -> Result<()> {
Ok(self.0.create_dir(path)?)
}
fn copy(&self, src: &str, dst: &str) -> Result<()> {
self.0.copy(src, dst)?;
Ok(())
}
fn copy_options(&self, src: &str, dst: &str, opts: ffi::FfiCopyOptions) -> Result<()> {
self.0.copy_options(src, dst, copy_options(opts))?;
Ok(())
}
fn rename(&self, src: &str, dst: &str) -> Result<()> {
Ok(self.0.rename(src, dst)?)
}
fn rename_options(&self, src: &str, dst: &str, opts: ffi::FfiRenameOptions) -> Result<()> {
Ok(self.0.rename_options(src, dst, rename_options(opts))?)
}
// We can't name it to delete because it's a keyword in C++
fn remove(&self, path: &str) -> Result<()> {
Ok(self.0.delete(path)?)
}
fn remove_options(&self, path: &str, opts: ffi::FfiDeleteOptions) -> Result<()> {
Ok(self.0.delete_options(path, delete_options(opts))?)
}
fn remove_all(&self, paths: Vec<String>) -> Result<()> {
Ok(self.0.delete_iter(paths)?)
}
fn stat(&self, path: &str) -> Result<ffi::Metadata> {
Ok(self.0.stat(path)?.into())
}
fn stat_options(&self, path: &str, opts: ffi::FfiStatOptions) -> Result<ffi::Metadata> {
Ok(self.0.stat_options(path, stat_options(opts)?)?.into())
}
fn list(&self, path: &str) -> Result<Vec<ffi::Entry>> {
Ok(self.0.list(path)?.into_iter().map(Into::into).collect())
}
fn list_options(&self, path: &str, opts: ffi::FfiListOptions) -> Result<Vec<ffi::Entry>> {
Ok(self
.0
.list_options(path, list_options(opts))?
.into_iter()
.map(Into::into)
.collect())
}
fn reader(&self, path: &str) -> Result<*mut Reader> {
let meta = self.0.stat(path)?;
let reader = Box::into_raw(Box::new(Reader::new(
self.0.reader(path)?,
meta.content_length(),
)));
Ok(reader)
}
fn reader_options(&self, path: &str, opts: ffi::FfiReaderOptions) -> Result<*mut Reader> {
let opts = reader_options(opts)?;
let content_length = match opts.content_length_hint {
Some(v) => v,
None => {
let stat_opts = od::options::StatOptions {
version: opts.version.clone(),
if_match: opts.if_match.clone(),
if_none_match: opts.if_none_match.clone(),
if_modified_since: opts.if_modified_since,
if_unmodified_since: opts.if_unmodified_since,
..Default::default()
};
self.0.stat_options(path, stat_opts)?.content_length()
}
};
let reader = Box::into_raw(Box::new(Reader::new(
self.0.reader_options(path, opts)?,
content_length,
)));
Ok(reader)
}
fn lister(&self, path: &str) -> Result<*mut Lister> {
let lister = Box::into_raw(Box::new(Lister(self.0.lister(path)?)));
Ok(lister)
}
fn lister_options(&self, path: &str, opts: ffi::FfiListOptions) -> Result<*mut Lister> {
let lister = Box::into_raw(Box::new(Lister(
self.0.lister_options(path, list_options(opts))?,
)));
Ok(lister)
}
fn writer(&self, path: &str) -> Result<*mut Writer> {
let writer = Box::into_raw(Box::new(Writer(self.0.writer(path)?)));
Ok(writer)
}
fn writer_options(&self, path: &str, opts: ffi::FfiWriteOptions) -> Result<*mut Writer> {
let writer = Box::into_raw(Box::new(Writer(
self.0.writer_options(path, write_options(opts))?,
)));
Ok(writer)
}
fn info(&self) -> Result<ffi::Capability> {
let info = self.0.info();
let cap = info.capability();
Ok(ffi::Capability {
stat: cap.stat,
stat_with_if_match: cap.stat_with_if_match,
stat_with_if_none_match: cap.stat_with_if_none_match,
read: cap.read,
read_with_if_match: cap.read_with_if_match,
read_with_if_none_match: cap.read_with_if_none_match,
read_with_override_cache_control: cap.read_with_override_cache_control,
read_with_override_content_disposition: cap.read_with_override_content_disposition,
read_with_override_content_type: cap.read_with_override_content_type,
write: cap.write,
write_can_multi: cap.write_can_multi,
write_can_empty: cap.write_can_empty,
write_can_append: cap.write_can_append,
write_with_content_type: cap.write_with_content_type,
write_with_content_disposition: cap.write_with_content_disposition,
write_with_cache_control: cap.write_with_cache_control,
write_multi_max_size: cap.write_multi_max_size.unwrap_or(0),
write_multi_min_size: cap.write_multi_min_size.unwrap_or(0),
write_total_max_size: cap.write_total_max_size.unwrap_or(0),
create_dir: cap.create_dir,
delete_feature: cap.delete,
copy: cap.copy,
rename: cap.rename,
list: cap.list,
list_with_limit: cap.list_with_limit,
list_with_start_after: cap.list_with_start_after,
list_with_recursive: cap.list_with_recursive,
presign: cap.presign,
presign_read: cap.presign_read,
presign_stat: cap.presign_stat,
presign_write: cap.presign_write,
shared: cap.shared,
})
}
}