nextcloud_trashbin/
client.rs1use std::fmt;
4
5use nextcloud_dav::{DEPTH_HEADER, DESTINATION_HEADER, OVERWRITE_HEADER, Overwrite};
6use nextcloud_sdk_core::{
7 ApiPath, AuthProvider, DecodeError, Error, Headers, HttpMethod, Request, Response, StatusCode,
8 StatusError, Transport,
9};
10
11use crate::item::TrashItem;
12use crate::path::{restore_destination_path, trash_item_path, trash_root_path};
13use crate::propfind::{build_propfind_body, decode_multistatus};
14
15const XML_CONTENT_TYPE: &str = "application/xml; charset=utf-8";
17
18#[derive(Clone, Copy, Debug)]
30pub struct TrashClient<'a, T: Transport, A: AuthProvider> {
31 transport: &'a T,
32 auth: &'a A,
33}
34
35impl<'a, T: Transport, A: AuthProvider> TrashClient<'a, T, A> {
36 pub const fn new(transport: &'a T, auth: &'a A) -> Self {
38 Self { transport, auth }
39 }
40
41 pub const fn transport(&self) -> &'a T {
43 self.transport
44 }
45
46 pub const fn auth(&self) -> &'a A {
48 self.auth
49 }
50
51 pub async fn list(&self, user_id: &str) -> Result<Vec<TrashItem>, Error> {
57 let path = trash_root_path(user_id)?;
58 let mut headers = self.authenticated_headers();
59 headers.set(DEPTH_HEADER, "1");
60 headers.set("Content-Type", XML_CONTENT_TYPE);
61
62 let body = build_propfind_body();
63 let request = Request::new(HttpMethod::Propfind, path.clone())
64 .with_headers(headers)
65 .body_bytes(body.into_bytes());
66
67 let response = self.transport.execute(request).await?;
68
69 if response.status() != StatusCode::new(207) {
70 return Err(Error::Status(StatusError::new(
71 HttpMethod::Propfind.as_str(),
72 path,
73 response.status(),
74 response.body(),
75 )));
76 }
77
78 let text = std::str::from_utf8(response.body())
79 .map_err(|error| Error::Decode(DecodeError::new(crate::propfind::TARGET, error)))?;
80 decode_multistatus(user_id, text).map_err(Error::Decode)
81 }
82
83 pub async fn restore(
91 &self,
92 user_id: &str,
93 item_id: &str,
94 restore_path: &str,
95 ) -> Result<(), Error> {
96 let source_path = trash_item_path(user_id, item_id)?;
97 let destination_path = restore_destination_path(user_id, restore_path)?;
98
99 let mut headers = self.authenticated_headers();
100 headers.set(DESTINATION_HEADER, destination_path.as_str());
101 headers.set(OVERWRITE_HEADER, Overwrite::Allow.as_header_value());
102
103 let request = Request::new(HttpMethod::Move, source_path.clone()).with_headers(headers);
104 let response = self.transport.execute(request).await?;
105
106 ensure_success(HttpMethod::Move, &source_path, &response)
107 }
108
109 pub async fn delete(&self, user_id: &str, item_id: &str) -> Result<(), Error> {
115 let path = trash_item_path(user_id, item_id)?;
116 let headers = self.authenticated_headers();
117 let request = Request::new(HttpMethod::Delete, path.clone()).with_headers(headers);
118 let response = self.transport.execute(request).await?;
119
120 ensure_success(HttpMethod::Delete, &path, &response)
121 }
122
123 pub async fn empty(&self, user_id: &str) -> Result<(), Error> {
129 let path = trash_root_path(user_id)?;
130 let headers = self.authenticated_headers();
131 let request = Request::new(HttpMethod::Delete, path.clone()).with_headers(headers);
132 let response = self.transport.execute(request).await?;
133
134 ensure_success(HttpMethod::Delete, &path, &response)
135 }
136
137 fn authenticated_headers(&self) -> Headers {
138 let mut headers = Headers::new();
139 self.auth.authenticate(&mut headers);
140 headers
141 }
142}
143
144fn ensure_success(method: HttpMethod, path: &ApiPath, response: &Response) -> Result<(), Error> {
145 if !response.status().is_success() {
146 return Err(Error::Status(StatusError::new(
147 method.as_str(),
148 path.clone(),
149 response.status(),
150 response.body(),
151 )));
152 }
153 Ok(())
154}
155
156impl<T: Transport, A: AuthProvider> fmt::Display for TrashClient<'_, T, A> {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.write_str("TrashClient")
159 }
160}