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