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
183 fn info(&self) -> ServiceInfo {
184 self.core.info.clone()
185 }
186
187 fn capability(&self) -> Capability {
188 self.core.capability
189 }
190
191 async fn create_dir(
192 &self,
193 ctx: &OperationContext,
194 path: &str,
195 _: OpCreateDir,
196 ) -> Result<RpCreateDir> {
197 self.core.ensure_dir_exists(ctx, path).await?;
198 Ok(RpCreateDir::default())
199 }
200
201 async fn stat(&self, ctx: &OperationContext, path: &str, _args: OpStat) -> Result<RpStat> {
202 let resp = self.core.stat(ctx, path).await?;
203
204 let status = resp.status();
205
206 match status {
207 StatusCode::OK => {
208 let bs = resp.into_body();
209 let resp: StatResponse =
210 serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
211 let result = resp.result;
212 if result == 2010 || result == 2055 || result == 2002 {
213 return Err(Error::new(ErrorKind::NotFound, format!("{resp:?}")));
214 }
215 if result != 0 {
216 return Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")));
217 }
218
219 if let Some(md) = resp.metadata {
220 let md = parse_stat_metadata(md);
221 return md.map(RpStat::new);
222 }
223
224 Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")))
225 }
226 _ => Err(parse_error(resp)),
227 }
228 }
229 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
230 let output: oio::StreamReader<PcloudReader> = {
231 Ok(oio::StreamReader::new(PcloudReader::new(
232 self.clone(),
233 ctx.clone(),
234 path,
235 args,
236 )))
237 }?;
238
239 Ok(output)
240 }
241
242 fn write(&self, ctx: &OperationContext, path: &str, _args: OpWrite) -> Result<Self::Writer> {
243 let output: PcloudWriters = {
244 let writer = PcloudWriter::new(self.core.clone(), ctx.clone(), path.to_string());
245
246 let w = oio::OneShotWriter::new(writer);
247
248 Ok(w)
249 }?;
250
251 Ok(output)
252 }
253
254 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
255 let output: oio::OneShotDeleter<PcloudDeleter> = {
256 Ok(oio::OneShotDeleter::new(PcloudDeleter::new(
257 self.core.clone(),
258 ctx.clone(),
259 )))
260 }?;
261
262 Ok(output)
263 }
264
265 fn list(&self, ctx: &OperationContext, path: &str, _args: OpList) -> Result<Self::Lister> {
266 let output: oio::PageLister<PcloudLister> = {
267 let l = PcloudLister::new(self.core.clone(), ctx.clone(), path);
268 Ok(oio::PageLister::new(l))
269 }?;
270
271 Ok(output)
272 }
273
274 fn copy(
275 &self,
276 ctx: &OperationContext,
277 from: &str,
278 to: &str,
279 _args: OpCopy,
280 _opts: OpCopier,
281 ) -> Result<Self::Copier> {
282 let core = self.core.clone();
283 let ctx = ctx.clone();
284 let from = from.to_string();
285 let to = to.to_string();
286
287 Ok(oio::OneShotCopier::new(async move {
288 core.ensure_dir_exists(&ctx, &to).await?;
289
290 let resp = if from.ends_with('/') {
291 core.copy_folder(&ctx, &from, &to).await?
292 } else {
293 core.copy_file(&ctx, &from, &to).await?
294 };
295
296 let status = resp.status();
297
298 match status {
299 StatusCode::OK => {
300 let bs = resp.into_body();
301 let resp: PcloudError =
302 serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
303 let result = resp.result;
304 if result == 2009 || result == 2010 || result == 2055 || result == 2002 {
305 Err(Error::new(ErrorKind::NotFound, format!("{resp:?}")))
306 } else if result != 0 {
307 Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")))
308 } else {
309 Ok(Metadata::default())
310 }
311 }
312 _ => Err(parse_error(resp)),
313 }
314 }))
315 }
316
317 async fn rename(
318 &self,
319 ctx: &OperationContext,
320 from: &str,
321 to: &str,
322 _args: OpRename,
323 ) -> Result<RpRename> {
324 self.core.ensure_dir_exists(ctx, to).await?;
325
326 let resp = if from.ends_with('/') {
327 self.core.rename_folder(ctx, from, to).await?
328 } else {
329 self.core.rename_file(ctx, from, to).await?
330 };
331
332 let status = resp.status();
333
334 match status {
335 StatusCode::OK => {
336 let bs = resp.into_body();
337 let resp: PcloudError =
338 serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
339 let result = resp.result;
340 if result == 2009 || result == 2010 || result == 2055 || result == 2002 {
341 return Err(Error::new(ErrorKind::NotFound, format!("{resp:?}")));
342 }
343 if result != 0 {
344 return Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")));
345 }
346
347 Ok(RpRename::default())
348 }
349 _ => Err(parse_error(resp)),
350 }
351 }
352
353 async fn presign(
354 &self,
355 _ctx: &OperationContext,
356 _path: &str,
357 _args: OpPresign,
358 ) -> Result<RpPresign> {
359 Err(Error::new(
360 ErrorKind::Unsupported,
361 "operation is not supported",
362 ))
363 }
364}