@@ -2618,12 +2618,32 @@ class NamespaceFS {
26182618 }
26192619 }
26202620
2621+ /**
2622+ * _delete_path_dirs deletes all the paths in the hierarchy that are empty after a successful delete
2623+ * if the original file_path to be deleted is a regular object which means file_path is not a directory and it's not a directory object path -
2624+ * before deletion of the parent directory -
2625+ * if the parent directory is a directory object (has CONTENT_DIR xattr) - stop the deletion loop
2626+ * else - delete the directory - if dir is not empty it will stop at the first non empty dir
2627+ * NOTE - the directory object check is needed because when object size is zero we won't create a .folder file and the dir will be empty
2628+ * therefore the deletion will succeed although we shouldn't delete the directory object
2629+ * @param {String } file_path
2630+ * @param {nb.NativeFSContext } fs_context
2631+ */
26212632 async _delete_path_dirs ( file_path , fs_context ) {
26222633 try {
2623- let dir = path . dirname ( file_path ) ;
2624- while ( dir !== this . bucket_path ) {
2625- await nb_native ( ) . fs . rmdir ( fs_context , dir ) ;
2626- dir = path . dirname ( dir ) ;
2634+ let dir_path = path . dirname ( file_path ) ;
2635+ const deleted_file_is_dir = file_path . endsWith ( '/' ) ;
2636+ const deleted_file_is_dir_object = file_path . endsWith ( config . NSFS_FOLDER_OBJECT_NAME ) ;
2637+ let should_check_dir_path_is_content_dir = ! deleted_file_is_dir && ! deleted_file_is_dir_object ;
2638+ while ( dir_path !== this . bucket_path ) {
2639+ if ( should_check_dir_path_is_content_dir ) {
2640+ const dir_stat = await nb_native ( ) . fs . stat ( fs_context , dir_path ) ;
2641+ const file_is_disabled_dir_content = dir_stat . xattr && dir_stat . xattr [ XATTR_DIR_CONTENT ] !== undefined ;
2642+ if ( file_is_disabled_dir_content ) break ;
2643+ }
2644+ await nb_native ( ) . fs . rmdir ( fs_context , dir_path ) ;
2645+ dir_path = path . dirname ( dir_path ) ;
2646+ should_check_dir_path_is_content_dir = true ;
26272647 }
26282648 } catch ( err ) {
26292649 if ( err . code !== 'ENOTEMPTY' &&
@@ -3068,10 +3088,18 @@ class NamespaceFS {
30683088 return res ;
30693089 }
30703090
3071- // delete version_id -
3072- // 1. get version info, if it's empty - return
3073- // 2. unlink key
3074- // 3. if version is latest version - promote second latest -> latest
3091+ /**
3092+ * delete version_id does the following -
3093+ * 1. get version info, if it's empty - return
3094+ * 2. unlink key
3095+ * 3. if version is latest version - promote second latest -> latest
3096+ * 4. if it's the latest version - delete the directory hirerachy of the key if it's empty
3097+ * if it's a past version - delete .versions/ and the directory hirerachy if it's empty
3098+ * @param {nb.NativeFSContext } fs_context
3099+ * @param {String } file_path
3100+ * @param {Object } params
3101+ * @returns {Promise<{deleted_delete_marker?: string, version_id?: string}> }
3102+ */
30753103 async _delete_version_id ( fs_context , file_path , params ) {
30763104 // TODO optimization - GPFS link overrides, no need to unlink before promoting, but if there is nothing to promote we should unlink
30773105 const del_obj_version_info = await this . _delete_single_object_versioned ( fs_context , params . key , params . version_id ) ;
0 commit comments