opendal/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::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/// Core of [seafile](https://www.seafile.com) services support.
36#[derive(Clone)]
37pub struct SeafileCore {
38    pub info: Arc<AccessorInfo>,
39    /// The root of this core.
40    pub root: String,
41    /// The endpoint of this backend.
42    pub endpoint: String,
43    /// The username of this backend.
44    pub username: String,
45    /// The password id of this backend.
46    pub password: String,
47    /// The repo name of this backend.
48    pub repo_name: String,
49
50    /// signer of this backend.
51    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    /// get auth info
72    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                    // repo not found
142                    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    /// get upload url
160    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    /// get download
221    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    /// download file
254    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    /// file detail
269    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    /// dir detail
301    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    /// delete file or dir
333    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            // return nothing when not exist
402            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    /// The repo id of this auth info.
452    pub repo_id: String,
453    /// The token of this auth info,
454    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}