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