Skip to content

Commit 3a9d8e4

Browse files
implement undelete for azblob
1 parent 6515a4e commit 3a9d8e4

File tree

14 files changed

+235
-0
lines changed

14 files changed

+235
-0
lines changed

core/core/src/raw/accessor.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,28 @@ pub trait Access: Send + Sync + Debug + Unpin + 'static {
177177
)))
178178
}
179179

180+
/// Invoke the `undelete` operation on the specified path to restore a soft-deleted object.
181+
///
182+
/// Require [`Capability::undelete`]
183+
///
184+
/// # Behavior
185+
///
186+
/// - `undelete` restores a soft-deleted object to its active state.
187+
/// - `undelete` SHOULD return `Ok(())` if the path is restored successfully.
188+
/// - `undelete` SHOULD return error if the path is not soft-deleted or doesn't exist.
189+
fn undelete(
190+
&self,
191+
path: &str,
192+
args: OpUndelete,
193+
) -> impl Future<Output = Result<RpUndelete>> + MaybeSend {
194+
let (_, _) = (path, args);
195+
196+
ready(Err(Error::new(
197+
ErrorKind::Unsupported,
198+
"operation is not supported",
199+
)))
200+
}
201+
180202
/// Invoke the `list` operation on the specified path.
181203
///
182204
/// Require [`Capability::list`]
@@ -286,6 +308,12 @@ pub trait AccessDyn: Send + Sync + Debug + Unpin {
286308
) -> BoxedFuture<'a, Result<(RpWrite, oio::Writer)>>;
287309
/// Dyn version of [`Accessor::delete`]
288310
fn delete_dyn(&self) -> BoxedFuture<'_, Result<(RpDelete, oio::Deleter)>>;
311+
/// Dyn version of [`Accessor::undelete`]
312+
fn undelete_dyn<'a>(
313+
&'a self,
314+
path: &'a str,
315+
args: OpUndelete,
316+
) -> BoxedFuture<'a, Result<RpUndelete>>;
289317
/// Dyn version of [`Accessor::list`]
290318
fn list_dyn<'a>(
291319
&'a self,
@@ -359,6 +387,14 @@ where
359387
Box::pin(self.delete())
360388
}
361389

390+
fn undelete_dyn<'a>(
391+
&'a self,
392+
path: &'a str,
393+
args: OpUndelete,
394+
) -> BoxedFuture<'a, Result<RpUndelete>> {
395+
Box::pin(self.undelete(path, args))
396+
}
397+
362398
fn list_dyn<'a>(
363399
&'a self,
364400
path: &'a str,
@@ -424,6 +460,10 @@ impl Access for dyn AccessDyn {
424460
self.delete_dyn().await
425461
}
426462

463+
async fn undelete(&self, path: &str, args: OpUndelete) -> Result<RpUndelete> {
464+
self.undelete_dyn(path, args).await
465+
}
466+
427467
async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
428468
self.list_dyn(path, args).await
429469
}
@@ -505,6 +545,14 @@ impl<T: Access + ?Sized> Access for Arc<T> {
505545
async move { self.as_ref().delete().await }
506546
}
507547

548+
fn undelete(
549+
&self,
550+
path: &str,
551+
args: OpUndelete,
552+
) -> impl Future<Output = Result<RpUndelete>> + MaybeSend {
553+
async move { self.as_ref().undelete(path, args).await }
554+
}
555+
508556
fn list(
509557
&self,
510558
path: &str,

core/core/src/raw/layer.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ pub trait LayeredAccess: Send + Sync + Debug + Unpin + 'static {
164164

165165
fn delete(&self) -> impl Future<Output = Result<(RpDelete, Self::Deleter)>> + MaybeSend;
166166

167+
fn undelete(
168+
&self,
169+
path: &str,
170+
args: OpUndelete,
171+
) -> impl Future<Output = Result<RpUndelete>> + MaybeSend {
172+
self.inner().undelete(path, args)
173+
}
174+
167175
fn list(
168176
&self,
169177
path: &str,
@@ -217,6 +225,10 @@ impl<L: LayeredAccess> Access for L {
217225
LayeredAccess::delete(self).await
218226
}
219227

228+
async fn undelete(&self, path: &str, args: OpUndelete) -> Result<RpUndelete> {
229+
LayeredAccess::undelete(self, path, args).await
230+
}
231+
220232
async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
221233
LayeredAccess::list(self, path, args).await
222234
}

core/core/src/raw/operation.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub enum Operation {
4545
Stat,
4646
/// Operation to delete files.
4747
Delete,
48+
/// Operation to restore soft-deleted files.
49+
Undelete,
4850
/// Operation to get the next file from the list.
4951
List,
5052
/// Operation to generate a presigned URL.
@@ -75,6 +77,7 @@ impl From<Operation> for &'static str {
7577
Operation::Rename => "rename",
7678
Operation::Stat => "stat",
7779
Operation::Delete => "delete",
80+
Operation::Undelete => "undelete",
7881
Operation::List => "list",
7982
Operation::Presign => "presign",
8083
}

core/core/src/raw/ops.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ impl OpDeleter {
9999
}
100100
}
101101

102+
/// Args for `undelete` operation.
103+
///
104+
/// The path must be normalized.
105+
#[derive(Debug, Clone, Default)]
106+
pub struct OpUndelete {}
107+
108+
impl OpUndelete {
109+
/// Create a new `OpUndelete`.
110+
pub fn new() -> Self {
111+
Self::default()
112+
}
113+
}
114+
102115
/// Args for `list` operation.
103116
#[derive(Debug, Clone, Default)]
104117
pub struct OpList {

core/core/src/raw/rps.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ pub struct RpCreateDir {}
2828
#[derive(Debug, Clone, Default)]
2929
pub struct RpDelete {}
3030

31+
/// Reply for `undelete` operation
32+
#[derive(Debug, Clone, Default)]
33+
pub struct RpUndelete {}
34+
3135
/// Reply for `list` operation.
3236
#[derive(Debug, Clone, Default)]
3337
pub struct RpList {}

core/core/src/types/capability.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ pub struct Capability {
148148
/// Maximum size supported for single delete operations.
149149
pub delete_max_size: Option<usize>,
150150

151+
/// Indicates if undelete (restore) operations are supported for soft-deleted objects.
152+
pub undelete: bool,
153+
151154
/// Indicates if copy operations are supported.
152155
pub copy: bool,
153156
/// Indicates if conditional copy operations with if-not-exists are supported.

core/core/src/types/operator/operator.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,34 @@ impl Operator {
14221422
self.delete_with(path).recursive(true).await
14231423
}
14241424

1425+
/// Restore a soft-deleted object.
1426+
///
1427+
/// # Notes
1428+
///
1429+
/// - This operation requires soft delete to be enabled on the storage backend.
1430+
/// - Restoring a non-existent or permanently deleted object will return an error.
1431+
///
1432+
/// # Examples
1433+
///
1434+
/// ```
1435+
/// # use anyhow::Result;
1436+
/// # use opendal_core::Operator;
1437+
/// #
1438+
/// # async fn test(op: Operator) -> Result<()> {
1439+
/// // Delete a file (with soft delete enabled, it's not permanently deleted)
1440+
/// op.delete("path/to/file").await?;
1441+
///
1442+
/// // Restore the soft-deleted file
1443+
/// op.undelete("path/to/file").await?;
1444+
/// # Ok(())
1445+
/// # }
1446+
/// ```
1447+
pub async fn undelete(&self, path: &str) -> Result<()> {
1448+
let path = normalize_path(path);
1449+
self.inner().undelete(&path, OpUndelete::new()).await?;
1450+
Ok(())
1451+
}
1452+
14251453
/// List entries whose paths start with the given prefix `path`.
14261454
///
14271455
/// # Semantics

core/layers/concurrent-limit/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@ where
293293
.map(|(rp, w)| (rp, ConcurrentLimitWrapper::new(w, permit)))
294294
}
295295

296+
async fn undelete(&self, path: &str, args: OpUndelete) -> Result<RpUndelete> {
297+
let _permit = self.semaphore.acquire().await;
298+
299+
self.inner.undelete(path, args).await
300+
}
301+
296302
async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
297303
let permit = self.semaphore.acquire().await;
298304

core/layers/logging/src/lib.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,38 @@ impl<A: Access, I: LoggingInterceptor> LayeredAccess for LoggingAccessor<A, I> {
477477
})
478478
}
479479

480+
async fn undelete(&self, path: &str, args: OpUndelete) -> Result<RpUndelete> {
481+
self.logger.log(
482+
&self.info,
483+
Operation::Undelete,
484+
&[("path", path)],
485+
"started",
486+
None,
487+
);
488+
489+
self.inner
490+
.undelete(path, args)
491+
.await
492+
.inspect(|_| {
493+
self.logger.log(
494+
&self.info,
495+
Operation::Undelete,
496+
&[("path", path)],
497+
"finished",
498+
None,
499+
);
500+
})
501+
.inspect_err(|err| {
502+
self.logger.log(
503+
&self.info,
504+
Operation::Undelete,
505+
&[("path", path)],
506+
"failed",
507+
Some(err),
508+
);
509+
})
510+
}
511+
480512
async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
481513
self.logger.log(
482514
&self.info,

core/layers/retry/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,15 @@ impl<A: Access, I: RetryInterceptor> LayeredAccess for RetryAccessor<A, I> {
340340
.map_err(|e| e.set_persistent())
341341
}
342342

343+
async fn undelete(&self, path: &str, args: OpUndelete) -> Result<RpUndelete> {
344+
{ || self.inner.undelete(path, args.clone()) }
345+
.retry(self.builder)
346+
.when(|e| e.is_temporary())
347+
.notify(|err, dur| self.notify.intercept(err, dur))
348+
.await
349+
.map_err(|e| e.set_persistent())
350+
}
351+
343352
async fn copy(&self, from: &str, to: &str, args: OpCopy) -> Result<RpCopy> {
344353
{ || self.inner.copy(from, to, args.clone()) }
345354
.retry(self.builder)

0 commit comments

Comments
 (0)