Skip to main content

opendal_service_seafile/
backend.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 log::debug;
22use mea::rwlock::RwLock;
23
24use super::SEAFILE_SCHEME;
25use super::config::SeafileConfig;
26use super::core::SeafileCore;
27use super::core::SeafileSigner;
28use super::core::parse_dir_detail;
29use super::core::parse_file_detail;
30use super::deleter::SeafileDeleter;
31use super::lister::SeafileLister;
32use super::reader::*;
33use super::writer::SeafileWriter;
34use super::writer::SeafileWriters;
35use opendal_core::raw::*;
36use opendal_core::*;
37
38/// [seafile](https://www.seafile.com) services support.
39#[doc = include_str!("docs.md")]
40#[derive(Default)]
41pub struct SeafileBuilder {
42    pub(super) config: SeafileConfig,
43}
44
45impl Debug for SeafileBuilder {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        f.debug_struct("SeafileBuilder")
48            .field("config", &self.config)
49            .finish_non_exhaustive()
50    }
51}
52
53impl SeafileBuilder {
54    /// Set root of this backend.
55    ///
56    /// All operations will happen under this root.
57    pub fn root(mut self, root: &str) -> Self {
58        self.config.root = if root.is_empty() {
59            None
60        } else {
61            Some(root.to_string())
62        };
63
64        self
65    }
66
67    /// endpoint of this backend.
68    ///
69    /// It is required. e.g. `http://127.0.0.1:80`
70    pub fn endpoint(mut self, endpoint: &str) -> Self {
71        self.config.endpoint = if endpoint.is_empty() {
72            None
73        } else {
74            Some(endpoint.to_string())
75        };
76
77        self
78    }
79
80    /// username of this backend.
81    ///
82    /// It is required. e.g. `me@example.com`
83    pub fn username(mut self, username: &str) -> Self {
84        self.config.username = if username.is_empty() {
85            None
86        } else {
87            Some(username.to_string())
88        };
89
90        self
91    }
92
93    /// password of this backend.
94    ///
95    /// It is required. e.g. `asecret`
96    pub fn password(mut self, password: &str) -> Self {
97        self.config.password = if password.is_empty() {
98            None
99        } else {
100            Some(password.to_string())
101        };
102
103        self
104    }
105
106    /// Set repo name of this backend.
107    ///
108    /// It is required. e.g. `myrepo`
109    pub fn repo_name(mut self, repo_name: &str) -> Self {
110        self.config.repo_name = repo_name.to_string();
111
112        self
113    }
114}
115
116impl Builder for SeafileBuilder {
117    type Config = SeafileConfig;
118
119    /// Builds the backend and returns the result of SeafileBackend.
120    fn build(self) -> Result<impl Service> {
121        debug!("backend build started: {:?}", self);
122
123        let root = normalize_root(&self.config.root.clone().unwrap_or_default());
124        debug!("backend use root {}", root);
125
126        // Handle bucket.
127        if self.config.repo_name.is_empty() {
128            return Err(Error::new(ErrorKind::ConfigInvalid, "repo_name is empty")
129                .with_operation("Builder::build")
130                .with_context("service", SEAFILE_SCHEME));
131        }
132
133        debug!("backend use repo_name {}", self.config.repo_name);
134
135        let endpoint = match &self.config.endpoint {
136            Some(endpoint) => Ok(endpoint.clone()),
137            None => Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
138                .with_operation("Builder::build")
139                .with_context("service", SEAFILE_SCHEME)),
140        }?;
141
142        let username = match &self.config.username {
143            Some(username) => Ok(username.clone()),
144            None => Err(Error::new(ErrorKind::ConfigInvalid, "username is empty")
145                .with_operation("Builder::build")
146                .with_context("service", SEAFILE_SCHEME)),
147        }?;
148
149        let password = match &self.config.password {
150            Some(password) => Ok(password.clone()),
151            None => Err(Error::new(ErrorKind::ConfigInvalid, "password is empty")
152                .with_operation("Builder::build")
153                .with_context("service", SEAFILE_SCHEME)),
154        }?;
155
156        Ok(SeafileBackend {
157            core: Arc::new(SeafileCore {
158                info: ServiceInfo::new(SEAFILE_SCHEME, &root, ""),
159                capability: Capability {
160                    create_dir: true,
161                    stat: true,
162
163                    read: true,
164
165                    write: true,
166                    write_can_empty: true,
167
168                    delete: true,
169
170                    list: true,
171
172                    shared: true,
173
174                    ..Default::default()
175                },
176                root,
177                endpoint,
178                username,
179                password,
180                repo_name: self.config.repo_name.clone(),
181                signer: Arc::new(RwLock::new(SeafileSigner::default())),
182            }),
183        })
184    }
185}
186
187/// Backend for seafile services.
188#[derive(Debug, Clone)]
189pub struct SeafileBackend {
190    pub(crate) core: Arc<SeafileCore>,
191}
192
193impl Service for SeafileBackend {
194    type Reader = oio::StreamReader<SeafileReader>;
195    type Writer = SeafileWriters;
196    type Lister = oio::PageLister<SeafileLister>;
197    type Deleter = oio::OneShotDeleter<SeafileDeleter>;
198    type Copier = ();
199
200    fn info(&self) -> ServiceInfo {
201        self.core.info.clone()
202    }
203
204    fn capability(&self) -> Capability {
205        self.core.capability
206    }
207
208    async fn create_dir(
209        &self,
210        ctx: &OperationContext,
211        path: &str,
212        _args: OpCreateDir,
213    ) -> Result<RpCreateDir> {
214        self.core.create_dir(ctx, path).await?;
215        Ok(RpCreateDir::default())
216    }
217
218    async fn stat(&self, ctx: &OperationContext, path: &str, _args: OpStat) -> Result<RpStat> {
219        if path == "/" {
220            return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
221        }
222
223        let metadata = if path.ends_with('/') {
224            let dir_detail = self.core.dir_detail(ctx, path).await?;
225            parse_dir_detail(dir_detail)
226        } else {
227            let file_detail = self.core.file_detail(ctx, path).await?;
228
229            parse_file_detail(file_detail)
230        };
231
232        metadata.map(RpStat::new)
233    }
234    fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
235        let output: oio::StreamReader<SeafileReader> = {
236            Ok(oio::StreamReader::new(SeafileReader::new(
237                self.clone(),
238                ctx.clone(),
239                path,
240                args,
241            )))
242        }?;
243
244        Ok(output)
245    }
246
247    fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
248        let output: SeafileWriters = {
249            let w = SeafileWriter::new(self.core.clone(), ctx.clone(), args, path.to_string());
250            let w = oio::OneShotWriter::new(w);
251
252            Ok(w)
253        }?;
254
255        Ok(output)
256    }
257
258    fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
259        let output: oio::OneShotDeleter<SeafileDeleter> = {
260            Ok(oio::OneShotDeleter::new(SeafileDeleter::new(
261                self.core.clone(),
262                ctx.clone(),
263            )))
264        }?;
265
266        Ok(output)
267    }
268
269    fn list(&self, ctx: &OperationContext, path: &str, _args: OpList) -> Result<Self::Lister> {
270        let output: oio::PageLister<SeafileLister> = {
271            let l = SeafileLister::new(self.core.clone(), ctx.clone(), path);
272            Ok(oio::PageLister::new(l))
273        }?;
274
275        Ok(output)
276    }
277
278    fn copy(
279        &self,
280        _ctx: &OperationContext,
281        _from: &str,
282        _to: &str,
283        _args: OpCopy,
284        _opts: OpCopier,
285    ) -> Result<Self::Copier> {
286        Err(Error::new(
287            ErrorKind::Unsupported,
288            "operation is not supported",
289        ))
290    }
291
292    async fn rename(
293        &self,
294        _ctx: &OperationContext,
295        _from: &str,
296        _to: &str,
297        _args: OpRename,
298    ) -> Result<RpRename> {
299        Err(Error::new(
300            ErrorKind::Unsupported,
301            "operation is not supported",
302        ))
303    }
304
305    async fn presign(
306        &self,
307        _ctx: &OperationContext,
308        _path: &str,
309        _args: OpPresign,
310    ) -> Result<RpPresign> {
311        Err(Error::new(
312            ErrorKind::Unsupported,
313            "operation is not supported",
314        ))
315    }
316}