diff --git a/app/monitor/dispatcher.py b/app/monitor/dispatcher.py index 6a99f6606..2f3c51f12 100644 --- a/app/monitor/dispatcher.py +++ b/app/monitor/dispatcher.py @@ -8,8 +8,10 @@ from app.chain.transfer import TransferChain from app.core.cache import TTLCache from app.core.config import settings from app.db.transferhistory_oper import TransferHistoryOper +from app.helper.directory import DirectoryHelper from app.log import logger from app.schemas import FileItem +from app.schemas.types import MediaType class TransferDispatcher: @@ -97,6 +99,39 @@ class TransferDispatcher: """ return f"{storage}:{Path(event_path).as_posix()}" + @staticmethod + def _get_monitor_media_type(storage: str, event_path: Path) -> Optional[MediaType]: + """ + 获取事件路径命中的目录监控媒体类型,嵌套配置优先使用最具体的根目录。 + + :param storage: 存储 + :param event_path: 事件文件路径 + :return: 配置的媒体类型,未配置或无匹配目录时返回 None + """ + matching_dirs = [ + dir_info + for dir_info in DirectoryHelper().get_download_dirs() + if dir_info.monitor_type == "monitor" + and dir_info.storage == storage + and event_path.is_relative_to(Path(dir_info.download_path)) + ] + if not matching_dirs: + return None + + dir_info = max( + matching_dirs, + key=lambda item: len(Path(item.download_path).parts), + ) + if not dir_info.media_type: + return None + try: + return MediaType(dir_info.media_type) + except ValueError: + logger.warning( + f"目录监控 {dir_info.download_path} 配置了未知媒体类型:{dir_info.media_type}" + ) + return None + def _register_pending(self, storage: str, event_path: Path, file_size: float = None): """ 登记历史查询失败的文件待重试,重复失败累计次数,超限后放弃。 @@ -202,7 +237,11 @@ class TransferDispatcher: basename=event_path.stem, extension=event_path.suffix[1:], size=file_size - ) + ), + mtype=self._get_monitor_media_type( + storage=storage, + event_path=event_path, + ), ) return True except Exception as e: diff --git a/tests/test_monitor_watchfiles.py b/tests/test_monitor_watchfiles.py index a7a9075ac..8d77dd061 100644 --- a/tests/test_monitor_watchfiles.py +++ b/tests/test_monitor_watchfiles.py @@ -5,6 +5,8 @@ from watchfiles import Change from app.monitor import DirectoryChangeEvent, LocalDirectoryWatcher, Monitor from app.monitor.dispatcher import TransferDispatcher +from app.schemas import TransferDirectoryConf +from app.schemas.types import MediaType class CallbackRecorder: @@ -302,3 +304,43 @@ def test_handle_file_invokes_transfer_when_history_missing(monkeypatch): assert fileitem.storage == "local" assert fileitem.path == event_path.as_posix() assert fileitem.size == 1024 + + +def test_handle_file_prefers_music_type_from_monitor_directory(monkeypatch): + """音乐目录监控触发整理时应透传音乐类型,避免音频按影视名称识别。""" + dispatcher = TransferDispatcher(all_exts=[".flac"], cache={}) + event_path = Path("/downloads/music/album/track.flac") + directories = [ + TransferDirectoryConf( + storage="local", + download_path="/downloads", + media_type=MediaType.MOVIE.value, + monitor_type="monitor", + ), + TransferDirectoryConf( + storage="local", + download_path="/downloads/music", + media_type=MediaType.MUSIC.value, + monitor_type="monitor", + ), + ] + transfer_chain_instance = MagicMock() + + monkeypatch.setattr(dispatcher, "_has_transfer_history", MagicMock(return_value=False)) + monkeypatch.setattr( + "app.monitor.dispatcher.DirectoryHelper", + MagicMock(return_value=MagicMock(get_download_dirs=MagicMock(return_value=directories))), + ) + monkeypatch.setattr( + "app.monitor.dispatcher.TransferChain", + MagicMock(return_value=transfer_chain_instance), + ) + + handled = dispatcher.handle_file( + storage="local", + event_path=event_path, + file_size=1024, + ) + + assert handled + assert transfer_chain_instance.do_transfer.call_args.kwargs["mtype"] == MediaType.MUSIC