1use 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 pub root: String,
40 pub endpoint: String,
42 pub username: String,
44 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 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 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 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 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 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 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 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 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 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 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 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 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}