1use 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 pub root: String,
39 pub endpoint: String,
41 pub username: String,
43 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 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 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 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 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 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 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 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 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 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 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 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 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}