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    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    /// get download
188    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    /// download file
220    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    /// file detail
234    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    /// dir detail
265    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    /// delete file or dir
296    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    /// The repo id of this auth info.
373    pub repo_id: String,
374    /// The token of this auth info,
375    pub token: String,
376}
377
378#[derive(Deserialize)]
379pub struct ListLibraryResponse {
380    pub name: String,
381    pub id: String,
382}