opendal_core/services/seafile/
core.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use 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/// Core of [seafile](https://www.seafile.com) services support.
35#[derive(Clone)]
36pub struct SeafileCore {
37    pub info: Arc<AccessorInfo>,
38    /// The root of this core.
39    pub root: String,
40    /// The endpoint of this backend.
41    pub endpoint: String,
42    /// The username of this backend.
43    pub username: String,
44    /// The password id of this backend.
45    pub password: String,
46    /// The repo name of this backend.
47    pub repo_name: String,
48
49    /// signer of this backend.
50    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    /// get auth info
71    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                    // repo not found
141                    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    /// get upload url
159    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    /// get download
220    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    /// download file
253    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    /// file detail
268    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    /// dir detail
300    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    /// delete file or dir
332    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            // return nothing when not exist
401            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    /// The repo id of this auth info.
451    pub repo_id: String,
452    /// The token of this auth info,
453    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}