Skip to content

Commit 6b20fd5

Browse files
authored
Automatically cleanup empty dirs in LocalFileSystem (apache#5978)
* automatically cleanup empty dirs * automatic cleanup toggle * configurable cleanup * test for automatic dir deletion * clippy * more comments
1 parent 9e601c9 commit 6b20fd5

1 file changed

Lines changed: 69 additions & 6 deletions

File tree

src/local.rs

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ impl From<Error> for super::Error {
240240
#[derive(Debug)]
241241
pub struct LocalFileSystem {
242242
config: Arc<Config>,
243+
// if you want to delete empty directories when deleting files
244+
automatic_cleanup: bool,
243245
}
244246

245247
#[derive(Debug)]
@@ -266,6 +268,7 @@ impl LocalFileSystem {
266268
config: Arc::new(Config {
267269
root: Url::parse("file:///").unwrap(),
268270
}),
271+
automatic_cleanup: false,
269272
}
270273
}
271274

@@ -282,6 +285,7 @@ impl LocalFileSystem {
282285
config: Arc::new(Config {
283286
root: absolute_path_to_url(path)?,
284287
}),
288+
automatic_cleanup: false,
285289
})
286290
}
287291

@@ -295,6 +299,12 @@ impl LocalFileSystem {
295299
);
296300
self.config.prefix_to_filesystem(location)
297301
}
302+
303+
/// Enable automatic cleanup of empty directories when deleting files
304+
pub fn with_automatic_cleanup(mut self, automatic_cleanup: bool) -> Self {
305+
self.automatic_cleanup = automatic_cleanup;
306+
self
307+
}
298308
}
299309

300310
impl Config {
@@ -465,13 +475,36 @@ impl ObjectStore for LocalFileSystem {
465475
}
466476

467477
async fn delete(&self, location: &Path) -> Result<()> {
478+
let config = Arc::clone(&self.config);
468479
let path = self.path_to_filesystem(location)?;
469-
maybe_spawn_blocking(move || match std::fs::remove_file(&path) {
470-
Ok(_) => Ok(()),
471-
Err(e) => Err(match e.kind() {
472-
ErrorKind::NotFound => Error::NotFound { path, source: e }.into(),
473-
_ => Error::UnableToDeleteFile { path, source: e }.into(),
474-
}),
480+
let automactic_cleanup = self.automatic_cleanup;
481+
maybe_spawn_blocking(move || {
482+
if let Err(e) = std::fs::remove_file(&path) {
483+
Err(match e.kind() {
484+
ErrorKind::NotFound => Error::NotFound { path, source: e }.into(),
485+
_ => Error::UnableToDeleteFile { path, source: e }.into(),
486+
})
487+
} else if automactic_cleanup {
488+
let root = &config.root;
489+
let root = root
490+
.to_file_path()
491+
.map_err(|_| Error::InvalidUrl { url: root.clone() })?;
492+
493+
// here we will try to traverse up and delete an empty dir if possible until we reach the root or get an error
494+
let mut parent = path.parent();
495+
496+
while let Some(loc) = parent {
497+
if loc != root && std::fs::remove_dir(loc).is_ok() {
498+
parent = loc.parent();
499+
} else {
500+
break;
501+
}
502+
}
503+
504+
Ok(())
505+
} else {
506+
Ok(())
507+
}
475508
})
476509
.await
477510
}
@@ -1010,6 +1043,8 @@ fn convert_walkdir_result(
10101043

10111044
#[cfg(test)]
10121045
mod tests {
1046+
use std::fs;
1047+
10131048
use futures::TryStreamExt;
10141049
use tempfile::{NamedTempFile, TempDir};
10151050

@@ -1445,6 +1480,34 @@ mod tests {
14451480
list.sort_unstable();
14461481
assert_eq!(list, vec![c, a]);
14471482
}
1483+
1484+
#[tokio::test]
1485+
async fn delete_dirs_automatically() {
1486+
let root = TempDir::new().unwrap();
1487+
let integration = LocalFileSystem::new_with_prefix(root.path())
1488+
.unwrap()
1489+
.with_automatic_cleanup(true);
1490+
let location = Path::from("nested/file/test_file");
1491+
let data = Bytes::from("arbitrary data");
1492+
1493+
integration
1494+
.put(&location, data.clone().into())
1495+
.await
1496+
.unwrap();
1497+
1498+
let read_data = integration
1499+
.get(&location)
1500+
.await
1501+
.unwrap()
1502+
.bytes()
1503+
.await
1504+
.unwrap();
1505+
1506+
assert_eq!(&*read_data, data);
1507+
assert!(fs::read_dir(root.path()).unwrap().count() > 0);
1508+
integration.delete(&location).await.unwrap();
1509+
assert!(fs::read_dir(root.path()).unwrap().count() == 0);
1510+
}
14481511
}
14491512

14501513
#[cfg(not(target_arch = "wasm32"))]

0 commit comments

Comments
 (0)