opentalk_nextcloud_client/
client.rs1use std::sync::Arc;
6
7use log::warn;
8use reqwest::StatusCode;
9use reqwest_dav as dav;
10use url::Url;
11
12use crate::{
13 types::{OcsPassword, ShareAnswer},
14 Error, Result, ShareCreator, ShareId, ShareType, ShareUpdater,
15};
16
17#[derive(Clone)]
18pub struct Client {
19 pub(crate) inner: Arc<ClientRef>,
20}
21
22pub(crate) struct ClientRef {
23 pub(crate) dav_client: dav::Client,
24 pub(crate) http_client: reqwest::Client,
25 pub(crate) base_url: Url,
26 pub(crate) username: String,
27 pub(crate) password: String,
28}
29
30impl Client {
31 pub fn new(base_url: Url, username: String, password: String) -> Result<Self> {
32 let dav_url = base_url.join("remote.php/dav")?;
33 let mut http_headers = reqwest::header::HeaderMap::new();
34 http_headers.insert(
35 "OCS-APIRequest",
36 reqwest::header::HeaderValue::from_static("true"),
37 );
38 http_headers.insert(
39 reqwest::header::ACCEPT,
40 reqwest::header::HeaderValue::from_static("application/json"),
41 );
42 Ok(Self {
43 inner: Arc::new(ClientRef {
44 dav_client: dav::ClientBuilder::new()
45 .set_host(dav_url.to_string())
46 .set_auth(dav::Auth::Basic(username.clone(), password.clone()))
47 .build()?,
48 http_client: reqwest::ClientBuilder::new()
49 .default_headers(http_headers)
50 .build()?,
51 base_url,
52 username,
53 password,
54 }),
55 })
56 }
57
58 pub async fn create_folder(&self, path: &str) -> Result<()> {
59 self.inner.dav_client.mkcol(path).await?;
60 Ok(())
61 }
62
63 pub async fn delete(&self, path: &str) -> Result<()> {
64 if let Err(e) = self.inner.dav_client.delete(path).await {
65 return Err(Error::FileNotFound {
66 file_path: path.to_owned(),
67 source: e,
68 });
69 }
70
71 Ok(())
72 }
73
74 pub fn create_share(&self, path: &str, share_type: ShareType) -> ShareCreator {
75 ShareCreator::new(self.clone(), path.to_string(), share_type)
76 }
77
78 pub fn update_share(&self, id: ShareId) -> ShareUpdater {
79 ShareUpdater::new(self.clone(), id)
80 }
81
82 pub async fn delete_share(&self, share_id: ShareId) -> Result<()> {
83 let url = self
84 .share_api_base_url()?
85 .join("shares/")?
86 .join(share_id.as_str())?;
87
88 let request = self
89 .inner
90 .http_client
91 .delete(url)
92 .basic_auth(&self.inner.username, Some(&self.inner.password));
93 let answer = request.send().await?;
94
95 match answer.status() {
96 StatusCode::CONTINUE | StatusCode::OK => {}
97 StatusCode::UNAUTHORIZED => {
98 return Err(Error::Unauthorized);
100 }
101 StatusCode::NOT_FOUND => {
102 return Err(Error::ShareNotFound { share_id });
104 }
105 status_code => {
106 warn!("Received unexpected status code {status_code} from NextCloud server.");
107 match answer.text().await {
108 Ok(text) => {
109 warn!("Response for unexpected status code {status_code}:\n{text}");
110 }
111 Err(e) => {
112 warn!("Error retrieving body from NextCloud: {e}");
113 }
114 }
115 return Err(Error::UnexpectedStatusCode { status_code });
116 }
117 }
118 Ok(())
119 }
120
121 pub async fn generate_password(&self) -> Result<String> {
122 let url = self.password_policy_base_url()?.join("generate")?;
123
124 let request = self
125 .inner
126 .http_client
127 .get(url)
128 .basic_auth(&self.inner.username, Some(&self.inner.password));
129
130 let answer = request.send().await?;
131
132 match answer.status() {
133 StatusCode::CONTINUE | StatusCode::OK => {}
134 StatusCode::UNAUTHORIZED => {
135 return Err(Error::Unauthorized);
137 }
138 status_code => {
139 warn!("Received unexpected status code {status_code} from NextCloud server.");
140 match answer.text().await {
141 Ok(text) => {
142 warn!("Response for unexpected status code {status_code}:\n{text}");
143 }
144 Err(e) => {
145 warn!("Error retrieving body from NextCloud: {e}");
146 }
147 }
148 return Err(Error::UnexpectedStatusCode { status_code });
149 }
150 }
151
152 let answer: ShareAnswer<OcsPassword> = answer.json().await?;
153
154 Ok(answer.ocs.data.password)
155 }
156
157 pub(crate) fn password_policy_base_url(&self) -> Result<Url> {
158 Ok(self
159 .inner
160 .base_url
161 .join("ocs/v2.php/apps/password_policy/api/v1/")?)
162 }
163
164 pub(crate) fn share_api_base_url(&self) -> Result<Url> {
165 Ok(self
166 .inner
167 .base_url
168 .join("ocs/v2.php/apps/files_sharing/api/v1/")?)
169 }
170}