opendal/services/seafile/
backend.rs1use std::fmt::Debug;
19use std::fmt::Formatter;
20use std::sync::Arc;
21
22use http::Response;
23use http::StatusCode;
24use log::debug;
25use tokio::sync::RwLock;
26
27use super::SEAFILE_SCHEME;
28use super::core::SeafileCore;
29use super::core::SeafileSigner;
30use super::core::parse_dir_detail;
31use super::core::parse_file_detail;
32use super::delete::SeafileDeleter;
33use super::error::parse_error;
34use super::lister::SeafileLister;
35use super::writer::SeafileWriter;
36use super::writer::SeafileWriters;
37use crate::raw::*;
38use crate::services::SeafileConfig;
39use crate::*;
40
41#[doc = include_str!("docs.md")]
43#[derive(Default)]
44pub struct SeafileBuilder {
45 pub(super) config: SeafileConfig,
46
47 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
48 pub(super) http_client: Option<HttpClient>,
49}
50
51impl Debug for SeafileBuilder {
52 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 let mut d = f.debug_struct("SeafileBuilder");
54
55 d.field("config", &self.config);
56 d.finish_non_exhaustive()
57 }
58}
59
60impl SeafileBuilder {
61 pub fn root(mut self, root: &str) -> Self {
65 self.config.root = if root.is_empty() {
66 None
67 } else {
68 Some(root.to_string())
69 };
70
71 self
72 }
73
74 pub fn endpoint(mut self, endpoint: &str) -> Self {
78 self.config.endpoint = if endpoint.is_empty() {
79 None
80 } else {
81 Some(endpoint.to_string())
82 };
83
84 self
85 }
86
87 pub fn username(mut self, username: &str) -> Self {
91 self.config.username = if username.is_empty() {
92 None
93 } else {
94 Some(username.to_string())
95 };
96
97 self
98 }
99
100 pub fn password(mut self, password: &str) -> Self {
104 self.config.password = if password.is_empty() {
105 None
106 } else {
107 Some(password.to_string())
108 };
109
110 self
111 }
112
113 pub fn repo_name(mut self, repo_name: &str) -> Self {
117 self.config.repo_name = repo_name.to_string();
118
119 self
120 }
121
122 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
129 #[allow(deprecated)]
130 pub fn http_client(mut self, client: HttpClient) -> Self {
131 self.http_client = Some(client);
132 self
133 }
134}
135
136impl Builder for SeafileBuilder {
137 type Config = SeafileConfig;
138
139 fn build(self) -> Result<impl Access> {
141 debug!("backend build started: {:?}", &self);
142
143 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
144 debug!("backend use root {}", &root);
145
146 if self.config.repo_name.is_empty() {
148 return Err(Error::new(ErrorKind::ConfigInvalid, "repo_name is empty")
149 .with_operation("Builder::build")
150 .with_context("service", Scheme::Seafile));
151 }
152
153 debug!("backend use repo_name {}", &self.config.repo_name);
154
155 let endpoint = match &self.config.endpoint {
156 Some(endpoint) => Ok(endpoint.clone()),
157 None => Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
158 .with_operation("Builder::build")
159 .with_context("service", Scheme::Seafile)),
160 }?;
161
162 let username = match &self.config.username {
163 Some(username) => Ok(username.clone()),
164 None => Err(Error::new(ErrorKind::ConfigInvalid, "username is empty")
165 .with_operation("Builder::build")
166 .with_context("service", Scheme::Seafile)),
167 }?;
168
169 let password = match &self.config.password {
170 Some(password) => Ok(password.clone()),
171 None => Err(Error::new(ErrorKind::ConfigInvalid, "password is empty")
172 .with_operation("Builder::build")
173 .with_context("service", Scheme::Seafile)),
174 }?;
175
176 Ok(SeafileBackend {
177 core: Arc::new(SeafileCore {
178 info: {
179 let am = AccessorInfo::default();
180 am.set_scheme(SEAFILE_SCHEME)
181 .set_root(&root)
182 .set_native_capability(Capability {
183 stat: true,
184
185 read: true,
186
187 write: true,
188 write_can_empty: true,
189
190 delete: true,
191
192 list: true,
193
194 shared: true,
195
196 ..Default::default()
197 });
198
199 #[allow(deprecated)]
201 if let Some(client) = self.http_client {
202 am.update_http_client(|_| client);
203 }
204
205 am.into()
206 },
207 root,
208 endpoint,
209 username,
210 password,
211 repo_name: self.config.repo_name.clone(),
212 signer: Arc::new(RwLock::new(SeafileSigner::default())),
213 }),
214 })
215 }
216}
217
218#[derive(Debug, Clone)]
220pub struct SeafileBackend {
221 core: Arc<SeafileCore>,
222}
223
224impl Access for SeafileBackend {
225 type Reader = HttpBody;
226 type Writer = SeafileWriters;
227 type Lister = oio::PageLister<SeafileLister>;
228 type Deleter = oio::OneShotDeleter<SeafileDeleter>;
229
230 fn info(&self) -> Arc<AccessorInfo> {
231 self.core.info.clone()
232 }
233
234 async fn stat(&self, path: &str, _args: OpStat) -> Result<RpStat> {
235 if path == "/" {
236 return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
237 }
238
239 let metadata = if path.ends_with('/') {
240 let dir_detail = self.core.dir_detail(path).await?;
241 parse_dir_detail(dir_detail)
242 } else {
243 let file_detail = self.core.file_detail(path).await?;
244
245 parse_file_detail(file_detail)
246 };
247
248 metadata.map(RpStat::new)
249 }
250
251 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
252 let resp = self.core.download_file(path, args.range()).await?;
253
254 let status = resp.status();
255
256 match status {
257 StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
258 Ok((RpRead::default(), resp.into_body()))
259 }
260 _ => {
261 let (part, mut body) = resp.into_parts();
262 let buf = body.to_buffer().await?;
263 Err(parse_error(Response::from_parts(part, buf)))
264 }
265 }
266 }
267
268 async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
269 let w = SeafileWriter::new(self.core.clone(), args, path.to_string());
270 let w = oio::OneShotWriter::new(w);
271
272 Ok((RpWrite::default(), w))
273 }
274
275 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
276 Ok((
277 RpDelete::default(),
278 oio::OneShotDeleter::new(SeafileDeleter::new(self.core.clone())),
279 ))
280 }
281
282 async fn list(&self, path: &str, _args: OpList) -> Result<(RpList, Self::Lister)> {
283 let l = SeafileLister::new(self.core.clone(), path);
284 Ok((RpList::default(), oio::PageLister::new(l)))
285 }
286}