1use std::fmt::Debug;
19use std::sync::Arc;
20
21use asyncband::mutex::Mutex;
22use asyncband::once::OnceCell;
23use bytes::Buf;
24use http::StatusCode;
25use log::debug;
26
27use super::KOOFR_SCHEME;
28use super::config::KoofrConfig;
29use super::core::File;
30use super::core::KoofrSigner;
31use super::core::parse_error;
32use super::core::{ErrorContext, KoofrCore};
33use super::deleter::KoofrDeleter;
34use super::lister::KoofrLister;
35use super::reader::*;
36use super::writer::KoofrWriter;
37use super::writer::KoofrWriters;
38use opendal_core::raw::*;
39use opendal_core::*;
40
41#[doc = include_str!("docs.md")]
43#[derive(Default)]
44pub struct KoofrBuilder {
45 pub(super) config: KoofrConfig,
46}
47
48impl Debug for KoofrBuilder {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("KoofrBuilder")
51 .field("config", &self.config)
52 .finish_non_exhaustive()
53 }
54}
55
56impl KoofrBuilder {
57 pub fn root(mut self, root: &str) -> Self {
61 self.config.root = if root.is_empty() {
62 None
63 } else {
64 Some(root.to_string())
65 };
66
67 self
68 }
69
70 pub fn endpoint(mut self, endpoint: &str) -> Self {
74 self.config.endpoint = endpoint.to_string();
75
76 self
77 }
78
79 pub fn email(mut self, email: &str) -> Self {
83 self.config.email = email.to_string();
84
85 self
86 }
87
88 pub fn password(mut self, password: &str) -> Self {
99 self.config.password = if password.is_empty() {
100 None
101 } else {
102 Some(password.to_string())
103 };
104
105 self
106 }
107}
108
109impl Builder for KoofrBuilder {
110 type Config = KoofrConfig;
111
112 fn build(self) -> Result<impl Service> {
114 debug!("backend build started: {:?}", self);
115
116 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
117 debug!("backend use root {}", root);
118
119 if self.config.endpoint.is_empty() {
120 return Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
121 .with_operation("Builder::build")
122 .with_context("service", KOOFR_SCHEME));
123 }
124
125 debug!("backend use endpoint {}", self.config.endpoint);
126
127 if self.config.email.is_empty() {
128 return Err(Error::new(ErrorKind::ConfigInvalid, "email is empty")
129 .with_operation("Builder::build")
130 .with_context("service", KOOFR_SCHEME));
131 }
132
133 debug!("backend use email {}", self.config.email);
134
135 let password = match &self.config.password {
136 Some(password) => Ok(password.clone()),
137 None => Err(Error::new(ErrorKind::ConfigInvalid, "password is empty")
138 .with_operation("Builder::build")
139 .with_context("service", KOOFR_SCHEME)),
140 }?;
141
142 let signer = Arc::new(Mutex::new(KoofrSigner::default()));
143
144 Ok(KoofrBackend {
145 core: Arc::new(KoofrCore {
146 info: ServiceInfo::new(KOOFR_SCHEME, &root, ""),
147 capability: Capability {
148 stat: true,
149
150 create_dir: true,
151
152 read: true,
153 read_with_suffix: true,
154
155 write: true,
156 write_can_empty: true,
157
158 delete: true,
159
160 rename: true,
161
162 copy: true,
163
164 list: true,
165
166 shared: true,
167
168 ..Default::default()
169 },
170 root,
171 endpoint: self.config.endpoint.clone(),
172 email: self.config.email.clone(),
173 password,
174 mount_id: OnceCell::new(),
175 signer,
176 }),
177 })
178 }
179}
180
181#[derive(Debug, Clone)]
183pub struct KoofrBackend {
184 pub(crate) core: Arc<KoofrCore>,
185}
186
187impl Service for KoofrBackend {
188 type Reader = oio::StreamReader<KoofrReader>;
189 type Writer = KoofrWriters;
190 type Lister = oio::PageLister<KoofrLister>;
191 type Deleter = oio::OneShotDeleter<KoofrDeleter>;
192 type Copier = oio::OneShotCopier;
193 type Composer = ();
194
195 fn info(&self) -> ServiceInfo {
196 self.core.info.clone()
197 }
198
199 fn capability(&self) -> Capability {
200 self.core.capability
201 }
202
203 async fn create_dir(
204 &self,
205 ctx: &OperationContext,
206 path: &str,
207 _: OpCreateDir,
208 ) -> Result<RpCreateDir> {
209 self.core.ensure_dir_exists(ctx, path).await?;
210 self.core
211 .create_dir(ctx, &build_abs_path(&self.core.root, path))
212 .await?;
213 Ok(RpCreateDir::default())
214 }
215
216 async fn stat(&self, ctx: &OperationContext, path: &str, _args: OpStat) -> Result<RpStat> {
217 let path = build_rooted_abs_path(&self.core.root, path);
218 let resp = self.core.info(ctx, &path).await?;
219
220 let status = resp.status();
221
222 match status {
223 StatusCode::OK => {
224 let bs = resp.into_body();
225
226 let file: File =
227 serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
228
229 let mode = if file.ty == "dir" {
230 EntryMode::DIR
231 } else {
232 EntryMode::FILE
233 };
234
235 let mut md = if mode == EntryMode::FILE {
236 MetadataBuilder::file(file.size)
237 } else {
238 MetadataBuilder::dir()
239 };
240
241 md.content_type(&file.content_type)
242 .last_modified(Timestamp::from_millisecond(file.modified)?);
243
244 Ok(RpStat::new(md.build()))
245 }
246 _ => Err(parse_error(
247 ErrorContext::new(ServiceOperation("FilesInfo")),
248 resp,
249 )),
250 }
251 }
252 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
253 let output: oio::StreamReader<KoofrReader> = {
254 Ok(oio::StreamReader::new(KoofrReader::new(
255 self.clone(),
256 ctx.clone(),
257 path,
258 args,
259 )))
260 }?;
261
262 Ok(output)
263 }
264
265 fn write(&self, ctx: &OperationContext, path: &str, _args: OpWrite) -> Result<Self::Writer> {
266 let output: KoofrWriters = {
267 let writer = KoofrWriter::new(self.core.clone(), ctx.clone(), path.to_string());
268
269 let w = oio::OneShotWriter::new(writer);
270
271 Ok(w)
272 }?;
273
274 Ok(output)
275 }
276
277 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
278 let output: oio::OneShotDeleter<KoofrDeleter> = {
279 Ok(oio::OneShotDeleter::new(KoofrDeleter::new(
280 self.core.clone(),
281 ctx.clone(),
282 )))
283 }?;
284
285 Ok(output)
286 }
287
288 fn list(&self, ctx: &OperationContext, path: &str, _args: OpList) -> Result<Self::Lister> {
289 let output: oio::PageLister<KoofrLister> = {
290 let l = KoofrLister::new(self.core.clone(), ctx.clone(), path);
291 Ok(oio::PageLister::new(l))
292 }?;
293
294 Ok(output)
295 }
296
297 fn copy(
298 &self,
299 ctx: &OperationContext,
300 from: &str,
301 to: &str,
302 args: OpCopy,
303 ) -> Result<Self::Copier> {
304 let backend = self.clone();
305 let core = self.core.clone();
306 let ctx = ctx.clone();
307 let from = from.to_string();
308 let to = to.to_string();
309 let source_content_length_hint = args.source_content_length_hint();
310
311 Ok(oio::OneShotCopier::new(async move {
312 let source_size = match source_content_length_hint {
313 Some(size) => size,
314 None => backend
315 .stat(&ctx, &from, OpStat::default())
316 .await?
317 .into_metadata()
318 .content_length(),
319 };
320
321 core.ensure_dir_exists(&ctx, &to).await?;
322 if from == to {
323 Ok(MetadataBuilder::file(source_size).build())
324 } else {
325 let resp = core.remove(&ctx, &to).await?;
326
327 let status = resp.status();
328
329 if status != StatusCode::OK && status != StatusCode::NOT_FOUND {
330 Err(parse_error(
331 ErrorContext::new(ServiceOperation("FilesRemove")),
332 resp,
333 ))
334 } else {
335 let resp = core.copy(&ctx, &from, &to).await?;
336
337 let status = resp.status();
338
339 match status {
340 StatusCode::OK => Ok(MetadataBuilder::file(source_size).build()),
341 _ => Err(parse_error(
342 ErrorContext::new(ServiceOperation("FilesCopy")),
343 resp,
344 )),
345 }
346 }
347 }
348 }))
349 }
350
351 async fn rename(
352 &self,
353 ctx: &OperationContext,
354 from: &str,
355 to: &str,
356 _args: OpRename,
357 ) -> Result<RpRename> {
358 self.core.ensure_dir_exists(ctx, to).await?;
359
360 if from == to {
361 return Ok(RpRename::default());
362 }
363
364 let resp = self.core.remove(ctx, to).await?;
365
366 let status = resp.status();
367
368 if status != StatusCode::OK && status != StatusCode::NOT_FOUND {
369 return Err(parse_error(
370 ErrorContext::new(ServiceOperation("FilesRemove")),
371 resp,
372 ));
373 }
374
375 let resp = self.core.move_object(ctx, from, to).await?;
376
377 let status = resp.status();
378
379 match status {
380 StatusCode::OK => Ok(RpRename::default()),
381 _ => Err(parse_error(
382 ErrorContext::new(ServiceOperation("FilesMove")),
383 resp,
384 )),
385 }
386 }
387
388 async fn presign(
389 &self,
390 _ctx: &OperationContext,
391 _path: &str,
392 _args: OpPresign,
393 ) -> Result<RpPresign> {
394 Err(Error::new(
395 ErrorKind::Unsupported,
396 "operation is not supported",
397 ))
398 }
399}