From 053e1b7562adccb43442c55ddd4c1596ad088698 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 21 Jul 2026 08:22:46 +0800 Subject: [PATCH] fix: handle files in newly moved monitor directories --- app/monitor.py | 25 +++++++++++++++++++++++++ tests/test_monitor_watchfiles.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/app/monitor.py b/app/monitor.py index 63d28786..b9282067 100644 --- a/app/monitor.py +++ b/app/monitor.py @@ -159,6 +159,7 @@ class LocalDirectoryWatcher: 将 watchfiles 原始变更转换为目录监控事件。 :param changes: watchfiles 返回的变更集合 """ + changes = self._expand_added_directories(changes) for change_type, path_str in sorted(changes, key=lambda item: item[1]): if change_type not in self._HANDLE_CHANGES: continue @@ -180,6 +181,30 @@ class LocalDirectoryWatcher: except Exception as err: logger.error(f"处理本地目录监控事件失败: {path_str} - {err}") + def _expand_added_directories(self, changes: set[tuple[Change, str]]) -> set[tuple[Change, str]]: + """ + 将整体移入监控范围的新增目录展开为内部文件事件。 + :param changes: watchfiles 返回的变更集合 + :return: 包含目录内新增文件的变更集合 + """ + expanded_changes = set(changes) + for change_type, path_str in changes: + if change_type != Change.added: + continue + event_path = Path(path_str) + try: + if not event_path.is_dir(): + continue + for nested_path in event_path.rglob("*"): + if not nested_path.is_file(): + continue + nested_path_str = nested_path.as_posix() + if self._watch_filter(Change.added, nested_path_str): + expanded_changes.add((Change.added, nested_path_str)) + except OSError as err: + logger.debug(f"扫描新增目录失败: {event_path} - {err}") + return expanded_changes + @staticmethod def _build_event(change_type: Change, event_path: Path) -> Optional[DirectoryChangeEvent]: """ diff --git a/tests/test_monitor_watchfiles.py b/tests/test_monitor_watchfiles.py index c02d9e73..2f008c03 100644 --- a/tests/test_monitor_watchfiles.py +++ b/tests/test_monitor_watchfiles.py @@ -86,6 +86,36 @@ def test_handle_changes_skips_missing_paths(tmp_path): assert callback.events == [] +def test_handle_changes_expands_added_directory_files(tmp_path): + """ + 整体移入的新增目录应递归转换成内部文件事件且不重复分发。 + """ + added_dir = tmp_path / "task" + nested_dir = added_dir / "season" + nested_dir.mkdir(parents=True) + movie_file = added_dir / "movie.mkv" + episode_file = nested_dir / "episode.mkv" + ignored_file = added_dir / ".DS_Store" + movie_file.write_bytes(b"movie") + episode_file.write_bytes(b"episode") + ignored_file.write_bytes(b"ignored") + + callback = CallbackRecorder() + watcher = LocalDirectoryWatcher(tmp_path, callback=callback, force_polling=False) + watcher._handle_changes({ + (Change.added, added_dir.as_posix()), + (Change.added, movie_file.as_posix()), + }) + + assert [ + (event.change_type, event_path, file_size) + for event, _, event_path, file_size in callback.events + ] == [ + (Change.added, movie_file.as_posix(), 5), + (Change.added, episode_file.as_posix(), 7), + ] + + def test_event_handler_routes_file_events_to_transfer_handler(): """ 文件事件应继续按 local 存储交给整理流程。