opendal_core/services/dropbox/
core.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::Debug;
19use std::sync::Arc;
20
21use bytes::Buf;
22use bytes::Bytes;
23use http::Request;
24use http::Response;
25use http::StatusCode;
26use http::header;
27use http::header::CONTENT_LENGTH;
28use http::header::CONTENT_TYPE;
29use mea::mutex::Mutex;
30use serde::Deserialize;
31use serde::Serialize;
32
33use super::error::parse_error;
34use crate::raw::*;
35use crate::*;
36
37pub struct DropboxCore {
38    pub info: Arc<AccessorInfo>,
39    pub root: String,
40    pub signer: Arc<Mutex<DropboxSigner>>,
41}
42
43impl Debug for DropboxCore {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        f.debug_struct("DropboxCore")
46            .field("root", &self.root)
47            .finish_non_exhaustive()
48    }
49}
50
51impl DropboxCore {
52    fn build_path(&self, path: &str) -> String {
53        let path = build_rooted_abs_path(&self.root, path);
54        // For dropbox, even the path is a directory,
55        // we still need to remove the trailing slash.
56        path.trim_end_matches('/').to_string()
57    }
58
59    pub async fn sign<T>(&self, req: &mut Request<T>) -> Result<()> {
60        let mut signer = self.signer.lock().await;
61
62        // Access token is valid, use it directly.
63        if !signer.access_token.is_empty() && signer.expires_in > Timestamp::now() {
64            let value = format!("Bearer {}", signer.access_token)
65                .parse()
66                .expect("token must be valid header value");
67            req.headers_mut().insert(header::AUTHORIZATION, value);
68            return Ok(());
69        }
70
71        // Refresh invalid token.
72        let url = "https://api.dropboxapi.com/oauth2/token".to_string();
73
74        let content = format!(
75            "grant_type=refresh_token&refresh_token={}&client_id={}&client_secret={}",
76            signer.refresh_token, signer.client_id, signer.client_secret
77        );
78        let bs = Bytes::from(content);
79
80        let request = Request::post(&url)
81            .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
82            .header(CONTENT_LENGTH, bs.len())
83            .body(Buffer::from(bs))
84            .map_err(new_request_build_error)?;
85
86        let resp = self.info.http_client().send(request).await?;
87        let body = resp.into_body();
88
89        let token: DropboxTokenResponse =
90            serde_json::from_reader(body.reader()).map_err(new_json_deserialize_error)?;
91
92        // Update signer after token refreshed.
93        signer.access_token.clone_from(&token.access_token);
94
95        // Refresh it 2 minutes earlier.
96        signer.expires_in =
97            Timestamp::now() + Duration::from_secs(token.expires_in) - Duration::from_secs(120);
98
99        let value = format!("Bearer {}", token.access_token)
100            .parse()
101            .expect("token must be valid header value");
102        req.headers_mut().insert(header::AUTHORIZATION, value);
103
104        Ok(())
105    }
106
107    pub async fn dropbox_get(
108        &self,
109        path: &str,
110        range: BytesRange,
111        _: &OpRead,
112    ) -> Result<Response<HttpBody>> {
113        let url: String = "https://content.dropboxapi.com/2/files/download".to_string();
114        let download_args = DropboxDownloadArgs {
115            path: build_rooted_abs_path(&self.root, path),
116        };
117        let request_payload =
118            serde_json::to_string(&download_args).map_err(new_json_serialize_error)?;
119
120        let mut req = Request::post(&url)
121            .header("Dropbox-API-Arg", request_payload)
122            .header(CONTENT_LENGTH, 0);
123
124        if !range.is_full() {
125            req = req.header(header::RANGE, range.to_header());
126        }
127
128        let req = req.extension(Operation::Read);
129
130        let mut request = req.body(Buffer::new()).map_err(new_request_build_error)?;
131
132        self.sign(&mut request).await?;
133        self.info.http_client().fetch(request).await
134    }
135
136    pub async fn dropbox_update(
137        &self,
138        path: &str,
139        size: Option<usize>,
140        args: &OpWrite,
141        body: Buffer,
142    ) -> Result<Response<Buffer>> {
143        let url = "https://content.dropboxapi.com/2/files/upload".to_string();
144        let dropbox_update_args = DropboxUploadArgs {
145            path: build_rooted_abs_path(&self.root, path),
146            ..Default::default()
147        };
148        let mut request_builder = Request::post(&url);
149        if let Some(size) = size {
150            request_builder = request_builder.header(CONTENT_LENGTH, size);
151        }
152        request_builder = request_builder.header(
153            CONTENT_TYPE,
154            args.content_type().unwrap_or("application/octet-stream"),
155        );
156
157        let request_builder = request_builder.extension(Operation::Write);
158
159        let mut request = request_builder
160            .header(
161                "Dropbox-API-Arg",
162                serde_json::to_string(&dropbox_update_args).map_err(new_json_serialize_error)?,
163            )
164            .body(body)
165            .map_err(new_request_build_error)?;
166
167        self.sign(&mut request).await?;
168        self.info.http_client().send(request).await
169    }
170
171    pub async fn dropbox_delete(&self, path: &str) -> Result<Response<Buffer>> {
172        let url = "https://api.dropboxapi.com/2/files/delete_v2".to_string();
173        let args = DropboxDeleteArgs {
174            path: self.build_path(path),
175        };
176
177        let bs = Bytes::from(serde_json::to_string(&args).map_err(new_json_serialize_error)?);
178
179        let mut request = Request::post(&url)
180            .header(CONTENT_TYPE, "application/json")
181            .header(CONTENT_LENGTH, bs.len())
182            .extension(Operation::Delete)
183            .body(Buffer::from(bs))
184            .map_err(new_request_build_error)?;
185
186        self.sign(&mut request).await?;
187        self.info.http_client().send(request).await
188    }
189
190    pub async fn dropbox_create_folder(&self, path: &str) -> Result<RpCreateDir> {
191        let url = "https://api.dropboxapi.com/2/files/create_folder_v2".to_string();
192        let args = DropboxCreateFolderArgs {
193            path: self.build_path(path),
194        };
195
196        let bs = Bytes::from(serde_json::to_string(&args).map_err(new_json_serialize_error)?);
197
198        let mut request = Request::post(&url)
199            .header(CONTENT_TYPE, "application/json")
200            .header(CONTENT_LENGTH, bs.len())
201            .extension(Operation::CreateDir)
202            .body(Buffer::from(bs))
203            .map_err(new_request_build_error)?;
204
205        self.sign(&mut request).await?;
206        let resp = self.info.http_client().send(request).await?;
207        let status = resp.status();
208        match status {
209            StatusCode::OK => Ok(RpCreateDir::default()),
210            _ => {
211                let err = parse_error(resp);
212                match err.kind() {
213                    ErrorKind::AlreadyExists => Ok(RpCreateDir::default()),
214                    _ => Err(err),
215                }
216            }
217        }
218    }
219
220    pub async fn dropbox_list(
221        &self,
222        path: &str,
223        recursive: bool,
224        limit: Option<usize>,
225    ) -> Result<Response<Buffer>> {
226        let url = "https://api.dropboxapi.com/2/files/list_folder".to_string();
227
228        // The default settings here align with the DropboxAPI default settings.
229        // Refer: https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
230        let args = DropboxListArgs {
231            path: self.build_path(path),
232            recursive,
233            limit: limit.unwrap_or(1000),
234        };
235
236        let bs = Bytes::from(serde_json::to_string(&args).map_err(new_json_serialize_error)?);
237
238        let mut request = Request::post(&url)
239            .header(CONTENT_TYPE, "application/json")
240            .header(CONTENT_LENGTH, bs.len())
241            .extension(Operation::List)
242            .body(Buffer::from(bs))
243            .map_err(new_request_build_error)?;
244
245        self.sign(&mut request).await?;
246        self.info.http_client().send(request).await
247    }
248
249    pub async fn dropbox_list_continue(&self, cursor: &str) -> Result<Response<Buffer>> {
250        let url = "https://api.dropboxapi.com/2/files/list_folder/continue".to_string();
251
252        let args = DropboxListContinueArgs {
253            cursor: cursor.to_string(),
254        };
255
256        let bs = Bytes::from(serde_json::to_string(&args).map_err(new_json_serialize_error)?);
257
258        let mut request = Request::post(&url)
259            .header(CONTENT_TYPE, "application/json")
260            .header(CONTENT_LENGTH, bs.len())
261            .extension(Operation::List)
262            .body(Buffer::from(bs))
263            .map_err(new_request_build_error)?;
264
265        self.sign(&mut request).await?;
266        self.info.http_client().send(request).await
267    }
268
269    pub async fn dropbox_copy(&self, from: &str, to: &str) -> Result<Response<Buffer>> {
270        let url = "https://api.dropboxapi.com/2/files/copy_v2".to_string();
271
272        let args = DropboxCopyArgs {
273            from_path: self.build_path(from),
274            to_path: self.build_path(to),
275        };
276
277        let bs = Bytes::from(serde_json::to_string(&args).map_err(new_json_serialize_error)?);
278
279        let mut request = Request::post(&url)
280            .header(CONTENT_TYPE, "application/json")
281            .header(CONTENT_LENGTH, bs.len())
282            .extension(Operation::Copy)
283            .body(Buffer::from(bs))
284            .map_err(new_request_build_error)?;
285
286        self.sign(&mut request).await?;
287        self.info.http_client().send(request).await
288    }
289
290    pub async fn dropbox_move(&self, from: &str, to: &str) -> Result<Response<Buffer>> {
291        let url = "https://api.dropboxapi.com/2/files/move_v2".to_string();
292
293        let args = DropboxMoveArgs {
294            from_path: self.build_path(from),
295            to_path: self.build_path(to),
296        };
297
298        let bs = Bytes::from(serde_json::to_string(&args).map_err(new_json_serialize_error)?);
299
300        let mut request = Request::post(&url)
301            .header(CONTENT_TYPE, "application/json")
302            .header(CONTENT_LENGTH, bs.len())
303            .extension(Operation::Rename)
304            .body(Buffer::from(bs))
305            .map_err(new_request_build_error)?;
306
307        self.sign(&mut request).await?;
308        self.info.http_client().send(request).await
309    }
310
311    pub async fn dropbox_get_metadata(&self, path: &str) -> Result<Response<Buffer>> {
312        let url = "https://api.dropboxapi.com/2/files/get_metadata".to_string();
313        let args = DropboxMetadataArgs {
314            path: self.build_path(path),
315            ..Default::default()
316        };
317
318        let bs = Bytes::from(serde_json::to_string(&args).map_err(new_json_serialize_error)?);
319
320        let mut request = Request::post(&url)
321            .header(CONTENT_TYPE, "application/json")
322            .header(CONTENT_LENGTH, bs.len())
323            .extension(Operation::Stat)
324            .body(Buffer::from(bs))
325            .map_err(new_request_build_error)?;
326
327        self.sign(&mut request).await?;
328
329        self.info.http_client().send(request).await
330    }
331}
332
333#[derive(Clone)]
334pub struct DropboxSigner {
335    pub client_id: String,
336    pub client_secret: String,
337    pub refresh_token: String,
338
339    pub access_token: String,
340    pub expires_in: Timestamp,
341}
342
343impl Default for DropboxSigner {
344    fn default() -> Self {
345        DropboxSigner {
346            refresh_token: String::new(),
347            client_id: String::new(),
348            client_secret: String::new(),
349
350            access_token: String::new(),
351            expires_in: Timestamp::MIN,
352        }
353    }
354}
355
356#[derive(Clone, Debug, Deserialize, Serialize)]
357struct DropboxDownloadArgs {
358    path: String,
359}
360
361#[derive(Clone, Debug, Deserialize, Serialize)]
362struct DropboxUploadArgs {
363    path: String,
364    mode: String,
365    mute: bool,
366    autorename: bool,
367    strict_conflict: bool,
368}
369
370impl Default for DropboxUploadArgs {
371    fn default() -> Self {
372        DropboxUploadArgs {
373            mode: "overwrite".to_string(),
374            path: "".to_string(),
375            mute: true,
376            autorename: false,
377            strict_conflict: false,
378        }
379    }
380}
381
382#[derive(Clone, Debug, Deserialize, Serialize)]
383struct DropboxDeleteArgs {
384    path: String,
385}
386
387#[derive(Clone, Debug, Deserialize, Serialize)]
388struct DropboxCreateFolderArgs {
389    path: String,
390}
391
392#[derive(Clone, Debug, Deserialize, Serialize)]
393struct DropboxListArgs {
394    path: String,
395    recursive: bool,
396    limit: usize,
397}
398
399#[derive(Clone, Debug, Deserialize, Serialize)]
400struct DropboxListContinueArgs {
401    cursor: String,
402}
403
404#[derive(Clone, Debug, Deserialize, Serialize)]
405struct DropboxCopyArgs {
406    from_path: String,
407    to_path: String,
408}
409
410#[derive(Clone, Debug, Deserialize, Serialize)]
411struct DropboxMoveArgs {
412    from_path: String,
413    to_path: String,
414}
415
416#[derive(Default, Clone, Debug, Deserialize, Serialize)]
417struct DropboxMetadataArgs {
418    include_deleted: bool,
419    include_has_explicit_shared_members: bool,
420    include_media_info: bool,
421    path: String,
422}
423
424#[derive(Clone, Deserialize)]
425struct DropboxTokenResponse {
426    access_token: String,
427    expires_in: u64,
428}
429
430#[derive(Default, Debug, Deserialize)]
431#[serde(default)]
432pub struct DropboxMetadataResponse {
433    #[serde(rename(deserialize = ".tag"))]
434    pub tag: String,
435    pub client_modified: String,
436    pub content_hash: Option<String>,
437    pub file_lock_info: Option<DropboxMetadataFileLockInfo>,
438    pub has_explicit_shared_members: Option<bool>,
439    pub id: String,
440    pub is_downloadable: Option<bool>,
441    pub name: String,
442    pub path_display: String,
443    pub path_lower: String,
444    pub property_groups: Option<Vec<DropboxMetadataPropertyGroup>>,
445    pub rev: Option<String>,
446    pub server_modified: Option<String>,
447    pub sharing_info: Option<DropboxMetadataSharingInfo>,
448    pub size: Option<u64>,
449}
450
451#[derive(Default, Debug, Deserialize)]
452#[serde(default)]
453pub struct DropboxMetadataFileLockInfo {
454    pub created: Option<String>,
455    pub is_lockholder: bool,
456    pub lockholder_name: Option<String>,
457}
458
459#[derive(Default, Debug, Deserialize)]
460#[serde(default)]
461pub struct DropboxMetadataPropertyGroup {
462    pub fields: Vec<DropboxMetadataPropertyGroupField>,
463    pub template_id: String,
464}
465
466#[derive(Default, Debug, Deserialize)]
467#[serde(default)]
468pub struct DropboxMetadataPropertyGroupField {
469    pub name: String,
470    pub value: String,
471}
472
473#[derive(Default, Debug, Deserialize)]
474#[serde(default)]
475pub struct DropboxMetadataSharingInfo {
476    pub modified_by: Option<String>,
477    pub parent_shared_folder_id: Option<String>,
478    pub read_only: Option<bool>,
479    pub shared_folder_id: Option<String>,
480    pub traverse_only: Option<bool>,
481    pub no_access: Option<bool>,
482}
483
484#[derive(Default, Debug, Deserialize)]
485#[serde(default)]
486pub struct DropboxListResponse {
487    pub entries: Vec<DropboxMetadataResponse>,
488    pub cursor: String,
489    pub has_more: bool,
490}