1use std::fmt::Debug;
19use std::sync::Arc;
20
21use bytes::Buf;
22use http::StatusCode;
23use log::debug;
24use opendal_core::raw::*;
25use opendal_core::*;
26
27use super::PCLOUD_SCHEME;
28use super::config::PcloudConfig;
29use super::core::PcloudError;
30use super::core::parse_error;
31use super::core::*;
32use super::deleter::PcloudDeleter;
33use super::lister::PcloudLister;
34use super::reader::*;
35use super::writer::PcloudWriter;
36use super::writer::PcloudWriters;
37
38#[doc = include_str!("docs.md")]
40#[derive(Default)]
41pub struct PcloudBuilder {
42 pub(super) config: PcloudConfig,
43}
44
45impl Debug for PcloudBuilder {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("PcloudBuilder")
48 .field("config", &self.config)
49 .finish_non_exhaustive()
50 }
51}
52
53impl PcloudBuilder {
54 pub fn root(mut self, root: &str) -> Self {
58 self.config.root = if root.is_empty() {
59 None
60 } else {
61 Some(root.to_string())
62 };
63
64 self
65 }
66
67 pub fn endpoint(mut self, endpoint: &str) -> Self {
73 self.config.endpoint = endpoint.to_string();
74
75 self
76 }
77
78 pub fn username(mut self, username: &str) -> Self {
82 self.config.username = if username.is_empty() {
83 None
84 } else {
85 Some(username.to_string())
86 };
87
88 self
89 }
90
91 pub fn password(mut self, password: &str) -> Self {
95 self.config.password = if password.is_empty() {
96 None
97 } else {
98 Some(password.to_string())
99 };
100
101 self
102 }
103}
104
105impl Builder for PcloudBuilder {
106 type Config = PcloudConfig;
107
108 fn build(self) -> Result<impl Service> {
110 debug!("backend build started: {:?}", self);
111
112 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
113 debug!("backend use root {}", root);
114
115 if self.config.endpoint.is_empty() {
117 return Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
118 .with_operation("Builder::build")
119 .with_context("service", PCLOUD_SCHEME));
120 }
121
122 debug!("backend use endpoint {}", self.config.endpoint);
123
124 let username = match &self.config.username {
125 Some(username) => Ok(username.clone()),
126 None => Err(Error::new(ErrorKind::ConfigInvalid, "username is empty")
127 .with_operation("Builder::build")
128 .with_context("service", PCLOUD_SCHEME)),
129 }?;
130
131 let password = match &self.config.password {
132 Some(password) => Ok(password.clone()),
133 None => Err(Error::new(ErrorKind::ConfigInvalid, "password is empty")
134 .with_operation("Builder::build")
135 .with_context("service", PCLOUD_SCHEME)),
136 }?;
137
138 Ok(PcloudBackend {
139 core: Arc::new(PcloudCore {
140 info: ServiceInfo::new(PCLOUD_SCHEME, &root, ""),
141 capability: Capability {
142 stat: true,
143
144 create_dir: true,
145
146 read: true,
147 read_with_suffix: true,
148
149 write: true,
150
151 delete: true,
152 rename: true,
153 copy: true,
154
155 list: true,
156
157 shared: true,
158
159 ..Default::default()
160 },
161 root,
162 endpoint: self.config.endpoint.clone(),
163 username,
164 password,
165 }),
166 })
167 }
168}
169
170#[derive(Debug, Clone)]
172pub struct PcloudBackend {
173 pub(crate) core: Arc<PcloudCore>,
174}
175
176impl Service for PcloudBackend {
177 type Reader = oio::StreamReader<PcloudReader>;
178 type Writer = PcloudWriters;
179 type Lister = oio::PageLister<PcloudLister>;
180 type Deleter = oio::OneShotDeleter<PcloudDeleter>;
181 type Copier = oio::OneShotCopier;
182 type Composer = ();
183
184 fn info(&self) -> ServiceInfo {
185 self.core.info.clone()
186 }
187
188 fn capability(&self) -> Capability {
189 self.core.capability
190 }
191
192 async fn create_dir(
193 &self,
194 ctx: &OperationContext,
195 path: &str,
196 _: OpCreateDir,
197 ) -> Result<RpCreateDir> {
198 self.core.ensure_dir_exists(ctx, path).await?;
199 Ok(RpCreateDir::default())
200 }
201
202 async fn stat(&self, ctx: &OperationContext, path: &str, _args: OpStat) -> Result<RpStat> {
203 let resp = self.core.stat(ctx, path).await?;
204
205 let status = resp.status();
206
207 match status {
208 StatusCode::OK => {
209 let bs = resp.into_body();
210 let resp: StatResponse =
211 serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
212 let result = resp.result;
213 if result == 2010 || result == 2055 || result == 2002 {
214 return Err(Error::new(ErrorKind::NotFound, format!("{resp:?}")));
215 }
216 if result != 0 {
217 return Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")));
218 }
219
220 if let Some(md) = resp.metadata {
221 let md = parse_stat_metadata(md);
222 return md.map(RpStat::new);
223 }
224
225 Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")))
226 }
227 _ => Err(parse_error(
228 ErrorContext::new(ServiceOperation("Stat")),
229 resp,
230 )),
231 }
232 }
233 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
234 let output: oio::StreamReader<PcloudReader> = {
235 Ok(oio::StreamReader::new(PcloudReader::new(
236 self.clone(),
237 ctx.clone(),
238 path,
239 args,
240 )))
241 }?;
242
243 Ok(output)
244 }
245
246 fn write(&self, ctx: &OperationContext, path: &str, _args: OpWrite) -> Result<Self::Writer> {
247 let output: PcloudWriters = {
248 let writer = PcloudWriter::new(self.core.clone(), ctx.clone(), path.to_string());
249
250 let w = oio::OneShotWriter::new(writer);
251
252 Ok(w)
253 }?;
254
255 Ok(output)
256 }
257
258 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
259 let output: oio::OneShotDeleter<PcloudDeleter> = {
260 Ok(oio::OneShotDeleter::new(PcloudDeleter::new(
261 self.core.clone(),
262 ctx.clone(),
263 )))
264 }?;
265
266 Ok(output)
267 }
268
269 fn list(&self, ctx: &OperationContext, path: &str, _args: OpList) -> Result<Self::Lister> {
270 let output: oio::PageLister<PcloudLister> = {
271 let l = PcloudLister::new(self.core.clone(), ctx.clone(), path);
272 Ok(oio::PageLister::new(l))
273 }?;
274
275 Ok(output)
276 }
277
278 fn copy(
279 &self,
280 ctx: &OperationContext,
281 from: &str,
282 to: &str,
283 args: OpCopy,
284 ) -> Result<Self::Copier> {
285 let backend = self.clone();
286 let core = self.core.clone();
287 let ctx = ctx.clone();
288 let from = from.to_string();
289 let to = to.to_string();
290 let source_content_length_hint = args.source_content_length_hint();
291
292 Ok(oio::OneShotCopier::new(async move {
293 let source_size = if from.ends_with('/') {
294 None
295 } else {
296 Some(match source_content_length_hint {
297 Some(size) => size,
298 None => backend
299 .stat(&ctx, &from, OpStat::default())
300 .await?
301 .into_metadata()
302 .content_length(),
303 })
304 };
305
306 core.ensure_dir_exists(&ctx, &to).await?;
307
308 let resp = if from.ends_with('/') {
309 core.copy_folder(&ctx, &from, &to).await?
310 } else {
311 core.copy_file(&ctx, &from, &to).await?
312 };
313
314 let status = resp.status();
315
316 match status {
317 StatusCode::OK => {
318 let bs = resp.into_body();
319 let resp: PcloudError =
320 serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
321 let result = resp.result;
322 if result == 2009 || result == 2010 || result == 2055 || result == 2002 {
323 Err(Error::new(ErrorKind::NotFound, format!("{resp:?}")))
324 } else if result != 0 {
325 Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")))
326 } else {
327 let metadata =
328 source_size.map_or_else(MetadataBuilder::dir, MetadataBuilder::file);
329 Ok(metadata.build())
330 }
331 }
332 _ => Err(parse_error(
333 ErrorContext::new(if from.ends_with('/') {
334 ServiceOperation("CopyFolder")
335 } else {
336 ServiceOperation("CopyFile")
337 }),
338 resp,
339 )),
340 }
341 }))
342 }
343
344 async fn rename(
345 &self,
346 ctx: &OperationContext,
347 from: &str,
348 to: &str,
349 _args: OpRename,
350 ) -> Result<RpRename> {
351 self.core.ensure_dir_exists(ctx, to).await?;
352
353 let resp = if from.ends_with('/') {
354 self.core.rename_folder(ctx, from, to).await?
355 } else {
356 self.core.rename_file(ctx, from, to).await?
357 };
358
359 let status = resp.status();
360
361 match status {
362 StatusCode::OK => {
363 let bs = resp.into_body();
364 let resp: PcloudError =
365 serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
366 let result = resp.result;
367 if result == 2009 || result == 2010 || result == 2055 || result == 2002 {
368 return Err(Error::new(ErrorKind::NotFound, format!("{resp:?}")));
369 }
370 if result != 0 {
371 return Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")));
372 }
373
374 Ok(RpRename::default())
375 }
376 _ => Err(parse_error(
377 ErrorContext::new(if from.ends_with('/') {
378 ServiceOperation("RenameFolder")
379 } else {
380 ServiceOperation("RenameFile")
381 }),
382 resp,
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}