1use 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 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 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 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 signer.access_token.clone_from(&token.access_token);
94
95 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 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}