Skip to content

Commit

Permalink
feat(services/dropbox): Support create/read/delete for Dropbox
Browse files Browse the repository at this point in the history
Signed-off-by: Manjusaka <[email protected]>
  • Loading branch information
Zheaoli committed Jun 19, 2023
1 parent 26deb25 commit 3bdc51a
Show file tree
Hide file tree
Showing 9 changed files with 607 additions and 0 deletions.
1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ services-gcs = [
"reqsign?/services-google",
"reqsign?/reqwest_request",
]
services-dropbox = []
services-gdrive = []
services-ghac = []
services-hdfs = ["dep:hdrs"]
Expand Down
108 changes: 108 additions & 0 deletions core/src/services/dropbox/backend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// 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 async_trait::async_trait;
use http::StatusCode;

use std::{fmt::Debug, sync::Arc};

use super::{core::DropboxCore, error::parse_error, writer::DropboxWriter};
use crate::{
raw::{
parse_into_metadata, Accessor, AccessorInfo, HttpClient, IncomingAsyncBody, OpDelete,
OpRead, OpWrite, RpDelete, RpRead, RpWrite,
},
types::Result,
Capability, Error, ErrorKind,
};

#[derive(Clone, Debug)]
pub struct DropboxBackend {
core: Arc<DropboxCore>,
}

impl DropboxBackend {
pub(crate) fn new(root: String, access_token: String, http_client: HttpClient) -> Self {
DropboxBackend {
core: Arc::new(DropboxCore {
token: access_token,
client: http_client,
root,
}),
}
}
}

#[async_trait]
impl Accessor for DropboxBackend {
type Reader = IncomingAsyncBody;
type BlockingReader = ();
type Writer = DropboxWriter;
type BlockingWriter = ();
type Pager = ();
type BlockingPager = ();
type Appender = ();

fn info(&self) -> AccessorInfo {
let mut ma = AccessorInfo::default();
ma.set_scheme(crate::Scheme::Dropbox)
.set_root(&self.core.root)
.set_capability(Capability {
read: true,
write: true,
delete: true,
..Default::default()
});
ma
}

async fn read(&self, path: &str, _args: OpRead) -> Result<(RpRead, Self::Reader)> {
let resp = self.core.dropbox_get(path).await?;
let status = resp.status();
match status {
StatusCode::OK => {
let meta = parse_into_metadata(path, resp.headers())?;
Ok((RpRead::with_metadata(meta), resp.into_body()))
}
_ => Err(parse_error(resp).await?),
}
}

async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
if args.content_length().is_none() {
return Err(Error::new(
ErrorKind::Unsupported,
"write without content length is not supported",
));
}
Ok((
RpWrite::default(),
DropboxWriter::new(self.core.clone(), args, String::from(path)),
))
}

async fn delete(&self, path: &str, _: OpDelete) -> Result<RpDelete> {
let resp = self.core.dropbox_delete(path).await?;

let status = resp.status();

match status {
StatusCode::OK => Ok(RpDelete::default()),
_ => Err(parse_error(resp).await?),
}
}
}
149 changes: 149 additions & 0 deletions core/src/services/dropbox/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// 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 std::collections::HashMap;
use std::fmt::{Debug, Formatter};

use log::debug;

use super::backend::DropboxBackend;
use crate::raw::{normalize_root, HttpClient};
use crate::Scheme;
use crate::*;

/// [Dropbox](https://www.dropbox.com/) backend support.
///
/// # Capabilities
///
/// This service can be used to:
///
/// - [x] read
/// - [x] write
/// - [x] delete
/// - [ ] copy
/// - [ ] create
/// - [ ] list
/// - [ ] rename
///
/// # Notes
///
///
/// # Configuration
///
/// - `access_token`: set the access_token for google drive api
/// - `root`: Set the work directory for backend
///
/// You can refer to [`DropboxBuilder`]'s docs for more information
///
/// # Example
///
/// ## Via Builder
///
/// ```
/// use anyhow::Result;
/// use opendal::services::Dropbox;
/// use opendal::Operator;
/// use opendal::raw::OpWrite;
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// // create backend builder
/// let mut builder = Dropbox::default();
///
/// builder.access_token("x").root("/");
///
/// let op: Operator = Operator::new(builder)?.finish();
/// let content = "who are you";
///
///
/// let write = op.write_with("abc2.txt", content)
/// .content_type("application/octet-stream")
/// .content_length(content.len() as u64).await?;
/// let read = op.read("abc2.txt").await?;
/// let s = String::from_utf8(read).unwrap();
/// println!("{}", s);
/// let delete = op.delete("abc.txt").await?;
/// Ok(())
/// }
/// ```
#[derive(Default)]
pub struct DropboxBuilder {
access_token: Option<String>,
root: Option<String>,
http_client: Option<HttpClient>,
}

impl Debug for DropboxBuilder {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Builder").finish()
}
}

impl DropboxBuilder {
/// default: no access token, which leads to failure
pub fn access_token(&mut self, access_token: &str) -> &mut Self {
self.access_token = Some(access_token.to_string());
self
}

pub fn root(&mut self, root: &str) -> &mut Self {
self.root = Some(root.to_string());
self
}

/// Specify the http client that used by this service.
///
/// # Notes
///
/// This API is part of OpenDAL's Raw API. `HttpClient` could be changed
/// during minor updates.
pub fn http_client(&mut self, http_client: HttpClient) -> &mut Self {
self.http_client = Some(http_client);
self
}
}

impl Builder for DropboxBuilder {
const SCHEME: Scheme = Scheme::Dropbox;
type Accessor = DropboxBackend;

fn from_map(map: HashMap<String, String>) -> Self {
let mut builder = Self::default();
map.get("access_token").map(|v| builder.access_token(v));
builder
}

fn build(&mut self) -> Result<Self::Accessor> {
let root = normalize_root(&self.root.take().unwrap_or_default());
let client = if let Some(client) = self.http_client.take() {
client
} else {
HttpClient::new().map_err(|err| {
err.with_operation("Builder::build")
.with_context("service", Scheme::Dropbox)
})?
};
match self.access_token.clone() {
Some(access_token) => Ok(DropboxBackend::new(root, access_token, client)),
None => Err(Error::new(
ErrorKind::ConfigInvalid,
"access_token is required",
)),
}
}
}
Loading

0 comments on commit 3bdc51a

Please sign in to comment.