1use std::fmt::Debug;
19use std::sync::Arc;
20
21use asyncband::rwlock::RwLock;
22use http::Request;
23use http::StatusCode;
24use log::debug;
25
26use super::B2_SCHEME;
27use super::config::B2Config;
28use super::core::B2Signer;
29use super::core::constants;
30use super::core::parse_error;
31use super::core::parse_file_info;
32use super::core::{B2Core, ErrorContext};
33use super::deleter::B2Deleter;
34use super::lister::B2Lister;
35use super::reader::*;
36use super::writer::B2Writer;
37use super::writer::B2Writers;
38use opendal_core::raw::*;
39use opendal_core::*;
40
41#[doc = include_str!("docs.md")]
43#[derive(Default)]
44pub struct B2Builder {
45 pub(super) config: B2Config,
46}
47
48impl Debug for B2Builder {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("B2Builder")
51 .field("config", &self.config)
52 .finish_non_exhaustive()
53 }
54}
55
56impl B2Builder {
57 pub fn root(mut self, root: &str) -> Self {
61 self.config.root = if root.is_empty() {
62 None
63 } else {
64 Some(root.to_string())
65 };
66
67 self
68 }
69
70 pub fn application_key_id(mut self, application_key_id: &str) -> Self {
72 self.config.application_key_id = if application_key_id.is_empty() {
73 None
74 } else {
75 Some(application_key_id.to_string())
76 };
77
78 self
79 }
80
81 pub fn application_key(mut self, application_key: &str) -> Self {
83 self.config.application_key = if application_key.is_empty() {
84 None
85 } else {
86 Some(application_key.to_string())
87 };
88
89 self
90 }
91
92 pub fn bucket(mut self, bucket: &str) -> Self {
95 self.config.bucket = bucket.to_string();
96
97 self
98 }
99
100 pub fn bucket_id(mut self, bucket_id: &str) -> Self {
103 self.config.bucket_id = bucket_id.to_string();
104
105 self
106 }
107}
108
109impl Builder for B2Builder {
110 type Config = B2Config;
111
112 fn build(self) -> Result<impl Service> {
114 debug!("backend build started: {:?}", self);
115
116 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
117 debug!("backend use root {}", root);
118
119 if self.config.bucket.is_empty() {
121 return Err(Error::new(ErrorKind::ConfigInvalid, "bucket is empty")
122 .with_operation("Builder::build")
123 .with_context("service", B2_SCHEME));
124 }
125
126 debug!("backend use bucket {}", self.config.bucket);
127
128 if self.config.bucket_id.is_empty() {
130 return Err(Error::new(ErrorKind::ConfigInvalid, "bucket_id is empty")
131 .with_operation("Builder::build")
132 .with_context("service", B2_SCHEME));
133 }
134
135 debug!("backend bucket_id {}", self.config.bucket_id);
136
137 let application_key_id = match &self.config.application_key_id {
138 Some(application_key_id) => Ok(application_key_id.clone()),
139 None => Err(
140 Error::new(ErrorKind::ConfigInvalid, "application_key_id is empty")
141 .with_operation("Builder::build")
142 .with_context("service", B2_SCHEME),
143 ),
144 }?;
145
146 let application_key = match &self.config.application_key {
147 Some(key_id) => Ok(key_id.clone()),
148 None => Err(
149 Error::new(ErrorKind::ConfigInvalid, "application_key is empty")
150 .with_operation("Builder::build")
151 .with_context("service", B2_SCHEME),
152 ),
153 }?;
154
155 let signer = B2Signer {
156 application_key_id,
157 application_key,
158 ..Default::default()
159 };
160
161 Ok(B2Backend {
162 core: Arc::new(B2Core {
163 info: ServiceInfo::new(B2_SCHEME, &root, ""),
164 capability: Capability {
165 stat: true,
166
167 read: true,
168 read_with_suffix: true,
169
170 write: true,
171 write_can_empty: true,
172 write_can_multi: true,
173 write_with_content_type: true,
174 write_with_user_metadata: true,
175 write_multi_min_size: Some(5 * 1024 * 1024),
179 write_multi_max_size: if cfg!(target_pointer_width = "64") {
183 Some(5 * 1024 * 1024 * 1024)
184 } else {
185 Some(usize::MAX)
186 },
187
188 delete: true,
189 copy: true,
190
191 list: true,
192 list_with_limit: true,
193 list_with_start_after: true,
194 list_with_recursive: true,
195
196 presign: true,
197 presign_read: true,
198 presign_write: true,
199 presign_stat: true,
200
201 shared: true,
202
203 ..Default::default()
204 },
205 signer: Arc::new(RwLock::new(signer)),
206 root,
207
208 bucket: self.config.bucket.clone(),
209 bucket_id: self.config.bucket_id.clone(),
210 }),
211 })
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct B2Backend {
218 pub(crate) core: Arc<B2Core>,
219}
220
221impl Service for B2Backend {
222 type Reader = oio::StreamReader<B2Reader>;
223 type Writer = B2Writers;
224 type Lister = oio::PageLister<B2Lister>;
225 type Deleter = oio::OneShotDeleter<B2Deleter>;
226 type Copier = oio::OneShotCopier;
227 type Composer = ();
228
229 fn info(&self) -> ServiceInfo {
230 self.core.info.clone()
231 }
232
233 fn capability(&self) -> Capability {
234 self.core.capability
235 }
236
237 async fn create_dir(
238 &self,
239 _ctx: &OperationContext,
240 _path: &str,
241 _args: OpCreateDir,
242 ) -> Result<RpCreateDir> {
243 Err(Error::new(
244 ErrorKind::Unsupported,
245 "operation is not supported",
246 ))
247 }
248
249 async fn stat(&self, ctx: &OperationContext, path: &str, _args: OpStat) -> Result<RpStat> {
252 if path == "/" {
254 return Ok(RpStat::new(MetadataBuilder::dir().build()));
255 }
256
257 let delimiter = if path.ends_with('/') { Some("/") } else { None };
258
259 let file_info = self.core.get_file_info(ctx, path, delimiter).await?;
260 let meta = parse_file_info(&file_info);
261 Ok(RpStat::new(meta))
262 }
263 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
264 let output: oio::StreamReader<B2Reader> = {
265 Ok(oio::StreamReader::new(B2Reader::new(
266 self.clone(),
267 ctx.clone(),
268 path,
269 args,
270 )))
271 }?;
272
273 Ok(output)
274 }
275
276 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
277 let output: B2Writers = {
278 let concurrent = args.concurrent();
279 let writer = B2Writer::new(self.core.clone(), ctx.clone(), path, args);
280
281 let w = oio::MultipartWriter::new(ctx.executor().clone(), writer, concurrent);
282
283 Ok(w)
284 }?;
285
286 Ok(output)
287 }
288
289 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
290 let output: oio::OneShotDeleter<B2Deleter> = {
291 Ok(oio::OneShotDeleter::new(B2Deleter::new(
292 self.core.clone(),
293 ctx.clone(),
294 )))
295 }?;
296
297 Ok(output)
298 }
299
300 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
301 let output: oio::PageLister<B2Lister> = {
302 Ok(oio::PageLister::new(B2Lister::new(
303 self.core.clone(),
304 ctx.clone(),
305 path,
306 args.recursive(),
307 args.limit(),
308 args.start_after(),
309 )))
310 }?;
311
312 Ok(output)
313 }
314
315 fn copy(
316 &self,
317 ctx: &OperationContext,
318 from: &str,
319 to: &str,
320 _args: OpCopy,
321 ) -> Result<Self::Copier> {
322 let core = self.core.clone();
323 let ctx = ctx.clone();
324 let from = from.to_string();
325 let to = to.to_string();
326
327 Ok(oio::OneShotCopier::new(async move {
328 let file_info = core.get_file_info(&ctx, &from, None).await?;
329 let source_file_id = file_info.file_id;
330 let source_content_length = file_info.content_length;
331
332 let Some(source_file_id) = source_file_id else {
333 return Err(Error::new(ErrorKind::IsADirectory, "is a directory"));
334 };
335
336 let resp = core.copy_file(&ctx, source_file_id, &to).await?;
337
338 let status = resp.status();
339
340 match status {
341 StatusCode::OK => {
342 let metadata = MetadataBuilder::file(source_content_length);
343 Ok(metadata.build())
344 }
345 _ => Err(parse_error(
346 ErrorContext::new(ServiceOperation("CopyFile")),
347 resp,
348 )),
349 }
350 }))
351 }
352
353 async fn rename(
354 &self,
355 _ctx: &OperationContext,
356 _from: &str,
357 _to: &str,
358 _args: OpRename,
359 ) -> Result<RpRename> {
360 Err(Error::new(
361 ErrorKind::Unsupported,
362 "operation is not supported",
363 ))
364 }
365
366 async fn presign(
367 &self,
368 ctx: &OperationContext,
369 path: &str,
370 args: OpPresign,
371 ) -> Result<RpPresign> {
372 match args.operation() {
373 PresignOperation::Stat(_) => {
374 let resp = self
375 .core
376 .get_download_authorization(ctx, path, args.expire())
377 .await?;
378 let path = build_abs_path(&self.core.root, path);
379
380 let auth_info = self.core.get_auth_info(ctx).await?;
381
382 let url = format!(
383 "{}/file/{}/{}?Authorization={}",
384 auth_info.download_url,
385 self.core.bucket,
386 percent_encode_path(&path),
387 resp.authorization_token
388 );
389
390 let req = Request::get(url);
391
392 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
393
394 let (parts, _) = req.into_parts();
396
397 Ok(RpPresign::new(PresignedRequest::new(
398 parts.method,
399 parts.uri,
400 parts.headers,
401 )))
402 }
403 PresignOperation::Read(range, _) => {
404 let resp = self
405 .core
406 .get_download_authorization(ctx, path, args.expire())
407 .await?;
408 let path = build_abs_path(&self.core.root, path);
409
410 let auth_info = self.core.get_auth_info(ctx).await?;
411
412 let url = format!(
413 "{}/file/{}/{}?Authorization={}",
414 auth_info.download_url,
415 self.core.bucket,
416 percent_encode_path(&path),
417 resp.authorization_token
418 );
419
420 let mut req = Request::get(url);
421 if !range.is_full() {
422 req = req.header(http::header::RANGE, range.to_header());
423 }
424
425 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
426
427 let (parts, _) = req.into_parts();
429
430 Ok(RpPresign::new(PresignedRequest::new(
431 parts.method,
432 parts.uri,
433 parts.headers,
434 )))
435 }
436 PresignOperation::Write(_) => {
437 let resp = self.core.get_upload_url(ctx).await?;
438
439 let mut req = Request::post(&resp.upload_url);
440
441 req = req.header(http::header::AUTHORIZATION, resp.authorization_token);
442 req = req.header(
443 "X-Bz-File-Name",
444 percent_encode_path(&build_abs_path(&self.core.root, path)),
445 );
446 req = req.header(http::header::CONTENT_TYPE, "b2/x-auto");
447 req = req.header(constants::X_BZ_CONTENT_SHA1, "do_not_verify");
448
449 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
450 let (parts, _) = req.into_parts();
452
453 Ok(RpPresign::new(PresignedRequest::new(
454 parts.method,
455 parts.uri,
456 parts.headers,
457 )))
458 }
459 PresignOperation::Delete(_) => Err(Error::new(
460 ErrorKind::Unsupported,
461 "operation is not supported",
462 )),
463 _ => Err(Error::new(
464 ErrorKind::Unsupported,
465 "operation is not supported",
466 )),
467 }
468 }
469}