From f4052d229a3ce47ed17c1a097cd996a0a700d283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E5=B0=8F=E4=B8=98?= Date: Wed, 25 Feb 2026 15:56:44 +0800 Subject: [PATCH] fix: clean up empty parent directories after file deletion Prevent local storage fragmentation by removing empty directories left behind when files are permanently deleted or moved to trash. Co-Authored-By: Claude Opus 4.6 --- service/storage/local_storage.py | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/service/storage/local_storage.py b/service/storage/local_storage.py index f40b771..d93fc85 100644 --- a/service/storage/local_storage.py +++ b/service/storage/local_storage.py @@ -263,15 +263,49 @@ class LocalStorageService: """ 删除文件(物理删除) + 删除文件后会尝试清理因此变空的父目录。 + :param path: 完整文件路径 """ if await self.file_exists(path): try: await aiofiles.os.remove(path) l.debug(f"已删除文件: {path}") + await self._cleanup_empty_parents(path) except OSError as e: l.warning(f"删除文件失败 {path}: {e}") + async def _cleanup_empty_parents(self, file_path: str) -> None: + """ + 从被删文件的父目录开始,向上逐级删除空目录 + + 在以下情况停止: + + - 到达存储根目录(_base_path) + - 遇到非空目录 + - 遇到 .trash 目录 + - 删除失败(权限、并发等) + + :param file_path: 被删文件的完整路径 + """ + current = Path(file_path).parent + + while current != self._base_path and str(current).startswith(str(self._base_path)): + if current.name == '.trash': + break + + try: + entries = await aiofiles.os.listdir(str(current)) + if entries: + break + + await aiofiles.os.rmdir(str(current)) + l.debug(f"已清理空目录: {current}") + current = current.parent + except OSError as e: + l.debug(f"清理空目录失败(忽略): {current}: {e}") + break + async def move_to_trash( self, source_path: str, @@ -304,6 +338,7 @@ class LocalStorageService: try: await aiofiles.os.rename(source_path, str(trash_path)) l.info(f"文件已移动到回收站: {source_path} -> {trash_path}") + await self._cleanup_empty_parents(source_path) return str(trash_path) except OSError as e: raise StorageException(f"移动文件到回收站失败: {e}")