This is an automated email from the ASF dual-hosted git repository. suyanhanx pushed a commit to branch gcs-rename in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
commit 815091f29c5aa4b622c237f7971e54e19a26d3d3 Author: suyanhanx <[email protected]> AuthorDate: Mon Sep 4 22:00:05 2023 +0800 feat(services/gdrive): implement rename Signed-off-by: suyanhanx <[email protected]> --- core/src/services/gdrive/backend.rs | 33 ++++++++++++++++-- core/src/services/gdrive/core.rs | 69 ++++++++++++++++++++++++++----------- 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/core/src/services/gdrive/backend.rs b/core/src/services/gdrive/backend.rs index da6f950b3..df8ee7451 100644 --- a/core/src/services/gdrive/backend.rs +++ b/core/src/services/gdrive/backend.rs @@ -58,6 +58,8 @@ impl Accessor for GdriveBackend { create_dir: true, + rename: true, + delete: true, ..Default::default() @@ -103,7 +105,10 @@ impl Accessor for GdriveBackend { if !meta.files.is_empty() { let mut cache = self.core.path_cache.lock().await; - cache.insert(path.to_string(), meta.files[0].id.clone()); + cache.insert( + build_abs_path(&self.core.root, path), + meta.files[0].id.clone(), + ); return Ok(RpCreateDir::default()); } @@ -123,7 +128,7 @@ impl Accessor for GdriveBackend { let mut cache = self.core.path_cache.lock().await; - cache.insert(path.to_string(), meta.id.clone()); + cache.insert(build_abs_path(&self.core.root, path), meta.id.clone()); Ok(RpCreateDir::default()) } @@ -193,6 +198,28 @@ impl Accessor for GdriveBackend { )) } + async fn rename(&self, from: &str, to: &str, _args: OpRename) -> Result<RpRename> { + let resp = self.core.gdrive_patch_metadata_request(from, to).await?; + + let status = resp.status(); + + match status { + StatusCode::OK => { + let body = resp.into_body().bytes().await?; + let meta = serde_json::from_slice::<GdriveFile>(&body) + .map_err(new_json_deserialize_error)?; + + let mut cache = self.core.path_cache.lock().await; + + cache.remove(&build_abs_path(&self.core.root, from)); + cache.insert(build_abs_path(&self.core.root, to), meta.id.clone()); + + Ok(RpRename::default()) + } + _ => Err(parse_error(resp).await?), + } + } + async fn delete(&self, path: &str, _: OpDelete) -> Result<RpDelete> { let resp = self.core.gdrive_delete(path).await; if let Ok(resp) = resp { @@ -202,7 +229,7 @@ impl Accessor for GdriveBackend { StatusCode::NO_CONTENT => { let mut cache = self.core.path_cache.lock().await; - cache.remove(path); + cache.remove(&build_abs_path(&self.core.root, path)); return Ok(RpDelete::default()); } diff --git a/core/src/services/gdrive/core.rs b/core/src/services/gdrive/core.rs index 24ff1cf89..6fe63759d 100644 --- a/core/src/services/gdrive/core.rs +++ b/core/src/services/gdrive/core.rs @@ -21,6 +21,7 @@ use std::fmt::Formatter; use std::sync::Arc; use bytes; +use bytes::Bytes; use chrono::DateTime; use chrono::Utc; use http::header; @@ -48,6 +49,11 @@ pub struct GdriveCore { /// /// Google Drive uses file id to identify a file. /// As the path is immutable, we can cache the mapping from path to file id. + /// + /// # Notes + /// + /// - The path is rooted at the root of the Google Drive. + /// - The path is absolute path, like `foo/bar`. pub path_cache: Arc<Mutex<HashMap<String, String>>>, } @@ -71,7 +77,7 @@ impl GdriveCore { /// - A file only knows its parent id, but not its name. /// - To find the file id of a file, we need to traverse the path from the root to the file. pub(crate) async fn get_file_id_by_path(&self, file_path: &str) -> Result<String> { - let path = build_rooted_abs_path(&self.root, file_path); + let path = build_abs_path(&self.root, file_path); let mut cache = self.path_cache.lock().await; @@ -148,7 +154,7 @@ impl GdriveCore { /// - The path is rooted at the root of the Google Drive. /// - Will create the parent path recursively. pub(crate) async fn ensure_parent_path(&self, path: &str) -> Result<String> { - let path = build_rooted_abs_path(&self.root, path); + let path = build_abs_path(&self.root, path); let mut parent: String = "root".to_owned(); let mut file_path_items: Vec<&str> = path.split('/').filter(|&x| !x.is_empty()).collect(); @@ -319,29 +325,44 @@ impl GdriveCore { self.client.send(req).await } - pub async fn gdrive_update( + // Update with content and metadata + pub async fn gdrive_patch_metadata_request( &self, - path: &str, - size: Option<usize>, - content_type: Option<&str>, - body: AsyncBody, + source_path: &str, + target_path: &str, ) -> Result<Response<IncomingAsyncBody>> { - let url = format!( - "https://www.googleapis.com/upload/drive/v3/files/{}?uploadType=media", - self.get_file_id_by_path(path).await? - ); + let file_id = self.get_file_id_by_path(source_path).await?; - let mut req = Request::put(&url); + let parent = self.ensure_parent_path(target_path).await?; - if let Some(size) = size { - req = req.header(header::CONTENT_LENGTH, size) - } + let url = format!("https://www.googleapis.com/drive/v3/files/{}", file_id); - if let Some(mime) = content_type { - req = req.header(header::CONTENT_TYPE, mime) - } + let source_abs_path = build_abs_path(&self.root, source_path); + let mut source_parent: Vec<&str> = source_abs_path + .split('/') + .filter(|&x| !x.is_empty()) + .collect(); + source_parent.pop(); + + let cache = self.path_cache.lock().await; + + let file_name = build_abs_path(&self.root, target_path) + .split('/') + .filter(|&x| !x.is_empty()) + .last() + .unwrap() + .to_string(); + + let metadata = &json!({ + "name": file_name, + "removeParents": [cache.get(&source_parent.join("/")).unwrap().to_string()], + "addParents": [parent], + }); + + let mut req = Request::patch(url) + .body(AsyncBody::Bytes(Bytes::from(metadata.to_string()))) + .map_err(new_request_build_error)?; - let mut req = req.body(body).map_err(new_request_build_error)?; self.sign(&mut req).await?; self.client.send(req).await @@ -362,11 +383,12 @@ impl GdriveCore { self.client.send(req).await } + /// Create a file with the content. pub async fn gdrive_upload_simple_request( &self, path: &str, size: u64, - body: bytes::Bytes, + body: Bytes, ) -> Result<Response<IncomingAsyncBody>> { let parent = self.ensure_parent_path(path).await?; @@ -406,11 +428,16 @@ impl GdriveCore { self.client.send(req).await } + /// Overwrite the file with the content. + /// + /// # Notes + /// + /// - The file id is required. Do not use this method to create a file. pub async fn gdrive_upload_overwrite_simple_request( &self, file_id: &str, size: u64, - body: bytes::Bytes, + body: Bytes, ) -> Result<Response<IncomingAsyncBody>> { let url = format!( "https://www.googleapis.com/upload/drive/v3/files/{}?uploadType=media",
