This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
The following commit(s) were added to refs/heads/main by this push:
new e0db2edb feat(webdav): support copy (#1870)
e0db2edb is described below
commit e0db2edbec988b98795cfaa7d4f257b40adb99e4
Author: Suyan <[email protected]>
AuthorDate: Fri Apr 7 20:56:35 2023 +0800
feat(webdav): support copy (#1870)
* feat(webdav): support copy
Signed-off-by: suyanhanx <[email protected]>
* ci(webdav): enable COPY method for test
Signed-off-by: suyanhanx <[email protected]>
* ensure parent path before copy
Signed-off-by: suyanhanx <[email protected]>
---------
Signed-off-by: suyanhanx <[email protected]>
---
core/src/services/webdav/backend.rs | 81 +++++++++++++++++-----
.../webdav/fixtures/nginx-with-basic-auth.conf | 2 +-
core/src/services/webdav/fixtures/nginx.conf | 2 +-
3 files changed, 67 insertions(+), 18 deletions(-)
diff --git a/core/src/services/webdav/backend.rs
b/core/src/services/webdav/backend.rs
index 5b1180c5..81ebbd41 100644
--- a/core/src/services/webdav/backend.rs
+++ b/core/src/services/webdav/backend.rs
@@ -44,6 +44,7 @@ use crate::*;
///
/// - [x] read
/// - [x] write
+/// - [x] copy
/// - [x] list
/// - [ ] ~~scan~~
/// - [ ] ~~presign~~
@@ -265,7 +266,10 @@ impl Accessor for WebdavBackend {
ma.set_scheme(Scheme::Webdav)
.set_root(&self.root)
.set_capabilities(
- AccessorCapability::Read | AccessorCapability::Write |
AccessorCapability::List,
+ AccessorCapability::Read
+ | AccessorCapability::Write
+ | AccessorCapability::Copy
+ | AccessorCapability::List,
)
.set_hints(AccessorHint::ReadStreamable);
@@ -273,22 +277,10 @@ impl Accessor for WebdavBackend {
}
async fn create(&self, path: &str, _: OpCreate) -> Result<RpCreate> {
- // create dir recursively, split path by `/` and create each dir
except the last one
- let abs_path = build_abs_path(&self.root, path);
- let abs_path = abs_path.as_str();
- let mut parts: Vec<&str> = abs_path.split('/').filter(|x|
!x.is_empty()).collect();
- if !parts.is_empty() {
- parts.pop();
- }
+ self.ensure_parent_path(path).await?;
- let mut sub_path = String::new();
- for sub_part in parts {
- let sub_path_with_slash = sub_part.to_owned() + "/";
- sub_path.push_str(&sub_path_with_slash);
- self.create_internal(&sub_path).await?;
- }
-
- self.create_internal(abs_path).await
+ let abs_path = build_abs_path(&self.root, path);
+ self.create_internal(&abs_path).await
}
async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead,
Self::Reader)> {
@@ -318,6 +310,19 @@ impl Accessor for WebdavBackend {
Ok((RpWrite::default(), WebdavWriter::new(self.clone(), args, p)))
}
+ async fn copy(&self, from: &str, to: &str, _args: OpCopy) ->
Result<RpCopy> {
+ self.ensure_parent_path(to).await?;
+
+ let resp = self.webdav_copy(from, to).await?;
+
+ let status = resp.status();
+
+ match status {
+ StatusCode::CREATED | StatusCode::NO_CONTENT =>
Ok(RpCopy::default()),
+ _ => Err(parse_error(resp).await?),
+ }
+ }
+
async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
// Stat root always returns a DIR.
if path == "/" {
@@ -549,6 +554,31 @@ impl WebdavBackend {
self.client.send_async(req).await
}
+ async fn webdav_copy(&self, from: &str, to: &str) ->
Result<Response<IncomingAsyncBody>> {
+ let source = build_abs_path(&self.root, from);
+ let target = build_abs_path(&self.root, to);
+
+ let source = format!("{}/{}", self.endpoint,
percent_encode_path(&source));
+ let target = format!("{}/{}", self.endpoint,
percent_encode_path(&target));
+
+ let mut req = Request::builder().method("COPY").uri(&source);
+
+ if let Some(auth) = &self.authorization {
+ req = req.header(header::AUTHORIZATION, auth);
+ }
+
+ req = req.header("Destination", target);
+
+ // We always specific "T" for keeping to overwrite the destination.
+ req = req.header("Overwrite", "T");
+
+ let req = req
+ .body(AsyncBody::Empty)
+ .map_err(new_request_build_error)?;
+
+ self.client.send_async(req).await
+ }
+
async fn create_internal(&self, abs_path: &str) -> Result<RpCreate> {
let resp = if abs_path.ends_with('/') {
self.webdav_mkcol(abs_path, None, None, AsyncBody::Empty)
@@ -575,4 +605,23 @@ impl WebdavBackend {
_ => Err(parse_error(resp).await?),
}
}
+
+ async fn ensure_parent_path(&self, path: &str) -> Result<()> {
+ // create dir recursively, split path by `/` and create each dir
except the last one
+ let abs_path = build_abs_path(&self.root, path);
+ let abs_path = abs_path.as_str();
+ let mut parts: Vec<&str> = abs_path.split('/').filter(|x|
!x.is_empty()).collect();
+ if !parts.is_empty() {
+ parts.pop();
+ }
+
+ let mut sub_path = String::new();
+ for sub_part in parts {
+ let sub_path_with_slash = sub_part.to_owned() + "/";
+ sub_path.push_str(&sub_path_with_slash);
+ self.create_internal(&sub_path).await?;
+ }
+
+ Ok(())
+ }
}
diff --git a/core/src/services/webdav/fixtures/nginx-with-basic-auth.conf
b/core/src/services/webdav/fixtures/nginx-with-basic-auth.conf
index 384defa3..b34b854e 100644
--- a/core/src/services/webdav/fixtures/nginx-with-basic-auth.conf
+++ b/core/src/services/webdav/fixtures/nginx-with-basic-auth.conf
@@ -17,7 +17,7 @@ http {
location / {
client_body_temp_path /tmp;
log_not_found off;
- dav_methods PUT DELETE MKCOL;
+ dav_methods PUT DELETE MKCOL COPY;
dav_ext_methods PROPFIND;
create_full_put_path on;
client_max_body_size 1024M;
diff --git a/core/src/services/webdav/fixtures/nginx.conf
b/core/src/services/webdav/fixtures/nginx.conf
index 22ab20df..20e15258 100644
--- a/core/src/services/webdav/fixtures/nginx.conf
+++ b/core/src/services/webdav/fixtures/nginx.conf
@@ -17,7 +17,7 @@ http {
location / {
client_body_temp_path /tmp;
log_not_found off;
- dav_methods PUT DELETE MKCOL;
+ dav_methods PUT DELETE MKCOL COPY;
dav_ext_methods PROPFIND;
create_full_put_path on;
client_max_body_size 1024M;