use std::fmt;
use nextcloud_dav::{DEPTH_HEADER, DESTINATION_HEADER, OVERWRITE_HEADER, Overwrite};
use nextcloud_sdk_core::{
ApiPath, AuthProvider, DecodeError, Error, Headers, HttpMethod, Request, Response, StatusCode,
StatusError, Transport,
};
use crate::item::TrashItem;
use crate::path::{restore_destination_path, trash_item_path, trash_root_path};
use crate::propfind::{build_propfind_body, decode_multistatus};
const XML_CONTENT_TYPE: &str = "application/xml; charset=utf-8";
#[derive(Clone, Copy, Debug)]
pub struct TrashClient<'a, T: Transport, A: AuthProvider> {
transport: &'a T,
auth: &'a A,
}
impl<'a, T: Transport, A: AuthProvider> TrashClient<'a, T, A> {
pub const fn new(transport: &'a T, auth: &'a A) -> Self {
Self { transport, auth }
}
pub const fn transport(&self) -> &'a T {
self.transport
}
pub const fn auth(&self) -> &'a A {
self.auth
}
pub async fn list(&self, user_id: &str) -> Result<Vec<TrashItem>, Error> {
let path = trash_root_path(user_id)?;
let mut headers = self.authenticated_headers();
headers.set(DEPTH_HEADER, "1");
headers.set("Content-Type", XML_CONTENT_TYPE);
let body = build_propfind_body();
let request = Request::new(HttpMethod::Propfind, path.clone())
.with_headers(headers)
.body_bytes(body.into_bytes());
let response = self.transport.execute(request).await?;
if response.status() != StatusCode::new(207) {
return Err(Error::Status(StatusError::new(
HttpMethod::Propfind.as_str(),
path,
response.status(),
response.body(),
)));
}
let text = std::str::from_utf8(response.body())
.map_err(|error| Error::Decode(DecodeError::new(crate::propfind::TARGET, error)))?;
decode_multistatus(user_id, text).map_err(Error::Decode)
}
pub async fn restore(
&self,
user_id: &str,
item_id: &str,
restore_path: &str,
) -> Result<(), Error> {
let source_path = trash_item_path(user_id, item_id)?;
let destination_path = restore_destination_path(user_id, restore_path)?;
let mut headers = self.authenticated_headers();
headers.set(DESTINATION_HEADER, destination_path.as_str());
headers.set(OVERWRITE_HEADER, Overwrite::Allow.as_header_value());
let request = Request::new(HttpMethod::Move, source_path.clone()).with_headers(headers);
let response = self.transport.execute(request).await?;
ensure_success(HttpMethod::Move, &source_path, &response)
}
pub async fn delete(&self, user_id: &str, item_id: &str) -> Result<(), Error> {
let path = trash_item_path(user_id, item_id)?;
let headers = self.authenticated_headers();
let request = Request::new(HttpMethod::Delete, path.clone()).with_headers(headers);
let response = self.transport.execute(request).await?;
ensure_success(HttpMethod::Delete, &path, &response)
}
pub async fn empty(&self, user_id: &str) -> Result<(), Error> {
let path = trash_root_path(user_id)?;
let headers = self.authenticated_headers();
let request = Request::new(HttpMethod::Delete, path.clone()).with_headers(headers);
let response = self.transport.execute(request).await?;
ensure_success(HttpMethod::Delete, &path, &response)
}
fn authenticated_headers(&self) -> Headers {
let mut headers = Headers::new();
self.auth.authenticate(&mut headers);
headers
}
}
fn ensure_success(method: HttpMethod, path: &ApiPath, response: &Response) -> Result<(), Error> {
if !response.status().is_success() {
return Err(Error::Status(StatusError::new(
method.as_str(),
path.clone(),
response.status(),
response.body(),
)));
}
Ok(())
}
impl<T: Transport, A: AuthProvider> fmt::Display for TrashClient<'_, T, A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("TrashClient")
}
}