1use std::fmt::Debug;
19use std::sync::Arc;
20
21use bytes::Buf;
22use http::StatusCode;
23use log::debug;
24use mea::mutex::Mutex;
25use mea::once::OnceCell;
26
27use super::KOOFR_SCHEME;
28use super::config::KoofrConfig;
29use super::core::File;
30use super::core::KoofrCore;
31use super::core::KoofrSigner;
32use super::core::parse_error;
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
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 _: OpCreateDir,
207 ) -> Result<RpCreateDir> {
208 self.core.ensure_dir_exists(ctx, path).await?;
209 self.core
210 .create_dir(ctx, &build_abs_path(&self.core.root, path))
211 .await?;
212 Ok(RpCreateDir::default())
213 }
214
215 async fn stat(&self, ctx: &OperationContext, path: &str, _args: OpStat) -> Result<RpStat> {
216 let path = build_rooted_abs_path(&self.core.root, path);
217 let resp = self.core.info(ctx, &path).await?;
218
219 let status = resp.status();
220
221 match status {
222 StatusCode::OK => {
223 let bs = resp.into_body();
224
225 let file: File =
226 serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
227
228 let mode = if file.ty == "dir" {
229 EntryMode::DIR
230 } else {
231 EntryMode::FILE
232 };
233
234 let mut md = Metadata::new(mode);
235
236 md.set_content_length(file.size)
237 .set_content_type(&file.content_type)
238 .set_last_modified(Timestamp::from_millisecond(file.modified)?);
239
240 Ok(RpStat::new(md))
241 }
242 _ => Err(parse_error(resp)),
243 }
244 }
245 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
246 let output: oio::StreamReader<KoofrReader> = {
247 Ok(oio::StreamReader::new(KoofrReader::new(
248 self.clone(),
249 ctx.clone(),
250 path,
251 args,
252 )))
253 }?;
254
255 Ok(output)
256 }
257
258 fn write(&self, ctx: &OperationContext, path: &str, _args: OpWrite) -> Result<Self::Writer> {
259 let output: KoofrWriters = {
260 let writer = KoofrWriter::new(self.core.clone(), ctx.clone(), path.to_string());
261
262 let w = oio::OneShotWriter::new(writer);
263
264 Ok(w)
265 }?;
266
267 Ok(output)
268 }
269
270 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
271 let output: oio::OneShotDeleter<KoofrDeleter> = {
272 Ok(oio::OneShotDeleter::new(KoofrDeleter::new(
273 self.core.clone(),
274 ctx.clone(),
275 )))
276 }?;
277
278 Ok(output)
279 }
280
281 fn list(&self, ctx: &OperationContext, path: &str, _args: OpList) -> Result<Self::Lister> {
282 let output: oio::PageLister<KoofrLister> = {
283 let l = KoofrLister::new(self.core.clone(), ctx.clone(), path);
284 Ok(oio::PageLister::new(l))
285 }?;
286
287 Ok(output)
288 }
289
290 fn copy(
291 &self,
292 ctx: &OperationContext,
293 from: &str,
294 to: &str,
295 _args: OpCopy,
296 _opts: OpCopier,
297 ) -> Result<Self::Copier> {
298 let core = self.core.clone();
299 let ctx = ctx.clone();
300 let from = from.to_string();
301 let to = to.to_string();
302
303 Ok(oio::OneShotCopier::new(async move {
304 core.ensure_dir_exists(&ctx, &to).await?;
305 if from == to {
306 Ok(Metadata::default())
307 } else {
308 let resp = core.remove(&ctx, &to).await?;
309
310 let status = resp.status();
311
312 if status != StatusCode::OK && status != StatusCode::NOT_FOUND {
313 Err(parse_error(resp))
314 } else {
315 let resp = core.copy(&ctx, &from, &to).await?;
316
317 let status = resp.status();
318
319 match status {
320 StatusCode::OK => Ok(Metadata::default()),
321 _ => Err(parse_error(resp)),
322 }
323 }
324 }
325 }))
326 }
327
328 async fn rename(
329 &self,
330 ctx: &OperationContext,
331 from: &str,
332 to: &str,
333 _args: OpRename,
334 ) -> Result<RpRename> {
335 self.core.ensure_dir_exists(ctx, to).await?;
336
337 if from == to {
338 return Ok(RpRename::default());
339 }
340
341 let resp = self.core.remove(ctx, to).await?;
342
343 let status = resp.status();
344
345 if status != StatusCode::OK && status != StatusCode::NOT_FOUND {
346 return Err(parse_error(resp));
347 }
348
349 let resp = self.core.move_object(ctx, from, to).await?;
350
351 let status = resp.status();
352
353 match status {
354 StatusCode::OK => Ok(RpRename::default()),
355 _ => Err(parse_error(resp)),
356 }
357 }
358
359 async fn presign(
360 &self,
361 _ctx: &OperationContext,
362 _path: &str,
363 _args: OpPresign,
364 ) -> Result<RpPresign> {
365 Err(Error::new(
366 ErrorKind::Unsupported,
367 "operation is not supported",
368 ))
369 }
370}