opendal_core/services/seafile/
core.rs1use std::fmt::Debug;
19use std::sync::Arc;
20
21use bytes::Buf;
22use bytes::Bytes;
23use http::Request;
24use http::Response;
25use http::StatusCode;
26use http::header;
27use mea::rwlock::RwLock;
28use serde::Deserialize;
29
30use super::error::parse_error;
31use crate::raw::*;
32use crate::*;
33
34#[derive(Clone)]
36pub struct SeafileCore {
37 pub info: Arc<AccessorInfo>,
38 pub root: String,
40 pub endpoint: String,
42 pub username: String,
44 pub password: String,
46 pub repo_name: String,
48
49 pub signer: Arc<RwLock<SeafileSigner>>,
51}
52
53impl Debug for SeafileCore {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.debug_struct("SeafileCore")
56 .field("root", &self.root)
57 .field("endpoint", &self.endpoint)
58 .field("username", &self.username)
59 .field("repo_name", &self.repo_name)
60 .finish_non_exhaustive()
61 }
62}
63
64impl SeafileCore {
65 #[inline]
66 pub async fn send(&self, req: Request<Buffer>) -> Result<Response<Buffer>> {
67 self.info.http_client().send(req).await
68 }
69
70 pub async fn get_auth_info(&self) -> Result<AuthInfo> {
72 {
73 let signer = self.signer.read().await;
74
75 if !signer.auth_info.token.is_empty() {
76 let auth_info = signer.auth_info.clone();
77 return Ok(auth_info.clone());
78 }
79 }
80
81 {
82 let mut signer = self.signer.write().await;
83 let body = format!(
84 "username={}&password={}",
85 percent_encode_path(&self.username),
86 percent_encode_path(&self.password)
87 );
88 let req = Request::post(format!("{}/api2/auth-token/", self.endpoint))
89 .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
90 .body(Buffer::from(Bytes::from(body)))
91 .map_err(new_request_build_error)?;
92
93 let resp = self.info.http_client().send(req).await?;
94 let status = resp.status();
95
96 match status {
97 StatusCode::OK => {
98 let resp_body = resp.into_body();
99 let auth_response: AuthTokenResponse =
100 serde_json::from_reader(resp_body.reader())
101 .map_err(new_json_deserialize_error)?;
102 signer.auth_info = AuthInfo {
103 token: auth_response.token,
104 repo_id: "".to_string(),
105 };
106 }
107 _ => {
108 return Err(parse_error(resp));
109 }
110 }
111
112 let url = format!("{}/api2/repos", self.endpoint);
113
114 let req = Request::get(url)
115 .header(
116 header::AUTHORIZATION,
117 format!("Token {}", signer.auth_info.token),
118 )
119 .body(Buffer::new())
120 .map_err(new_request_build_error)?;
121
122 let resp = self.info.http_client().send(req).await?;
123
124 let status = resp.status();
125
126 match status {
127 StatusCode::OK => {
128 let resp_body = resp.into_body();
129 let list_library_response: Vec<ListLibraryResponse> =
130 serde_json::from_reader(resp_body.reader())
131 .map_err(new_json_deserialize_error)?;
132
133 for library in list_library_response {
134 if library.name == self.repo_name {
135 signer.auth_info.repo_id = library.id;
136 break;
137 }
138 }
139
140 if signer.auth_info.repo_id.is_empty() {
142 return Err(Error::new(
143 ErrorKind::NotFound,
144 format!("repo {} not found", self.repo_name),
145 ));
146 }
147 }
148 _ => {
149 return Err(parse_error(resp));
150 }
151 }
152 Ok(signer.auth_info.clone())
153 }
154 }
155}
156
157impl SeafileCore {
158 async fn get_upload_url(&self) -> Result<String> {
160 let auth_info = self.get_auth_info().await?;
161
162 let req = Request::get(format!(
163 "{}/api2/repos/{}/upload-link/",
164 self.endpoint, auth_info.repo_id
165 ));
166
167 let req = req
168 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
169 .extension(Operation::Write)
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 upload_file(&self, path: &str, body: Buffer) -> Result<Response<Buffer>> {
188 let upload_url = self.get_upload_url().await?;
189
190 let req = Request::post(upload_url).extension(Operation::Write);
191
192 let (filename, relative_path) = if path.ends_with('/') {
193 ("", build_abs_path(&self.root, path))
194 } else {
195 let (filename, relative_path) = (get_basename(path), get_parent(path));
196 (filename, build_abs_path(&self.root, relative_path))
197 };
198
199 let file_part = FormDataPart::new("file")
200 .header(
201 header::CONTENT_DISPOSITION,
202 format!("form-data; name=\"file\"; filename=\"{filename}\"")
203 .parse()
204 .unwrap(),
205 )
206 .content(body);
207
208 let multipart = Multipart::new()
209 .part(FormDataPart::new("parent_dir").content("/"))
210 .part(FormDataPart::new("relative_path").content(relative_path.clone()))
211 .part(FormDataPart::new("replace").content("1"))
212 .part(file_part);
213
214 let req = multipart.apply(req)?;
215
216 self.send(req).await
217 }
218
219 async fn get_download_url(&self, path: &str) -> Result<String> {
221 let path = build_abs_path(&self.root, path);
222 let path = percent_encode_path(&path);
223
224 let auth_info = self.get_auth_info().await?;
225
226 let req = Request::get(format!(
227 "{}/api2/repos/{}/file/?p={}",
228 self.endpoint, auth_info.repo_id, path
229 ));
230
231 let req = req
232 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
233 .extension(Operation::Read)
234 .body(Buffer::new())
235 .map_err(new_request_build_error)?;
236
237 let resp = self.send(req).await?;
238 let status = resp.status();
239
240 match status {
241 StatusCode::OK => {
242 let resp_body = resp.into_body();
243 let download_url = serde_json::from_reader(resp_body.reader())
244 .map_err(new_json_deserialize_error)?;
245
246 Ok(download_url)
247 }
248 _ => Err(parse_error(resp)),
249 }
250 }
251
252 pub async fn download_file(&self, path: &str, range: BytesRange) -> Result<Response<HttpBody>> {
254 let download_url = self.get_download_url(path).await?;
255
256 let req = Request::get(download_url);
257
258 let req = req
259 .header(header::RANGE, range.to_header())
260 .extension(Operation::Read)
261 .body(Buffer::new())
262 .map_err(new_request_build_error)?;
263
264 self.info.http_client().fetch(req).await
265 }
266
267 pub async fn file_detail(&self, path: &str) -> Result<FileDetail> {
269 let path = build_abs_path(&self.root, path);
270 let path = percent_encode_path(&path);
271
272 let auth_info = self.get_auth_info().await?;
273
274 let req = Request::get(format!(
275 "{}/api2/repos/{}/file/detail/?p={}",
276 self.endpoint, auth_info.repo_id, path
277 ));
278
279 let req = req
280 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
281 .extension(Operation::Stat)
282 .body(Buffer::new())
283 .map_err(new_request_build_error)?;
284
285 let resp = self.send(req).await?;
286 let status = resp.status();
287
288 match status {
289 StatusCode::OK => {
290 let resp_body = resp.into_body();
291 let file_detail: FileDetail = serde_json::from_reader(resp_body.reader())
292 .map_err(new_json_deserialize_error)?;
293 Ok(file_detail)
294 }
295 _ => Err(parse_error(resp)),
296 }
297 }
298
299 pub async fn dir_detail(&self, path: &str) -> Result<DirDetail> {
301 let path = build_abs_path(&self.root, path);
302 let path = percent_encode_path(&path);
303
304 let auth_info = self.get_auth_info().await?;
305
306 let req = Request::get(format!(
307 "{}/api/v2.1/repos/{}/dir/detail/?path={}",
308 self.endpoint, auth_info.repo_id, path
309 ));
310
311 let req = req
312 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
313 .extension(Operation::Stat)
314 .body(Buffer::new())
315 .map_err(new_request_build_error)?;
316
317 let resp = self.send(req).await?;
318 let status = resp.status();
319
320 match status {
321 StatusCode::OK => {
322 let resp_body = resp.into_body();
323 let dir_detail: DirDetail = serde_json::from_reader(resp_body.reader())
324 .map_err(new_json_deserialize_error)?;
325 Ok(dir_detail)
326 }
327 _ => Err(parse_error(resp)),
328 }
329 }
330
331 pub async fn delete(&self, path: &str) -> Result<()> {
333 let path = build_abs_path(&self.root, path);
334 let path = percent_encode_path(&path);
335
336 let auth_info = self.get_auth_info().await?;
337
338 let url = if path.ends_with('/') {
339 format!(
340 "{}/api2/repos/{}/dir/?p={}",
341 self.endpoint, auth_info.repo_id, path
342 )
343 } else {
344 format!(
345 "{}/api2/repos/{}/file/?p={}",
346 self.endpoint, auth_info.repo_id, path
347 )
348 };
349
350 let req = Request::delete(url);
351
352 let req = req
353 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
354 .extension(Operation::Delete)
355 .body(Buffer::new())
356 .map_err(new_request_build_error)?;
357
358 let resp = self.send(req).await?;
359
360 let status = resp.status();
361
362 match status {
363 StatusCode::OK => Ok(()),
364 _ => Err(parse_error(resp)),
365 }
366 }
367
368 pub async fn list(&self, path: &str) -> Result<ListResponse> {
369 let rooted_abs_path = build_rooted_abs_path(&self.root, path);
370
371 let auth_info = self.get_auth_info().await?;
372
373 let url = format!(
374 "{}/api2/repos/{}/dir/?p={}",
375 self.endpoint,
376 auth_info.repo_id,
377 percent_encode_path(&rooted_abs_path)
378 );
379
380 let req = Request::get(url);
381
382 let req = req
383 .header(header::AUTHORIZATION, format!("Token {}", auth_info.token))
384 .extension(Operation::List)
385 .body(Buffer::new())
386 .map_err(new_request_build_error)?;
387
388 let resp = self.send(req).await?;
389
390 match resp.status() {
391 StatusCode::OK => {
392 let resp_body = resp.into_body();
393 let infos: Vec<Info> = serde_json::from_reader(resp_body.reader())
394 .map_err(new_json_deserialize_error)?;
395 Ok(ListResponse {
396 infos: Some(infos),
397 rooted_abs_path,
398 })
399 }
400 StatusCode::NOT_FOUND => Ok(ListResponse {
402 infos: None,
403 rooted_abs_path,
404 }),
405 _ => Err(parse_error(resp)),
406 }
407 }
408}
409
410#[derive(Deserialize)]
411pub struct AuthTokenResponse {
412 pub token: String,
413}
414
415#[derive(Deserialize)]
416pub struct FileDetail {
417 pub last_modified: String,
418 pub size: u64,
419}
420
421#[derive(Debug, Deserialize)]
422pub struct DirDetail {
423 mtime: String,
424}
425
426pub fn parse_dir_detail(dir_detail: DirDetail) -> Result<Metadata> {
427 let mut md = Metadata::new(EntryMode::DIR);
428
429 md.set_last_modified(dir_detail.mtime.parse::<Timestamp>()?);
430
431 Ok(md)
432}
433
434pub fn parse_file_detail(file_detail: FileDetail) -> Result<Metadata> {
435 let mut md = Metadata::new(EntryMode::FILE);
436
437 md.set_content_length(file_detail.size);
438 md.set_last_modified(file_detail.last_modified.parse::<Timestamp>()?);
439
440 Ok(md)
441}
442
443#[derive(Clone, Default)]
444pub struct SeafileSigner {
445 pub auth_info: AuthInfo,
446}
447
448#[derive(Clone, Default)]
449pub struct AuthInfo {
450 pub repo_id: String,
452 pub token: String,
454}
455
456#[derive(Deserialize)]
457pub struct ListLibraryResponse {
458 pub name: String,
459 pub id: String,
460}
461
462#[derive(Debug, Deserialize)]
463pub struct Info {
464 #[serde(rename = "type")]
465 pub type_field: String,
466 pub mtime: i64,
467 pub size: Option<u64>,
468 pub name: String,
469}
470
471pub struct ListResponse {
472 pub infos: Option<Vec<Info>>,
473 pub rooted_abs_path: String,
474}