opendal/services/seafile/
core.rs
1use std::fmt::Debug;
19use std::fmt::Formatter;
20use std::sync::Arc;
21
22use bytes::Buf;
23use bytes::Bytes;
24use http::header;
25use http::Request;
26use http::Response;
27use http::StatusCode;
28use serde::Deserialize;
29use tokio::sync::RwLock;
30
31use super::error::parse_error;
32use crate::raw::*;
33use crate::*;
34
35#[derive(Clone)]
37pub struct SeafileCore {
38 pub info: Arc<AccessorInfo>,
39 pub root: String,
41 pub endpoint: String,
43 pub username: String,
45 pub password: String,
47 pub repo_name: String,
49
50 pub signer: Arc<RwLock<SeafileSigner>>,
52}
53
54impl Debug for SeafileCore {
55 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
56 f.debug_struct("Backend")
57 .field("root", &self.root)
58 .field("endpoint", &self.endpoint)
59 .field("username", &self.username)
60 .field("repo_name", &self.repo_name)
61 .finish_non_exhaustive()
62 }
63}
64
65impl SeafileCore {
66 #[inline]
67 pub async fn send(&self, req: Request<Buffer>) -> Result<Response<Buffer>> {
68 self.info.http_client().send(req).await
69 }
70
71 pub async fn get_auth_info(&self) -> Result<AuthInfo> {
73 {
74 let signer = self.signer.read().await;
75
76 if !signer.auth_info.token.is_empty() {
77 let auth_info = signer.auth_info.clone();
78 return Ok(auth_info.clone());
79 }
80 }
81
82 {
83 let mut signer = self.signer.write().await;
84 let body = format!(
85 "username={}&password={}",
86 percent_encode_path(&self.username),
87 percent_encode_path(&self.password)
88 );
89 let req = Request::post(format!("{}/api2/auth-token/", self.endpoint))
90 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
91 .body(Buffer::from(Bytes::from(body)))
92 .map_err(new_request_build_error)?;
93
94 let resp = self.info.http_client().send(req).await?;
95 let status = resp.status();
96
97 match status {
98 StatusCode::OK => {
99 let resp_body = resp.into_body();
100 let auth_response: AuthTokenResponse =
101 serde_json::from_reader(resp_body.reader())
102 .map_err(new_json_deserialize_error)?;
103 signer.auth_info = AuthInfo {
104 token: auth_response.token,
105 repo_id: "".to_string(),
106 };
107 }
108 _ => {
109 return Err(parse_error(resp));
110 }
111 }
112
113 let url = format!("{}/api2/repos", self.endpoint);
114
115 let req = Request::get(url)
116 .header(
117 header::AUTHORIZATION,
118 format!("Token {}", signer.auth_info.token),
119 )
120 .body(Buffer::new())
121 .map_err(new_request_build_error)?;
122
123 let resp = self.info.http_client().send(req).await?;
124
125 let status = resp.status();
126
127 match status {
128 StatusCode::OK => {
129 let resp_body = resp.into_body();
130 let list_library_response: Vec<ListLibraryResponse> =
131 serde_json::from_reader(resp_body.reader())
132 .map_err(new_json_deserialize_error)?;
133
134 for library in list_library_response {
135 if library.name == self.repo_name {
136 signer.auth_info.repo_id = library.id;
137 break;
138 }
139 }
140
141 if signer.auth_info.repo_id.is_empty() {
143 return Err(Error::new(
144 ErrorKind::NotFound,
145 format!("repo {} not found", self.repo_name),
146 ));
147 }
148 }
149 _ => {
150 return Err(parse_error(resp));
151 }
152 }
153 Ok(signer.auth_info.clone())
154 }
155 }
156}
157
158impl SeafileCore {
159 pub async fn get_upload_url(&self) -> Result<String> {
161 let auth_info = self.get_auth_info().await?;
162
163 let req = Request::get(format!(
164 "{}/api2/repos/{}/upload-link/",
165 self.endpoint, auth_info.repo_id
166 ));
167
168 let req = req
169 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
170 .body(Buffer::new())
171 .map_err(new_request_build_error)?;
172
173 let resp = self.send(req).await?;
174 let status = resp.status();
175
176 match status {
177 StatusCode::OK => {
178 let resp_body = resp.into_body();
179 let upload_url = serde_json::from_reader(resp_body.reader())
180 .map_err(new_json_deserialize_error)?;
181 Ok(upload_url)
182 }
183 _ => Err(parse_error(resp)),
184 }
185 }
186
187 pub async fn get_download_url(&self, path: &str) -> Result<String> {
189 let path = build_abs_path(&self.root, path);
190 let path = percent_encode_path(&path);
191
192 let auth_info = self.get_auth_info().await?;
193
194 let req = Request::get(format!(
195 "{}/api2/repos/{}/file/?p={}",
196 self.endpoint, auth_info.repo_id, path
197 ));
198
199 let req = req
200 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
201 .body(Buffer::new())
202 .map_err(new_request_build_error)?;
203
204 let resp = self.send(req).await?;
205 let status = resp.status();
206
207 match status {
208 StatusCode::OK => {
209 let resp_body = resp.into_body();
210 let download_url = serde_json::from_reader(resp_body.reader())
211 .map_err(new_json_deserialize_error)?;
212
213 Ok(download_url)
214 }
215 _ => Err(parse_error(resp)),
216 }
217 }
218
219 pub async fn download_file(&self, path: &str, range: BytesRange) -> Result<Response<HttpBody>> {
221 let download_url = self.get_download_url(path).await?;
222
223 let req = Request::get(download_url);
224
225 let req = req
226 .header(header::RANGE, range.to_header())
227 .body(Buffer::new())
228 .map_err(new_request_build_error)?;
229
230 self.info.http_client().fetch(req).await
231 }
232
233 pub async fn file_detail(&self, path: &str) -> Result<FileDetail> {
235 let path = build_abs_path(&self.root, path);
236 let path = percent_encode_path(&path);
237
238 let auth_info = self.get_auth_info().await?;
239
240 let req = Request::get(format!(
241 "{}/api2/repos/{}/file/detail/?p={}",
242 self.endpoint, auth_info.repo_id, path
243 ));
244
245 let req = req
246 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
247 .body(Buffer::new())
248 .map_err(new_request_build_error)?;
249
250 let resp = self.send(req).await?;
251 let status = resp.status();
252
253 match status {
254 StatusCode::OK => {
255 let resp_body = resp.into_body();
256 let file_detail: FileDetail = serde_json::from_reader(resp_body.reader())
257 .map_err(new_json_deserialize_error)?;
258 Ok(file_detail)
259 }
260 _ => Err(parse_error(resp)),
261 }
262 }
263
264 pub async fn dir_detail(&self, path: &str) -> Result<DirDetail> {
266 let path = build_abs_path(&self.root, path);
267 let path = percent_encode_path(&path);
268
269 let auth_info = self.get_auth_info().await?;
270
271 let req = Request::get(format!(
272 "{}/api/v2.1/repos/{}/dir/detail/?path={}",
273 self.endpoint, auth_info.repo_id, path
274 ));
275
276 let req = req
277 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
278 .body(Buffer::new())
279 .map_err(new_request_build_error)?;
280
281 let resp = self.send(req).await?;
282 let status = resp.status();
283
284 match status {
285 StatusCode::OK => {
286 let resp_body = resp.into_body();
287 let dir_detail: DirDetail = serde_json::from_reader(resp_body.reader())
288 .map_err(new_json_deserialize_error)?;
289 Ok(dir_detail)
290 }
291 _ => Err(parse_error(resp)),
292 }
293 }
294
295 pub async fn delete(&self, path: &str) -> Result<()> {
297 let path = build_abs_path(&self.root, path);
298 let path = percent_encode_path(&path);
299
300 let auth_info = self.get_auth_info().await?;
301
302 let url = if path.ends_with('/') {
303 format!(
304 "{}/api2/repos/{}/dir/?p={}",
305 self.endpoint, auth_info.repo_id, path
306 )
307 } else {
308 format!(
309 "{}/api2/repos/{}/file/?p={}",
310 self.endpoint, auth_info.repo_id, path
311 )
312 };
313
314 let req = Request::delete(url);
315
316 let req = req
317 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
318 .body(Buffer::new())
319 .map_err(new_request_build_error)?;
320
321 let resp = self.send(req).await?;
322
323 let status = resp.status();
324
325 match status {
326 StatusCode::OK => Ok(()),
327 _ => Err(parse_error(resp)),
328 }
329 }
330}
331
332#[derive(Deserialize)]
333pub struct AuthTokenResponse {
334 pub token: String,
335}
336
337#[derive(Deserialize)]
338pub struct FileDetail {
339 pub last_modified: String,
340 pub size: u64,
341}
342
343#[derive(Debug, Deserialize)]
344pub struct DirDetail {
345 mtime: String,
346}
347
348pub fn parse_dir_detail(dir_detail: DirDetail) -> Result<Metadata> {
349 let mut md = Metadata::new(EntryMode::DIR);
350
351 md.set_last_modified(parse_datetime_from_rfc3339(&dir_detail.mtime)?);
352
353 Ok(md)
354}
355
356pub fn parse_file_detail(file_detail: FileDetail) -> Result<Metadata> {
357 let mut md = Metadata::new(EntryMode::FILE);
358
359 md.set_content_length(file_detail.size);
360 md.set_last_modified(parse_datetime_from_rfc3339(&file_detail.last_modified)?);
361
362 Ok(md)
363}
364
365#[derive(Clone, Default)]
366pub struct SeafileSigner {
367 pub auth_info: AuthInfo,
368}
369
370#[derive(Clone, Default)]
371pub struct AuthInfo {
372 pub repo_id: String,
374 pub token: String,
376}
377
378#[derive(Deserialize)]
379pub struct ListLibraryResponse {
380 pub name: String,
381 pub id: String,
382}