1use std::sync::Arc;
19
20use bytes::Buf;
21use http::StatusCode;
22use log::debug;
23use serde::Deserialize;
24
25use super::DBFS_SCHEME;
26use super::config::DbfsConfig;
27use super::core::DbfsCore;
28use super::core::parse_error;
29use super::deleter::DbfsDeleter;
30use super::lister::DbfsLister;
31use super::writer::DbfsWriter;
32use opendal_core::raw::*;
33use opendal_core::*;
34
35#[doc = include_str!("docs.md")]
37#[derive(Debug, Default)]
38pub struct DbfsBuilder {
39 pub(super) config: DbfsConfig,
40}
41
42impl DbfsBuilder {
43 pub fn root(mut self, root: &str) -> Self {
47 self.config.root = if root.is_empty() {
48 None
49 } else {
50 Some(root.to_string())
51 };
52
53 self
54 }
55
56 pub fn endpoint(mut self, endpoint: &str) -> Self {
63 self.config.endpoint = if endpoint.is_empty() {
64 None
65 } else {
66 Some(endpoint.trim_end_matches('/').to_string())
67 };
68 self
69 }
70
71 pub fn token(mut self, token: &str) -> Self {
73 if !token.is_empty() {
74 self.config.token = Some(token.to_string());
75 }
76 self
77 }
78}
79
80impl Builder for DbfsBuilder {
81 type Config = DbfsConfig;
82
83 fn build(self) -> Result<impl Service> {
85 debug!("backend build started: {:?}", self);
86
87 let root = normalize_root(&self.config.root.unwrap_or_default());
88 debug!("backend use root {root}");
89
90 let endpoint = match &self.config.endpoint {
91 Some(endpoint) => Ok(endpoint.clone()),
92 None => Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
93 .with_operation("Builder::build")
94 .with_context("service", DBFS_SCHEME)),
95 }?;
96 debug!("backend use endpoint: {}", endpoint);
97
98 let token = match self.config.token {
99 Some(token) => token,
100 None => {
101 return Err(Error::new(
102 ErrorKind::ConfigInvalid,
103 "missing token for Dbfs",
104 ));
105 }
106 };
107
108 let capability = Capability {
109 stat: true,
110
111 write: true,
112 create_dir: true,
113 delete: true,
114 rename: true,
115
116 list: true,
117
118 shared: true,
119
120 ..Default::default()
121 };
122
123 Ok(DbfsBackend {
124 core: Arc::new(DbfsCore {
125 root,
126 endpoint: endpoint.to_string(),
127 token,
128 }),
129 capability,
130 })
131 }
132}
133
134#[derive(Debug, Clone)]
136pub struct DbfsBackend {
137 core: Arc<DbfsCore>,
138 capability: Capability,
139}
140
141impl Service for DbfsBackend {
142 type Reader = ();
143 type Writer = oio::OneShotWriter<DbfsWriter>;
144 type Lister = oio::PageLister<DbfsLister>;
145 type Deleter = oio::OneShotDeleter<DbfsDeleter>;
146 type Copier = ();
147
148 fn info(&self) -> ServiceInfo {
149 ServiceInfo::new(DBFS_SCHEME, &self.core.root, "")
150 }
151
152 fn capability(&self) -> Capability {
153 self.capability
154 }
155
156 async fn create_dir(
157 &self,
158 ctx: &OperationContext,
159 path: &str,
160 _: OpCreateDir,
161 ) -> Result<RpCreateDir> {
162 let resp = self.core.dbfs_create_dir(ctx, path).await?;
163
164 let status = resp.status();
165
166 match status {
167 StatusCode::CREATED | StatusCode::OK => Ok(RpCreateDir::default()),
168 _ => Err(parse_error(resp)),
169 }
170 }
171
172 async fn stat(&self, ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
173 if path == "/" {
175 return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
176 }
177
178 let resp = self.core.dbfs_get_status(ctx, path).await?;
179
180 let status = resp.status();
181
182 match status {
183 StatusCode::OK => {
184 let mut meta = parse_into_metadata(path, resp.headers())?;
185 let bs = resp.into_body();
186 let decoded_response: DbfsStatus =
187 serde_json::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
188 meta.set_last_modified(Timestamp::from_millisecond(
189 decoded_response.modification_time,
190 )?);
191 match decoded_response.is_dir {
192 true => meta.set_mode(EntryMode::DIR),
193 false => {
194 meta.set_mode(EntryMode::FILE);
195 meta.set_content_length(decoded_response.file_size as u64)
196 }
197 };
198 Ok(RpStat::new(meta))
199 }
200 StatusCode::NOT_FOUND if path.ends_with('/') => {
201 Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
202 }
203 _ => Err(parse_error(resp)),
204 }
205 }
206
207 fn read(&self, _ctx: &OperationContext, _path: &str, _args: OpRead) -> Result<Self::Reader> {
208 Err(Error::new(
209 ErrorKind::Unsupported,
210 "operation is not supported",
211 ))
212 }
213
214 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
215 let output: oio::OneShotWriter<DbfsWriter> = {
216 Ok(oio::OneShotWriter::new(DbfsWriter::new(
217 self.core.clone(),
218 ctx.clone(),
219 args,
220 path.to_string(),
221 )))
222 }?;
223
224 Ok(output)
225 }
226
227 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
228 let output: oio::OneShotDeleter<DbfsDeleter> = {
229 Ok(oio::OneShotDeleter::new(DbfsDeleter::new(
230 self.core.clone(),
231 ctx.clone(),
232 )))
233 }?;
234
235 Ok(output)
236 }
237
238 fn list(&self, ctx: &OperationContext, path: &str, _args: OpList) -> Result<Self::Lister> {
239 let output: oio::PageLister<DbfsLister> = {
240 let l = DbfsLister::new(self.core.clone(), ctx.clone(), path.to_string());
241
242 Ok(oio::PageLister::new(l))
243 }?;
244
245 Ok(output)
246 }
247
248 async fn rename(
249 &self,
250 ctx: &OperationContext,
251 from: &str,
252 to: &str,
253 _args: OpRename,
254 ) -> Result<RpRename> {
255 self.core.dbfs_ensure_parent_path(ctx, to).await?;
256
257 let resp = self.core.dbfs_rename(ctx, from, to).await?;
258
259 let status = resp.status();
260
261 match status {
262 StatusCode::OK => Ok(RpRename::default()),
263 _ => Err(parse_error(resp)),
264 }
265 }
266
267 fn copy(
268 &self,
269 _ctx: &OperationContext,
270 _from: &str,
271 _to: &str,
272 _args: OpCopy,
273 _opts: OpCopier,
274 ) -> Result<Self::Copier> {
275 Err(Error::new(
276 ErrorKind::Unsupported,
277 "operation is not supported",
278 ))
279 }
280
281 async fn presign(
282 &self,
283 _ctx: &OperationContext,
284 _path: &str,
285 _args: OpPresign,
286 ) -> Result<RpPresign> {
287 Err(Error::new(
288 ErrorKind::Unsupported,
289 "operation is not supported",
290 ))
291 }
292}
293
294#[derive(Deserialize)]
295struct DbfsStatus {
296 is_dir: bool,
299 file_size: i64,
300 modification_time: i64,
301}