opendal 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +24 -24
- data/Cargo.toml +1 -1
- data/DEPENDENCIES.rust.tsv +24 -24
- data/core/CHANGELOG.md +9 -1
- data/core/Cargo.lock +110 -110
- data/core/Cargo.toml +94 -94
- data/core/DEPENDENCIES.rust.tsv +12 -12
- data/core/benches/types/buffer.rs +61 -0
- data/core/benches/vs_fs/Cargo.toml +1 -1
- data/core/benches/vs_s3/Cargo.toml +1 -1
- data/core/core/CHANGELOG.md +9 -1
- data/core/core/src/raw/futures_util.rs +5 -0
- data/core/core/src/raw/oio/copy/block_copy.rs +1 -1
- data/core/core/src/raw/oio/copy/multipart_copy.rs +1 -1
- data/core/core/src/raw/oio/write/block_write.rs +1 -1
- data/core/core/src/raw/oio/write/multipart_write.rs +1 -1
- data/core/core/src/raw/oio/write/position_write.rs +41 -1
- data/core/core/src/types/buffer.rs +131 -0
- data/core/core/src/types/delete/deleter.rs +13 -0
- data/core/core/src/types/mod.rs +1 -0
- data/core/http-transports/reqwest/Cargo.toml +1 -1
- data/core/layers/async-backtrace/Cargo.toml +2 -2
- data/core/layers/await-tree/Cargo.toml +2 -2
- data/core/layers/capability-check/Cargo.toml +2 -2
- data/core/layers/chaos/Cargo.toml +2 -2
- data/core/layers/concurrent-limit/Cargo.toml +2 -2
- data/core/layers/dtrace/Cargo.toml +2 -2
- data/core/layers/fastmetrics/Cargo.toml +3 -3
- data/core/layers/fastrace/Cargo.toml +2 -2
- data/core/layers/foyer/Cargo.toml +2 -2
- data/core/layers/hotpath/Cargo.toml +2 -2
- data/core/layers/immutable-index/Cargo.toml +2 -2
- data/core/layers/logging/Cargo.toml +2 -2
- data/core/layers/metrics/Cargo.toml +3 -3
- data/core/layers/mime-guess/Cargo.toml +2 -2
- data/core/layers/observe-metrics-common/Cargo.toml +1 -1
- data/core/layers/otelmetrics/Cargo.toml +3 -3
- data/core/layers/oteltrace/Cargo.toml +2 -2
- data/core/layers/prometheus/Cargo.toml +3 -3
- data/core/layers/prometheus-client/Cargo.toml +3 -3
- data/core/layers/retry/Cargo.toml +4 -4
- data/core/layers/route/Cargo.toml +3 -3
- data/core/layers/tail-cut/Cargo.toml +2 -2
- data/core/layers/throttle/Cargo.toml +2 -2
- data/core/layers/timeout/Cargo.toml +3 -3
- data/core/layers/tracing/Cargo.toml +2 -2
- data/core/services/aliyun-drive/Cargo.toml +1 -1
- data/core/services/alluxio/Cargo.toml +1 -1
- data/core/services/azblob/Cargo.toml +2 -2
- data/core/services/azdls/Cargo.toml +2 -2
- data/core/services/azfile/Cargo.toml +2 -2
- data/core/services/azure-common/Cargo.toml +1 -1
- data/core/services/b2/Cargo.toml +1 -1
- data/core/services/cacache/Cargo.toml +1 -1
- data/core/services/cloudflare-kv/Cargo.toml +1 -1
- data/core/services/compfs/Cargo.toml +1 -1
- data/core/services/cos/Cargo.toml +1 -1
- data/core/services/d1/Cargo.toml +1 -1
- data/core/services/dashmap/Cargo.toml +1 -1
- data/core/services/dbfs/Cargo.toml +1 -1
- data/core/services/dropbox/Cargo.toml +1 -1
- data/core/services/etcd/Cargo.toml +1 -1
- data/core/services/foundationdb/Cargo.toml +1 -1
- data/core/services/foyer/Cargo.toml +3 -3
- data/core/services/fs/Cargo.toml +1 -1
- data/core/services/ftp/Cargo.toml +1 -1
- data/core/services/gcs/Cargo.toml +1 -1
- data/core/services/gcs-grpc/Cargo.toml +1 -1
- data/core/services/gdrive/Cargo.toml +1 -1
- data/core/services/ghac/Cargo.toml +2 -2
- data/core/services/github/Cargo.toml +1 -1
- data/core/services/goosefs/Cargo.toml +2 -2
- data/core/services/goosefs/src/backend.rs +43 -16
- data/core/services/goosefs/src/config.rs +4 -1
- data/core/services/goosefs/src/docs.md +1 -1
- data/core/services/gridfs/Cargo.toml +1 -1
- data/core/services/hdfs/Cargo.toml +2 -2
- data/core/services/hdfs/src/backend.rs +15 -7
- data/core/services/hdfs/src/core.rs +55 -16
- data/core/services/hdfs/src/docs.md +9 -1
- data/core/services/hdfs-native/Cargo.toml +1 -1
- data/core/services/hf/Cargo.toml +5 -2
- data/core/services/hf/src/backend.rs +8 -3
- data/core/services/hf/src/config.rs +4 -2
- data/core/services/hf/src/docs.md +6 -2
- data/core/services/hf/src/reader.rs +163 -68
- data/core/services/http/Cargo.toml +3 -3
- data/core/services/ipfs/Cargo.toml +1 -1
- data/core/services/ipmfs/Cargo.toml +1 -1
- data/core/services/koofr/Cargo.toml +1 -1
- data/core/services/lakefs/Cargo.toml +1 -1
- data/core/services/memcached/Cargo.toml +1 -1
- data/core/services/mini_moka/Cargo.toml +1 -1
- data/core/services/moka/Cargo.toml +1 -1
- data/core/services/mongodb/Cargo.toml +1 -1
- data/core/services/monoiofs/Cargo.toml +1 -1
- data/core/services/mysql/Cargo.toml +1 -1
- data/core/services/obs/Cargo.toml +1 -1
- data/core/services/onedrive/Cargo.toml +1 -1
- data/core/services/opfs/Cargo.toml +1 -1
- data/core/services/oss/Cargo.toml +1 -1
- data/core/services/oss/src/backend.rs +143 -2
- data/core/services/oss/src/docs.md +12 -0
- data/core/services/pcloud/Cargo.toml +1 -1
- data/core/services/persy/Cargo.toml +1 -1
- data/core/services/postgresql/Cargo.toml +1 -1
- data/core/services/redb/Cargo.toml +1 -1
- data/core/services/redis/Cargo.toml +1 -1
- data/core/services/rocksdb/Cargo.toml +1 -1
- data/core/services/s3/Cargo.toml +1 -1
- data/core/services/seafile/Cargo.toml +1 -1
- data/core/services/sftp/Cargo.toml +1 -1
- data/core/services/sled/Cargo.toml +1 -1
- data/core/services/sqlite/Cargo.toml +1 -1
- data/core/services/surrealdb/Cargo.toml +1 -1
- data/core/services/swift/Cargo.toml +1 -1
- data/core/services/tikv/Cargo.toml +1 -1
- data/core/services/tos/Cargo.toml +1 -1
- data/core/services/upyun/Cargo.toml +1 -1
- data/core/services/vercel-artifacts/Cargo.toml +1 -1
- data/core/services/vercel-blob/Cargo.toml +1 -1
- data/core/services/webdav/Cargo.toml +1 -1
- data/core/services/webdav/src/backend.rs +10 -21
- data/core/services/webdav/src/config.rs +5 -14
- data/core/services/webhdfs/Cargo.toml +1 -1
- data/core/services/yandex-disk/Cargo.toml +1 -1
- data/core/testkit/Cargo.toml +4 -4
- metadata +2 -2
|
@@ -32,7 +32,7 @@ version = { workspace = true }
|
|
|
32
32
|
all-features = true
|
|
33
33
|
|
|
34
34
|
[dependencies]
|
|
35
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
35
|
+
opendal-core = { path = "../../core", version = "0.59.4", default-features = false }
|
|
36
36
|
|
|
37
37
|
asyncband = { workspace = true, features = ["mutex", "singleflight"] }
|
|
38
38
|
bytes = { workspace = true }
|
|
@@ -36,8 +36,8 @@ bytes = { workspace = true }
|
|
|
36
36
|
ghac = { version = "0.3.0", default-features = false }
|
|
37
37
|
http = { workspace = true }
|
|
38
38
|
log = { workspace = true }
|
|
39
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
40
|
-
opendal-service-azblob = { path = "../azblob", version = "0.59.
|
|
39
|
+
opendal-core = { path = "../../core", version = "0.59.4", default-features = false }
|
|
40
|
+
opendal-service-azblob = { path = "../azblob", version = "0.59.4", default-features = false }
|
|
41
41
|
prost = { version = "0.14.3", default-features = false }
|
|
42
42
|
reqsign-azure-storage = { version = "3.1.1", default-features = false }
|
|
43
43
|
reqsign-core = { version = "3.2.0", default-features = false }
|
|
@@ -36,7 +36,7 @@ base64 = { workspace = true }
|
|
|
36
36
|
bytes = { workspace = true }
|
|
37
37
|
http = { workspace = true }
|
|
38
38
|
log = { workspace = true }
|
|
39
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
39
|
+
opendal-core = { path = "../../core", version = "0.59.4", default-features = false }
|
|
40
40
|
serde = { workspace = true, features = ["derive"] }
|
|
41
41
|
serde_json = { workspace = true }
|
|
42
42
|
|
|
@@ -45,11 +45,11 @@ asyncband = { workspace = true, features = ["rwlock"] }
|
|
|
45
45
|
bytes = { workspace = true }
|
|
46
46
|
goosefs-sdk = { version = "0.2.1", default-features = false }
|
|
47
47
|
log = { workspace = true }
|
|
48
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
48
|
+
opendal-core = { path = "../../core", version = "0.59.4", default-features = false }
|
|
49
49
|
serde = { workspace = true, features = ["derive"] }
|
|
50
50
|
|
|
51
51
|
[dev-dependencies]
|
|
52
|
-
opendal = { path = "../..", version = "0.59.
|
|
52
|
+
opendal = { path = "../..", version = "0.59.4", features = [
|
|
53
53
|
"services-goosefs",
|
|
54
54
|
] }
|
|
55
55
|
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
|
@@ -90,7 +90,9 @@ impl GoosefsBuilder {
|
|
|
90
90
|
|
|
91
91
|
/// Set default write type.
|
|
92
92
|
///
|
|
93
|
-
/// Values: `"must_cache"`, `"
|
|
93
|
+
/// Values: `"must_cache"`, `"try_cache"`, `"cache_through"`, `"through"`,
|
|
94
|
+
/// `"async_through"`. Matching is case-insensitive. `build()` fails with
|
|
95
|
+
/// [`ErrorKind::ConfigInvalid`] when the value is not one of these.
|
|
94
96
|
pub fn write_type(mut self, wt: &str) -> Self {
|
|
95
97
|
if !wt.is_empty() {
|
|
96
98
|
self.config.write_type = Some(wt.to_string());
|
|
@@ -340,22 +342,18 @@ impl Builder for GoosefsBuilder {
|
|
|
340
342
|
goosefs_config.chunk_size = chunk_size;
|
|
341
343
|
}
|
|
342
344
|
|
|
343
|
-
// Parse write_type
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
// `must_cache` and `MUST_CACHE` branches — this mirrors how the
|
|
347
|
-
// GooseFS server-side config parser (`WritePType::valueOf`) treats
|
|
348
|
-
// the value as case-insensitive.
|
|
345
|
+
// Parse write_type through the SDK so unknown values fail instead of
|
|
346
|
+
// silently becoming MUST_CACHE. `with_write_type_str` is
|
|
347
|
+
// case-insensitive, matching GooseFS `WritePType::valueOf`.
|
|
349
348
|
if let Some(ref wt) = self.config.write_type {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
"
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
goosefs_config.write_type = Some(wt_i32);
|
|
349
|
+
goosefs_config = goosefs_config.with_write_type_str(wt).map_err(|e| {
|
|
350
|
+
Error::new(
|
|
351
|
+
ErrorKind::ConfigInvalid,
|
|
352
|
+
format!("invalid write_type: {}", e),
|
|
353
|
+
)
|
|
354
|
+
.with_operation("Builder::build")
|
|
355
|
+
.with_context("service", GOOSEFS_SCHEME)
|
|
356
|
+
})?;
|
|
359
357
|
}
|
|
360
358
|
|
|
361
359
|
// Parse auth_type string → goosefs_sdk::auth::AuthType
|
|
@@ -775,6 +773,35 @@ mod tests {
|
|
|
775
773
|
);
|
|
776
774
|
}
|
|
777
775
|
|
|
776
|
+
#[test]
|
|
777
|
+
fn test_builder_unknown_write_type_fails() {
|
|
778
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
779
|
+
for wt in ["INVALID_WT", "cache_throughh"] {
|
|
780
|
+
let err = GoosefsBuilder::default()
|
|
781
|
+
.root("/data")
|
|
782
|
+
.master_addr("127.0.0.1:9200")
|
|
783
|
+
.write_type(wt)
|
|
784
|
+
.build()
|
|
785
|
+
.expect_err("build must fail on unknown write_type");
|
|
786
|
+
assert_eq!(err.kind(), ErrorKind::ConfigInvalid);
|
|
787
|
+
assert!(
|
|
788
|
+
err.to_string().contains("write_type"),
|
|
789
|
+
"unexpected error message for {wt}: {err}"
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
#[test]
|
|
795
|
+
fn test_builder_write_type_is_case_insensitive() {
|
|
796
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
797
|
+
let backend = GoosefsBuilder::default()
|
|
798
|
+
.root("/data")
|
|
799
|
+
.master_addr("127.0.0.1:9200")
|
|
800
|
+
.write_type("CACHE_THROUGH")
|
|
801
|
+
.build();
|
|
802
|
+
assert!(backend.is_ok());
|
|
803
|
+
}
|
|
804
|
+
|
|
778
805
|
#[test]
|
|
779
806
|
fn test_capability_rename_with_if_not_exists() {
|
|
780
807
|
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
@@ -69,7 +69,10 @@ pub struct GoosefsConfig {
|
|
|
69
69
|
|
|
70
70
|
/// Default write type for new files.
|
|
71
71
|
///
|
|
72
|
-
/// Supported values: `"must_cache"`, `"
|
|
72
|
+
/// Supported values: `"must_cache"`, `"try_cache"`, `"cache_through"`,
|
|
73
|
+
/// `"through"`, `"async_through"`. Matching is case-insensitive.
|
|
74
|
+
/// `build()` fails with `ConfigInvalid` when the value is not one of
|
|
75
|
+
/// these.
|
|
73
76
|
/// Default: `"must_cache"`.
|
|
74
77
|
pub write_type: Option<String>,
|
|
75
78
|
|
|
@@ -28,7 +28,7 @@ Features:
|
|
|
28
28
|
- **HA support**: Comma-separated master addresses for automatic Primary Master discovery.
|
|
29
29
|
- **Block-level I/O**: Data reads/writes go through block-level gRPC bidirectional streaming.
|
|
30
30
|
- **Consistent hash routing**: Worker selection uses consistent hashing on block IDs.
|
|
31
|
-
- **All WriteTypes**: Supports MUST_CACHE, CACHE_THROUGH, THROUGH, and ASYNC_THROUGH.
|
|
31
|
+
- **All WriteTypes**: Supports MUST_CACHE, TRY_CACHE, CACHE_THROUGH, THROUGH, and ASYNC_THROUGH. Unknown `write_type` values fail at `build()` with `ConfigInvalid`.
|
|
32
32
|
- **Conditional Create**: `write_with_if_not_exists` publishes via Master no-replace
|
|
33
33
|
rename (`rename_with_if_not_exists`); destination is never deleted on the Create path.
|
|
34
34
|
- **Rename parent directories**: `rename` creates a missing destination parent only
|
|
@@ -35,7 +35,7 @@ all-features = true
|
|
|
35
35
|
asyncband = { workspace = true, features = ["once-cell"] }
|
|
36
36
|
futures = { workspace = true }
|
|
37
37
|
mongodb = "3.3.0"
|
|
38
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
38
|
+
opendal-core = { path = "../../core", version = "0.59.4", default-features = false }
|
|
39
39
|
serde = { workspace = true, features = ["derive"] }
|
|
40
40
|
|
|
41
41
|
[dev-dependencies]
|
|
@@ -32,11 +32,11 @@ version = { workspace = true }
|
|
|
32
32
|
all-features = true
|
|
33
33
|
|
|
34
34
|
[dependencies]
|
|
35
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
35
|
+
opendal-core = { path = "../../core", version = "0.59.4", default-features = false }
|
|
36
36
|
|
|
37
37
|
bytes = { workspace = true }
|
|
38
38
|
futures = { workspace = true }
|
|
39
|
-
hdrs = { version = "0.3.
|
|
39
|
+
hdrs = { version = "0.3.3", features = ["async_file"] }
|
|
40
40
|
log = { workspace = true }
|
|
41
41
|
serde = { workspace = true, features = ["derive"] }
|
|
42
42
|
tokio = { workspace = true, features = ["rt"] }
|
|
@@ -169,6 +169,8 @@ impl Builder for HdfsBuilder {
|
|
|
169
169
|
|
|
170
170
|
list: true,
|
|
171
171
|
|
|
172
|
+
copy: true,
|
|
173
|
+
|
|
172
174
|
rename: true,
|
|
173
175
|
rename_with_if_not_exists: true,
|
|
174
176
|
|
|
@@ -195,7 +197,7 @@ impl Service for HdfsBackend {
|
|
|
195
197
|
type Writer = HdfsLazyWriter;
|
|
196
198
|
type Lister = Option<HdfsLister>;
|
|
197
199
|
type Deleter = oio::OneShotDeleter<HdfsDeleter>;
|
|
198
|
-
type Copier =
|
|
200
|
+
type Copier = oio::OneShotCopier;
|
|
199
201
|
type Composer = ();
|
|
200
202
|
|
|
201
203
|
fn info(&self) -> ServiceInfo {
|
|
@@ -258,14 +260,20 @@ impl Service for HdfsBackend {
|
|
|
258
260
|
fn copy(
|
|
259
261
|
&self,
|
|
260
262
|
_ctx: &OperationContext,
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
from: &str,
|
|
264
|
+
to: &str,
|
|
263
265
|
_args: OpCopy,
|
|
264
266
|
) -> Result<Self::Copier> {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
267
|
+
let core = self.core.clone();
|
|
268
|
+
let from = from.to_string();
|
|
269
|
+
let to = to.to_string();
|
|
270
|
+
// Recreate the future so retry layers can rerun the copy after a temporary error.
|
|
271
|
+
Ok(oio::OneShotCopier::new_with(move || {
|
|
272
|
+
let core = core.clone();
|
|
273
|
+
let from = from.clone();
|
|
274
|
+
let to = to.clone();
|
|
275
|
+
async move { core.hdfs_copy(&from, &to).await }
|
|
276
|
+
}))
|
|
269
277
|
}
|
|
270
278
|
|
|
271
279
|
async fn rename(
|
|
@@ -124,8 +124,7 @@ impl HdfsCore {
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
if !target_exists {
|
|
127
|
-
|
|
128
|
-
self.client.create_dir(parent).map_err(new_std_io_error)?;
|
|
127
|
+
self.ensure_parent_dir(&target_path)?;
|
|
129
128
|
}
|
|
130
129
|
if !should_append {
|
|
131
130
|
initial_size = 0;
|
|
@@ -175,20 +174,7 @@ impl HdfsCore {
|
|
|
175
174
|
return Err(new_std_io_error(err));
|
|
176
175
|
}
|
|
177
176
|
|
|
178
|
-
|
|
179
|
-
.parent()
|
|
180
|
-
.ok_or_else(|| {
|
|
181
|
-
Error::new(
|
|
182
|
-
ErrorKind::Unexpected,
|
|
183
|
-
"path should have parent but not, it must be malformed",
|
|
184
|
-
)
|
|
185
|
-
.with_context("to", &to_path)
|
|
186
|
-
})?
|
|
187
|
-
.to_path_buf();
|
|
188
|
-
|
|
189
|
-
self.client
|
|
190
|
-
.create_dir(&parent.to_string_lossy())
|
|
191
|
-
.map_err(new_std_io_error)?;
|
|
177
|
+
self.ensure_parent_dir(&to_path)?;
|
|
192
178
|
}
|
|
193
179
|
Ok(metadata) => {
|
|
194
180
|
if metadata.is_file() {
|
|
@@ -215,6 +201,59 @@ impl HdfsCore {
|
|
|
215
201
|
|
|
216
202
|
Ok(())
|
|
217
203
|
}
|
|
204
|
+
|
|
205
|
+
pub async fn hdfs_copy(&self, from: &str, to: &str) -> Result<Metadata> {
|
|
206
|
+
let from_path = build_rooted_abs_path(&self.root, from);
|
|
207
|
+
// OpenDAL copy is file-to-file only. Reject directory sources before
|
|
208
|
+
// the HDFS API can recursively copy their contents.
|
|
209
|
+
let from_meta = self.client.metadata(&from_path).map_err(new_std_io_error)?;
|
|
210
|
+
if !from_meta.is_file() {
|
|
211
|
+
return Err(
|
|
212
|
+
Error::new(ErrorKind::IsADirectory, "from path should be a file")
|
|
213
|
+
.with_context("from", &from_path),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let to_path = build_rooted_abs_path(&self.root, to);
|
|
218
|
+
match self.client.metadata(&to_path) {
|
|
219
|
+
Ok(meta) => {
|
|
220
|
+
// OpenDAL treats `to` as the exact destination file path. Reject
|
|
221
|
+
// an existing directory instead of copying the source into it.
|
|
222
|
+
if meta.is_dir() {
|
|
223
|
+
return Err(
|
|
224
|
+
Error::new(ErrorKind::IsADirectory, "to path should be a file")
|
|
225
|
+
.with_context("to", &to_path),
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
// The HDFS copy API does not replace an existing destination,
|
|
229
|
+
// so remove it first to preserve OpenDAL's overwrite semantics.
|
|
230
|
+
self.client
|
|
231
|
+
.remove_file(&to_path)
|
|
232
|
+
.map_err(new_std_io_error)?;
|
|
233
|
+
}
|
|
234
|
+
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
|
235
|
+
// hdrs copy_file requires the destination parent to already exist.
|
|
236
|
+
self.ensure_parent_dir(&to_path)?;
|
|
237
|
+
}
|
|
238
|
+
Err(err) => return Err(new_std_io_error(err)),
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let client = self.client.clone();
|
|
242
|
+
let copy_from = from_path.clone();
|
|
243
|
+
let copy_to = to_path.clone();
|
|
244
|
+
tokio::task::spawn_blocking(move || client.copy_file(©_from, ©_to))
|
|
245
|
+
.await
|
|
246
|
+
.map_err(|e| Error::new(ErrorKind::Unexpected, "tokio task join failed").set_source(e))?
|
|
247
|
+
.map_err(new_std_io_error)?;
|
|
248
|
+
|
|
249
|
+
Ok(MetadataBuilder::file(from_meta.len()).build())
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fn ensure_parent_dir(&self, path: &str) -> Result<()> {
|
|
253
|
+
self.client
|
|
254
|
+
.create_dir(get_parent(path))
|
|
255
|
+
.map_err(new_std_io_error)
|
|
256
|
+
}
|
|
218
257
|
}
|
|
219
258
|
|
|
220
259
|
#[cfg(test)]
|
|
@@ -10,13 +10,21 @@ Depending on its configuration and the backing system, this service can expose:
|
|
|
10
10
|
- [x] write
|
|
11
11
|
- [x] delete
|
|
12
12
|
- [x] list
|
|
13
|
-
- [
|
|
13
|
+
- [x] copy
|
|
14
14
|
- [x] rename
|
|
15
15
|
- [ ] ~~presign~~
|
|
16
16
|
|
|
17
17
|
Inspect the effective capability set with [`opendal_core::Operator::info`] and
|
|
18
18
|
[`opendal_core::OperatorInfo::capability`] after building an operator.
|
|
19
19
|
|
|
20
|
+
## Copy behavior
|
|
21
|
+
|
|
22
|
+
The HDFS service copies files to exact destination file paths and rejects
|
|
23
|
+
directory sources and destinations. Copy overwrites an existing destination by
|
|
24
|
+
removing it before copying the source. This replacement is not atomic: if the
|
|
25
|
+
copy fails after removing the destination, the destination can be missing or
|
|
26
|
+
incomplete.
|
|
27
|
+
|
|
20
28
|
## Differences with webhdfs
|
|
21
29
|
|
|
22
30
|
The [WebHDFS service](https://docs.rs/opendal-service-webhdfs) uses HDFS's
|
|
@@ -36,5 +36,5 @@ bytes = { workspace = true }
|
|
|
36
36
|
futures = { workspace = true }
|
|
37
37
|
hdfs-native = { version = "0.14" }
|
|
38
38
|
log = { workspace = true }
|
|
39
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
39
|
+
opendal-core = { path = "../../core", version = "0.59.4", default-features = false }
|
|
40
40
|
serde = { workspace = true, features = ["derive"] }
|
data/core/services/hf/Cargo.toml
CHANGED
|
@@ -37,7 +37,7 @@ bytes = { workspace = true }
|
|
|
37
37
|
hf-xet = "1.6.0"
|
|
38
38
|
http = { workspace = true }
|
|
39
39
|
log = { workspace = true }
|
|
40
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
40
|
+
opendal-core = { path = "../../core", version = "0.59.4", default-features = false }
|
|
41
41
|
percent-encoding = "2"
|
|
42
42
|
serde = { workspace = true, features = ["derive"] }
|
|
43
43
|
serde_json = { workspace = true }
|
|
@@ -45,6 +45,9 @@ serde_json = { workspace = true }
|
|
|
45
45
|
[dev-dependencies]
|
|
46
46
|
base64 = { workspace = true }
|
|
47
47
|
futures = { workspace = true }
|
|
48
|
-
opendal-
|
|
48
|
+
opendal-core = { path = "../../core", version = "0.59.4", features = [
|
|
49
|
+
"executors-tokio",
|
|
50
|
+
] }
|
|
51
|
+
opendal-http-transport-reqwest = { path = "../../http-transports/reqwest", version = "0.59.4" }
|
|
49
52
|
serde_json = { workspace = true }
|
|
50
53
|
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
|
@@ -135,9 +135,14 @@ impl HfBuilder {
|
|
|
135
135
|
|
|
136
136
|
/// Enable caching of resolved HTTP download addresses and XET file metadata.
|
|
137
137
|
///
|
|
138
|
-
/// Defaults to `false`:
|
|
139
|
-
///
|
|
140
|
-
///
|
|
138
|
+
/// Defaults to `false`: each new reader resolves through the Hub. An XET-mode
|
|
139
|
+
/// reader retains the XET metadata returned by its first read for its lifetime,
|
|
140
|
+
/// even when this option is disabled. Its subsequent ranges use that file
|
|
141
|
+
/// version. Create a new reader to resolve the path again. HTTP reads resolve
|
|
142
|
+
/// each range.
|
|
143
|
+
///
|
|
144
|
+
/// Set to `true` to share resolve results across readers on the same backend.
|
|
145
|
+
/// HTTP addresses refresh near expiry.
|
|
141
146
|
///
|
|
142
147
|
/// Enable this only when previously written files are not modified. Changed
|
|
143
148
|
/// files can remain invisible while cached results are reused, including
|
|
@@ -64,8 +64,10 @@ pub struct HfConfig {
|
|
|
64
64
|
///
|
|
65
65
|
/// Defaults to `false`. Set to `true` to share resolve results across readers
|
|
66
66
|
/// on the same backend. Changed files may remain invisible while cached
|
|
67
|
-
/// results are reused.
|
|
68
|
-
///
|
|
67
|
+
/// results are reused. A reader retains XET metadata from its first read for
|
|
68
|
+
/// its lifetime. Create a new reader to resolve the path again when this
|
|
69
|
+
/// option is disabled.
|
|
70
|
+
/// See [`HfBuilder::enable_resolve_cache`] for freshness semantics.
|
|
69
71
|
pub enable_resolve_cache: bool,
|
|
70
72
|
}
|
|
71
73
|
|
|
@@ -34,8 +34,12 @@ accepted values, defaults, and environment interaction.
|
|
|
34
34
|
|
|
35
35
|
### Caching resolve results
|
|
36
36
|
|
|
37
|
-
By default,
|
|
38
|
-
XET
|
|
37
|
+
By default, each new reader resolves through the Hugging Face Hub. An XET-mode
|
|
38
|
+
reader retains the XET metadata returned by its first read for its lifetime.
|
|
39
|
+
Subsequent ranges, including concurrent reads, use that file version. Create a
|
|
40
|
+
new reader to resolve an updated path. HTTP reads resolve each range.
|
|
41
|
+
|
|
42
|
+
Enable
|
|
39
43
|
[`enable_resolve_cache`](crate::Hf::enable_resolve_cache) to share resolved HTTP
|
|
40
44
|
download addresses and XET file metadata across readers and batches on the same
|
|
41
45
|
backend:
|