opendal/services/seafile/
core.rs1use 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 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 .extension(Operation::Write)
171 .body(Buffer::new())
172 .map_err(new_request_build_error)?;
173
174 let resp = self.send(req).await?;
175 let status = resp.status();
176
177 match status {
178 StatusCode::OK => {
179 let resp_body = resp.into_body();
180 let upload_url = serde_json::from_reader(resp_body.reader())
181 .map_err(new_json_deserialize_error)?;
182 Ok(upload_url)
183 }
184 _ => Err(parse_error(resp)),
185 }
186 }
187
188 pub async fn upload_file(&self, path: &str, body: Buffer) -> Result<Response<Buffer>> {
189 let upload_url = self.get_upload_url().await?;
190
191 let req = Request::post(upload_url).extension(Operation::Write);
192
193 let (filename, relative_path) = if path.ends_with('/') {
194 ("", build_abs_path(&self.root, path))
195 } else {
196 let (filename, relative_path) = (get_basename(path), get_parent(path));
197 (filename, build_abs_path(&self.root, relative_path))
198 };
199
200 let file_part = FormDataPart::new("file")
201 .header(
202 header::CONTENT_DISPOSITION,
203 format!("form-data; name=\"file\"; filename=\"{filename}\"")
204 .parse()
205 .unwrap(),
206 )
207 .content(body);
208
209 let multipart = Multipart::new()
210 .part(FormDataPart::new("parent_dir").content("/"))
211 .part(FormDataPart::new("relative_path").content(relative_path.clone()))
212 .part(FormDataPart::new("replace").content("1"))
213 .part(file_part);
214
215 let req = multipart.apply(req)?;
216
217 self.send(req).await
218 }
219
220 async fn get_download_url(&self, path: &str) -> Result<String> {
222 let path = build_abs_path(&self.root, path);
223 let path = percent_encode_path(&path);
224
225 let auth_info = self.get_auth_info().await?;
226
227 let req = Request::get(format!(
228 "{}/api2/repos/{}/file/?p={}",
229 self.endpoint, auth_info.repo_id, path
230 ));
231
232 let req = req
233 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
234 .extension(Operation::Read)
235 .body(Buffer::new())
236 .map_err(new_request_build_error)?;
237
238 let resp = self.send(req).await?;
239 let status = resp.status();
240
241 match status {
242 StatusCode::OK => {
243 let resp_body = resp.into_body();
244 let download_url = serde_json::from_reader(resp_body.reader())
245 .map_err(new_json_deserialize_error)?;
246
247 Ok(download_url)
248 }
249 _ => Err(parse_error(resp)),
250 }
251 }
252
253 pub async fn download_file(&self, path: &str, range: BytesRange) -> Result<Response<HttpBody>> {
255 let download_url = self.get_download_url(path).await?;
256
257 let req = Request::get(download_url);
258
259 let req = req
260 .header(header::RANGE, range.to_header())
261 .extension(Operation::Read)
262 .body(Buffer::new())
263 .map_err(new_request_build_error)?;
264
265 self.info.http_client().fetch(req).await
266 }
267
268 pub async fn file_detail(&self, path: &str) -> Result<FileDetail> {
270 let path = build_abs_path(&self.root, path);
271 let path = percent_encode_path(&path);
272
273 let auth_info = self.get_auth_info().await?;
274
275 let req = Request::get(format!(
276 "{}/api2/repos/{}/file/detail/?p={}",
277 self.endpoint, auth_info.repo_id, path
278 ));
279
280 let req = req
281 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
282 .extension(Operation::Stat)
283 .body(Buffer::new())
284 .map_err(new_request_build_error)?;
285
286 let resp = self.send(req).await?;
287 let status = resp.status();
288
289 match status {
290 StatusCode::OK => {
291 let resp_body = resp.into_body();
292 let file_detail: FileDetail = serde_json::from_reader(resp_body.reader())
293 .map_err(new_json_deserialize_error)?;
294 Ok(file_detail)
295 }
296 _ => Err(parse_error(resp)),
297 }
298 }
299
300 pub async fn dir_detail(&self, path: &str) -> Result<DirDetail> {
302 let path = build_abs_path(&self.root, path);
303 let path = percent_encode_path(&path);
304
305 let auth_info = self.get_auth_info().await?;
306
307 let req = Request::get(format!(
308 "{}/api/v2.1/repos/{}/dir/detail/?path={}",
309 self.endpoint, auth_info.repo_id, path
310 ));
311
312 let req = req
313 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
314 .extension(Operation::Stat)
315 .body(Buffer::new())
316 .map_err(new_request_build_error)?;
317
318 let resp = self.send(req).await?;
319 let status = resp.status();
320
321 match status {
322 StatusCode::OK => {
323 let resp_body = resp.into_body();
324 let dir_detail: DirDetail = serde_json::from_reader(resp_body.reader())
325 .map_err(new_json_deserialize_error)?;
326 Ok(dir_detail)
327 }
328 _ => Err(parse_error(resp)),
329 }
330 }
331
332 pub async fn delete(&self, path: &str) -> Result<()> {
334 let path = build_abs_path(&self.root, path);
335 let path = percent_encode_path(&path);
336
337 let auth_info = self.get_auth_info().await?;
338
339 let url = if path.ends_with('/') {
340 format!(
341 "{}/api2/repos/{}/dir/?p={}",
342 self.endpoint, auth_info.repo_id, path
343 )
344 } else {
345 format!(
346 "{}/api2/repos/{}/file/?p={}",
347 self.endpoint, auth_info.repo_id, path
348 )
349 };
350
351 let req = Request::delete(url);
352
353 let req = req
354 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
355 .extension(Operation::Delete)
356 .body(Buffer::new())
357 .map_err(new_request_build_error)?;
358
359 let resp = self.send(req).await?;
360
361 let status = resp.status();
362
363 match status {
364 StatusCode::OK => Ok(()),
365 _ => Err(parse_error(resp)),
366 }
367 }
368
369 pub async fn list(&self, path: &str) -> Result<ListResponse> {
370 let rooted_abs_path = build_rooted_abs_path(&self.root, path);
371
372 let auth_info = self.get_auth_info().await?;
373
374 let url = format!(
375 "{}/api2/repos/{}/dir/?p={}",
376 self.endpoint,
377 auth_info.repo_id,
378 percent_encode_path(&rooted_abs_path)
379 );
380
381 let req = Request::get(url);
382
383 let req = req
384 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
385 .extension(Operation::List)
386 .body(Buffer::new())
387 .map_err(new_request_build_error)?;
388
389 let resp = self.send(req).await?;
390
391 match resp.status() {
392 StatusCode::OK => {
393 let resp_body = resp.into_body();
394 let infos: Vec<Info> = serde_json::from_reader(resp_body.reader())
395 .map_err(new_json_deserialize_error)?;
396 Ok(ListResponse {
397 infos: Some(infos),
398 rooted_abs_path,
399 })
400 }
401 StatusCode::NOT_FOUND => Ok(ListResponse {
403 infos: None,
404 rooted_abs_path,
405 }),
406 _ => Err(parse_error(resp)),
407 }
408 }
409}
410
411#[derive(Deserialize)]
412pub struct AuthTokenResponse {
413 pub token: String,
414}
415
416#[derive(Deserialize)]
417pub struct FileDetail {
418 pub last_modified: String,
419 pub size: u64,
420}
421
422#[derive(Debug, Deserialize)]
423pub struct DirDetail {
424 mtime: String,
425}
426
427pub fn parse_dir_detail(dir_detail: DirDetail) -> Result<Metadata> {
428 let mut md = Metadata::new(EntryMode::DIR);
429
430 md.set_last_modified(parse_datetime_from_rfc3339(&dir_detail.mtime)?);
431
432 Ok(md)
433}
434
435pub fn parse_file_detail(file_detail: FileDetail) -> Result<Metadata> {
436 let mut md = Metadata::new(EntryMode::FILE);
437
438 md.set_content_length(file_detail.size);
439 md.set_last_modified(parse_datetime_from_rfc3339(&file_detail.last_modified)?);
440
441 Ok(md)
442}
443
444#[derive(Clone, Default)]
445pub struct SeafileSigner {
446 pub auth_info: AuthInfo,
447}
448
449#[derive(Clone, Default)]
450pub struct AuthInfo {
451 pub repo_id: String,
453 pub token: String,
455}
456
457#[derive(Deserialize)]
458pub struct ListLibraryResponse {
459 pub name: String,
460 pub id: String,
461}
462
463#[derive(Debug, Deserialize)]
464pub struct Info {
465 #[serde(rename = "type")]
466 pub type_field: String,
467 pub mtime: i64,
468 pub size: Option<u64>,
469 pub name: String,
470}
471
472pub struct ListResponse {
473 pub infos: Option<Vec<Info>>,
474 pub rooted_abs_path: String,
475}