1use crate::{
2 DbxRequestErrorSummary, DbxRequestLimitsErrorSummary, DropboxError, DropboxResult,
3 MoveCopyOption, UploadMode, UploadOption,
4};
5#[cfg(feature = "non-blocking")]
6use async_trait::async_trait;
7#[cfg(feature = "non-blocking")]
8use reqwest::{header, header::HeaderMap, StatusCode};
9use serde_json::json;
10#[cfg(feature = "blocking")]
11use std::io::Read;
12use std::time;
13
14const CONTENT_END_POINT: &str = "https://content.dropboxapi.com";
15const OPERATION_END_POINT: &str = "https://api.dropboxapi.com";
16
17#[cfg(feature = "non-blocking")]
18#[derive(Debug, Clone)]
19pub struct AsyncDBXClient {
20 client: reqwest::Client,
21}
22
23#[cfg(feature = "non-blocking")]
24impl AsyncDBXClient {
25 pub fn new(token: &str) -> Self {
26 let mut auth_value = header::HeaderValue::from_str(&format!("Bearer {}", token)).unwrap();
27 auth_value.set_sensitive(true);
28 let mut headers = HeaderMap::new();
29 headers.insert(header::AUTHORIZATION, auth_value);
30 let client = reqwest::ClientBuilder::new()
31 .connect_timeout(time::Duration::from_secs(100))
32 .default_headers(headers)
33 .build()
34 .unwrap();
35 Self { client }
36 }
37
38 pub async fn check_user(&self, ping_str: &str) -> DropboxResult<()> {
39 let url = format!("{}{}", OPERATION_END_POINT, "/2/check/user");
40 let res = self
41 .client
42 .post(&url)
43 .header("Content-Type", "application/json")
44 .body(
45 json!(
46 {
47 "query":ping_str,
48 }
49 )
50 .to_string(),
51 )
52 .send()
53 .await?;
54 match res.status() {
55 reqwest::StatusCode::BAD_REQUEST => {
56 let text = res.text().await?;
57 return Err(DropboxError::DbxInvalidTokenError(text));
58 }
59 _ => handle_async_dbx_request_response(res).await,
60 }
61 }
62 pub async fn upload(
64 &self,
65 file: Vec<u8>,
66 path: &str,
67 option: UploadOption,
68 ) -> DropboxResult<()> {
69 let mode = match option.mode {
70 UploadMode::Add => "add".to_string(),
71 UploadMode::Overwrite => "overwrite".to_string(),
72 UploadMode::Update(ref rev) => json!({
73 ".tag":"update",
74 "update":rev
75 })
76 .to_string(),
77 };
78 let url = format!("{}{}", CONTENT_END_POINT, "/2/files/upload");
79 let res = self
80 .client
81 .post(&url)
82 .header("Content-Type", "application/octet-stream")
83 .header(
84 "Dropbox-API-Arg",
85 json!({
86 "path":path,
87 "mode":mode,
88 "autorename":option.allow_auto_rename,
89 "mute":option.mute_notification,
90 "strict_conflict":option.allow_strict_conflict
91 })
92 .to_string(),
93 )
94 .body(file)
95 .send()
96 .await?;
97 handle_async_dbx_request_response(res).await
98 }
99
100 pub async fn download(&self, path: &str) -> DropboxResult<Vec<u8>> {
102 let url = format!("{}{}", CONTENT_END_POINT, "/2/files/download");
103 let res = self
104 .client
105 .post(&url)
106 .header("Dropbox-API-Arg", json!({ "path": path }).to_string())
107 .send()
108 .await?;
109 handle_async_dbx_request_response(res).await
110 }
111
112 pub async fn move_file(
114 &self,
115 from_path: &str,
116 to_path: &str,
117 option: MoveCopyOption,
118 ) -> DropboxResult<()> {
119 let url = format!("{}{}", OPERATION_END_POINT, "/2/files/move_v2");
120 let res = self
121 .client
122 .post(&url)
123 .header("Content-Type", "application/json")
124 .body(
125 json!(
126 {
127 "from_path":from_path,
128 "to_path":to_path,
129 "allow_shared_folder": option.allow_shared_folder,
130 "autorename": option.auto_rename,
131 "allow_ownership_transfer": option.allow_ownership_transfer
132 }
133 )
134 .to_string(),
135 )
136 .send()
137 .await?;
138 handle_async_dbx_request_response(res).await
139 }
140
141 pub async fn copy(
142 &self,
143 from_path: &str,
144 to_path: &str,
145 option: MoveCopyOption,
146 ) -> DropboxResult<()> {
147 let url = format!("{}{}", OPERATION_END_POINT, "/2/files/copy_v2");
148 let res = self
149 .client
150 .post(&url)
151 .header("Content-Type", "application/json")
152 .body(
153 json!(
154 {
155 "from_path":from_path,
156 "to_path":to_path,
157 "allow_shared_folder": option.allow_shared_folder,
158 "autorename": option.auto_rename,
159 "allow_ownership_transfer": option.allow_ownership_transfer
160 }
161 )
162 .to_string(),
163 )
164 .send()
165 .await?;
166 handle_async_dbx_request_response(res).await
167 }
168}
169
170#[inline]
171#[cfg(feature = "non-blocking")]
172async fn handle_async_dbx_request_response<T: AsyncFrom<reqwest::Response>>(
173 res: reqwest::Response,
174) -> DropboxResult<T> {
175 if res.status() != StatusCode::OK {
176 match res.status() {
177 StatusCode::BAD_REQUEST => {
178 let text = res.text().await?;
179 return Err(DropboxError::DbxPathError(text));
180 }
181 StatusCode::UNAUTHORIZED => {
182 let error_summary = res.json::<DbxRequestErrorSummary>().await?;
183 return Err(DropboxError::DbxInvalidTokenError(
184 error_summary.error_summary,
185 ));
186 }
187 StatusCode::FORBIDDEN => {
188 let error_summary = res.json::<DbxRequestErrorSummary>().await?;
189 return Err(DropboxError::DbxAccessError(error_summary.error_summary));
190 }
191 StatusCode::CONFLICT => {
192 let error_summary = res.json::<DbxRequestErrorSummary>().await?;
193 let content: Vec<&str> = error_summary.error_summary.split("/").collect();
194 match content[0] == "path" {
195 true => return Err(DropboxError::DbxPathError(content[1].to_string())),
196 false => match content[0] == "from_lookup" {
197 true => {
198 return Err(DropboxError::DbxFromLookUpError(content[1].to_string()))
199 }
200 false => match content[0] == "to" {
201 true => {
202 return Err(DropboxError::DbxExistedError(content[1].to_string()))
203 }
204 false => {
205 return Err(DropboxError::DbxConflictError(
206 error_summary.error_summary,
207 ))
208 }
209 },
210 },
211 }
212 }
213 StatusCode::TOO_MANY_REQUESTS => {
214 let text = res.text().await?;
215 match serde_json::from_str::<DbxRequestLimitsErrorSummary>(&text) {
216 Ok(error_summary) => {
217 return Err(DropboxError::DbxRequestLimitsError(format!(
218 "{} , retry after {}",
219 error_summary.error_summary, error_summary.error.retry_after
220 )));
221 }
222 Err(_) => {
223 return Err(DropboxError::DbxRequestLimitsError(text));
224 }
225 }
226 }
227 StatusCode::INTERNAL_SERVER_ERROR | StatusCode::SERVICE_UNAVAILABLE => {
228 let text = res.text().await?;
229 return Err(DropboxError::DbxServerError(text));
230 }
231 _ => {
232 let text = res.text().await?;
233 match serde_json::from_str::<DbxRequestErrorSummary>(&text) {
234 Ok(error_summary) => {
235 return Err(DropboxError::OtherError(error_summary.error_summary));
236 }
237 Err(_) => {
238 return Err(DropboxError::OtherError(text));
239 }
240 }
241 }
242 }
243 }
244 T::from(res).await.map(|i| *i)
245}
246
247#[cfg(feature = "non-blocking")]
248#[async_trait]
249trait AsyncFrom<T> {
250 async fn from(t: T) -> DropboxResult<Box<Self>>;
251}
252
253#[cfg(feature = "non-blocking")]
254#[async_trait]
255impl AsyncFrom<reqwest::Response> for Vec<u8> {
256 async fn from(res: reqwest::Response) -> DropboxResult<Box<Self>> {
257 res.bytes()
258 .await
259 .map(|b| Box::new(b.to_vec()))
260 .map_err(|e| DropboxError::NonBlockingRequestError(e))
261 }
262}
263
264#[cfg(feature = "non-blocking")]
265#[async_trait]
266impl AsyncFrom<reqwest::Response> for () {
267 async fn from(_res: reqwest::Response) -> DropboxResult<Box<Self>> {
268 DropboxResult::Ok(Box::new(()))
269 }
270}
271
272#[cfg(feature = "blocking")]
274pub struct DBXClient {
276 client: ureq::Agent,
277 token: String,
278}
279
280#[cfg(feature = "blocking")]
281impl DBXClient {
282 pub fn new(token: &str) -> Self {
283 let client = ureq::AgentBuilder::new()
284 .timeout(time::Duration::from_secs(10))
285 .build();
286 let token = token.to_string();
287 Self { client, token }
288 }
289
290 pub fn check_user(&self, ping_str: &str) -> DropboxResult<()> {
291 let url = format!("{}{}", OPERATION_END_POINT, "/2/check/user");
292 let res = self
293 .client
294 .post(&url)
295 .set("Authorization", &format!("Bearer {}", self.token))
296 .set("Content-Type", "application/json")
297 .send_json(json!(
298 {
299 "query":ping_str,
300 }
301 ))?;
302 match res.status() {
303 400 => {
304 let text = res.into_string()?;
305 return Err(DropboxError::DbxInvalidTokenError(text));
306 }
307 _ => handle_dbx_request_response(res),
308 }
309 }
310 pub fn upload(&self, file: Vec<u8>, path: &str, option: UploadOption) -> DropboxResult<()> {
312 let mode = match option.mode {
313 UploadMode::Add => "add".to_string(),
314 UploadMode::Overwrite => "overwrite".to_string(),
315 UploadMode::Update(ref rev) => json!({
316 ".tag":"update",
317 "update":rev
318 })
319 .to_string(),
320 };
321 let url = format!("{}{}", CONTENT_END_POINT, "/2/files/upload");
322 let res = self
323 .client
324 .post(&url)
325 .set("Authorization", &format!("Bearer {}", self.token))
326 .set("Content-Type", "application/octet-stream")
327 .set(
328 "Dropbox-API-Arg",
329 json!({
330 "path":path,
331 "mode":mode,
332 "autorename":option.allow_auto_rename,
333 "mute":option.mute_notification,
334 "strict_conflict":option.allow_strict_conflict
335 })
336 .to_string()
337 .as_str(),
338 )
339 .send_bytes(&file)?;
340
341 handle_dbx_request_response(res)
342 }
343
344 pub fn download(&self, path: &str) -> DropboxResult<Vec<u8>> {
346 let url = format!("{}{}", CONTENT_END_POINT, "/2/files/download");
347 let res = self
348 .client
349 .post(&url)
350 .set("Authorization", &format!("Bearer {}", self.token))
351 .set(
352 "Dropbox-API-Arg",
353 json!({ "path": path }).to_string().as_str(),
354 )
355 .call()?;
356
357 handle_dbx_request_response(res)
358 }
359
360 pub fn move_file(
362 &self,
363 from_path: &str,
364 to_path: &str,
365 option: MoveCopyOption,
366 ) -> DropboxResult<()> {
367 let url = format!("{}{}", OPERATION_END_POINT, "/2/files/move_v2");
368 let res = self
369 .client
370 .post(&url)
371 .set("Content-Type", "application/json")
372 .set("Authorization", &format!("Bearer {}", self.token))
373 .send_json(json!(
374 {
375 "from_path":from_path,
376 "to_path":to_path,
377 "allow_shared_folder": option.allow_shared_folder,
378 "autorename": option.auto_rename,
379 "allow_ownership_transfer": option.allow_ownership_transfer
380 }
381 ))?;
382 handle_dbx_request_response(res)
383 }
384
385 pub fn copy(
386 &self,
387 from_path: &str,
388 to_path: &str,
389 option: MoveCopyOption,
390 ) -> DropboxResult<()> {
391 let url = format!("{}{}", OPERATION_END_POINT, "/2/files/copy_v2");
392 let res = self
393 .client
394 .post(&url)
395 .set("Content-Type", "application/json")
396 .set("Authorization", &format!("Bearer {}", self.token))
397 .send_json(json!(
398 {
399 "from_path":from_path,
400 "to_path":to_path,
401 "allow_shared_folder": option.allow_shared_folder,
402 "autorename": option.auto_rename,
403 "allow_ownership_transfer": option.allow_ownership_transfer
404 }
405 ))?;
406 handle_dbx_request_response(res)
407 }
408}
409
410#[cfg(feature = "blocking")]
411trait FromRes<T> {
412 fn from_res(t: T) -> DropboxResult<Box<Self>>;
413}
414
415#[cfg(feature = "blocking")]
416impl FromRes<ureq::Response> for Vec<u8> {
417 fn from_res(res: ureq::Response) -> DropboxResult<Box<Self>> {
418 let len = res
419 .header("Content-Length")
420 .and_then(|s| s.parse::<usize>().ok())
421 .unwrap();
422 let mut bytes: Vec<u8> = Vec::with_capacity(len);
423 res.into_reader().read_to_end(&mut bytes)?;
424 Ok(Box::new(bytes))
425 }
426}
427
428#[cfg(feature = "blocking")]
429impl FromRes<ureq::Response> for () {
430 fn from_res(_res: ureq::Response) -> DropboxResult<Box<Self>> {
431 DropboxResult::Ok(Box::new(()))
432 }
433}
434
435#[inline]
436#[cfg(feature = "blocking")]
437fn handle_dbx_request_response<T: FromRes<ureq::Response>>(
438 res: ureq::Response,
439) -> DropboxResult<T> {
440 if res.status() != 200 {
441 match res.status() {
442 400 => {
443 let text = res.into_string()?;
444 return Err(DropboxError::DbxPathError(text));
445 }
446 401 => {
447 let error_summary = res.into_json::<DbxRequestErrorSummary>()?;
448 return Err(DropboxError::DbxInvalidTokenError(
449 error_summary.error_summary,
450 ));
451 }
452 403 => {
453 let error_summary = res.into_json::<DbxRequestErrorSummary>()?;
454 return Err(DropboxError::DbxAccessError(error_summary.error_summary));
455 }
456 409 => {
457 let error_summary = res.into_json::<DbxRequestErrorSummary>()?;
458 let content: Vec<&str> = error_summary.error_summary.split("/").collect();
459 match content[0] == "path" {
460 true => return Err(DropboxError::DbxPathError(content[1].to_string())),
461 false => match content[0] == "from_lookup" {
462 true => {
463 return Err(DropboxError::DbxFromLookUpError(content[1].to_string()))
464 }
465 false => match content[0] == "to" {
466 true => {
467 return Err(DropboxError::DbxExistedError(content[1].to_string()))
468 }
469 false => {
470 return Err(DropboxError::DbxConflictError(
471 error_summary.error_summary,
472 ))
473 }
474 },
475 },
476 }
477 }
478 429 => {
479 let text = res.into_string()?;
480 match serde_json::from_str::<DbxRequestLimitsErrorSummary>(&text) {
481 Ok(error_summary) => {
482 return Err(DropboxError::DbxRequestLimitsError(format!(
483 "{} , retry after {}",
484 error_summary.error_summary, error_summary.error.retry_after
485 )));
486 }
487 Err(_) => {
488 return Err(DropboxError::DbxRequestLimitsError(text));
489 }
490 }
491 }
492 500 | 503 => {
493 let text = res.into_string()?;
494 return Err(DropboxError::DbxServerError(text));
495 }
496 _ => {
497 let text = res.into_string()?;
498 match serde_json::from_str::<DbxRequestErrorSummary>(&text) {
499 Ok(error_summary) => {
500 return Err(DropboxError::OtherError(error_summary.error_summary));
501 }
502 Err(_) => {
503 return Err(DropboxError::OtherError(text));
504 }
505 }
506 }
507 }
508 }
509 T::from_res(res).map(|i| *i)
510}