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 80f15f5bb feat(service): Support stat for Dropbox (#2588)
80f15f5bb is described below

commit 80f15f5bb6cc4f48e671e85a9f89ca728b365b58
Author: Nadeshiko Manju <[email protected]>
AuthorDate: Wed Jul 5 15:13:52 2023 +0800

    feat(service): Support stat for Dropbox (#2588)
    
    * feat(service): Support stat for Dropbox
    
    * feat(service): Support stat for Dropbox
    
    * feat(service): Support stat for Dropbox
    
    * feat(service): Support stat for Dropbox
    
    * Update code
    
    * Update code
---
 core/src/services/dropbox/backend.rs  | 34 ++++++++++++++
 core/src/services/dropbox/core.rs     | 36 +++++++++++++++
 core/src/services/dropbox/error.rs    | 18 +-------
 core/src/services/dropbox/mod.rs      |  1 +
 core/src/services/dropbox/response.rs | 86 +++++++++++++++++++++++++++++++++++
 5 files changed, 159 insertions(+), 16 deletions(-)

diff --git a/core/src/services/dropbox/backend.rs 
b/core/src/services/dropbox/backend.rs
index da8e3e690..e9a2fecf1 100644
--- a/core/src/services/dropbox/backend.rs
+++ b/core/src/services/dropbox/backend.rs
@@ -23,6 +23,7 @@ use http::StatusCode;
 
 use super::core::DropboxCore;
 use super::error::parse_error;
+use super::response::DropboxMetadataResponse;
 use super::writer::DropboxWriter;
 use crate::raw::*;
 use crate::*;
@@ -90,4 +91,37 @@ impl Accessor for DropboxBackend {
             _ => Err(parse_error(resp).await?),
         }
     }
+
+    async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
+        if path == "/" {
+            return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
+        }
+        let resp = self.core.dropbox_get_metadata(path).await?;
+        let status = resp.status();
+        match status {
+            StatusCode::OK => {
+                let bytes = resp.into_body().bytes().await?;
+                let decoded_response = 
serde_json::from_slice::<DropboxMetadataResponse>(&bytes)
+                    .map_err(new_json_deserialize_error)?;
+                let entry_mode: EntryMode = match 
decoded_response.tag.as_str() {
+                    "file" => EntryMode::FILE,
+                    "folder" => EntryMode::DIR,
+                    _ => EntryMode::Unknown,
+                };
+                let mut metadata = Metadata::new(entry_mode);
+                let last_modified = decoded_response.client_modified;
+                let date_utc_last_modified = 
parse_datetime_from_rfc3339(&last_modified)?;
+                metadata.set_last_modified(date_utc_last_modified);
+                if decoded_response.size.is_some() {
+                    let size = decoded_response.size.unwrap();
+                    metadata.set_content_length(size);
+                }
+                Ok(RpStat::new(metadata))
+            }
+            StatusCode::NOT_FOUND if path.ends_with('/') => {
+                Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
+            }
+            _ => Err(parse_error(resp).await?),
+        }
+    }
 }
diff --git a/core/src/services/dropbox/core.rs 
b/core/src/services/dropbox/core.rs
index 31b597d4c..ad4655a32 100644
--- a/core/src/services/dropbox/core.rs
+++ b/core/src/services/dropbox/core.rs
@@ -111,6 +111,23 @@ impl DropboxCore {
         self.client.send(request).await
     }
 
+    pub async fn dropbox_get_metadata(&self, path: &str) -> 
Result<Response<IncomingAsyncBody>> {
+        let url = 
"https://api.dropboxapi.com/2/files/get_metadata".to_string();
+        let args = DropboxMetadataArgs {
+            path: build_rooted_abs_path(&self.root, path),
+            ..Default::default()
+        };
+
+        let bs = 
Bytes::from(serde_json::to_string(&args).map_err(new_json_serialize_error)?);
+
+        let request = self
+            .build_auth_header(Request::post(&url))
+            .header(header::CONTENT_TYPE, "application/json")
+            .body(AsyncBody::Bytes(bs))
+            .map_err(new_request_build_error)?;
+        self.client.send(request).await
+    }
+
     fn build_auth_header(&self, mut req: Builder) -> Builder {
         let auth_header_content = format!("Bearer {}", self.token);
         req = req.header(header::AUTHORIZATION, auth_header_content);
@@ -137,6 +154,14 @@ struct DropboxDeleteArgs {
     path: String,
 }
 
+#[derive(Clone, Debug, Deserialize, Serialize)]
+struct DropboxMetadataArgs {
+    include_deleted: bool,
+    include_has_explicit_shared_members: bool,
+    include_media_info: bool,
+    path: String,
+}
+
 impl Default for DropboxUploadArgs {
     fn default() -> Self {
         DropboxUploadArgs {
@@ -148,3 +173,14 @@ impl Default for DropboxUploadArgs {
         }
     }
 }
+
+impl Default for DropboxMetadataArgs {
+    fn default() -> Self {
+        DropboxMetadataArgs {
+            include_deleted: false,
+            include_has_explicit_shared_members: false,
+            include_media_info: false,
+            path: "".to_string(),
+        }
+    }
+}
diff --git a/core/src/services/dropbox/error.rs 
b/core/src/services/dropbox/error.rs
index aa7981ba0..db878f6a0 100644
--- a/core/src/services/dropbox/error.rs
+++ b/core/src/services/dropbox/error.rs
@@ -17,27 +17,13 @@
 
 use http::Response;
 use http::StatusCode;
-use serde::Deserialize;
 
+use super::response::DropboxErrorResponse;
 use crate::raw::*;
 use crate::Error;
 use crate::ErrorKind;
 use crate::Result;
 
-#[derive(Default, Debug, Deserialize)]
-#[serde(default)]
-struct DropboxError {
-    error_summary: String,
-    error: DropboxErrorDetail,
-}
-
-#[derive(Default, Debug, Deserialize)]
-#[serde(default)]
-struct DropboxErrorDetail {
-    #[serde(rename(deserialize = ".tag"))]
-    tag: String,
-}
-
 /// Parse error response into Error.
 pub async fn parse_error(resp: Response<IncomingAsyncBody>) -> Result<Error> {
     let (parts, body) = resp.into_parts();
@@ -53,7 +39,7 @@ pub async fn parse_error(resp: Response<IncomingAsyncBody>) 
-> Result<Error> {
         _ => (ErrorKind::Unexpected, false),
     };
     let dropbox_error =
-        
serde_json::from_slice::<DropboxError>(&bs).map_err(new_json_deserialize_error);
+        
serde_json::from_slice::<DropboxErrorResponse>(&bs).map_err(new_json_deserialize_error);
     match dropbox_error {
         Ok(dropbox_error) => {
             let mut err = Error::new(kind, 
dropbox_error.error_summary.as_ref())
diff --git a/core/src/services/dropbox/mod.rs b/core/src/services/dropbox/mod.rs
index 239974a75..048b7d74d 100644
--- a/core/src/services/dropbox/mod.rs
+++ b/core/src/services/dropbox/mod.rs
@@ -19,6 +19,7 @@ mod backend;
 mod builder;
 mod core;
 mod error;
+mod response;
 mod writer;
 
 pub use builder::DropboxBuilder as Dropbox;
diff --git a/core/src/services/dropbox/response.rs 
b/core/src/services/dropbox/response.rs
new file mode 100644
index 000000000..2e5c15a81
--- /dev/null
+++ b/core/src/services/dropbox/response.rs
@@ -0,0 +1,86 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use serde::Deserialize;
+
+#[derive(Default, Debug, Deserialize)]
+#[serde(default)]
+pub struct DropboxErrorResponse {
+    pub error_summary: String,
+    pub error: DropboxErrorDetail,
+}
+
+#[derive(Default, Debug, Deserialize)]
+#[serde(default)]
+pub struct DropboxErrorDetail {
+    #[serde(rename(deserialize = ".tag"))]
+    pub tag: String,
+}
+
+#[derive(Default, Debug, Deserialize)]
+#[serde(default)]
+pub struct DropboxMetadataResponse {
+    #[serde(rename(deserialize = ".tag"))]
+    pub tag: String,
+    pub client_modified: String,
+    pub content_hash: Option<String>,
+    pub file_lock_info: Option<DropboxMetadataFileLockInfo>,
+    pub has_explicit_shared_members: Option<bool>,
+    pub id: String,
+    pub is_downloadable: Option<bool>,
+    pub name: String,
+    pub path_display: String,
+    pub path_lower: String,
+    pub property_groups: Option<Vec<DropboxMetadataPropertyGroup>>,
+    pub rev: Option<String>,
+    pub server_modified: Option<String>,
+    pub sharing_info: Option<DropboxMetadataSharingInfo>,
+    pub size: Option<u64>,
+}
+
+#[derive(Default, Debug, Deserialize)]
+#[serde(default)]
+pub struct DropboxMetadataFileLockInfo {
+    pub created: Option<String>,
+    pub is_lockholder: bool,
+    pub lockholder_name: Option<String>,
+}
+
+#[derive(Default, Debug, Deserialize)]
+#[serde(default)]
+pub struct DropboxMetadataPropertyGroup {
+    pub fields: Vec<DropboxMetadataPropertyGroupField>,
+    pub template_id: String,
+}
+
+#[derive(Default, Debug, Deserialize)]
+#[serde(default)]
+pub struct DropboxMetadataPropertyGroupField {
+    pub name: String,
+    pub value: String,
+}
+
+#[derive(Default, Debug, Deserialize)]
+#[serde(default)]
+pub struct DropboxMetadataSharingInfo {
+    pub modified_by: Option<String>,
+    pub parent_shared_folder_id: Option<String>,
+    pub read_only: Option<bool>,
+    pub shared_folder_id: Option<String>,
+    pub traverse_only: Option<bool>,
+    pub no_access: Option<bool>,
+}

Reply via email to