1use std::sync::Arc;
19
20use bytes::Buf;
21use http::StatusCode;
22
23use super::core::*;
24use super::deleter::DropboxDeleter;
25use super::lister::DropboxLister;
26use super::reader::*;
27use super::writer::DropboxWriter;
28use opendal_core::raw::*;
29use opendal_core::*;
30
31use std::fmt::Debug;
32
33use asyncband::mutex::Mutex;
34
35use super::DROPBOX_SCHEME;
36use super::config::DropboxConfig;
37use super::core::DropboxCore;
38use super::core::DropboxSigner;
39
40#[doc = include_str!("docs.md")]
42#[derive(Default)]
43pub struct DropboxBuilder {
44 pub(super) config: DropboxConfig,
45}
46
47impl Debug for DropboxBuilder {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct("Builder")
50 .field("config", &self.config)
51 .finish_non_exhaustive()
52 }
53}
54
55impl DropboxBuilder {
56 pub fn root(mut self, root: &str) -> Self {
60 self.config.root = if root.is_empty() {
61 None
62 } else {
63 Some(root.to_string())
64 };
65
66 self
67 }
68
69 pub fn access_token(mut self, access_token: &str) -> Self {
76 self.config.access_token = Some(access_token.to_string());
77 self
78 }
79
80 pub fn refresh_token(mut self, refresh_token: &str) -> Self {
86 self.config.refresh_token = Some(refresh_token.to_string());
87 self
88 }
89
90 pub fn client_id(mut self, client_id: &str) -> Self {
94 self.config.client_id = Some(client_id.to_string());
95 self
96 }
97
98 pub fn client_secret(mut self, client_secret: &str) -> Self {
102 self.config.client_secret = Some(client_secret.to_string());
103 self
104 }
105}
106
107impl Builder for DropboxBuilder {
108 type Config = DropboxConfig;
109
110 fn build(self) -> Result<impl Service> {
111 let root = normalize_root(&self.config.root.unwrap_or_default());
112
113 let signer = match (self.config.access_token, self.config.refresh_token) {
114 (Some(access_token), None) => DropboxSigner {
115 access_token,
116 expires_in: Timestamp::MAX,
118 ..Default::default()
119 },
120 (None, Some(refresh_token)) => {
121 let client_id = self.config.client_id.ok_or_else(|| {
122 Error::new(
123 ErrorKind::ConfigInvalid,
124 "client_id must be set when refresh_token is set",
125 )
126 .with_context("service", DROPBOX_SCHEME)
127 })?;
128 let client_secret = self.config.client_secret.ok_or_else(|| {
129 Error::new(
130 ErrorKind::ConfigInvalid,
131 "client_secret must be set when refresh_token is set",
132 )
133 .with_context("service", DROPBOX_SCHEME)
134 })?;
135
136 DropboxSigner {
137 refresh_token,
138 client_id,
139 client_secret,
140 ..Default::default()
141 }
142 }
143 (Some(_), Some(_)) => {
144 return Err(Error::new(
145 ErrorKind::ConfigInvalid,
146 "access_token and refresh_token can not be set at the same time",
147 )
148 .with_context("service", DROPBOX_SCHEME));
149 }
150 (None, None) => {
151 return Err(Error::new(
152 ErrorKind::ConfigInvalid,
153 "access_token or refresh_token must be set",
154 )
155 .with_context("service", DROPBOX_SCHEME));
156 }
157 };
158
159 Ok(DropboxBackend {
160 core: Arc::new(DropboxCore {
161 info: ServiceInfo::new(DROPBOX_SCHEME, &root, ""),
162 capability: Capability {
163 stat: true,
164
165 read: true,
166 read_with_suffix: true,
167
168 write: true,
169
170 create_dir: true,
171
172 delete: true,
173
174 list: true,
175 list_with_recursive: true,
176
177 copy: true,
178
179 rename: true,
180
181 shared: true,
182
183 ..Default::default()
184 },
185 root,
186 signer: Arc::new(Mutex::new(signer)),
187 }),
188 })
189 }
190}
191
192#[derive(Clone, Debug)]
193pub struct DropboxBackend {
194 pub core: Arc<DropboxCore>,
195}
196
197impl Service for DropboxBackend {
198 type Reader = oio::StreamReader<DropboxReader>;
199 type Writer = oio::OneShotWriter<DropboxWriter>;
200 type Lister = oio::PageLister<DropboxLister>;
201 type Deleter = oio::OneShotDeleter<DropboxDeleter>;
202 type Copier = oio::OneShotCopier;
203 type Composer = ();
204
205 fn info(&self) -> ServiceInfo {
206 self.core.info.clone()
207 }
208
209 fn capability(&self) -> Capability {
210 self.core.capability
211 }
212
213 async fn create_dir(
214 &self,
215 ctx: &OperationContext,
216 path: &str,
217 _args: OpCreateDir,
218 ) -> Result<RpCreateDir> {
219 let resp = self.core.dropbox_get_metadata(ctx, path).await?;
221 if StatusCode::OK == resp.status() {
222 let bytes = resp.into_body();
223 let decoded_response: DropboxMetadataResponse =
224 serde_json::from_reader(bytes.reader()).map_err(new_json_deserialize_error)?;
225 if "folder" == decoded_response.tag {
226 return Ok(RpCreateDir::default());
227 }
228 if "file" == decoded_response.tag {
229 return Err(Error::new(
230 ErrorKind::NotADirectory,
231 format!("it's not a directory {path}"),
232 ));
233 }
234 }
235
236 let res = self.core.dropbox_create_folder(ctx, path).await?;
237 Ok(res)
238 }
239
240 async fn stat(&self, ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
241 let resp = self.core.dropbox_get_metadata(ctx, path).await?;
242 let status = resp.status();
243 match status {
244 StatusCode::OK => {
245 let bytes = resp.into_body();
246 let decoded_response: DropboxMetadataResponse =
247 serde_json::from_reader(bytes.reader()).map_err(new_json_deserialize_error)?;
248 let entry_mode: EntryMode = match decoded_response.tag.as_str() {
249 "file" => EntryMode::FILE,
250 "folder" => EntryMode::DIR,
251 _ => EntryMode::Unknown,
252 };
253
254 let mut metadata = match entry_mode {
255 EntryMode::FILE => {
256 MetadataBuilder::file(decoded_response.size.ok_or_else(|| {
257 Error::new(
258 ErrorKind::Unexpected,
259 format!("no size found for file {path}"),
260 )
261 })?)
262 }
263 EntryMode::DIR => MetadataBuilder::dir(),
264 EntryMode::Unknown => MetadataBuilder::unknown(),
265 };
266 if entry_mode == EntryMode::FILE {
270 let date_utc_last_modified =
271 decoded_response.client_modified.parse::<Timestamp>()?;
272 metadata.last_modified(date_utc_last_modified);
273 }
274 Ok(RpStat::new(metadata.build()))
275 }
276 _ => Err(parse_error(
277 ErrorContext::new(ServiceOperation("GetMetadata")),
278 resp,
279 )),
280 }
281 }
282 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
283 let output: oio::StreamReader<DropboxReader> = {
284 Ok(oio::StreamReader::new(DropboxReader::new(
285 self.clone(),
286 ctx.clone(),
287 path,
288 args,
289 )))
290 }?;
291
292 Ok(output)
293 }
294
295 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
296 let output: oio::OneShotWriter<DropboxWriter> = {
297 Ok(oio::OneShotWriter::new(DropboxWriter::new(
298 self.core.clone(),
299 ctx.clone(),
300 args,
301 String::from(path),
302 )))
303 }?;
304
305 Ok(output)
306 }
307
308 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
309 let output: oio::OneShotDeleter<DropboxDeleter> = {
310 Ok(oio::OneShotDeleter::new(DropboxDeleter::new(
311 self.core.clone(),
312 ctx.clone(),
313 )))
314 }?;
315
316 Ok(output)
317 }
318
319 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
320 let output: oio::PageLister<DropboxLister> = {
321 Ok(oio::PageLister::new(DropboxLister::new(
322 self.core.clone(),
323 ctx.clone(),
324 path.to_string(),
325 args.recursive(),
326 args.limit(),
327 )))
328 }?;
329
330 Ok(output)
331 }
332
333 fn copy(
334 &self,
335 ctx: &OperationContext,
336 from: &str,
337 to: &str,
338 _: OpCopy,
339 ) -> Result<Self::Copier> {
340 let core = self.core.clone();
341 let ctx = ctx.clone();
342 let from = from.to_string();
343 let to = to.to_string();
344
345 Ok(oio::OneShotCopier::new(async move {
346 let resp = core.dropbox_copy(&ctx, &from, &to).await?;
347 let status = resp.status();
348
349 match status {
350 StatusCode::OK => {
351 let decoded_response: DropboxMetadataResponse =
352 serde_json::from_reader(resp.into_body().reader())
353 .map_err(new_json_deserialize_error)?;
354 DropboxWriter::parse_metadata(decoded_response)
355 }
356 _ => Err(parse_error(
357 ErrorContext::new(ServiceOperation("CopyFile")),
358 resp,
359 )),
360 }
361 }))
362 }
363
364 async fn rename(
365 &self,
366 ctx: &OperationContext,
367 from: &str,
368 to: &str,
369 _: OpRename,
370 ) -> Result<RpRename> {
371 let resp = self.core.dropbox_move(ctx, from, to).await?;
372
373 let status = resp.status();
374
375 match status {
376 StatusCode::OK => Ok(RpRename::default()),
377 _ => {
378 let err = parse_error(ErrorContext::new(ServiceOperation("MoveFile")), resp);
379 match err.kind() {
380 ErrorKind::NotFound => Ok(RpRename::default()),
381 _ => Err(err),
382 }
383 }
384 }
385 }
386
387 async fn presign(
388 &self,
389 _ctx: &OperationContext,
390 _path: &str,
391 _args: OpPresign,
392 ) -> Result<RpPresign> {
393 Err(Error::new(
394 ErrorKind::Unsupported,
395 "operation is not supported",
396 ))
397 }
398}