opendal_service_aliyun_drive/
backend.rs1use std::fmt::Debug;
19use std::sync::Arc;
20
21use asyncband::mutex::Mutex;
22use bytes::Buf;
23use log::debug;
24
25use super::ALIYUN_DRIVE_SCHEME;
26use super::config::AliyunDriveConfig;
27use super::core::*;
28use super::deleter::AliyunDriveDeleter;
29use super::lister::AliyunDriveLister;
30use super::reader::*;
31use super::writer::AliyunDriveLazyWriter;
32use opendal_core::raw::*;
33use opendal_core::*;
34
35#[doc = include_str!("docs.md")]
36#[derive(Default)]
37pub struct AliyunDriveBuilder {
38 pub(super) config: AliyunDriveConfig,
39}
40
41impl Debug for AliyunDriveBuilder {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("AliyunDriveBuilder")
44 .field("config", &self.config)
45 .finish_non_exhaustive()
46 }
47}
48
49impl AliyunDriveBuilder {
50 pub fn root(mut self, root: &str) -> Self {
54 self.config.root = if root.is_empty() {
55 None
56 } else {
57 Some(root.to_string())
58 };
59
60 self
61 }
62
63 pub fn access_token(mut self, access_token: &str) -> Self {
65 self.config.access_token = Some(access_token.to_string());
66
67 self
68 }
69
70 pub fn client_id(mut self, client_id: &str) -> Self {
72 self.config.client_id = Some(client_id.to_string());
73
74 self
75 }
76
77 pub fn client_secret(mut self, client_secret: &str) -> Self {
79 self.config.client_secret = Some(client_secret.to_string());
80
81 self
82 }
83
84 pub fn refresh_token(mut self, refresh_token: &str) -> Self {
86 self.config.refresh_token = Some(refresh_token.to_string());
87
88 self
89 }
90
91 pub fn drive_type(mut self, drive_type: &str) -> Self {
93 self.config.drive_type = drive_type.to_string();
94
95 self
96 }
97}
98
99impl Builder for AliyunDriveBuilder {
100 type Config = AliyunDriveConfig;
101
102 fn build(self) -> Result<impl Service> {
103 debug!("backend build started: {:?}", self);
104
105 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
106 debug!("backend use root {}", root);
107
108 let sign = match self.config.access_token.clone() {
109 Some(access_token) if !access_token.is_empty() => {
110 AliyunDriveSign::Access(access_token)
111 }
112 _ => match (
113 self.config.client_id.clone(),
114 self.config.client_secret.clone(),
115 self.config.refresh_token.clone(),
116 ) {
117 (Some(client_id), Some(client_secret), Some(refresh_token)) if
118 !client_id.is_empty() && !client_secret.is_empty() && !refresh_token.is_empty() => {
119 AliyunDriveSign::Refresh(client_id, client_secret, refresh_token, None, 0)
120 }
121 _ => return Err(Error::new(
122 ErrorKind::ConfigInvalid,
123 "access_token and a set of client_id, client_secret, and refresh_token are both missing.")
124 .with_operation("Builder::build")
125 .with_context("service", ALIYUN_DRIVE_SCHEME)),
126 },
127 };
128
129 let drive_type = match self.config.drive_type.as_str() {
130 "" | "default" => DriveType::Default,
131 "resource" => DriveType::Resource,
132 "backup" => DriveType::Backup,
133 _ => {
134 return Err(Error::new(
135 ErrorKind::ConfigInvalid,
136 "drive_type is invalid.",
137 ));
138 }
139 };
140 debug!("backend use drive_type {drive_type:?}");
141
142 Ok(AliyunDriveBackend {
143 core: Arc::new(AliyunDriveCore {
144 info: ServiceInfo::new(ALIYUN_DRIVE_SCHEME, &root, ""),
145 capability: Capability {
146 stat: true,
147 create_dir: true,
148 read: true,
149 read_with_suffix: true,
150 write: true,
151 write_can_multi: true,
152 write_multi_min_size: Some(100 * 1024),
154 write_multi_max_size: if cfg!(target_pointer_width = "64") {
156 Some(5 * 1024 * 1024 * 1024)
157 } else {
158 Some(usize::MAX)
159 },
160 delete: true,
161 copy: true,
162 rename: true,
163 list: true,
164 list_with_limit: true,
165 shared: true,
166 ..Default::default()
167 },
168 endpoint: "https://openapi.alipan.com".to_string(),
169 root,
170 drive_type,
171 signer: Arc::new(Mutex::new(AliyunDriveSigner {
172 drive_id: None,
173 sign,
174 })),
175 dir_lock: Arc::new(Mutex::new(())),
176 }),
177 })
178 }
179}
180
181#[derive(Clone, Debug)]
182pub struct AliyunDriveBackend {
183 pub(crate) core: Arc<AliyunDriveCore>,
184}
185
186impl Service for AliyunDriveBackend {
187 type Reader = oio::StreamReader<AliyunDriveReader>;
188 type Writer = AliyunDriveLazyWriter;
189 type Lister = oio::PageLister<AliyunDriveLister>;
190 type Deleter = oio::OneShotDeleter<AliyunDriveDeleter>;
191 type Copier = oio::OneShotCopier;
192 type Composer = ();
193
194 fn info(&self) -> ServiceInfo {
195 self.core.info.clone()
196 }
197
198 fn capability(&self) -> Capability {
199 self.core.capability
200 }
201
202 async fn create_dir(
203 &self,
204 ctx: &OperationContext,
205 path: &str,
206 _args: OpCreateDir,
207 ) -> Result<RpCreateDir> {
208 self.core.ensure_dir_exists(ctx, path).await?;
209
210 Ok(RpCreateDir::default())
211 }
212
213 async fn rename(
214 &self,
215 ctx: &OperationContext,
216 from: &str,
217 to: &str,
218 _args: OpRename,
219 ) -> Result<RpRename> {
220 if from == to {
221 return Ok(RpRename::default());
222 }
223 let res = self.core.get_by_path(ctx, from).await?;
224 let file: AliyunDriveFile =
225 serde_json::from_reader(res.reader()).map_err(new_json_serialize_error)?;
226 match self.core.get_by_path(ctx, to).await {
228 Err(err) if err.kind() == ErrorKind::NotFound => {}
229 Err(err) => return Err(err),
230 Ok(res) => {
231 let file: AliyunDriveFile =
232 serde_json::from_reader(res.reader()).map_err(new_json_serialize_error)?;
233 self.core.delete_path(ctx, &file.file_id).await?;
234 }
235 };
236
237 let parent_file_id = self.core.ensure_dir_exists(ctx, get_parent(to)).await?;
238 self.core
239 .move_path(ctx, &file.file_id, &parent_file_id)
240 .await?;
241
242 let from_name = get_basename(from);
243 let to_name = get_basename(to);
244
245 if from_name != to_name {
246 self.core.update_path(ctx, &file.file_id, to_name).await?;
247 }
248
249 Ok(RpRename::default())
250 }
251
252 fn copy(
253 &self,
254 ctx: &OperationContext,
255 from: &str,
256 to: &str,
257 args: OpCopy,
258 ) -> Result<Self::Copier> {
259 let core = self.core.clone();
260 let ctx = ctx.clone();
261 let from = from.to_string();
262 let to = to.to_string();
263 let source_content_length_hint = args.source_content_length_hint();
264
265 Ok(oio::OneShotCopier::new(async move {
266 let res = core.get_by_path(&ctx, &from).await?;
267 let file: AliyunDriveFile =
268 serde_json::from_reader(res.reader()).map_err(new_json_serialize_error)?;
269 let source_content_length =
270 file.size.or(source_content_length_hint).ok_or_else(|| {
271 Error::new(
272 ErrorKind::Unexpected,
273 "Aliyun Drive source file does not contain a size",
274 )
275 })?;
276
277 if from == to {
278 Ok(MetadataBuilder::file(source_content_length).build())
279 } else {
280 match core.get_by_path(&ctx, &to).await {
282 Err(err) if err.kind() == ErrorKind::NotFound => {}
283 Err(err) => Err(err)?,
284 Ok(res) => {
285 let file: AliyunDriveFile = serde_json::from_reader(res.reader())
286 .map_err(new_json_serialize_error)?;
287 core.delete_path(&ctx, &file.file_id).await?;
288 }
289 }
290 let parent_path = get_parent(&to);
293 let parent_file_id = core.ensure_dir_exists(&ctx, parent_path).await?;
294
295 let auto_rename = file.parent_file_id == parent_file_id;
299 let res = core
300 .copy_path(&ctx, &file.file_id, &parent_file_id, auto_rename)
301 .await?;
302 let file: CopyResponse =
303 serde_json::from_reader(res.reader()).map_err(new_json_serialize_error)?;
304 let file_id = file.file_id;
305
306 let from_name = get_basename(&from);
307 let to_name = get_basename(&to);
308
309 if from_name != to_name {
310 core.update_path(&ctx, &file_id, to_name).await?;
311 }
312
313 Ok(MetadataBuilder::file(source_content_length).build())
314 }
315 }))
316 }
317
318 async fn stat(&self, ctx: &OperationContext, path: &str, _args: OpStat) -> Result<RpStat> {
319 let res = self.core.get_by_path(ctx, path).await?;
320 let file: AliyunDriveFile =
321 serde_json::from_reader(res.reader()).map_err(new_json_serialize_error)?;
322
323 if file.path_type == "folder" {
324 let mut meta = MetadataBuilder::dir();
325 meta.last_modified(file.updated_at.parse::<Timestamp>().map_err(|e| {
326 Error::new(ErrorKind::Unexpected, "parse last modified time").set_source(e)
327 })?);
328
329 return Ok(RpStat::new(meta.build()));
330 }
331
332 let size = file.size.ok_or_else(|| {
333 Error::new(
334 ErrorKind::Unexpected,
335 "aliyun drive stat response does not contain file size",
336 )
337 })?;
338 let mut meta = MetadataBuilder::file(size);
339 meta.last_modified(file.updated_at.parse::<Timestamp>().map_err(|e| {
340 Error::new(ErrorKind::Unexpected, "parse last modified time").set_source(e)
341 })?);
342 if let Some(v) = file.content_type {
343 meta.content_type(v);
344 }
345
346 Ok(RpStat::new(meta.build()))
347 }
348 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
349 let output: oio::StreamReader<AliyunDriveReader> = {
350 Ok(oio::StreamReader::new(AliyunDriveReader::new(
351 self.clone(),
352 ctx.clone(),
353 path,
354 args,
355 )))
356 }?;
357
358 Ok(output)
359 }
360
361 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
362 let output: oio::OneShotDeleter<AliyunDriveDeleter> = {
363 Ok(oio::OneShotDeleter::new(AliyunDriveDeleter::new(
364 self.core.clone(),
365 ctx.clone(),
366 )))
367 }?;
368
369 Ok(output)
370 }
371
372 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
373 let output: oio::PageLister<AliyunDriveLister> = {
374 let l = AliyunDriveLister::new_with_path(
375 self.core.clone(),
376 ctx.clone(),
377 path.to_string(),
378 args.limit(),
379 );
380
381 Ok(oio::PageLister::new(l))
382 }?;
383
384 Ok(output)
385 }
386
387 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
388 Ok(AliyunDriveLazyWriter::new(
389 self.core.clone(),
390 ctx.clone(),
391 path.to_string(),
392 args,
393 ))
394 }
395
396 async fn presign(
397 &self,
398 _ctx: &OperationContext,
399 _path: &str,
400 _args: OpPresign,
401 ) -> Result<RpPresign> {
402 Err(Error::new(
403 ErrorKind::Unsupported,
404 "operation is not supported",
405 ))
406 }
407}