@@ -240,6 +240,8 @@ impl From<Error> for super::Error {
240240#[ derive( Debug ) ]
241241pub 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
300310impl 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) ]
10121045mod 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