This is page 71 of 74. Use http://codebase.md/apache/opendal?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .asf.yaml
├── .config
│ └── nextest.toml
├── .devcontainer
│ ├── devcontainer.json
│ └── post_create.sh
├── .editorconfig
├── .env.example
├── .gitattributes
├── .github
│ ├── actions
│ │ ├── fuzz_test
│ │ │ └── action.yaml
│ │ ├── setup
│ │ │ └── action.yaml
│ │ ├── setup-hadoop
│ │ │ └── action.yaml
│ │ ├── setup-ocaml
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_c
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_cpp
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_go
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_java
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_nodejs
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_python
│ │ │ └── action.yaml
│ │ ├── test_behavior_core
│ │ │ └── action.yaml
│ │ └── test_behavior_integration_object_store
│ │ └── action.yml
│ ├── CODEOWNERS
│ ├── dependabot.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── 1-bug-report.yml
│ │ ├── 2-feature-request.yml
│ │ ├── 3-new-release.md
│ │ └── config.yml
│ ├── pull_request_template.md
│ ├── release.yml
│ ├── scripts
│ │ ├── test_behavior
│ │ │ ├── __init__.py
│ │ │ ├── plan.py
│ │ │ └── test_plan.py
│ │ ├── test_go_binding
│ │ │ ├── generate_test_scheme.py
│ │ │ └── matrix.yaml
│ │ └── weekly_update
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── main.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── services
│ │ ├── aliyun_drive
│ │ │ └── aliyun_drive
│ │ │ └── disable_action.yml
│ │ ├── alluxio
│ │ │ └── alluxio
│ │ │ └── action.yml
│ │ ├── azblob
│ │ │ ├── azure_azblob
│ │ │ │ └── action.yml
│ │ │ └── azurite_azblob
│ │ │ └── action.yml
│ │ ├── azdls
│ │ │ └── azdls
│ │ │ └── action.yml
│ │ ├── azfile
│ │ │ └── azfile
│ │ │ └── action.yml
│ │ ├── b2
│ │ │ └── b2
│ │ │ └── action.yml
│ │ ├── cacache
│ │ │ └── cacache
│ │ │ └── action.yml
│ │ ├── compfs
│ │ │ └── compfs
│ │ │ └── action.yml
│ │ ├── cos
│ │ │ └── cos
│ │ │ └── action.yml
│ │ ├── dashmap
│ │ │ └── dashmap
│ │ │ └── action.yml
│ │ ├── dropbox
│ │ │ └── dropbox
│ │ │ └── disable_action.yml
│ │ ├── etcd
│ │ │ ├── etcd
│ │ │ │ └── action.yml
│ │ │ ├── etcd-cluster
│ │ │ │ └── action.yml
│ │ │ └── etcd-tls
│ │ │ └── action.yml
│ │ ├── fs
│ │ │ └── local_fs
│ │ │ └── action.yml
│ │ ├── ftp
│ │ │ └── vsftpd
│ │ │ └── disable_action.yml
│ │ ├── gcs
│ │ │ ├── gcs
│ │ │ │ └── action.yml
│ │ │ └── gcs_with_default_storage_class
│ │ │ └── action.yml
│ │ ├── gdrive
│ │ │ └── gdrive
│ │ │ └── action.yml
│ │ ├── gridfs
│ │ │ ├── gridfs
│ │ │ │ └── action.yml
│ │ │ └── gridfs_with_basic_auth
│ │ │ └── action.yml
│ │ ├── hdfs
│ │ │ ├── hdfs_cluster
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_cluster_with_atomic_write_dir
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_default
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_default_gcs
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_default_on_azurite_azblob
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_default_on_minio_s3
│ │ │ │ └── action.yml
│ │ │ └── hdfs_default_with_atomic_write_dir
│ │ │ └── action.yml
│ │ ├── hdfs_native
│ │ │ └── hdfs_native_cluster
│ │ │ └── action.yml
│ │ ├── http
│ │ │ ├── caddy
│ │ │ │ └── action.yml
│ │ │ └── nginx
│ │ │ └── action.yml
│ │ ├── huggingface
│ │ │ └── huggingface
│ │ │ └── action.yml
│ │ ├── koofr
│ │ │ └── koofr
│ │ │ └── disable_action.yml
│ │ ├── memcached
│ │ │ ├── memcached
│ │ │ │ └── action.yml
│ │ │ └── memcached_with_auth
│ │ │ └── action.yml
│ │ ├── memory
│ │ │ └── memory
│ │ │ └── action.yml
│ │ ├── mini_moka
│ │ │ └── mini_moka
│ │ │ └── action.yml
│ │ ├── moka
│ │ │ └── moka
│ │ │ └── action.yml
│ │ ├── mongodb
│ │ │ ├── mongodb_with_basic_auth
│ │ │ │ └── action.yml
│ │ │ └── mongodb_with_no_auth
│ │ │ └── action.yml
│ │ ├── monoiofs
│ │ │ └── monoiofs
│ │ │ └── action.yml
│ │ ├── mysql
│ │ │ └── mysql
│ │ │ └── action.yml
│ │ ├── oss
│ │ │ ├── oss
│ │ │ │ └── action.yml
│ │ │ └── oss_with_versioning
│ │ │ └── action.yml
│ │ ├── persy
│ │ │ └── persy
│ │ │ └── action.yml
│ │ ├── postgresql
│ │ │ └── postgresql
│ │ │ └── action.yml
│ │ ├── redb
│ │ │ └── redb
│ │ │ └── action.yml
│ │ ├── redis
│ │ │ ├── dragonfly
│ │ │ │ └── action.yml
│ │ │ ├── kvrocks
│ │ │ │ └── action.yml
│ │ │ ├── redis
│ │ │ │ └── action.yml
│ │ │ ├── redis_tls
│ │ │ │ └── action.yml
│ │ │ ├── redis_with_cluster
│ │ │ │ └── action.yml
│ │ │ └── redis_with_cluster_tls
│ │ │ └── action.yml
│ │ ├── rocksdb
│ │ │ └── rocksdb
│ │ │ └── action.yml
│ │ ├── s3
│ │ │ ├── 0_minio_s3
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3_with_list_objects_v1
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3_with_sse_c
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3_with_versioning
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3_with_virtual_host
│ │ │ │ └── action.yml
│ │ │ ├── ceph_radios_s3_with_versioning
│ │ │ │ └── disable_action.yml
│ │ │ ├── ceph_rados_s3
│ │ │ │ └── disable_action.yml
│ │ │ ├── minio_s3_with_anonymous
│ │ │ │ └── action.yml
│ │ │ ├── minio_s3_with_list_objects_v1
│ │ │ │ └── action.yml
│ │ │ ├── minio_s3_with_versioning
│ │ │ │ └── action.yml
│ │ │ └── r2
│ │ │ └── disabled_action.yml
│ │ ├── seafile
│ │ │ └── seafile
│ │ │ └── action.yml
│ │ ├── sftp
│ │ │ ├── sftp
│ │ │ │ └── action.yml
│ │ │ └── sftp_with_default_root
│ │ │ └── action.yml
│ │ ├── sled
│ │ │ ├── sled
│ │ │ │ └── action.yml
│ │ │ └── sled_with_tree
│ │ │ └── action.yml
│ │ ├── sqlite
│ │ │ └── sqlite
│ │ │ └── action.yml
│ │ ├── swift
│ │ │ ├── ceph_rados_swift
│ │ │ │ └── action.yml
│ │ │ └── swift
│ │ │ └── action.yml
│ │ ├── tikv
│ │ │ └── tikv
│ │ │ └── disable_action.yml
│ │ ├── webdav
│ │ │ ├── 0_nginx
│ │ │ │ └── action.yml
│ │ │ ├── jfrog
│ │ │ │ └── disabled_action.yml
│ │ │ ├── nextcloud
│ │ │ │ └── action.yml
│ │ │ ├── nginx_with_empty_password
│ │ │ │ └── action.yml
│ │ │ ├── nginx_with_password
│ │ │ │ └── action.yml
│ │ │ ├── nginx_with_redirect
│ │ │ │ └── action.yml
│ │ │ └── owncloud
│ │ │ └── action.yml
│ │ └── webhdfs
│ │ ├── webhdfs
│ │ │ └── action.yml
│ │ ├── webhdfs_with_list_batch_disabled
│ │ │ └── action.yml
│ │ └── webhdfs_with_user_name
│ │ └── action.yml
│ └── workflows
│ ├── ci_bindings_c.yml
│ ├── ci_bindings_cpp.yml
│ ├── ci_bindings_d.yml
│ ├── ci_bindings_dart.yml
│ ├── ci_bindings_dotnet.yml
│ ├── ci_bindings_go.yml
│ ├── ci_bindings_haskell.yml
│ ├── ci_bindings_java.yml
│ ├── ci_bindings_lua.yml
│ ├── ci_bindings_nodejs.yml
│ ├── ci_bindings_ocaml.yml
│ ├── ci_bindings_php.yml
│ ├── ci_bindings_python.yml
│ ├── ci_bindings_ruby.yml
│ ├── ci_bindings_swift.yml
│ ├── ci_bindings_zig.yml
│ ├── ci_check.yml
│ ├── ci_core.yml
│ ├── ci_integration_dav_server.yml
│ ├── ci_integration_object_store.yml
│ ├── ci_integration_parquet.yml
│ ├── ci_integration_spring.yml
│ ├── ci_integration_unftp_sbe.yml
│ ├── ci_odev.yml
│ ├── ci_weekly_update.yml
│ ├── discussion-thread-link.yml
│ ├── docs.yml
│ ├── full-ci-promote.yml
│ ├── release_dart.yml
│ ├── release_java.yml
│ ├── release_nodejs.yml
│ ├── release_python.yml
│ ├── release_ruby.yml
│ ├── release_rust.yml
│ ├── service_test_ghac.yml
│ ├── test_behavior_binding_c.yml
│ ├── test_behavior_binding_cpp.yml
│ ├── test_behavior_binding_go.yml
│ ├── test_behavior_binding_java.yml
│ ├── test_behavior_binding_nodejs.yml
│ ├── test_behavior_binding_python.yml
│ ├── test_behavior_core.yml
│ ├── test_behavior_integration_object_store.yml
│ ├── test_behavior.yml
│ ├── test_edge.yml
│ └── test_fuzz.yml
├── .gitignore
├── .taplo.toml
├── .typos.toml
├── .vscode
│ └── settings.json
├── .yamlfmt
├── AGENTS.md
├── bindings
│ ├── java
│ │ ├── .cargo
│ │ │ └── config.toml
│ │ ├── .gitignore
│ │ ├── .mvn
│ │ │ └── wrapper
│ │ │ └── maven-wrapper.properties
│ │ ├── Cargo.toml
│ │ ├── DEPENDENCIES.md
│ │ ├── DEPENDENCIES.rust.tsv
│ │ ├── mvnw
│ │ ├── mvnw.cmd
│ │ ├── pom.xml
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── async_operator.rs
│ │ │ ├── convert.rs
│ │ │ ├── error.rs
│ │ │ ├── executor.rs
│ │ │ ├── layer.rs
│ │ │ ├── lib.rs
│ │ │ ├── main
│ │ │ │ ├── java
│ │ │ │ │ └── org
│ │ │ │ │ └── apache
│ │ │ │ │ └── opendal
│ │ │ │ │ ├── AsyncExecutor.java
│ │ │ │ │ ├── AsyncOperator.java
│ │ │ │ │ ├── Capability.java
│ │ │ │ │ ├── Entry.java
│ │ │ │ │ ├── Environment.java
│ │ │ │ │ ├── layer
│ │ │ │ │ │ ├── ConcurrentLimitLayer.java
│ │ │ │ │ │ ├── package-info.java
│ │ │ │ │ │ └── RetryLayer.java
│ │ │ │ │ ├── Layer.java
│ │ │ │ │ ├── ListOptions.java
│ │ │ │ │ ├── Metadata.java
│ │ │ │ │ ├── NativeLibrary.java
│ │ │ │ │ ├── NativeObject.java
│ │ │ │ │ ├── OpenDAL.java
│ │ │ │ │ ├── OpenDALException.java
│ │ │ │ │ ├── Operator.java
│ │ │ │ │ ├── OperatorInfo.java
│ │ │ │ │ ├── OperatorInputStream.java
│ │ │ │ │ ├── OperatorOutputStream.java
│ │ │ │ │ ├── package-info.java
│ │ │ │ │ ├── PresignedRequest.java
│ │ │ │ │ ├── ReadOptions.java
│ │ │ │ │ ├── ServiceConfig.java
│ │ │ │ │ ├── StatOptions.java
│ │ │ │ │ └── WriteOptions.java
│ │ │ │ └── resources
│ │ │ │ ├── bindings.properties
│ │ │ │ └── META-INF
│ │ │ │ └── NOTICE
│ │ │ ├── operator_input_stream.rs
│ │ │ ├── operator_output_stream.rs
│ │ │ ├── operator.rs
│ │ │ ├── test
│ │ │ │ └── java
│ │ │ │ └── org
│ │ │ │ └── apache
│ │ │ │ └── opendal
│ │ │ │ └── test
│ │ │ │ ├── AsyncExecutorTest.java
│ │ │ │ ├── behavior
│ │ │ │ │ ├── AsyncCopyTest.java
│ │ │ │ │ ├── AsyncCreateDirTest.java
│ │ │ │ │ ├── AsyncListTest.java
│ │ │ │ │ ├── AsyncPresignTest.java
│ │ │ │ │ ├── AsyncReadOnlyTest.java
│ │ │ │ │ ├── AsyncRenameTest.java
│ │ │ │ │ ├── AsyncStatOptionsTest.java
│ │ │ │ │ ├── AsyncWriteOptionsTest.java
│ │ │ │ │ ├── AsyncWriteTest.java
│ │ │ │ │ ├── BehaviorExtension.java
│ │ │ │ │ ├── BehaviorTestBase.java
│ │ │ │ │ ├── BlockingCopyTest.java
│ │ │ │ │ ├── BlockingCreateDirTest.java
│ │ │ │ │ ├── BlockingListTest.java
│ │ │ │ │ ├── BlockingReadOnlyTest.java
│ │ │ │ │ ├── BlockingRenameTest.java
│ │ │ │ │ ├── BlockingStatOptionsTest.java
│ │ │ │ │ ├── BlockingWriteOptionTest.java
│ │ │ │ │ ├── BlockingWriteTest.java
│ │ │ │ │ └── RegressionTest.java
│ │ │ │ ├── condition
│ │ │ │ │ └── OpenDALExceptionCondition.java
│ │ │ │ ├── LayerTest.java
│ │ │ │ ├── MetadataTest.java
│ │ │ │ ├── OperatorDuplicateTest.java
│ │ │ │ ├── OperatorInfoTest.java
│ │ │ │ ├── OperatorInputOutputStreamTest.java
│ │ │ │ ├── OperatorUtf8DecodeTest.java
│ │ │ │ └── UtilityTest.java
│ │ │ └── utility.rs
│ │ ├── tools
│ │ │ └── build.py
│ │ ├── upgrade.md
│ │ └── users.md
│ ├── nodejs
│ │ ├── .cargo
│ │ │ └── config.toml
│ │ ├── .gitignore
│ │ ├── .node-version
│ │ ├── .npmignore
│ │ ├── .npmrc
│ │ ├── .prettierignore
│ │ ├── benchmark
│ │ │ ├── deno.ts
│ │ │ ├── node.js
│ │ │ └── README.md
│ │ ├── build.rs
│ │ ├── Cargo.toml
│ │ ├── CONTRIBUTING.md
│ │ ├── DEPENDENCIES.md
│ │ ├── DEPENDENCIES.rust.tsv
│ │ ├── devbox.json
│ │ ├── devbox.lock
│ │ ├── generated.d.ts
│ │ ├── generated.js
│ │ ├── index.cjs
│ │ ├── index.d.ts
│ │ ├── index.mjs
│ │ ├── npm
│ │ │ ├── darwin-arm64
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── darwin-x64
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── linux-arm64-gnu
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── linux-arm64-musl
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── linux-x64-gnu
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── linux-x64-musl
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── win32-arm64-msvc
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ └── win32-x64-msvc
│ │ │ ├── package.json
│ │ │ └── README.md
│ │ ├── package.json
│ │ ├── pnpm-lock.yaml
│ │ ├── README.md
│ │ ├── scripts
│ │ │ └── header.mjs
│ │ ├── src
│ │ │ ├── capability.rs
│ │ │ ├── layer.rs
│ │ │ ├── lib.rs
│ │ │ └── options.rs
│ │ ├── tests
│ │ │ ├── service.test.mjs
│ │ │ ├── suites
│ │ │ │ ├── async.suite.mjs
│ │ │ │ ├── asyncDeleteOptions.suite.mjs
│ │ │ │ ├── asyncLister.suite.mjs
│ │ │ │ ├── asyncListOptions.suite.mjs
│ │ │ │ ├── asyncReadOptions.suite.mjs
│ │ │ │ ├── asyncStatOptions.suite.mjs
│ │ │ │ ├── asyncWriteOptions.suite.mjs
│ │ │ │ ├── index.mjs
│ │ │ │ ├── layer.suite.mjs
│ │ │ │ ├── services.suite.mjs
│ │ │ │ ├── sync.suite.mjs
│ │ │ │ ├── syncDeleteOptions.suite.mjs
│ │ │ │ ├── syncLister.suite.mjs
│ │ │ │ ├── syncListOptions.suite.mjs
│ │ │ │ ├── syncReadOptions.suite.mjs
│ │ │ │ ├── syncStatOptions.suite.mjs
│ │ │ │ └── syncWriteOptions.suite.mjs
│ │ │ └── utils.mjs
│ │ ├── theme
│ │ │ ├── index.tsx
│ │ │ └── package.json
│ │ ├── tsconfig.json
│ │ ├── tsconfig.theme.json
│ │ ├── typedoc.json
│ │ ├── upgrade.md
│ │ └── vitest.config.mjs
│ ├── python
│ │ ├── .gitignore
│ │ ├── benchmark
│ │ │ ├── async_opendal_benchmark.py
│ │ │ ├── async_origin_s3_benchmark_with_gevent.py
│ │ │ └── README.md
│ │ ├── Cargo.toml
│ │ ├── CONTRIBUTING.md
│ │ ├── DEPENDENCIES.md
│ │ ├── DEPENDENCIES.rust.tsv
│ │ ├── docs
│ │ │ ├── api
│ │ │ │ ├── async_file.md
│ │ │ │ ├── async_operator.md
│ │ │ │ ├── capability.md
│ │ │ │ ├── exceptions.md
│ │ │ │ ├── file.md
│ │ │ │ ├── layers.md
│ │ │ │ ├── operator.md
│ │ │ │ └── types.md
│ │ │ └── index.md
│ │ ├── justfile
│ │ ├── mkdocs.yml
│ │ ├── pyproject.toml
│ │ ├── pyrightconfig.json
│ │ ├── python
│ │ │ └── opendal
│ │ │ ├── __init__.py
│ │ │ ├── capability.pyi
│ │ │ ├── exceptions.pyi
│ │ │ ├── file.pyi
│ │ │ ├── layers.pyi
│ │ │ ├── operator.pyi
│ │ │ ├── py.typed
│ │ │ ├── services.pyi
│ │ │ └── types.pyi
│ │ ├── README.md
│ │ ├── ruff.toml
│ │ ├── src
│ │ │ ├── capability.rs
│ │ │ ├── errors.rs
│ │ │ ├── file.rs
│ │ │ ├── layers.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── metadata.rs
│ │ │ ├── operator.rs
│ │ │ ├── options.rs
│ │ │ ├── services.rs
│ │ │ └── utils.rs
│ │ ├── template
│ │ │ └── module.html.jinja2
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_async_check.py
│ │ │ ├── test_async_copy.py
│ │ │ ├── test_async_delete.py
│ │ │ ├── test_async_exists.py
│ │ │ ├── test_async_list.py
│ │ │ ├── test_async_pickle_types.py
│ │ │ ├── test_async_rename.py
│ │ │ ├── test_capability.py
│ │ │ ├── test_exceptions.py
│ │ │ ├── test_pickle_rw.py
│ │ │ ├── test_read.py
│ │ │ ├── test_sync_check.py
│ │ │ ├── test_sync_copy.py
│ │ │ ├── test_sync_delete.py
│ │ │ ├── test_sync_exists.py
│ │ │ ├── test_sync_list.py
│ │ │ ├── test_sync_pickle_types.py
│ │ │ ├── test_sync_rename.py
│ │ │ └── test_write.py
│ │ ├── upgrade.md
│ │ ├── users.md
│ │ └── uv.lock
│ └── README.md
├── CHANGELOG.md
├── CITATION.cff
├── CLAUDE.md
├── CONTRIBUTING.md
├── core
│ ├── benches
│ │ ├── ops
│ │ │ ├── main.rs
│ │ │ ├── read.rs
│ │ │ ├── README.md
│ │ │ ├── utils.rs
│ │ │ └── write.rs
│ │ ├── README.md
│ │ ├── types
│ │ │ ├── buffer.rs
│ │ │ ├── main.rs
│ │ │ ├── README.md
│ │ │ └── tasks.rs
│ │ ├── vs_fs
│ │ │ ├── Cargo.toml
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ └── main.rs
│ │ └── vs_s3
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ │ └── main.rs
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── CHANGELOG.md
│ ├── CONTRIBUTING.md
│ ├── core
│ │ ├── Cargo.toml
│ │ └── src
│ │ ├── blocking
│ │ │ ├── delete.rs
│ │ │ ├── list.rs
│ │ │ ├── mod.rs
│ │ │ ├── operator.rs
│ │ │ ├── read
│ │ │ │ ├── buffer_iterator.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── reader.rs
│ │ │ │ ├── std_bytes_iterator.rs
│ │ │ │ └── std_reader.rs
│ │ │ └── write
│ │ │ ├── mod.rs
│ │ │ ├── std_writer.rs
│ │ │ └── writer.rs
│ │ ├── docs
│ │ │ ├── comparisons
│ │ │ │ ├── mod.rs
│ │ │ │ └── vs_object_store.md
│ │ │ ├── concepts.rs
│ │ │ ├── internals
│ │ │ │ ├── accessor.rs
│ │ │ │ ├── layer.rs
│ │ │ │ └── mod.rs
│ │ │ ├── mod.rs
│ │ │ ├── performance
│ │ │ │ ├── concurrent_write.md
│ │ │ │ ├── http_optimization.md
│ │ │ │ └── mod.rs
│ │ │ ├── rfcs
│ │ │ │ ├── 0000_example.md
│ │ │ │ ├── 0041_object_native_api.md
│ │ │ │ ├── 0044_error_handle.md
│ │ │ │ ├── 0057_auto_region.md
│ │ │ │ ├── 0069_object_stream.md
│ │ │ │ ├── 0090_limited_reader.md
│ │ │ │ ├── 0112_path_normalization.md
│ │ │ │ ├── 0191_async_streaming_io.md
│ │ │ │ ├── 0203_remove_credential.md
│ │ │ │ ├── 0221_create_dir.md
│ │ │ │ ├── 0247_retryable_error.md
│ │ │ │ ├── 0293_object_id.md
│ │ │ │ ├── 0337_dir_entry.md
│ │ │ │ ├── 0409_accessor_capabilities.md
│ │ │ │ ├── 0413_presign.md
│ │ │ │ ├── 0423_command_line_interface.md
│ │ │ │ ├── 0429_init_from_iter.md
│ │ │ │ ├── 0438_multipart.md
│ │ │ │ ├── 0443_gateway.md
│ │ │ │ ├── 0501_new_builder.md
│ │ │ │ ├── 0554_write_refactor.md
│ │ │ │ ├── 0561_list_metadata_reuse.md
│ │ │ │ ├── 0599_blocking_api.md
│ │ │ │ ├── 0623_redis_service.md
│ │ │ │ ├── 0627_split_capabilities.md
│ │ │ │ ├── 0661_path_in_accessor.md
│ │ │ │ ├── 0793_generic_kv_services.md
│ │ │ │ ├── 0926_object_reader.md
│ │ │ │ ├── 0977_refactor_error.md
│ │ │ │ ├── 1085_object_handler.md
│ │ │ │ ├── 1391_object_metadataer.md
│ │ │ │ ├── 1398_query_based_metadata.md
│ │ │ │ ├── 1420_object_writer.md
│ │ │ │ ├── 1477_remove_object_concept.md
│ │ │ │ ├── 1735_operation_extension.md
│ │ │ │ ├── 2083_writer_sink_api.md
│ │ │ │ ├── 2133_append_api.md
│ │ │ │ ├── 2299_chain_based_operator_api.md
│ │ │ │ ├── 2602_object_versioning.md
│ │ │ │ ├── 2758_merge_append_into_write.md
│ │ │ │ ├── 2774_lister_api.md
│ │ │ │ ├── 2779_list_with_metakey.md
│ │ │ │ ├── 2852_native_capability.md
│ │ │ │ ├── 2884_merge_range_read_into_read.md
│ │ │ │ ├── 3017_remove_write_copy_from.md
│ │ │ │ ├── 3197_config.md
│ │ │ │ ├── 3232_align_list_api.md
│ │ │ │ ├── 3243_list_prefix.md
│ │ │ │ ├── 3356_lazy_reader.md
│ │ │ │ ├── 3526_list_recursive.md
│ │ │ │ ├── 3574_concurrent_stat_in_list.md
│ │ │ │ ├── 3734_buffered_reader.md
│ │ │ │ ├── 3898_concurrent_writer.md
│ │ │ │ ├── 3911_deleter_api.md
│ │ │ │ ├── 4382_range_based_read.md
│ │ │ │ ├── 4638_executor.md
│ │ │ │ ├── 5314_remove_metakey.md
│ │ │ │ ├── 5444_operator_from_uri.md
│ │ │ │ ├── 5479_context.md
│ │ │ │ ├── 5485_conditional_reader.md
│ │ │ │ ├── 5495_list_with_deleted.md
│ │ │ │ ├── 5556_write_returns_metadata.md
│ │ │ │ ├── 5871_read_returns_metadata.md
│ │ │ │ ├── 6189_remove_native_blocking.md
│ │ │ │ ├── 6209_glob_support.md
│ │ │ │ ├── 6213_options_api.md
│ │ │ │ ├── 6370_foyer_integration.md
│ │ │ │ ├── 6678_simulate_layer.md
│ │ │ │ ├── 6707_capability_override_layer.md
│ │ │ │ ├── 6817_checksum.md
│ │ │ │ ├── 6828_core.md
│ │ │ │ ├── 7130_route_layer.md
│ │ │ │ ├── mod.rs
│ │ │ │ └── README.md
│ │ │ └── upgrade.md
│ │ ├── layers
│ │ │ ├── complete.rs
│ │ │ ├── correctness_check.rs
│ │ │ ├── error_context.rs
│ │ │ ├── http_client.rs
│ │ │ ├── mod.rs
│ │ │ ├── simulate.rs
│ │ │ └── type_eraser.rs
│ │ ├── lib.rs
│ │ ├── raw
│ │ │ ├── accessor.rs
│ │ │ ├── atomic_util.rs
│ │ │ ├── enum_utils.rs
│ │ │ ├── futures_util.rs
│ │ │ ├── http_util
│ │ │ │ ├── body.rs
│ │ │ │ ├── bytes_content_range.rs
│ │ │ │ ├── bytes_range.rs
│ │ │ │ ├── client.rs
│ │ │ │ ├── error.rs
│ │ │ │ ├── header.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── multipart.rs
│ │ │ │ └── uri.rs
│ │ │ ├── layer.rs
│ │ │ ├── mod.rs
│ │ │ ├── oio
│ │ │ │ ├── buf
│ │ │ │ │ ├── flex_buf.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── pooled_buf.rs
│ │ │ │ │ └── queue_buf.rs
│ │ │ │ ├── delete
│ │ │ │ │ ├── api.rs
│ │ │ │ │ ├── batch_delete.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ └── one_shot_delete.rs
│ │ │ │ ├── entry.rs
│ │ │ │ ├── list
│ │ │ │ │ ├── api.rs
│ │ │ │ │ ├── flat_list.rs
│ │ │ │ │ ├── hierarchy_list.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── page_list.rs
│ │ │ │ │ └── prefix_list.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── read
│ │ │ │ │ ├── api.rs
│ │ │ │ │ └── mod.rs
│ │ │ │ └── write
│ │ │ │ ├── api.rs
│ │ │ │ ├── append_write.rs
│ │ │ │ ├── block_write.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── multipart_write.rs
│ │ │ │ ├── one_shot_write.rs
│ │ │ │ └── position_write.rs
│ │ │ ├── operation.rs
│ │ │ ├── ops.rs
│ │ │ ├── path_cache.rs
│ │ │ ├── path.rs
│ │ │ ├── rps.rs
│ │ │ ├── serde_util.rs
│ │ │ ├── std_io_util.rs
│ │ │ ├── time.rs
│ │ │ ├── tokio_util.rs
│ │ │ └── version.rs
│ │ ├── services
│ │ │ ├── memory
│ │ │ │ ├── backend.rs
│ │ │ │ ├── config.rs
│ │ │ │ ├── core.rs
│ │ │ │ ├── deleter.rs
│ │ │ │ ├── docs.md
│ │ │ │ ├── lister.rs
│ │ │ │ ├── mod.rs
│ │ │ │ └── writer.rs
│ │ │ └── mod.rs
│ │ └── types
│ │ ├── buffer.rs
│ │ ├── builder.rs
│ │ ├── capability.rs
│ │ ├── context
│ │ │ ├── mod.rs
│ │ │ ├── read.rs
│ │ │ └── write.rs
│ │ ├── delete
│ │ │ ├── deleter.rs
│ │ │ ├── futures_delete_sink.rs
│ │ │ ├── input.rs
│ │ │ └── mod.rs
│ │ ├── entry.rs
│ │ ├── error.rs
│ │ ├── execute
│ │ │ ├── api.rs
│ │ │ ├── executor.rs
│ │ │ ├── executors
│ │ │ │ ├── mod.rs
│ │ │ │ └── tokio_executor.rs
│ │ │ └── mod.rs
│ │ ├── list.rs
│ │ ├── metadata.rs
│ │ ├── mod.rs
│ │ ├── mode.rs
│ │ ├── operator
│ │ │ ├── builder.rs
│ │ │ ├── info.rs
│ │ │ ├── mod.rs
│ │ │ ├── operator_futures.rs
│ │ │ ├── operator.rs
│ │ │ ├── registry.rs
│ │ │ └── uri.rs
│ │ ├── options.rs
│ │ ├── read
│ │ │ ├── buffer_stream.rs
│ │ │ ├── futures_async_reader.rs
│ │ │ ├── futures_bytes_stream.rs
│ │ │ ├── mod.rs
│ │ │ └── reader.rs
│ │ └── write
│ │ ├── buffer_sink.rs
│ │ ├── futures_async_writer.rs
│ │ ├── futures_bytes_sink.rs
│ │ ├── mod.rs
│ │ └── writer.rs
│ ├── DEPENDENCIES.md
│ ├── DEPENDENCIES.rust.tsv
│ ├── edge
│ │ ├── file_write_on_full_disk
│ │ │ ├── Cargo.toml
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ └── main.rs
│ │ ├── README.md
│ │ ├── s3_aws_assume_role_with_web_identity
│ │ │ ├── Cargo.toml
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ └── main.rs
│ │ └── s3_read_on_wasm
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ ├── src
│ │ │ └── lib.rs
│ │ └── webdriver.json
│ ├── fuzz
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── fuzz_reader.rs
│ │ ├── fuzz_writer.rs
│ │ └── README.md
│ ├── layers
│ │ ├── async-backtrace
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── await-tree
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── capability-check
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── chaos
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── concurrent-limit
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── dtrace
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── fastmetrics
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── fastrace
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── foyer
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── hotpath
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── immutable-index
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── logging
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── metrics
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── mime-guess
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── observe-metrics-common
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── otelmetrics
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── oteltrace
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── prometheus
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── prometheus-client
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── retry
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── route
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── tail-cut
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── throttle
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── timeout
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ └── tracing
│ │ ├── Cargo.toml
│ │ └── src
│ │ └── lib.rs
│ ├── LICENSE
│ ├── README.md
│ ├── services
│ │ ├── aliyun-drive
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── alluxio
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── azblob
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── azdls
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── azfile
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── azure-common
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── b2
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── cacache
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── cloudflare-kv
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── model.rs
│ │ │ └── writer.rs
│ │ ├── compfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── cos
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── d1
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── model.rs
│ │ │ └── writer.rs
│ │ ├── dashmap
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── dbfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── dropbox
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── etcd
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── foundationdb
│ │ │ ├── build.rs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── fs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── ftp
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── err.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── gcs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── uri.rs
│ │ │ └── writer.rs
│ │ ├── gdrive
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── ghac
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── github
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── mod.rs
│ │ │ └── writer.rs
│ │ ├── gridfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── hdfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── hdfs-native
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── http
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ └── lib.rs
│ │ ├── huggingface
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ └── lister.rs
│ │ ├── ipfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── ipld.rs
│ │ │ └── lib.rs
│ │ ├── ipmfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── koofr
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── lakefs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── memcached
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── binary.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── mini_moka
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── moka
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── mongodb
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── monoiofs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── mysql
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── obs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── onedrive
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── graph_model.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── opfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ └── utils.rs
│ │ ├── oss
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── pcloud
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── persy
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── postgresql
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── redb
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── redis
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── delete.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── rocksdb
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── s3
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── compatible_services.md
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── mod.rs
│ │ │ └── writer.rs
│ │ ├── seafile
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── sftp
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ ├── utils.rs
│ │ │ └── writer.rs
│ │ ├── sled
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── sqlite
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── surrealdb
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── swift
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── compatible_services.md
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── tikv
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── upyun
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── vercel-artifacts
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── vercel-blob
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── webdav
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── webhdfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── message.rs
│ │ │ └── writer.rs
│ │ └── yandex-disk
│ │ ├── Cargo.toml
│ │ └── src
│ │ ├── backend.rs
│ │ ├── config.rs
│ │ ├── core.rs
│ │ ├── deleter.rs
│ │ ├── docs.md
│ │ ├── error.rs
│ │ ├── lib.rs
│ │ ├── lister.rs
│ │ └── writer.rs
│ ├── src
│ │ └── lib.rs
│ ├── testkit
│ │ ├── Cargo.toml
│ │ └── src
│ │ ├── lib.rs
│ │ ├── read.rs
│ │ ├── utils.rs
│ │ └── write.rs
│ ├── tests
│ │ ├── behavior
│ │ │ ├── async_copy.rs
│ │ │ ├── async_create_dir.rs
│ │ │ ├── async_delete.rs
│ │ │ ├── async_list.rs
│ │ │ ├── async_presign.rs
│ │ │ ├── async_read.rs
│ │ │ ├── async_rename.rs
│ │ │ ├── async_stat.rs
│ │ │ ├── async_write.rs
│ │ │ ├── main.rs
│ │ │ ├── README.md
│ │ │ └── utils.rs
│ │ └── data
│ │ ├── normal_dir
│ │ │ └── .gitkeep
│ │ ├── normal_file.txt
│ │ ├── special_dir !@#$%^&()_+-=;',
│ │ │ └── .gitkeep
│ │ └── special_file !@#$%^&()_+-=;',.txt
│ ├── upgrade.md
│ └── users.md
├── deny.toml
├── DEPENDENCIES.md
├── dev
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── generate
│ │ ├── java.j2
│ │ ├── java.rs
│ │ ├── mod.rs
│ │ ├── parser.rs
│ │ ├── python.j2
│ │ └── python.rs
│ ├── main.rs
│ └── release
│ ├── mod.rs
│ └── package.rs
├── doap.rdf
├── fixtures
│ ├── alluxio
│ │ └── docker-compose-alluxio.yml
│ ├── azblob
│ │ └── docker-compose-azurite.yml
│ ├── data
│ │ ├── normal_dir
│ │ │ └── .gitkeep
│ │ ├── normal_file.txt
│ │ ├── special_dir !@#$%^&()_+-=;',
│ │ │ └── .gitkeep
│ │ └── special_file !@#$%^&()_+-=;',.txt
│ ├── etcd
│ │ ├── ca-key.pem
│ │ ├── ca.pem
│ │ ├── client-key.pem
│ │ ├── client.pem
│ │ ├── docker-compose-cluster.yml
│ │ ├── docker-compose-standalone-tls.yml
│ │ ├── docker-compose-standalone.yml
│ │ ├── server-key.pem
│ │ └── server.pem
│ ├── ftp
│ │ └── docker-compose-vsftpd.yml
│ ├── hdfs
│ │ ├── azurite-azblob-core-site.xml
│ │ ├── docker-compose-hdfs-cluster.yml
│ │ ├── gcs-core-site.xml
│ │ ├── hdfs-site.xml
│ │ └── minio-s3-core-site.xml
│ ├── http
│ │ ├── Caddyfile
│ │ ├── docker-compose-caddy.yml
│ │ ├── docker-compose-nginx.yml
│ │ └── nginx.conf
│ ├── libsql
│ │ ├── docker-compose-auth.yml
│ │ └── docker-compose.yml
│ ├── memcached
│ │ ├── docker-compose-memcached-with-auth.yml
│ │ └── docker-compose-memcached.yml
│ ├── mongodb
│ │ ├── docker-compose-basic-auth.yml
│ │ └── docker-compose-no-auth.yml
│ ├── mysql
│ │ ├── docker-compose.yml
│ │ └── init.sql
│ ├── postgresql
│ │ ├── docker-compose.yml
│ │ └── init.sql
│ ├── redis
│ │ ├── docker-compose-dragonfly.yml
│ │ ├── docker-compose-kvrocks.yml
│ │ ├── docker-compose-redis-cluster-tls.yml
│ │ ├── docker-compose-redis-cluster.yml
│ │ ├── docker-compose-redis-tls.yml
│ │ ├── docker-compose-redis.yml
│ │ └── ssl
│ │ ├── .gitignore
│ │ ├── ca.crt
│ │ ├── ca.key
│ │ ├── ca.srl
│ │ ├── README.md
│ │ ├── redis.crt
│ │ ├── redis.key
│ │ └── req.conf
│ ├── s3
│ │ ├── docker-compose-ceph-rados.yml
│ │ └── docker-compose-minio.yml
│ ├── seafile
│ │ └── docker-compose-seafile.yml
│ ├── sftp
│ │ ├── change_root_dir.sh
│ │ ├── docker-compose-sftp-with-default-root.yml
│ │ ├── docker-compose-sftp.yml
│ │ ├── health-check.sh
│ │ ├── test_ssh_key
│ │ └── test_ssh_key.pub
│ ├── sqlite
│ │ └── data.sql
│ ├── swift
│ │ ├── docker-compose-ceph-rados.yml
│ │ └── docker-compose-swift.yml
│ ├── tikv
│ │ ├── gen_cert.sh
│ │ ├── pd-tls.toml
│ │ ├── pd.toml
│ │ ├── ssl
│ │ │ ├── ca-key.pem
│ │ │ ├── ca.pem
│ │ │ ├── client-key.pem
│ │ │ ├── client.pem
│ │ │ ├── pd-server-key.pem
│ │ │ ├── pd-server.pem
│ │ │ ├── tikv-server-key.pem
│ │ │ └── tikv-server.pem
│ │ ├── tikv-tls.toml
│ │ └── tikv.toml
│ ├── webdav
│ │ ├── config
│ │ │ └── nginx
│ │ │ └── http.conf
│ │ ├── docker-compose-webdav-jfrog.yml
│ │ ├── docker-compose-webdav-nextcloud.yml
│ │ ├── docker-compose-webdav-owncloud.yml
│ │ ├── docker-compose-webdav-with-auth.yml
│ │ ├── docker-compose-webdav-with-empty-passwd.yml
│ │ ├── docker-compose-webdav.yml
│ │ └── health-check-nextcloud.sh
│ └── webhdfs
│ └── docker-compose-webhdfs.yml
├── justfile
├── LICENSE
├── licenserc.toml
├── NOTICE
├── README.md
├── rust-toolchain.toml
├── rustfmt.toml
└── scripts
├── constants.py
├── dependencies.py
├── merge_local_staging.py
├── README.md
├── verify.py
└── workspace.py
```
# Files
--------------------------------------------------------------------------------
/core/core/src/types/operator/operator.rs:
--------------------------------------------------------------------------------
```rust
1 | // Licensed to the Apache Software Foundation (ASF) under one
2 | // or more contributor license agreements. See the NOTICE file
3 | // distributed with this work for additional information
4 | // regarding copyright ownership. The ASF licenses this file
5 | // to you under the Apache License, Version 2.0 (the
6 | // "License"); you may not use this file except in compliance
7 | // with the License. You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing,
12 | // software distributed under the License is distributed on an
13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | // KIND, either express or implied. See the License for the
15 | // specific language governing permissions and limitations
16 | // under the License.
17 |
18 | use std::future::Future;
19 |
20 | use futures::Stream;
21 | use futures::StreamExt;
22 | use futures::TryStreamExt;
23 |
24 | use crate::operator_futures::*;
25 | use crate::raw::oio::DeleteDyn;
26 | use crate::raw::*;
27 | use crate::types::delete::Deleter;
28 | use crate::*;
29 |
30 | /// The `Operator` serves as the entry point for all public asynchronous APIs.
31 | ///
32 | /// For more details about the `Operator`, refer to the [`concepts`][crate::docs::concepts] section.
33 | ///
34 | /// All cloned `Operator` instances share the same internal state, such as
35 | /// `HttpClient` and `Runtime`. Some layers may modify the internal state of
36 | /// the `Operator` too like inject logging and metrics for `HttpClient`.
37 | ///
38 | /// ## Build
39 | ///
40 | /// Users can initialize an `Operator` through the following methods:
41 | ///
42 | /// - [`Operator::new`]: Creates an operator using a [`services`] builder, such as [`services::Memory`].
43 | /// - [`Operator::from_config`]: Creates an operator using a [`services`] configuration, such as [`services::MemoryConfig`].
44 | /// - [`Operator::from_iter`]: Creates an operator from an iterator of configuration key-value pairs.
45 | ///
46 | /// ```
47 | /// # use anyhow::Result;
48 | /// use opendal_core::services::Memory;
49 | /// use opendal_core::Operator;
50 | /// async fn test() -> Result<()> {
51 | /// // Build an `Operator` to start operating the storage.
52 | /// let _: Operator = Operator::new(Memory::default())?.finish();
53 | ///
54 | /// Ok(())
55 | /// }
56 | /// ```
57 | ///
58 | /// ## Layer
59 | ///
60 | /// After the operator is built, users can add the layers they need on top of it.
61 | ///
62 | /// OpenDAL offers various layers for users to choose from. Visit [`layers`] for further details.
63 | ///
64 | /// Please note that `Layer` can modify internal contexts such as `HttpClient`
65 | /// and `Runtime` for all clones of given operator. Therefore, it is recommended
66 | /// to add layers before interacting with the storage. Adding or duplicating
67 | /// layers after accessing the storage may result in unexpected behavior.
68 | ///
69 | /// ```
70 | /// use opendal_core::layers::HttpClientLayer;
71 | /// use opendal_core::raw::HttpClient;
72 | /// use opendal_core::services::Memory;
73 | /// use opendal_core::Operator;
74 | /// use opendal_core::Result;
75 | ///
76 | /// async fn test() -> Result<()> {
77 | /// let op: Operator = Operator::new(Memory::default())?.finish();
78 | ///
79 | /// // OpenDAL will replace the default HTTP client now.
80 | /// let client = HttpClient::new()?;
81 | /// let op = op.layer(HttpClientLayer::new(client));
82 | ///
83 | /// Ok(())
84 | /// }
85 | /// ```
86 | ///
87 | /// ## Operate
88 | ///
89 | /// After the operator is built and the layers are added, users can start operating the storage.
90 | ///
91 | /// The operator is `Send`, `Sync`, and `Clone`. It has no internal state, and all APIs only take
92 | /// a `&self` reference, making it safe to share the operator across threads.
93 | ///
94 | /// Operator provides a consistent API pattern for data operations. For reading operations, it exposes:
95 | ///
96 | /// - [`Operator::read`]: Executes a read operation.
97 | /// - [`Operator::read_with`]: Executes a read operation with additional options using the builder pattern.
98 | /// - [`Operator::read_options`]: Executes a read operation with extra options provided via a [`options::ReadOptions`] struct.
99 | /// - [`Operator::reader`]: Creates a reader for streaming data, allowing for flexible access.
100 | /// - [`Operator::reader_with`]: Creates a reader with advanced options using the builder pattern.
101 | /// - [`Operator::reader_options`]: Creates a reader with extra options provided via a [`options::ReadOptions`] struct.
102 | ///
103 | /// The [`Reader`] created by [`Operator`] supports custom read control methods and can be converted
104 | /// into [`futures::AsyncRead`] or [`futures::Stream`] for broader ecosystem compatibility.
105 | ///
106 | /// ```no_run
107 | /// use opendal_core::options;
108 | /// use opendal_core::services;
109 | /// use opendal_core::Operator;
110 | /// use opendal_core::Result;
111 | ///
112 | /// #[tokio::main]
113 | /// async fn main() -> Result<()> {
114 | /// // Pick a builder and configure it.
115 | /// let builder = services::Memory::default();
116 | ///
117 | /// // Init an operator
118 | /// let op = Operator::new(builder)?.finish();
119 | ///
120 | /// // Fetch this file's metadata
121 | /// let meta = op.stat("hello.txt").await?;
122 | /// let length = meta.content_length();
123 | ///
124 | /// // Read data from `hello.txt` with options.
125 | /// let bs = op
126 | /// .read_with("hello.txt")
127 | /// .range(0..8 * 1024 * 1024)
128 | /// .chunk(1024 * 1024)
129 | /// .concurrent(4)
130 | /// .await?;
131 | ///
132 | /// // The same to:
133 | /// let bs = op
134 | /// .read_options("hello.txt", options::ReadOptions {
135 | /// range: (0..8 * 1024 * 1024).into(),
136 | /// chunk: Some(1024 * 1024),
137 | /// concurrent: 4,
138 | /// ..Default::default()
139 | /// })
140 | /// .await?;
141 | ///
142 | /// Ok(())
143 | /// }
144 | /// ```
145 | #[derive(Clone, Debug)]
146 | pub struct Operator {
147 | // accessor is what Operator delegates for
148 | accessor: Accessor,
149 | }
150 |
151 | /// # Operator basic API.
152 | impl Operator {
153 | /// Fetch the internal accessor.
154 | pub fn inner(&self) -> &Accessor {
155 | &self.accessor
156 | }
157 |
158 | /// Convert inner accessor into operator.
159 | pub fn from_inner(accessor: Accessor) -> Self {
160 | Self { accessor }
161 | }
162 |
163 | /// Convert operator into inner accessor.
164 | pub fn into_inner(self) -> Accessor {
165 | self.accessor
166 | }
167 |
168 | /// Get information of underlying accessor.
169 | ///
170 | /// # Examples
171 | ///
172 | /// ```
173 | /// # use std::sync::Arc;
174 | /// # use anyhow::Result;
175 | /// use opendal_core::Operator;
176 | ///
177 | /// # async fn test(op: Operator) -> Result<()> {
178 | /// let info = op.info();
179 | /// # Ok(())
180 | /// # }
181 | /// ```
182 | pub fn info(&self) -> OperatorInfo {
183 | OperatorInfo::new(self.accessor.info())
184 | }
185 |
186 | /// Get the executor used by current operator.
187 | pub fn executor(&self) -> Executor {
188 | self.accessor.info().executor()
189 | }
190 |
191 | /// Update executor for the context.
192 | ///
193 | /// All cloned `Operator` instances share the same internal state, such as
194 | /// `HttpClient` and `Runtime`. Some layers may modify the internal state of
195 | /// the `Operator` too like inject logging and metrics for `HttpClient`.
196 | ///
197 | /// # Note
198 | ///
199 | /// Tasks must be forwarded to the old executor after the update. Otherwise, features such as retry, timeout, and metrics may not function properly.
200 | pub fn update_executor(&self, f: impl FnOnce(Executor) -> Executor) {
201 | self.accessor.info().update_executor(f);
202 | }
203 | }
204 |
205 | /// # Operator async API.
206 | impl Operator {
207 | /// Check if this operator can work correctly.
208 | ///
209 | /// We will send a `list` request to path and return any errors we met.
210 | ///
211 | /// ```
212 | /// # use std::sync::Arc;
213 | /// # use anyhow::Result;
214 | /// use opendal_core::Operator;
215 | ///
216 | /// # async fn test(op: Operator) -> Result<()> {
217 | /// op.check().await?;
218 | /// # Ok(())
219 | /// # }
220 | /// ```
221 | pub async fn check(&self) -> Result<()> {
222 | let mut ds = self.lister_with("/").limit(1).await?;
223 |
224 | match ds.next().await {
225 | Some(Err(e)) if e.kind() != ErrorKind::NotFound => Err(e),
226 | _ => Ok(()),
227 | }
228 | }
229 |
230 | /// Retrieve the metadata for the specified path.
231 | ///
232 | /// # Notes
233 | ///
234 | /// ## Extra Options
235 | ///
236 | /// [`Operator::stat`] is a wrapper around [`Operator::stat_with`] that uses no additional options.
237 | /// To specify extra options such as `if_match` and `if_none_match`, please use [`Operator::stat_with`] instead.
238 | ///
239 | /// # Examples
240 | ///
241 | /// ## Check if file exists
242 | ///
243 | /// ```
244 | /// # use anyhow::Result;
245 | /// # use futures::io;
246 | /// # use opendal_core::Operator;
247 | /// use opendal_core::ErrorKind;
248 | /// #
249 | /// # async fn test(op: Operator) -> Result<()> {
250 | /// if let Err(e) = op.stat("test").await {
251 | /// if e.kind() == ErrorKind::NotFound {
252 | /// println!("file not exist")
253 | /// }
254 | /// }
255 | /// # Ok(())
256 | /// # }
257 | /// ```
258 | pub async fn stat(&self, path: &str) -> Result<Metadata> {
259 | self.stat_with(path).await
260 | }
261 |
262 | /// Retrieve the metadata of the specified path with additional options.
263 | ///
264 | /// # Options
265 | ///
266 | /// Check [`options::StatOptions`] for all available options.
267 | ///
268 | /// # Examples
269 | ///
270 | /// ## Get metadata while `ETag` matches
271 | ///
272 | /// `stat_with` will
273 | ///
274 | /// - return `Ok(metadata)` if `ETag` matches
275 | /// - return `Err(error)` and `error.kind() == ErrorKind::ConditionNotMatch` if file exists but
276 | /// `ETag` mismatch
277 | /// - return `Err(err)` if other errors occur, for example, `NotFound`.
278 | ///
279 | /// ```
280 | /// # use anyhow::Result;
281 | /// # use futures::io;
282 | /// # use opendal_core::Operator;
283 | /// use opendal_core::ErrorKind;
284 | /// #
285 | /// # async fn test(op: Operator) -> Result<()> {
286 | /// if let Err(e) = op.stat_with("test").if_match("<etag>").await {
287 | /// if e.kind() == ErrorKind::ConditionNotMatch {
288 | /// println!("file exists, but etag mismatch")
289 | /// }
290 | /// if e.kind() == ErrorKind::NotFound {
291 | /// println!("file not exist")
292 | /// }
293 | /// }
294 | /// # Ok(())
295 | /// # }
296 | /// ```
297 | pub fn stat_with(&self, path: &str) -> FutureStat<impl Future<Output = Result<Metadata>>> {
298 | let path = normalize_path(path);
299 | OperatorFuture::new(
300 | self.inner().clone(),
301 | path,
302 | options::StatOptions::default(),
303 | Self::stat_inner,
304 | )
305 | }
306 |
307 | /// Retrieve the metadata of the specified path with additional options.
308 | ///
309 | /// # Examples
310 | ///
311 | /// ## Get metadata while `ETag` matches
312 | ///
313 | /// `stat_with` will
314 | ///
315 | /// - return `Ok(metadata)` if `ETag` matches
316 | /// - return `Err(error)` and `error.kind() == ErrorKind::ConditionNotMatch` if file exists but
317 | /// `ETag` mismatch
318 | /// - return `Err(err)` if other errors occur, for example, `NotFound`.
319 | ///
320 | /// ```
321 | /// # use anyhow::Result;
322 | /// # use futures::io;
323 | /// # use opendal_core::Operator;
324 | /// use opendal_core::options;
325 | /// use opendal_core::ErrorKind;
326 | /// #
327 | /// # async fn test(op: Operator) -> Result<()> {
328 | /// let res = op
329 | /// .stat_options("test", options::StatOptions {
330 | /// if_match: Some("<etag>".to_string()),
331 | /// ..Default::default()
332 | /// })
333 | /// .await;
334 | /// if let Err(e) = res {
335 | /// if e.kind() == ErrorKind::ConditionNotMatch {
336 | /// println!("file exists, but etag mismatch")
337 | /// }
338 | /// if e.kind() == ErrorKind::NotFound {
339 | /// println!("file not exist")
340 | /// }
341 | /// }
342 | /// # Ok(())
343 | /// # }
344 | /// ```
345 | pub async fn stat_options(&self, path: &str, opts: options::StatOptions) -> Result<Metadata> {
346 | let path = normalize_path(path);
347 | Self::stat_inner(self.accessor.clone(), path, opts).await
348 | }
349 |
350 | #[inline]
351 | async fn stat_inner(
352 | acc: Accessor,
353 | path: String,
354 | opts: options::StatOptions,
355 | ) -> Result<Metadata> {
356 | let rp = acc.stat(&path, opts.into()).await?;
357 | Ok(rp.into_metadata())
358 | }
359 |
360 | /// Check whether this path exists.
361 | ///
362 | /// # Example
363 | ///
364 | /// ```
365 | /// use anyhow::Result;
366 | /// use futures::io;
367 | /// use opendal_core::Operator;
368 | ///
369 | /// async fn test(op: Operator) -> Result<()> {
370 | /// let _ = op.exists("test").await?;
371 | ///
372 | /// Ok(())
373 | /// }
374 | /// ```
375 | pub async fn exists(&self, path: &str) -> Result<bool> {
376 | let r = self.stat(path).await;
377 | match r {
378 | Ok(_) => Ok(true),
379 | Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
380 | Err(err) => Err(err),
381 | }
382 | }
383 |
384 | /// Create a directory at the specified path.
385 | ///
386 | /// # Notes
387 | ///
388 | /// To specify that a path is a directory, you must include a trailing slash (/).
389 | /// Omitting the trailing slash may cause OpenDAL to return a `NotADirectory` error.
390 | ///
391 | /// # Behavior
392 | ///
393 | /// - Creating a directory that already exists will succeed.
394 | /// - Directory creation is always recursive, functioning like `mkdir -p`.
395 | ///
396 | /// # Examples
397 | ///
398 | /// ```
399 | /// # use opendal_core::Result;
400 | /// # use opendal_core::Operator;
401 | /// # async fn test(op: Operator) -> Result<()> {
402 | /// op.create_dir("path/to/dir/").await?;
403 | /// # Ok(())
404 | /// # }
405 | /// ```
406 | pub async fn create_dir(&self, path: &str) -> Result<()> {
407 | let path = normalize_path(path);
408 |
409 | if !validate_path(&path, EntryMode::DIR) {
410 | return Err(Error::new(
411 | ErrorKind::NotADirectory,
412 | "the path trying to create should end with `/`",
413 | )
414 | .with_operation("create_dir")
415 | .with_context("service", self.inner().info().scheme())
416 | .with_context("path", &path));
417 | }
418 |
419 | self.inner().create_dir(&path, OpCreateDir::new()).await?;
420 |
421 | Ok(())
422 | }
423 |
424 | /// Read the entire file into bytes from given path.
425 | ///
426 | /// # Notes
427 | ///
428 | /// ## Additional Options
429 | ///
430 | /// [`Operator::read`] is a simplified method that does not support additional options. To access features like `range` and `if_match`, please use [`Operator::read_with`] or [`Operator::read_options`] instead.
431 | ///
432 | /// ## Streaming Read
433 | ///
434 | /// This function reads all content into memory at once. For more precise memory management or to read big file lazily, please use [`Operator::reader`].
435 | ///
436 | /// # Examples
437 | ///
438 | /// ```
439 | /// # use opendal_core::Result;
440 | /// # use opendal_core::Operator;
441 | /// # use futures::TryStreamExt;
442 | /// # async fn test(op: Operator) -> Result<()> {
443 | /// let bs = op.read("path/to/file").await?;
444 | /// # Ok(())
445 | /// # }
446 | /// ```
447 | pub async fn read(&self, path: &str) -> Result<Buffer> {
448 | self.read_options(path, options::ReadOptions::default())
449 | .await
450 | }
451 |
452 | /// Read the entire file into bytes from given path with additional options.
453 | ///
454 | /// # Notes
455 | ///
456 | /// ## Streaming Read
457 | ///
458 | /// This function reads all content into memory at once. For more precise memory management or to read big file lazily, please use [`Operator::reader`].
459 | ///
460 | /// # Options
461 | ///
462 | /// Visit [`options::ReadOptions`] for all available options.
463 | ///
464 | /// # Examples
465 | ///
466 | /// Read the first 10 bytes of a file:
467 | ///
468 | /// ```
469 | /// # use opendal_core::Result;
470 | /// # use opendal_core::Operator;
471 | /// # async fn test(op: Operator) -> Result<()> {
472 | /// let bs = op.read_with("path/to/file").range(0..10).await?;
473 | /// # Ok(())
474 | /// # }
475 | /// ```
476 | pub fn read_with(&self, path: &str) -> FutureRead<impl Future<Output = Result<Buffer>>> {
477 | let path = normalize_path(path);
478 |
479 | OperatorFuture::new(
480 | self.inner().clone(),
481 | path,
482 | options::ReadOptions::default(),
483 | Self::read_inner,
484 | )
485 | }
486 |
487 | /// Read the entire file into bytes from given path with additional options.
488 | ///
489 | /// # Notes
490 | ///
491 | /// ## Streaming Read
492 | ///
493 | /// This function reads all content into memory at once. For more precise memory management or to read big file lazily, please use [`Operator::reader`].
494 | ///
495 | /// # Examples
496 | ///
497 | /// Read the first 10 bytes of a file:
498 | ///
499 | /// ```
500 | /// # use opendal_core::Result;
501 | /// # use opendal_core::Operator;
502 | /// use opendal_core::options;
503 | /// # async fn test(op: Operator) -> Result<()> {
504 | /// let bs = op
505 | /// .read_options("path/to/file", options::ReadOptions {
506 | /// range: (0..10).into(),
507 | /// ..Default::default()
508 | /// })
509 | /// .await?;
510 | /// # Ok(())
511 | /// # }
512 | /// ```
513 | pub async fn read_options(&self, path: &str, opts: options::ReadOptions) -> Result<Buffer> {
514 | let path = normalize_path(path);
515 | Self::read_inner(self.inner().clone(), path, opts).await
516 | }
517 |
518 | #[inline]
519 | async fn read_inner(acc: Accessor, path: String, opts: options::ReadOptions) -> Result<Buffer> {
520 | if !validate_path(&path, EntryMode::FILE) {
521 | return Err(
522 | Error::new(ErrorKind::IsADirectory, "read path is a directory")
523 | .with_operation("read")
524 | .with_context("service", acc.info().scheme())
525 | .with_context("path", &path),
526 | );
527 | }
528 |
529 | let (args, opts) = opts.into();
530 | let range = args.range();
531 | let context = ReadContext::new(acc, path, args, opts);
532 | let r = Reader::new(context);
533 | let buf = r.read(range.to_range()).await?;
534 | Ok(buf)
535 | }
536 |
537 | /// Create a new reader of given path.
538 | ///
539 | /// # Notes
540 | ///
541 | /// ## Extra Options
542 | ///
543 | /// [`Operator::reader`] is a simplified method without any options. To use additional options such as `concurrent` or `if_match`, please use [`Operator::reader_with`] or [`Operator::reader_options`] instead.
544 | ///
545 | /// # Examples
546 | ///
547 | /// ```
548 | /// # use opendal_core::Result;
549 | /// # use opendal_core::Operator;
550 | /// # use futures::TryStreamExt;
551 | /// # async fn test(op: Operator) -> Result<()> {
552 | /// let r = op.reader("path/to/file").await?;
553 | /// // Read the first 10 bytes of the file
554 | /// let data = r.read(0..10).await?;
555 | /// # Ok(())
556 | /// # }
557 | /// ```
558 | pub async fn reader(&self, path: &str) -> Result<Reader> {
559 | self.reader_with(path).await
560 | }
561 |
562 | /// Create a new reader of given path with additional options.
563 | ///
564 | /// # Options
565 | ///
566 | /// Visit [`options::ReaderOptions`] for all available options.
567 | ///
568 | /// # Examples
569 | ///
570 | /// Create a reader with a specific version ID:
571 | ///
572 | /// ```
573 | /// # use opendal_core::Result;
574 | /// # use opendal_core::Operator;
575 | /// # async fn test(op: Operator) -> Result<()> {
576 | /// let r = op.reader_with("path/to/file").version("version_id").await?;
577 | /// // Read the first 10 bytes of the file
578 | /// let data = r.read(0..10).await?;
579 | /// # Ok(())
580 | /// # }
581 | /// ```
582 | pub fn reader_with(&self, path: &str) -> FutureReader<impl Future<Output = Result<Reader>>> {
583 | let path = normalize_path(path);
584 |
585 | OperatorFuture::new(
586 | self.inner().clone(),
587 | path,
588 | options::ReaderOptions::default(),
589 | Self::reader_inner,
590 | )
591 | }
592 |
593 | /// Create a new reader of given path with additional options.
594 | ///
595 | /// # Examples
596 | ///
597 | /// Create a reader with a specific version ID:
598 | ///
599 | /// ```
600 | /// # use opendal_core::Result;
601 | /// # use opendal_core::Operator;
602 | /// use opendal_core::options;
603 | /// # async fn test(op: Operator) -> Result<()> {
604 | /// let r = op
605 | /// .reader_options("path/to/file", options::ReaderOptions {
606 | /// version: Some("version_id".to_string()),
607 | /// ..Default::default()
608 | /// })
609 | /// .await?;
610 | /// // Read the first 10 bytes of the file
611 | /// let data = r.read(0..10).await?;
612 | /// # Ok(())
613 | /// # }
614 | /// ```
615 | pub async fn reader_options(&self, path: &str, opts: options::ReaderOptions) -> Result<Reader> {
616 | let path = normalize_path(path);
617 | Self::reader_inner(self.inner().clone(), path, opts).await
618 | }
619 |
620 | /// Allow this unused async since we don't want
621 | /// to change our public API.
622 | #[allow(clippy::unused_async)]
623 | #[inline]
624 | async fn reader_inner(
625 | acc: Accessor,
626 | path: String,
627 | options: options::ReaderOptions,
628 | ) -> Result<Reader> {
629 | if !validate_path(&path, EntryMode::FILE) {
630 | return Err(
631 | Error::new(ErrorKind::IsADirectory, "read path is a directory")
632 | .with_operation("Operator::reader")
633 | .with_context("service", acc.info().scheme())
634 | .with_context("path", path),
635 | );
636 | }
637 |
638 | let (args, opts) = options.into();
639 | let context = ReadContext::new(acc, path, args, opts);
640 | Ok(Reader::new(context))
641 | }
642 |
643 | /// Write all data to the specified path at once.
644 | ///
645 | /// # Notes
646 | ///
647 | /// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
648 | ///
649 | /// ## Extra Options
650 | ///
651 | /// [`Operator::write`] is a simplified method that does not include additional options.
652 | /// For advanced features such as `chunk` and `concurrent`, use [`Operator::write_with`] or [`Operator::write_options`] instead.
653 | ///
654 | /// ## Streaming Write
655 | ///
656 | /// This method executes a single bulk write operation. For more precise memory management
657 | /// or to write data in a streaming fashion, use [`Operator::writer`] instead.
658 | ///
659 | /// ## Multipart Uploads
660 | ///
661 | /// OpenDAL offers multipart upload capabilities through the [`Writer`] abstraction,
662 | /// automatically managing all upload details for you. You can fine-tune the upload process
663 | /// by adjusting the `chunk` size and the number of `concurrent` operations using [`Operator::writer_with`].
664 | ///
665 | /// # Examples
666 | ///
667 | /// ```
668 | /// # use opendal_core::Result;
669 | /// # use opendal_core::Operator;
670 | /// # use futures::StreamExt;
671 | /// # use futures::SinkExt;
672 | /// use bytes::Bytes;
673 | ///
674 | /// # async fn test(op: Operator) -> Result<()> {
675 | /// op.write("path/to/file", vec![0; 4096]).await?;
676 | /// # Ok(())
677 | /// # }
678 | /// ```
679 | pub async fn write(&self, path: &str, bs: impl Into<Buffer>) -> Result<Metadata> {
680 | let bs = bs.into();
681 | self.write_with(path, bs).await
682 | }
683 |
684 | /// Write all data to the specified path at once with additional options.
685 | ///
686 | /// # Notes
687 | ///
688 | /// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
689 | ///
690 | /// ## Streaming Write
691 | ///
692 | /// This method executes a single bulk write operation. For more precise memory management
693 | /// or to write data in a streaming fashion, use [`Operator::writer`] instead.
694 | ///
695 | /// ## Multipart Uploads
696 | ///
697 | /// OpenDAL offers multipart upload capabilities through the [`Writer`] abstraction,
698 | /// automatically managing all upload details for you. You can fine-tune the upload process
699 | /// by adjusting the `chunk` size and the number of `concurrent` operations using [`Operator::writer_with`].
700 | ///
701 | /// # Options
702 | ///
703 | /// Visit [`options::WriteOptions`] for all available options.
704 | ///
705 | /// # Examples
706 | ///
707 | /// Write data to a file only when it does not already exist:
708 | ///
709 | /// ```
710 | /// # use opendal_core::Result;
711 | /// # use opendal_core::Operator;
712 | /// use bytes::Bytes;
713 | ///
714 | /// # async fn test(op: Operator) -> Result<()> {
715 | /// let _ = op
716 | /// .write_with("path/to/file", vec![0; 4096])
717 | /// .if_not_exists(true)
718 | /// .await?;
719 | /// # Ok(())
720 | /// # }
721 | /// ```
722 | pub fn write_with(
723 | &self,
724 | path: &str,
725 | bs: impl Into<Buffer>,
726 | ) -> FutureWrite<impl Future<Output = Result<Metadata>>> {
727 | let path = normalize_path(path);
728 | let bs = bs.into();
729 |
730 | OperatorFuture::new(
731 | self.inner().clone(),
732 | path,
733 | (options::WriteOptions::default(), bs),
734 | |inner, path, (opts, bs)| Self::write_inner(inner, path, bs, opts),
735 | )
736 | }
737 |
738 | /// Write all data to the specified path at once with additional options.
739 | ///
740 | /// # Notes
741 | ///
742 | /// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
743 | ///
744 | /// ## Streaming Write
745 | ///
746 | /// This method executes a single bulk write operation. For more precise memory management
747 | /// or to write data in a streaming fashion, use [`Operator::writer`] instead.
748 | ///
749 | /// ## Multipart Uploads
750 | ///
751 | /// OpenDAL offers multipart upload capabilities through the [`Writer`] abstraction,
752 | /// automatically managing all upload details for you. You can fine-tune the upload process
753 | /// by adjusting the `chunk` size and the number of `concurrent` operations using [`Operator::writer_with`].
754 | ///
755 | /// # Examples
756 | ///
757 | /// Write data to a file only when it does not already exist:
758 | ///
759 | /// ```
760 | /// # use opendal_core::Result;
761 | /// # use opendal_core::Operator;
762 | /// use opendal_core::options;
763 | ///
764 | /// # async fn test(op: Operator) -> Result<()> {
765 | /// let _ = op
766 | /// .write_options("path/to/file", vec![0; 4096], options::WriteOptions {
767 | /// if_not_exists: true,
768 | /// ..Default::default()
769 | /// })
770 | /// .await?;
771 | /// # Ok(())
772 | /// # }
773 | /// ```
774 | pub async fn write_options(
775 | &self,
776 | path: &str,
777 | bs: impl Into<Buffer>,
778 | opts: options::WriteOptions,
779 | ) -> Result<Metadata> {
780 | let path = normalize_path(path);
781 | Self::write_inner(self.inner().clone(), path, bs.into(), opts).await
782 | }
783 |
784 | #[inline]
785 | async fn write_inner(
786 | acc: Accessor,
787 | path: String,
788 | bs: Buffer,
789 | opts: options::WriteOptions,
790 | ) -> Result<Metadata> {
791 | if !validate_path(&path, EntryMode::FILE) {
792 | return Err(
793 | Error::new(ErrorKind::IsADirectory, "write path is a directory")
794 | .with_operation("Operator::write")
795 | .with_context("service", acc.info().scheme())
796 | .with_context("path", &path),
797 | );
798 | }
799 |
800 | let (args, opts) = opts.into();
801 |
802 | let context = WriteContext::new(acc, path, args, opts);
803 | let mut w = Writer::new(context).await?;
804 | w.write(bs).await?;
805 | w.close().await
806 | }
807 |
808 | /// Create a new writer of given path.
809 | ///
810 | /// # Notes
811 | ///
812 | /// ## Writer Features
813 | ///
814 | /// The writer provides several powerful capabilities:
815 | /// - Streaming writes for continuous data transfer
816 | /// - Automatic multipart upload handling
817 | /// - Memory-efficient chunk-based writing
818 | ///
819 | /// ## Extra Options
820 | ///
821 | /// [`Operator::writer`] is a simplified version that does not include additional options.
822 | /// For advanced features such as `chunk` and `concurrent`, use [`Operator::writer_with`] or [`Operator::writer_options`] instead.
823 | ///
824 | /// # Examples
825 | ///
826 | /// ```
827 | /// # use opendal_core::Result;
828 | /// # use opendal_core::Operator;
829 | /// use bytes::Bytes;
830 | ///
831 | /// # async fn test(op: Operator) -> Result<()> {
832 | /// let mut w = op.writer("path/to/file").await?;
833 | /// w.write(vec![0; 4096]).await?;
834 | /// w.write(vec![1; 4096]).await?;
835 | /// w.close().await?;
836 | /// # Ok(())
837 | /// # }
838 | /// ```
839 | pub async fn writer(&self, path: &str) -> Result<Writer> {
840 | self.writer_with(path).await
841 | }
842 |
843 | /// Create a new writer of given path with additional options.
844 | ///
845 | /// # Notes
846 | ///
847 | /// ## Writer Features
848 | ///
849 | /// The writer provides several powerful capabilities:
850 | /// - Streaming writes for continuous data transfer
851 | /// - Automatic multipart upload handling
852 | /// - Memory-efficient chunk-based writing
853 | ///
854 | /// ## Chunk Size Handling
855 | ///
856 | /// Storage services often have specific requirements for chunk sizes:
857 | /// - Services like `s3` may return `EntityTooSmall` errors for undersized chunks
858 | /// - Using small chunks in cloud storage services can lead to increased costs
859 | ///
860 | /// OpenDAL automatically determines optimal chunk sizes based on the service's
861 | /// [Capability](crate::types::Capability). However, you can override this by explicitly
862 | /// setting the `chunk` parameter.
863 | ///
864 | /// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
865 | ///
866 | /// # Examples
867 | ///
868 | /// ```
869 | /// # use opendal_core::Result;
870 | /// # use opendal_core::Operator;
871 | /// use bytes::Bytes;
872 | ///
873 | /// # async fn test(op: Operator) -> Result<()> {
874 | /// let mut w = op
875 | /// .writer_with("path/to/file")
876 | /// .chunk(4 * 1024 * 1024)
877 | /// .concurrent(8)
878 | /// .await?;
879 | /// w.write(vec![0; 4096]).await?;
880 | /// w.write(vec![1; 4096]).await?;
881 | /// w.close().await?;
882 | /// # Ok(())
883 | /// # }
884 | /// ```
885 | pub fn writer_with(&self, path: &str) -> FutureWriter<impl Future<Output = Result<Writer>>> {
886 | let path = normalize_path(path);
887 |
888 | OperatorFuture::new(
889 | self.inner().clone(),
890 | path,
891 | options::WriteOptions::default(),
892 | Self::writer_inner,
893 | )
894 | }
895 |
896 | /// Create a new writer of given path with additional options.
897 | ///
898 | /// # Notes
899 | ///
900 | /// ## Writer Features
901 | ///
902 | /// The writer provides several powerful capabilities:
903 | /// - Streaming writes for continuous data transfer
904 | /// - Automatic multipart upload handling
905 | /// - Memory-efficient chunk-based writing
906 | ///
907 | /// ## Chunk Size Handling
908 | ///
909 | /// Storage services often have specific requirements for chunk sizes:
910 | /// - Services like `s3` may return `EntityTooSmall` errors for undersized chunks
911 | /// - Using small chunks in cloud storage services can lead to increased costs
912 | ///
913 | /// OpenDAL automatically determines optimal chunk sizes based on the service's
914 | /// [Capability](crate::types::Capability). However, you can override this by explicitly
915 | /// setting the `chunk` parameter.
916 | ///
917 | /// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
918 | ///
919 | /// # Examples
920 | ///
921 | /// Write data to a file in 4MiB chunk size and at 8 concurrency:
922 | ///
923 | /// ```
924 | /// # use opendal_core::Result;
925 | /// # use opendal_core::Operator;
926 | /// use bytes::Bytes;
927 | ///
928 | /// # async fn test(op: Operator) -> Result<()> {
929 | /// let mut w = op
930 | /// .writer_with("path/to/file")
931 | /// .chunk(4 * 1024 * 1024)
932 | /// .concurrent(8)
933 | /// .await?;
934 | /// w.write(vec![0; 4096]).await?;
935 | /// w.write(vec![1; 4096]).await?;
936 | /// w.close().await?;
937 | /// # Ok(())
938 | /// # }
939 | /// ```
940 | pub async fn writer_options(&self, path: &str, opts: options::WriteOptions) -> Result<Writer> {
941 | let path = normalize_path(path);
942 | Self::writer_inner(self.inner().clone(), path, opts).await
943 | }
944 |
945 | #[inline]
946 | async fn writer_inner(
947 | acc: Accessor,
948 | path: String,
949 | opts: options::WriteOptions,
950 | ) -> Result<Writer> {
951 | if !validate_path(&path, EntryMode::FILE) {
952 | return Err(
953 | Error::new(ErrorKind::IsADirectory, "write path is a directory")
954 | .with_operation("Operator::writer")
955 | .with_context("service", acc.info().scheme())
956 | .with_context("path", &path),
957 | );
958 | }
959 |
960 | let (args, opts) = opts.into();
961 | let context = WriteContext::new(acc, path, args, opts);
962 | let w = Writer::new(context).await?;
963 | Ok(w)
964 | }
965 |
966 | /// Copy a file from `from` to `to`.
967 | ///
968 | /// # Notes
969 | ///
970 | /// - `from` and `to` must be a file.
971 | /// - `to` will be overwritten if it exists.
972 | /// - If `from` and `to` are the same, an `IsSameFile` error will occur.
973 | /// - `copy` is idempotent. For same `from` and `to` input, the result will be the same.
974 | ///
975 | /// # Examples
976 | ///
977 | /// ```
978 | /// # use opendal_core::Result;
979 | /// # use opendal_core::Operator;
980 | ///
981 | /// # async fn test(op: Operator) -> Result<()> {
982 | /// op.copy("path/to/file", "path/to/file2").await?;
983 | /// # Ok(())
984 | /// # }
985 | /// ```
986 | pub async fn copy(&self, from: &str, to: &str) -> Result<()> {
987 | let from = normalize_path(from);
988 |
989 | if !validate_path(&from, EntryMode::FILE) {
990 | return Err(
991 | Error::new(ErrorKind::IsADirectory, "from path is a directory")
992 | .with_operation("Operator::copy")
993 | .with_context("service", self.info().scheme())
994 | .with_context("from", from),
995 | );
996 | }
997 |
998 | let to = normalize_path(to);
999 |
1000 | if !validate_path(&to, EntryMode::FILE) {
1001 | return Err(
1002 | Error::new(ErrorKind::IsADirectory, "to path is a directory")
1003 | .with_operation("Operator::copy")
1004 | .with_context("service", self.info().scheme())
1005 | .with_context("to", to),
1006 | );
1007 | }
1008 |
1009 | if from == to {
1010 | return Err(
1011 | Error::new(ErrorKind::IsSameFile, "from and to paths are same")
1012 | .with_operation("Operator::copy")
1013 | .with_context("service", self.info().scheme())
1014 | .with_context("from", from)
1015 | .with_context("to", to),
1016 | );
1017 | }
1018 |
1019 | self.inner().copy(&from, &to, OpCopy::new()).await?;
1020 |
1021 | Ok(())
1022 | }
1023 |
1024 | /// Copy a file from `from` to `to` with additional options.
1025 | ///
1026 | /// # Notes
1027 | ///
1028 | /// - `from` and `to` must be a file.
1029 | /// - If `from` and `to` are the same, an `IsSameFile` error will occur.
1030 | /// - `copy` is idempotent. For same `from` and `to` input, the result will be the same.
1031 | ///
1032 | /// # Options
1033 | ///
1034 | /// Visit [`options::CopyOptions`] for all available options.
1035 | ///
1036 | /// # Examples
1037 | ///
1038 | /// Copy a file only if the destination doesn't exist:
1039 | ///
1040 | /// ```
1041 | /// # use opendal_core::Result;
1042 | /// # use opendal_core::Operator;
1043 | ///
1044 | /// # async fn test(op: Operator) -> Result<()> {
1045 | /// op.copy_with("path/to/file", "path/to/file2")
1046 | /// .if_not_exists(true)
1047 | /// .await?;
1048 | /// # Ok(())
1049 | /// # }
1050 | /// ```
1051 | pub fn copy_with(&self, from: &str, to: &str) -> FutureCopy<impl Future<Output = Result<()>>> {
1052 | let from = normalize_path(from);
1053 | let to = normalize_path(to);
1054 |
1055 | OperatorFuture::new(
1056 | self.inner().clone(),
1057 | from,
1058 | (options::CopyOptions::default(), to),
1059 | Self::copy_inner,
1060 | )
1061 | }
1062 |
1063 | /// Copy a file from `from` to `to` with additional options.
1064 | ///
1065 | /// # Notes
1066 | ///
1067 | /// - `from` and `to` must be a file.
1068 | /// - If `from` and `to` are the same, an `IsSameFile` error will occur.
1069 | /// - `copy` is idempotent. For same `from` and `to` input, the result will be the same.
1070 | ///
1071 | /// # Options
1072 | ///
1073 | /// Check [`options::CopyOptions`] for all available options.
1074 | ///
1075 | /// # Examples
1076 | ///
1077 | /// Copy a file only if the destination doesn't exist:
1078 | ///
1079 | /// ```
1080 | /// # use opendal_core::Result;
1081 | /// # use opendal_core::Operator;
1082 | /// # use opendal_core::options::CopyOptions;
1083 | ///
1084 | /// # async fn test(op: Operator) -> Result<()> {
1085 | /// let mut opts = CopyOptions::default();
1086 | /// opts.if_not_exists = true;
1087 | /// op.copy_options("path/to/file", "path/to/file2", opts).await?;
1088 | /// # Ok(())
1089 | /// # }
1090 | /// ```
1091 | pub async fn copy_options(
1092 | &self,
1093 | from: &str,
1094 | to: &str,
1095 | opts: impl Into<options::CopyOptions>,
1096 | ) -> Result<()> {
1097 | let from = normalize_path(from);
1098 | let to = normalize_path(to);
1099 | let opts = opts.into();
1100 |
1101 | Self::copy_inner(self.inner().clone(), from, (opts, to)).await
1102 | }
1103 |
1104 | async fn copy_inner(
1105 | acc: Accessor,
1106 | from: String,
1107 | (opts, to): (options::CopyOptions, String),
1108 | ) -> Result<()> {
1109 | if !validate_path(&from, EntryMode::FILE) {
1110 | return Err(
1111 | Error::new(ErrorKind::IsADirectory, "from path is a directory")
1112 | .with_operation("Operator::copy")
1113 | .with_context("service", acc.info().scheme())
1114 | .with_context("from", from),
1115 | );
1116 | }
1117 |
1118 | if !validate_path(&to, EntryMode::FILE) {
1119 | return Err(
1120 | Error::new(ErrorKind::IsADirectory, "to path is a directory")
1121 | .with_operation("Operator::copy")
1122 | .with_context("service", acc.info().scheme())
1123 | .with_context("to", to),
1124 | );
1125 | }
1126 |
1127 | if from == to {
1128 | return Err(
1129 | Error::new(ErrorKind::IsSameFile, "from and to paths are same")
1130 | .with_operation("Operator::copy")
1131 | .with_context("service", acc.info().scheme())
1132 | .with_context("from", &from)
1133 | .with_context("to", &to),
1134 | );
1135 | }
1136 |
1137 | let mut op = OpCopy::new();
1138 | if opts.if_not_exists {
1139 | op = op.with_if_not_exists(true);
1140 | }
1141 |
1142 | acc.copy(&from, &to, op).await.map(|_| ())
1143 | }
1144 |
1145 | /// Rename a file from `from` to `to`.
1146 | ///
1147 | /// # Notes
1148 | ///
1149 | /// - `from` and `to` must be a file.
1150 | /// - `to` will be overwritten if it exists.
1151 | /// - If `from` and `to` are the same, an `IsSameFile` error will occur.
1152 | ///
1153 | /// # Examples
1154 | ///
1155 | /// ```
1156 | /// # use opendal_core::Result;
1157 | /// # use opendal_core::Operator;
1158 | ///
1159 | /// # async fn test(op: Operator) -> Result<()> {
1160 | /// op.rename("path/to/file", "path/to/file2").await?;
1161 | /// # Ok(())
1162 | /// # }
1163 | /// ```
1164 | pub async fn rename(&self, from: &str, to: &str) -> Result<()> {
1165 | let from = normalize_path(from);
1166 |
1167 | if !validate_path(&from, EntryMode::FILE) {
1168 | return Err(
1169 | Error::new(ErrorKind::IsADirectory, "from path is a directory")
1170 | .with_operation("Operator::move_")
1171 | .with_context("service", self.info().scheme())
1172 | .with_context("from", from),
1173 | );
1174 | }
1175 |
1176 | let to = normalize_path(to);
1177 |
1178 | if !validate_path(&to, EntryMode::FILE) {
1179 | return Err(
1180 | Error::new(ErrorKind::IsADirectory, "to path is a directory")
1181 | .with_operation("Operator::move_")
1182 | .with_context("service", self.info().scheme())
1183 | .with_context("to", to),
1184 | );
1185 | }
1186 |
1187 | if from == to {
1188 | return Err(
1189 | Error::new(ErrorKind::IsSameFile, "from and to paths are same")
1190 | .with_operation("Operator::move_")
1191 | .with_context("service", self.info().scheme())
1192 | .with_context("from", from)
1193 | .with_context("to", to),
1194 | );
1195 | }
1196 |
1197 | self.inner().rename(&from, &to, OpRename::new()).await?;
1198 |
1199 | Ok(())
1200 | }
1201 |
1202 | /// Delete the given path.
1203 | ///
1204 | /// # Notes
1205 | ///
1206 | /// - Deleting a file that does not exist won't return errors.
1207 | ///
1208 | /// # Examples
1209 | ///
1210 | /// ```
1211 | /// # use anyhow::Result;
1212 | /// # use futures::io;
1213 | /// # use opendal_core::Operator;
1214 | /// # async fn test(op: Operator) -> Result<()> {
1215 | /// op.delete("test").await?;
1216 | /// # Ok(())
1217 | /// # }
1218 | /// ```
1219 | pub async fn delete(&self, path: &str) -> Result<()> {
1220 | self.delete_with(path).await
1221 | }
1222 |
1223 | /// Delete the given path with additional options.
1224 | ///
1225 | /// # Notes
1226 | ///
1227 | /// - Deleting a file that does not exist won't return errors.
1228 | ///
1229 | /// # Options
1230 | ///
1231 | /// Visit [`options::DeleteOptions`] for all available options.
1232 | ///
1233 | /// # Examples
1234 | ///
1235 | /// Delete a specific version of a file:
1236 | ///
1237 | /// ```
1238 | /// # use opendal_core::Result;
1239 | /// # use opendal_core::Operator;
1240 | ///
1241 | /// # async fn test(op: Operator, version: &str) -> Result<()> {
1242 | /// op.delete_with("path/to/file").version(version).await?;
1243 | /// # Ok(())
1244 | /// # }
1245 | /// ```
1246 | pub fn delete_with(&self, path: &str) -> FutureDelete<impl Future<Output = Result<()>>> {
1247 | let path = normalize_path(path);
1248 |
1249 | OperatorFuture::new(
1250 | self.inner().clone(),
1251 | path,
1252 | options::DeleteOptions::default(),
1253 | Self::delete_inner,
1254 | )
1255 | }
1256 |
1257 | /// Delete the given path with additional options.
1258 | ///
1259 | /// # Notes
1260 | ///
1261 | /// - Deleting a file that does not exist won't return errors.
1262 | ///
1263 | /// # Examples
1264 | ///
1265 | /// Delete a specific version of a file:
1266 | ///
1267 | /// ```
1268 | /// # use opendal_core::Result;
1269 | /// # use opendal_core::Operator;
1270 | /// use opendal_core::options;
1271 | ///
1272 | /// # async fn test(op: Operator, version: &str) -> Result<()> {
1273 | /// op.delete_options("path/to/file", options::DeleteOptions {
1274 | /// version: Some(version.to_string()),
1275 | /// ..Default::default()
1276 | /// })
1277 | /// .await?;
1278 | /// # Ok(())
1279 | /// # }
1280 | /// ```
1281 | pub async fn delete_options(&self, path: &str, opts: options::DeleteOptions) -> Result<()> {
1282 | let path = normalize_path(path);
1283 | Self::delete_inner(self.inner().clone(), path, opts).await
1284 | }
1285 |
1286 | async fn delete_inner(acc: Accessor, path: String, opts: options::DeleteOptions) -> Result<()> {
1287 | let (_, mut deleter) = acc.delete_dyn().await?;
1288 | let args = opts.into();
1289 | deleter.delete_dyn(&path, args).await?;
1290 | deleter.close_dyn().await?;
1291 | Ok(())
1292 | }
1293 |
1294 | /// Delete an infallible iterator of paths.
1295 | ///
1296 | /// Also see:
1297 | ///
1298 | /// - [`Operator::delete_try_iter`]: delete a fallible iterator of paths.
1299 | /// - [`Operator::delete_stream`]: delete an infallible stream of paths.
1300 | /// - [`Operator::delete_try_stream`]: delete a fallible stream of paths.
1301 | pub async fn delete_iter<I, D>(&self, iter: I) -> Result<()>
1302 | where
1303 | I: IntoIterator<Item = D>,
1304 | D: IntoDeleteInput,
1305 | {
1306 | let mut deleter = self.deleter().await?;
1307 | deleter.delete_iter(iter).await?;
1308 | deleter.close().await?;
1309 | Ok(())
1310 | }
1311 |
1312 | /// Delete a fallible iterator of paths.
1313 | ///
1314 | /// Also see:
1315 | ///
1316 | /// - [`Operator::delete_iter`]: delete an infallible iterator of paths.
1317 | /// - [`Operator::delete_stream`]: delete an infallible stream of paths.
1318 | /// - [`Operator::delete_try_stream`]: delete a fallible stream of paths.
1319 | pub async fn delete_try_iter<I, D>(&self, try_iter: I) -> Result<()>
1320 | where
1321 | I: IntoIterator<Item = Result<D>>,
1322 | D: IntoDeleteInput,
1323 | {
1324 | let mut deleter = self.deleter().await?;
1325 | deleter.delete_try_iter(try_iter).await?;
1326 | deleter.close().await?;
1327 | Ok(())
1328 | }
1329 |
1330 | /// Delete an infallible stream of paths.
1331 | ///
1332 | /// Also see:
1333 | ///
1334 | /// - [`Operator::delete_iter`]: delete an infallible iterator of paths.
1335 | /// - [`Operator::delete_try_iter`]: delete a fallible iterator of paths.
1336 | /// - [`Operator::delete_try_stream`]: delete a fallible stream of paths.
1337 | pub async fn delete_stream<S, D>(&self, stream: S) -> Result<()>
1338 | where
1339 | S: Stream<Item = D>,
1340 | D: IntoDeleteInput,
1341 | {
1342 | let mut deleter = self.deleter().await?;
1343 | deleter.delete_stream(stream).await?;
1344 | deleter.close().await?;
1345 | Ok(())
1346 | }
1347 |
1348 | /// Delete a fallible stream of paths.
1349 | ///
1350 | /// Also see:
1351 | ///
1352 | /// - [`Operator::delete_iter`]: delete an infallible iterator of paths.
1353 | /// - [`Operator::delete_try_iter`]: delete a fallible iterator of paths.
1354 | /// - [`Operator::delete_stream`]: delete an infallible stream of paths.
1355 | pub async fn delete_try_stream<S, D>(&self, try_stream: S) -> Result<()>
1356 | where
1357 | S: Stream<Item = Result<D>>,
1358 | D: IntoDeleteInput,
1359 | {
1360 | let mut deleter = self.deleter().await?;
1361 | deleter.delete_try_stream(try_stream).await?;
1362 | deleter.close().await?;
1363 | Ok(())
1364 | }
1365 |
1366 | /// Create a [`Deleter`] to continuously remove content from storage.
1367 | ///
1368 | /// It leverages batch deletion capabilities provided by storage services for efficient removal.
1369 | ///
1370 | /// Users can have more control over the deletion process by using [`Deleter`] directly.
1371 | pub async fn deleter(&self) -> Result<Deleter> {
1372 | Deleter::create(self.inner().clone()).await
1373 | }
1374 |
1375 | /// Remove the path and all nested dirs and files recursively.
1376 | ///
1377 | /// # Deprecated
1378 | ///
1379 | /// This method is deprecated since v0.55.0. Use [`Operator::delete_with`] with
1380 | /// `recursive(true)` instead.
1381 | ///
1382 | /// ## Migration Example
1383 | ///
1384 | /// Instead of:
1385 | /// ```ignore
1386 | /// op.remove_all("path/to/dir").await?;
1387 | /// ```
1388 | ///
1389 | /// Use:
1390 | /// ```ignore
1391 | /// op.delete_with("path/to/dir").recursive(true).await?;
1392 | /// ```
1393 | ///
1394 | /// # Notes
1395 | ///
1396 | /// If underlying services support delete in batch, we will use batch
1397 | /// delete instead.
1398 | ///
1399 | /// # Examples
1400 | ///
1401 | /// ```
1402 | /// # use anyhow::Result;
1403 | /// # use futures::io;
1404 | /// # use opendal_core::Operator;
1405 | /// #
1406 | /// # async fn test(op: Operator) -> Result<()> {
1407 | /// op.remove_all("path/to/dir").await?;
1408 | /// # Ok(())
1409 | /// # }
1410 | /// ```
1411 | #[deprecated(
1412 | since = "0.55.0",
1413 | note = "Use `delete_with` with `recursive(true)` instead"
1414 | )]
1415 | pub async fn remove_all(&self, path: &str) -> Result<()> {
1416 | self.delete_with(path).recursive(true).await
1417 | }
1418 |
1419 | /// List entries whose paths start with the given prefix `path`.
1420 | ///
1421 | /// # Semantics
1422 | ///
1423 | /// - Listing is **prefix-based**. It does not require the parent directory to exist.
1424 | /// - If `path` itself exists (file or dir), it will be returned as an entry in addition to any prefixed children.
1425 | /// - If `path` is absent but deeper objects exist (e.g. `path/child`), the list succeeds and returns those prefixed entries instead of an error.
1426 | ///
1427 | /// ## Streaming List
1428 | ///
1429 | /// This function materializes the entire list into memory. For large listings, prefer [`Operator::lister`] to stream entries.
1430 | ///
1431 | /// # Examples
1432 | ///
1433 | /// This example will list all entries under the dir `path/to/dir/`.
1434 | ///
1435 | /// ```
1436 | /// # use anyhow::Result;
1437 | /// use opendal_core::EntryMode;
1438 | /// use opendal_core::Operator;
1439 | /// # async fn test(op: Operator) -> Result<()> {
1440 | /// let mut entries = op.list("path/to/dir/").await?;
1441 | /// for entry in entries {
1442 | /// match entry.metadata().mode() {
1443 | /// EntryMode::FILE => {
1444 | /// println!("Handling file")
1445 | /// }
1446 | /// EntryMode::DIR => {
1447 | /// println!("Handling dir {}", entry.path())
1448 | /// }
1449 | /// EntryMode::Unknown => continue,
1450 | /// }
1451 | /// }
1452 | /// # Ok(())
1453 | /// # }
1454 | /// ```
1455 | pub async fn list(&self, path: &str) -> Result<Vec<Entry>> {
1456 | self.list_with(path).await
1457 | }
1458 |
1459 | /// List entries whose paths start with the given prefix `path` with additional options.
1460 | ///
1461 | /// # Semantics
1462 | ///
1463 | /// Inherits the prefix semantics described in [`Operator::list`]: returns `path` itself if it exists and tolerates missing parents when prefixed objects exist.
1464 | ///
1465 | /// # Notes
1466 | ///
1467 | /// ## Streaming List
1468 | ///
1469 | /// This function materializes the entire list into memory. For large listings, prefer [`Operator::lister`] to stream entries.
1470 | ///
1471 | /// ## Options
1472 | ///
1473 | /// See [`options::ListOptions`] for the full set. Common knobs:
1474 | /// - Traversal: `recursive` (default `false`) toggles depth-first listing under the prefix.
1475 | /// - Pagination: `limit` and `start_after` tune page size and resume positions (backend dependent).
1476 | /// - Versioning: `versions` / `deleted` ask versioned backends to return extra entries.
1477 | ///
1478 | /// # Examples
1479 | ///
1480 | /// This example will list all entries recursively under the prefix `path/to/prefix`.
1481 | ///
1482 | /// ```
1483 | /// # use anyhow::Result;
1484 | /// use opendal_core::EntryMode;
1485 | /// use opendal_core::Operator;
1486 | /// # async fn test(op: Operator) -> Result<()> {
1487 | /// let mut entries = op.list_with("path/to/prefix").recursive(true).await?;
1488 | /// for entry in entries {
1489 | /// match entry.metadata().mode() {
1490 | /// EntryMode::FILE => {
1491 | /// println!("Handling file")
1492 | /// }
1493 | /// EntryMode::DIR => {
1494 | /// println!("Handling dir like start a new list via meta.path()")
1495 | /// }
1496 | /// EntryMode::Unknown => continue,
1497 | /// }
1498 | /// }
1499 | /// # Ok(())
1500 | /// # }
1501 | /// ```
1502 | pub fn list_with(&self, path: &str) -> FutureList<impl Future<Output = Result<Vec<Entry>>>> {
1503 | let path = normalize_path(path);
1504 |
1505 | OperatorFuture::new(
1506 | self.inner().clone(),
1507 | path,
1508 | options::ListOptions::default(),
1509 | Self::list_inner,
1510 | )
1511 | }
1512 |
1513 | /// List entries whose paths start with the given prefix `path` using explicit options.
1514 | ///
1515 | /// # Semantics
1516 | ///
1517 | /// Same prefix behavior as [`Operator::list`]: returns `path` itself if present and tolerates missing parents when prefixed objects exist.
1518 | ///
1519 | /// # Options
1520 | ///
1521 | /// Accepts [`options::ListOptions`] (see field docs for meaning).
1522 | ///
1523 | /// ## Streaming List
1524 | ///
1525 | /// Materializes the entire list; use [`Operator::lister`] to stream large result sets.
1526 | ///
1527 | /// # Examples
1528 | ///
1529 | /// This example will list all entries recursively under the prefix `path/to/prefix`.
1530 | ///
1531 | /// ```
1532 | /// # use anyhow::Result;
1533 | /// use opendal_core::options;
1534 | /// use opendal_core::EntryMode;
1535 | /// use opendal_core::Operator;
1536 | /// # async fn test(op: Operator) -> Result<()> {
1537 | /// let mut entries = op
1538 | /// .list_options("path/to/prefix", options::ListOptions {
1539 | /// recursive: true,
1540 | /// ..Default::default()
1541 | /// })
1542 | /// .await?;
1543 | /// for entry in entries {
1544 | /// match entry.metadata().mode() {
1545 | /// EntryMode::FILE => {
1546 | /// println!("Handling file")
1547 | /// }
1548 | /// EntryMode::DIR => {
1549 | /// println!("Handling dir like start a new list via meta.path()")
1550 | /// }
1551 | /// EntryMode::Unknown => continue,
1552 | /// }
1553 | /// }
1554 | /// # Ok(())
1555 | /// # }
1556 | /// ```
1557 | pub async fn list_options(&self, path: &str, opts: options::ListOptions) -> Result<Vec<Entry>> {
1558 | let path = normalize_path(path);
1559 | Self::list_inner(self.inner().clone(), path, opts).await
1560 | }
1561 |
1562 | #[inline]
1563 | async fn list_inner(
1564 | acc: Accessor,
1565 | path: String,
1566 | opts: options::ListOptions,
1567 | ) -> Result<Vec<Entry>> {
1568 | let args = opts.into();
1569 | let lister = Lister::create(acc, &path, args).await?;
1570 | lister.try_collect().await
1571 | }
1572 |
1573 | /// Create a streaming lister for entries whose paths start with the given prefix `path`.
1574 | ///
1575 | /// # Semantics
1576 | ///
1577 | /// Shares the same prefix semantics as [`Operator::list`]: the parent directory is not required to exist; the entry for `path` is yielded if present; missing parents with deeper objects are accepted.
1578 | ///
1579 | /// # Options
1580 | ///
1581 | /// Takes the same [`options::ListOptions`] as [`list_with`](Operator::list_with): traversal (`recursive`), pagination (`limit`, `start_after`), and versioning (`versions`, `deleted`).
1582 | ///
1583 | /// # Examples
1584 | ///
1585 | /// ```
1586 | /// # use anyhow::Result;
1587 | /// # use futures::io;
1588 | /// use futures::TryStreamExt;
1589 | /// use opendal_core::EntryMode;
1590 | /// use opendal_core::Operator;
1591 | /// # async fn test(op: Operator) -> Result<()> {
1592 | /// let mut ds = op.lister("path/to/dir/").await?;
1593 | /// while let Some(mut de) = ds.try_next().await? {
1594 | /// match de.metadata().mode() {
1595 | /// EntryMode::FILE => {
1596 | /// println!("Handling file")
1597 | /// }
1598 | /// EntryMode::DIR => {
1599 | /// println!("Handling dir like start a new list via meta.path()")
1600 | /// }
1601 | /// EntryMode::Unknown => continue,
1602 | /// }
1603 | /// }
1604 | /// # Ok(())
1605 | /// # }
1606 | /// ```
1607 | pub async fn lister(&self, path: &str) -> Result<Lister> {
1608 | self.lister_with(path).await
1609 | }
1610 |
1611 | /// Create a new lister to list entries that start with the given prefix `path` using additional options.
1612 | ///
1613 | /// # Options
1614 | ///
1615 | /// Same as [`lister_with`](Operator::lister_with); see [`options::ListOptions`] for traversal, pagination, and versioning knobs.
1616 | ///
1617 | /// # Examples
1618 | ///
1619 | /// ## List all files recursively
1620 | ///
1621 | /// ```
1622 | /// # use anyhow::Result;
1623 | /// use futures::TryStreamExt;
1624 | /// use opendal_core::EntryMode;
1625 | /// use opendal_core::Operator;
1626 | /// # async fn test(op: Operator) -> Result<()> {
1627 | /// let mut lister = op.lister_with("path/to/dir/").recursive(true).await?;
1628 | /// while let Some(mut entry) = lister.try_next().await? {
1629 | /// match entry.metadata().mode() {
1630 | /// EntryMode::FILE => {
1631 | /// println!("Handling file {}", entry.path())
1632 | /// }
1633 | /// EntryMode::DIR => {
1634 | /// println!("Handling dir {}", entry.path())
1635 | /// }
1636 | /// EntryMode::Unknown => continue,
1637 | /// }
1638 | /// }
1639 | /// # Ok(())
1640 | /// # }
1641 | /// ```
1642 | pub fn lister_with(&self, path: &str) -> FutureLister<impl Future<Output = Result<Lister>>> {
1643 | let path = normalize_path(path);
1644 |
1645 | OperatorFuture::new(
1646 | self.inner().clone(),
1647 | path,
1648 | options::ListOptions::default(),
1649 | Self::lister_inner,
1650 | )
1651 | }
1652 |
1653 | /// Create a new lister to list entries that start with the given prefix `path` using additional options.
1654 | ///
1655 | /// # Semantics
1656 | ///
1657 | /// Inherits the prefix behavior of [`Operator::lister_with`].
1658 | ///
1659 | /// # Options
1660 | ///
1661 | /// Uses [`options::ListOptions`] to control traversal, pagination, and versioning.
1662 | ///
1663 | /// # Examples
1664 | ///
1665 | /// ## List all files recursively
1666 | ///
1667 | /// ```
1668 | /// # use anyhow::Result;
1669 | /// use futures::TryStreamExt;
1670 | /// use opendal_core::options;
1671 | /// use opendal_core::EntryMode;
1672 | /// use opendal_core::Operator;
1673 | /// # async fn test(op: Operator) -> Result<()> {
1674 | /// let mut lister = op
1675 | /// .lister_options("path/to/dir/", options::ListOptions {
1676 | /// recursive: true,
1677 | /// ..Default::default()
1678 | /// })
1679 | /// .await?;
1680 | /// while let Some(mut entry) = lister.try_next().await? {
1681 | /// match entry.metadata().mode() {
1682 | /// EntryMode::FILE => {
1683 | /// println!("Handling file {}", entry.path())
1684 | /// }
1685 | /// EntryMode::DIR => {
1686 | /// println!("Handling dir {}", entry.path())
1687 | /// }
1688 | /// EntryMode::Unknown => continue,
1689 | /// }
1690 | /// }
1691 | /// # Ok(())
1692 | /// # }
1693 | /// ```
1694 | pub async fn lister_options(&self, path: &str, opts: options::ListOptions) -> Result<Lister> {
1695 | let path = normalize_path(path);
1696 | Self::lister_inner(self.inner().clone(), path, opts).await
1697 | }
1698 |
1699 | #[inline]
1700 | async fn lister_inner(
1701 | acc: Accessor,
1702 | path: String,
1703 | opts: options::ListOptions,
1704 | ) -> Result<Lister> {
1705 | let args = opts.into();
1706 | let lister = Lister::create(acc, &path, args).await?;
1707 | Ok(lister)
1708 | }
1709 | }
1710 |
1711 | /// Operator presign API.
1712 | impl Operator {
1713 | /// Presign an operation for stat(head).
1714 | ///
1715 | /// # Example
1716 | ///
1717 | /// ```
1718 | /// use anyhow::Result;
1719 | /// use futures::io;
1720 | /// use opendal_core::Operator;
1721 | /// use std::time::Duration;
1722 | ///
1723 | /// async fn test(op: Operator) -> Result<()> {
1724 | /// let signed_req = op.presign_stat("test",Duration::from_secs(3600)).await?;
1725 | /// let req = http::Request::builder()
1726 | /// .method(signed_req.method())
1727 | /// .uri(signed_req.uri())
1728 | /// .body(())?;
1729 | ///
1730 | /// # Ok(())
1731 | /// # }
1732 | /// ```
1733 | pub async fn presign_stat(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
1734 | let path = normalize_path(path);
1735 |
1736 | let op = OpPresign::new(OpStat::new(), expire);
1737 |
1738 | let rp = self.inner().presign(&path, op).await?;
1739 | Ok(rp.into_presigned_request())
1740 | }
1741 |
1742 | /// Presign an operation for stat(head).
1743 | ///
1744 | /// # Example
1745 | ///
1746 | /// ```
1747 | /// use anyhow::Result;
1748 | /// use futures::io;
1749 | /// use opendal_core::Operator;
1750 | /// use std::time::Duration;
1751 | ///
1752 | /// async fn test(op: Operator) -> Result<()> {
1753 | /// let signed_req = op.presign_stat_with("test",Duration::from_secs(3600)).override_content_disposition("attachment; filename=\"othertext.txt\"").await?;
1754 | /// # Ok(())
1755 | /// # }
1756 | /// ```
1757 | pub fn presign_stat_with(
1758 | &self,
1759 | path: &str,
1760 | expire: Duration,
1761 | ) -> FuturePresignStat<impl Future<Output = Result<PresignedRequest>>> {
1762 | let path = normalize_path(path);
1763 |
1764 | OperatorFuture::new(
1765 | self.inner().clone(),
1766 | path,
1767 | (options::StatOptions::default(), expire),
1768 | Self::presign_stat_inner,
1769 | )
1770 | }
1771 |
1772 | /// Presign an operation for stat(head) with additional options.
1773 | ///
1774 | /// # Options
1775 | ///
1776 | /// Visit [`options::StatOptions`] for all available options.
1777 | ///
1778 | /// # Example
1779 | ///
1780 | /// ```
1781 | /// use anyhow::Result;
1782 | /// use opendal_core::Operator;
1783 | /// use opendal_core::options;
1784 | /// use std::time::Duration;
1785 | ///
1786 | /// async fn test(op: Operator) -> Result<()> {
1787 | /// let signed_req = op.presign_stat_options(
1788 | /// "test",
1789 | /// Duration::from_secs(3600),
1790 | /// options::StatOptions {
1791 | /// if_match: Some("<etag>".to_string()),
1792 | /// ..Default::default()
1793 | /// }
1794 | /// ).await?;
1795 | /// let req = http::Request::builder()
1796 | /// .method(signed_req.method())
1797 | /// .uri(signed_req.uri())
1798 | /// .body(())?;
1799 | ///
1800 | /// # Ok(())
1801 | /// # }
1802 | /// ```
1803 | pub async fn presign_stat_options(
1804 | &self,
1805 | path: &str,
1806 | expire: Duration,
1807 | opts: options::StatOptions,
1808 | ) -> Result<PresignedRequest> {
1809 | let path = normalize_path(path);
1810 | Self::presign_stat_inner(self.inner().clone(), path, (opts, expire)).await
1811 | }
1812 |
1813 | #[inline]
1814 | async fn presign_stat_inner(
1815 | acc: Accessor,
1816 | path: String,
1817 | (opts, expire): (options::StatOptions, Duration),
1818 | ) -> Result<PresignedRequest> {
1819 | let op = OpPresign::new(OpStat::from(opts), expire);
1820 | let rp = acc.presign(&path, op).await?;
1821 | Ok(rp.into_presigned_request())
1822 | }
1823 |
1824 | /// Presign an operation for read.
1825 | ///
1826 | /// # Notes
1827 | ///
1828 | /// ## Extra Options
1829 | ///
1830 | /// `presign_read` is a wrapper of [`Self::presign_read_with`] without any options. To use
1831 | /// extra options like `override_content_disposition`, please use [`Self::presign_read_with`] or
1832 | /// [`Self::presign_read_options] instead.
1833 | ///
1834 | /// # Example
1835 | ///
1836 | /// ```
1837 | /// use anyhow::Result;
1838 | /// use futures::io;
1839 | /// use opendal_core::Operator;
1840 | /// use std::time::Duration;
1841 | ///
1842 | /// async fn test(op: Operator) -> Result<()> {
1843 | /// let signed_req = op.presign_read("test.txt", Duration::from_secs(3600)).await?;
1844 | /// # Ok(())
1845 | /// # }
1846 | /// ```
1847 | ///
1848 | /// - `signed_req.method()`: `GET`
1849 | /// - `signed_req.uri()`: `https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>`
1850 | /// - `signed_req.headers()`: `{ "host": "s3.amazonaws.com" }`
1851 | ///
1852 | /// We can download this file via `curl` or other tools without credentials:
1853 | ///
1854 | /// ```shell
1855 | /// curl "https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>" -O /tmp/test.txt
1856 | /// ```
1857 | pub async fn presign_read(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
1858 | let path = normalize_path(path);
1859 |
1860 | let op = OpPresign::new(OpRead::new(), expire);
1861 |
1862 | let rp = self.inner().presign(&path, op).await?;
1863 | Ok(rp.into_presigned_request())
1864 | }
1865 |
1866 | /// Presign an operation for read with extra options.
1867 | ///
1868 | /// # Options
1869 | ///
1870 | /// Visit [`options::ReadOptions`] for all available options.
1871 | ///
1872 | /// # Example
1873 | ///
1874 | /// ```
1875 | /// use std::time::Duration;
1876 | ///
1877 | /// use anyhow::Result;
1878 | /// use futures::io;
1879 | /// use opendal_core::Operator;
1880 | ///
1881 | /// async fn test(op: Operator) -> Result<()> {
1882 | /// let signed_req = op
1883 | /// .presign_read_with("test.txt", Duration::from_secs(3600))
1884 | /// .override_content_type("text/plain")
1885 | /// .await?;
1886 | /// Ok(())
1887 | /// }
1888 | /// ```
1889 | pub fn presign_read_with(
1890 | &self,
1891 | path: &str,
1892 | expire: Duration,
1893 | ) -> FuturePresignRead<impl Future<Output = Result<PresignedRequest>>> {
1894 | let path = normalize_path(path);
1895 |
1896 | OperatorFuture::new(
1897 | self.inner().clone(),
1898 | path,
1899 | (options::ReadOptions::default(), expire),
1900 | Self::presign_read_inner,
1901 | )
1902 | }
1903 |
1904 | /// Presign an operation for read with additional options.
1905 | ///
1906 | /// # Options
1907 | ///
1908 | /// Visit [`options::ReadOptions`] for all available options.
1909 | ///
1910 | /// # Example
1911 | ///
1912 | /// ```
1913 | /// use anyhow::Result;
1914 | /// use opendal_core::Operator;
1915 | /// use opendal_core::options;
1916 | /// use std::time::Duration;
1917 | ///
1918 | /// async fn test(op: Operator) -> Result<()> {
1919 | /// let signed_req = op.presign_read_options(
1920 | /// "file",
1921 | /// Duration::from_secs(3600),
1922 | /// options::ReadOptions {
1923 | /// override_content_disposition: Some("attachment; filename=\"othertext.txt\"".to_string()),
1924 | /// ..Default::default()
1925 | /// }
1926 | /// ).await?;
1927 | /// let req = http::Request::builder()
1928 | /// .method(signed_req.method())
1929 | /// .uri(signed_req.uri())
1930 | /// .body(())?;
1931 | ///
1932 | /// # Ok(())
1933 | /// # }
1934 | /// ```
1935 | pub async fn presign_read_options(
1936 | &self,
1937 | path: &str,
1938 | expire: Duration,
1939 | opts: options::ReadOptions,
1940 | ) -> Result<PresignedRequest> {
1941 | let path = normalize_path(path);
1942 | Self::presign_read_inner(self.inner().clone(), path, (opts, expire)).await
1943 | }
1944 |
1945 | #[inline]
1946 | async fn presign_read_inner(
1947 | acc: Accessor,
1948 | path: String,
1949 | (opts, expire): (options::ReadOptions, Duration),
1950 | ) -> Result<PresignedRequest> {
1951 | let (op_read, _) = opts.into();
1952 | let op = OpPresign::new(op_read, expire);
1953 | let rp = acc.presign(&path, op).await?;
1954 | Ok(rp.into_presigned_request())
1955 | }
1956 |
1957 | /// Presign an operation for write.
1958 | ///
1959 | /// # Notes
1960 | ///
1961 | /// ## Extra Options
1962 | ///
1963 | /// `presign_write` is a wrapper of [`Self::presign_write_with`] without any options. To use
1964 | /// extra options like `content_type`, please use [`Self::presign_write_with`] or
1965 | /// [`Self::presign_write_options`] instead.
1966 | ///
1967 | /// # Example
1968 | ///
1969 | /// ```
1970 | /// use std::time::Duration;
1971 | ///
1972 | /// use anyhow::Result;
1973 | /// use opendal_core::Operator;
1974 | ///
1975 | /// async fn test(op: Operator) -> Result<()> {
1976 | /// let signed_req = op
1977 | /// .presign_write("test.txt", Duration::from_secs(3600))
1978 | /// .await?;
1979 | /// Ok(())
1980 | /// }
1981 | /// ```
1982 | ///
1983 | /// - `signed_req.method()`: `PUT`
1984 | /// - `signed_req.uri()`: `https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>`
1985 | /// - `signed_req.headers()`: `{ "host": "s3.amazonaws.com" }`
1986 | ///
1987 | /// We can upload file as this file via `curl` or other tools without credential:
1988 | ///
1989 | /// ```shell
1990 | /// curl -X PUT "https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>" -d "Hello, World!"
1991 | /// ```
1992 | pub async fn presign_write(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
1993 | self.presign_write_with(path, expire).await
1994 | }
1995 |
1996 | /// Presign an operation for write with extra options.
1997 | ///
1998 | /// # Options
1999 | ///
2000 | /// Visit [`options::WriteOptions`] for all available options.
2001 | ///
2002 | /// # Example
2003 | ///
2004 | /// ```
2005 | /// use std::time::Duration;
2006 | ///
2007 | /// use anyhow::Result;
2008 | /// use opendal_core::Operator;
2009 | ///
2010 | /// async fn test(op: Operator) -> Result<()> {
2011 | /// let signed_req = op
2012 | /// .presign_write_with("test", Duration::from_secs(3600))
2013 | /// .cache_control("no-store")
2014 | /// .await?;
2015 | /// let req = http::Request::builder()
2016 | /// .method(signed_req.method())
2017 | /// .uri(signed_req.uri())
2018 | /// .body(())?;
2019 | ///
2020 | /// Ok(())
2021 | /// }
2022 | /// ```
2023 | pub fn presign_write_with(
2024 | &self,
2025 | path: &str,
2026 | expire: Duration,
2027 | ) -> FuturePresignWrite<impl Future<Output = Result<PresignedRequest>>> {
2028 | let path = normalize_path(path);
2029 |
2030 | OperatorFuture::new(
2031 | self.inner().clone(),
2032 | path,
2033 | (options::WriteOptions::default(), expire),
2034 | Self::presign_write_inner,
2035 | )
2036 | }
2037 |
2038 | /// Presign an operation for write with additional options.
2039 | ///
2040 | /// # Options
2041 | ///
2042 | /// Check [`options::WriteOptions`] for all available options.
2043 | ///
2044 | /// # Example
2045 | ///
2046 | /// ```
2047 | /// use anyhow::Result;
2048 | /// use opendal_core::Operator;
2049 | /// use opendal_core::options;
2050 | /// use std::time::Duration;
2051 | ///
2052 | /// async fn test(op: Operator) -> Result<()> {
2053 | /// let signed_req = op.presign_write_options(
2054 | /// "file",
2055 | /// Duration::from_secs(3600),
2056 | /// options::WriteOptions {
2057 | /// content_type: Some("application/json".to_string()),
2058 | /// cache_control: Some("max-age=3600".to_string()),
2059 | /// if_not_exists: true,
2060 | /// ..Default::default()
2061 | /// }
2062 | /// ).await?;
2063 | /// let req = http::Request::builder()
2064 | /// .method(signed_req.method())
2065 | /// .uri(signed_req.uri())
2066 | /// .body(())?;
2067 | ///
2068 | /// # Ok(())
2069 | /// # }
2070 | /// ```
2071 | pub async fn presign_write_options(
2072 | &self,
2073 | path: &str,
2074 | expire: Duration,
2075 | opts: options::WriteOptions,
2076 | ) -> Result<PresignedRequest> {
2077 | let path = normalize_path(path);
2078 | Self::presign_write_inner(self.inner().clone(), path, (opts, expire)).await
2079 | }
2080 |
2081 | #[inline]
2082 | async fn presign_write_inner(
2083 | acc: Accessor,
2084 | path: String,
2085 | (opts, expire): (options::WriteOptions, Duration),
2086 | ) -> Result<PresignedRequest> {
2087 | let (op_write, _) = opts.into();
2088 | let op = OpPresign::new(op_write, expire);
2089 | let rp = acc.presign(&path, op).await?;
2090 | Ok(rp.into_presigned_request())
2091 | }
2092 |
2093 | /// Presign an operation for delete.
2094 | ///
2095 | /// # Notes
2096 | ///
2097 | /// ## Extra Options
2098 | ///
2099 | /// `presign_delete` is a wrapper of [`Self::presign_delete_with`] without any options.
2100 | ///
2101 | /// # Example
2102 | ///
2103 | /// ```
2104 | /// use std::time::Duration;
2105 | ///
2106 | /// use anyhow::Result;
2107 | /// use opendal_core::Operator;
2108 | ///
2109 | /// async fn test(op: Operator) -> Result<()> {
2110 | /// let signed_req = op
2111 | /// .presign_delete("test.txt", Duration::from_secs(3600))
2112 | /// .await?;
2113 | /// Ok(())
2114 | /// }
2115 | /// ```
2116 | ///
2117 | /// - `signed_req.method()`: `DELETE`
2118 | /// - `signed_req.uri()`: `https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>`
2119 | /// - `signed_req.headers()`: `{ "host": "s3.amazonaws.com" }`
2120 | ///
2121 | /// We can delete file as this file via `curl` or other tools without credential:
2122 | ///
2123 | /// ```shell
2124 | /// curl -X DELETE "https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>"
2125 | /// ```
2126 | pub async fn presign_delete(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
2127 | self.presign_delete_with(path, expire).await
2128 | }
2129 |
2130 | /// Presign an operation for delete without extra options.
2131 | pub fn presign_delete_with(
2132 | &self,
2133 | path: &str,
2134 | expire: Duration,
2135 | ) -> FuturePresignDelete<impl Future<Output = Result<PresignedRequest>>> {
2136 | let path = normalize_path(path);
2137 |
2138 | OperatorFuture::new(
2139 | self.inner().clone(),
2140 | path,
2141 | (options::DeleteOptions::default(), expire),
2142 | Self::presign_delete_inner,
2143 | )
2144 | }
2145 |
2146 | /// Presign an operation for delete with additional options.
2147 | ///
2148 | /// # Options
2149 | ///
2150 | /// Visit [`options::DeleteOptions`] for all available options.
2151 | ///
2152 | /// # Example
2153 | ///
2154 | /// ```
2155 | /// use anyhow::Result;
2156 | /// use opendal_core::Operator;
2157 | /// use opendal_core::options;
2158 | /// use std::time::Duration;
2159 | ///
2160 | /// async fn test(op: Operator) -> Result<()> {
2161 | /// let signed_req = op.presign_delete_options(
2162 | /// "path/to/file",
2163 | /// Duration::from_secs(3600),
2164 | /// options::DeleteOptions {
2165 | /// ..Default::default()
2166 | /// }
2167 | /// ).await?;
2168 | /// let req = http::Request::builder()
2169 | /// .method(signed_req.method())
2170 | /// .uri(signed_req.uri())
2171 | /// .body(())?;
2172 | ///
2173 | /// # Ok(())
2174 | /// # }
2175 | /// ```
2176 | pub async fn presign_delete_options(
2177 | &self,
2178 | path: &str,
2179 | expire: Duration,
2180 | opts: options::DeleteOptions,
2181 | ) -> Result<PresignedRequest> {
2182 | let path = normalize_path(path);
2183 | Self::presign_delete_inner(self.inner().clone(), path, (opts, expire)).await
2184 | }
2185 |
2186 | #[inline]
2187 | async fn presign_delete_inner(
2188 | acc: Accessor,
2189 | path: String,
2190 | (opts, expire): (options::DeleteOptions, Duration),
2191 | ) -> Result<PresignedRequest> {
2192 | let op = OpPresign::new(OpDelete::from(opts), expire);
2193 | let rp = acc.presign(&path, op).await?;
2194 | Ok(rp.into_presigned_request())
2195 | }
2196 | }
2197 |
```