opendal/services/pcloud/
core.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use bytes::Buf;
19use http::header;
20use http::Request;
21use http::Response;
22use http::StatusCode;
23use serde::Deserialize;
24use std::fmt::Debug;
25use std::fmt::Formatter;
26use std::sync::Arc;
27
28use super::error::parse_error;
29use super::error::PcloudError;
30use crate::raw::*;
31use crate::*;
32
33#[derive(Clone)]
34pub struct PcloudCore {
35    pub info: Arc<AccessorInfo>,
36
37    /// The root of this core.
38    pub root: String,
39    /// The endpoint of this backend.
40    pub endpoint: String,
41    /// The username id of this backend.
42    pub username: String,
43    /// The password of this backend.
44    pub password: String,
45}
46
47impl Debug for PcloudCore {
48    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49        f.debug_struct("Backend")
50            .field("root", &self.root)
51            .field("endpoint", &self.endpoint)
52            .field("username", &self.username)
53            .finish_non_exhaustive()
54    }
55}
56
57impl PcloudCore {
58    #[inline]
59    pub async fn send(&self, req: Request<Buffer>) -> Result<Response<Buffer>> {
60        self.info.http_client().send(req).await
61    }
62}
63
64impl PcloudCore {
65    pub async fn get_file_link(&self, path: &str) -> Result<String> {
66        let path = build_abs_path(&self.root, path);
67
68        let url = format!(
69            "{}/getfilelink?path=/{}&username={}&password={}",
70            self.endpoint,
71            percent_encode_path(&path),
72            self.username,
73            self.password
74        );
75
76        let req = Request::get(url);
77
78        // set body
79        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
80
81        let resp = self.send(req).await?;
82
83        let status = resp.status();
84        match status {
85            StatusCode::OK => {
86                let bs = resp.into_body();
87                let resp: GetFileLinkResponse =
88                    serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
89                let result = resp.result;
90                if result == 2010 || result == 2055 || result == 2002 {
91                    return Err(Error::new(ErrorKind::NotFound, format!("{resp:?}")));
92                }
93                if result != 0 {
94                    return Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")));
95                }
96
97                if let Some(hosts) = resp.hosts {
98                    if let Some(path) = resp.path {
99                        if !hosts.is_empty() {
100                            return Ok(format!("https://{}{}", hosts[0], path));
101                        }
102                    }
103                }
104                Err(Error::new(ErrorKind::Unexpected, "hosts is empty"))
105            }
106            _ => Err(parse_error(resp)),
107        }
108    }
109
110    pub async fn download(&self, url: &str, range: BytesRange) -> Result<Response<HttpBody>> {
111        let req = Request::get(url);
112
113        // set body
114        let req = req
115            .header(header::RANGE, range.to_header())
116            .body(Buffer::new())
117            .map_err(new_request_build_error)?;
118
119        self.info.http_client().fetch(req).await
120    }
121
122    pub async fn ensure_dir_exists(&self, path: &str) -> Result<()> {
123        let path = build_abs_path(&self.root, path);
124
125        let paths = path.split('/').collect::<Vec<&str>>();
126
127        for i in 0..paths.len() - 1 {
128            let path = paths[..i + 1].join("/");
129            let resp = self.create_folder_if_not_exists(&path).await?;
130
131            let status = resp.status();
132
133            match status {
134                StatusCode::OK => {
135                    let bs = resp.into_body();
136                    let resp: PcloudError =
137                        serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
138                    let result = resp.result;
139                    if result == 2010 || result == 2055 || result == 2002 {
140                        return Err(Error::new(ErrorKind::NotFound, format!("{resp:?}")));
141                    }
142                    if result != 0 {
143                        return Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")));
144                    }
145
146                    if result != 0 {
147                        return Err(Error::new(ErrorKind::Unexpected, format!("{resp:?}")));
148                    }
149                }
150                _ => return Err(parse_error(resp)),
151            }
152        }
153        Ok(())
154    }
155
156    pub async fn create_folder_if_not_exists(&self, path: &str) -> Result<Response<Buffer>> {
157        let url = format!(
158            "{}/createfolderifnotexists?path=/{}&username={}&password={}",
159            self.endpoint,
160            percent_encode_path(path),
161            self.username,
162            self.password
163        );
164
165        let req = Request::post(url);
166
167        // set body
168        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
169
170        self.send(req).await
171    }
172
173    pub async fn rename_file(&self, from: &str, to: &str) -> Result<Response<Buffer>> {
174        let from = build_abs_path(&self.root, from);
175        let to = build_abs_path(&self.root, to);
176
177        let url = format!(
178            "{}/renamefile?path=/{}&topath=/{}&username={}&password={}",
179            self.endpoint,
180            percent_encode_path(&from),
181            percent_encode_path(&to),
182            self.username,
183            self.password
184        );
185
186        let req = Request::post(url);
187
188        // set body
189        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
190
191        self.send(req).await
192    }
193
194    pub async fn rename_folder(&self, from: &str, to: &str) -> Result<Response<Buffer>> {
195        let from = build_abs_path(&self.root, from);
196        let to = build_abs_path(&self.root, to);
197        let url = format!(
198            "{}/renamefolder?path=/{}&topath=/{}&username={}&password={}",
199            self.endpoint,
200            percent_encode_path(&from),
201            percent_encode_path(&to),
202            self.username,
203            self.password
204        );
205
206        let req = Request::post(url);
207
208        // set body
209        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
210
211        self.send(req).await
212    }
213
214    pub async fn delete_folder(&self, path: &str) -> Result<Response<Buffer>> {
215        let path = build_abs_path(&self.root, path);
216
217        let url = format!(
218            "{}/deletefolder?path=/{}&username={}&password={}",
219            self.endpoint,
220            percent_encode_path(&path),
221            self.username,
222            self.password
223        );
224
225        let req = Request::post(url);
226
227        // set body
228        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
229
230        self.send(req).await
231    }
232
233    pub async fn delete_file(&self, path: &str) -> Result<Response<Buffer>> {
234        let path = build_abs_path(&self.root, path);
235
236        let url = format!(
237            "{}/deletefile?path=/{}&username={}&password={}",
238            self.endpoint,
239            percent_encode_path(&path),
240            self.username,
241            self.password
242        );
243
244        let req = Request::post(url);
245
246        // set body
247        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
248
249        self.send(req).await
250    }
251
252    pub async fn copy_file(&self, from: &str, to: &str) -> Result<Response<Buffer>> {
253        let from = build_abs_path(&self.root, from);
254        let to = build_abs_path(&self.root, to);
255
256        let url = format!(
257            "{}/copyfile?path=/{}&topath=/{}&username={}&password={}",
258            self.endpoint,
259            percent_encode_path(&from),
260            percent_encode_path(&to),
261            self.username,
262            self.password
263        );
264
265        let req = Request::post(url);
266
267        // set body
268        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
269
270        self.send(req).await
271    }
272
273    pub async fn copy_folder(&self, from: &str, to: &str) -> Result<Response<Buffer>> {
274        let from = build_abs_path(&self.root, from);
275        let to = build_abs_path(&self.root, to);
276
277        let url = format!(
278            "{}/copyfolder?path=/{}&topath=/{}&username={}&password={}",
279            self.endpoint,
280            percent_encode_path(&from),
281            percent_encode_path(&to),
282            self.username,
283            self.password
284        );
285
286        let req = Request::post(url);
287
288        // set body
289        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
290
291        self.send(req).await
292    }
293
294    pub async fn stat(&self, path: &str) -> Result<Response<Buffer>> {
295        let path = build_abs_path(&self.root, path);
296
297        let path = path.trim_end_matches('/');
298
299        let url = format!(
300            "{}/stat?path=/{}&username={}&password={}",
301            self.endpoint,
302            percent_encode_path(path),
303            self.username,
304            self.password
305        );
306
307        let req = Request::post(url);
308
309        // set body
310        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
311
312        self.send(req).await
313    }
314
315    pub async fn upload_file(&self, path: &str, bs: Buffer) -> Result<Response<Buffer>> {
316        let path = build_abs_path(&self.root, path);
317
318        let (name, path) = (get_basename(&path), get_parent(&path).trim_end_matches('/'));
319
320        let url = format!(
321            "{}/uploadfile?path=/{}&filename={}&username={}&password={}",
322            self.endpoint,
323            percent_encode_path(path),
324            percent_encode_path(name),
325            self.username,
326            self.password
327        );
328
329        let req = Request::put(url);
330
331        // set body
332        let req = req.body(bs).map_err(new_request_build_error)?;
333
334        self.send(req).await
335    }
336
337    pub async fn list_folder(&self, path: &str) -> Result<Response<Buffer>> {
338        let path = build_abs_path(&self.root, path);
339
340        let path = normalize_root(&path);
341
342        let path = path.trim_end_matches('/');
343
344        let url = format!(
345            "{}/listfolder?path={}&username={}&password={}",
346            self.endpoint,
347            percent_encode_path(path),
348            self.username,
349            self.password
350        );
351
352        let req = Request::get(url);
353
354        // set body
355        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
356
357        self.send(req).await
358    }
359}
360
361pub(super) fn parse_stat_metadata(content: StatMetadata) -> Result<Metadata> {
362    let mut md = if content.isfolder {
363        Metadata::new(EntryMode::DIR)
364    } else {
365        Metadata::new(EntryMode::FILE)
366    };
367
368    if let Some(size) = content.size {
369        md.set_content_length(size);
370    }
371
372    md.set_last_modified(parse_datetime_from_rfc2822(&content.modified)?);
373
374    Ok(md)
375}
376
377pub(super) fn parse_list_metadata(content: ListMetadata) -> Result<Metadata> {
378    let mut md = if content.isfolder {
379        Metadata::new(EntryMode::DIR)
380    } else {
381        Metadata::new(EntryMode::FILE)
382    };
383
384    if let Some(size) = content.size {
385        md.set_content_length(size);
386    }
387
388    md.set_last_modified(parse_datetime_from_rfc2822(&content.modified)?);
389
390    Ok(md)
391}
392
393#[derive(Debug, Deserialize)]
394pub struct GetFileLinkResponse {
395    pub result: u64,
396    pub path: Option<String>,
397    pub hosts: Option<Vec<String>>,
398}
399
400#[derive(Debug, Deserialize)]
401pub struct StatResponse {
402    pub result: u64,
403    pub metadata: Option<StatMetadata>,
404}
405
406#[derive(Debug, Deserialize)]
407pub struct StatMetadata {
408    pub modified: String,
409    pub isfolder: bool,
410    pub size: Option<u64>,
411}
412
413#[derive(Debug, Deserialize)]
414pub struct ListFolderResponse {
415    pub result: u64,
416    pub metadata: Option<ListMetadata>,
417}
418
419#[derive(Debug, Deserialize)]
420pub struct ListMetadata {
421    pub path: String,
422    pub modified: String,
423    pub isfolder: bool,
424    pub size: Option<u64>,
425    pub contents: Option<Vec<ListMetadata>>,
426}