From 51848b8d8d8341d0029b684c0d67204325d832aa Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 10 Jul 2025 10:20:00 +0800 Subject: [PATCH] fix #4581 --- app/chain/media.py | 6 +- app/chain/transfer.py | 21 +- app/modules/filemanager/transhandler.py | 376 ++++++++++++------------ 3 files changed, 210 insertions(+), 193 deletions(-) diff --git a/app/chain/media.py b/app/chain/media.py index d4b71096..12af3f63 100644 --- a/app/chain/media.py +++ b/app/chain/media.py @@ -367,12 +367,12 @@ class MediaChain(ChainBase): overwrite=overwrite) else: # 检查目的目录下是否已经有nfo刮削文件 - sub_files = storagechain.list_files(fileitem) - if any(f.name.endswith('.nfo') for f in sub_files): + has_nfo_file = storagechain.any_files(fileitem, extensions=['.nfo']) + if has_nfo_file and file_list: logger.info(f"目录 {fileitem.path} 已有NFO文件,开始增量刮削...") for file_path in file_list: file_item = storagechain.get_file_item(storage=fileitem.storage, - path=Path(file_path)) + path=Path(file_path)) if file_item: # 对于电视剧文件,应该保存到与视频文件相同的目录 # 而不是电视剧根目录 diff --git a/app/chain/transfer.py b/app/chain/transfer.py index e439e12b..bdd962a8 100755 --- a/app/chain/transfer.py +++ b/app/chain/transfer.py @@ -372,6 +372,8 @@ class TransferChain(ChainBase, metaclass=Singleton): self._transfer_interval = 15 # 事件管理器 self.jobview = JobManager() + # 车移成功的文件清单 + self._success_target_files: Dict[str, List[str]] = {} # 启动整理任务 self.__init() @@ -441,6 +443,13 @@ class TransferChain(ChainBase, metaclass=Singleton): }) with task_lock: + # 登记转移成功文件清单 + target_dir_path = transferinfo.target_diritem.path + target_files = transferinfo.file_list_new + if self._success_target_files.get(target_dir_path): + self._success_target_files[target_dir_path].extend(target_files) + else: + self._success_target_files[target_dir_path] = target_files # 全部整理成功时 if self.jobview.is_success(task): # 移动模式删除空目录 @@ -461,6 +470,13 @@ class TransferChain(ChainBase, metaclass=Singleton): storagechain.delete_media_file(t.fileitem, delete_self=False) # 整理完成且有成功的任务时 if self.jobview.is_finished(task): + # 更新文件数量 + transferinfo.file_count = self.jobview.count(task.mediainfo, task.meta.begin_season) or 1 + # 更新文件大小 + transferinfo.total_size = self.jobview.size(task.mediainfo, + task.meta.begin_season) or task.fileitem.size + # 更新文件清单 + transferinfo.file_list_new = self._success_target_files.pop(transferinfo.target_diritem.path, []) # 发送通知,实时手动整理时不发 if transferinfo.need_notify and (task.background or not task.manual): se_str = None @@ -470,11 +486,6 @@ class TransferChain(ChainBase, metaclass=Singleton): se_str = f"{task.meta.season} {StringUtils.format_ep(season_episodes)}" else: se_str = f"{task.meta.season}" - # 更新文件数量 - transferinfo.file_count = self.jobview.count(task.mediainfo, task.meta.begin_season) or 1 - # 更新文件大小 - transferinfo.total_size = self.jobview.size(task.mediainfo, - task.meta.begin_season) or task.fileitem.size self.send_transfer_message(meta=task.meta, mediainfo=task.mediainfo, transferinfo=transferinfo, diff --git a/app/modules/filemanager/transhandler.py b/app/modules/filemanager/transhandler.py index 6fb1a311..43d23b2f 100644 --- a/app/modules/filemanager/transhandler.py +++ b/app/modules/filemanager/transhandler.py @@ -27,11 +27,10 @@ class TransHandler: 文件转移整理类 """ - result: Optional[TransferInfo] = None inner_lock: Lock = Lock() def __init__(self): - self.__reset_result() + self.result = None def __reset_result(self): """ @@ -104,208 +103,215 @@ class TransHandler: # 重置结果 self.__reset_result() - # 重命名格式 - rename_format = settings.TV_RENAME_FORMAT \ - if mediainfo.type == MediaType.TV else settings.MOVIE_RENAME_FORMAT + try: - # 判断是否为文件夹 - if fileitem.type == "dir": - # 整理整个目录,一般为蓝光原盘 - if need_rename: - new_path = self.get_rename_path( - path=target_path, - template_string=rename_format, - rename_dict=self.get_naming_dict(meta=in_meta, - mediainfo=mediainfo) - ) - new_path = DirectoryHelper.get_media_root_path( - rename_format, rename_path=new_path - ) - if not new_path: - self.__set_result( - success=False, - message="重命名格式无效", - fileitem=fileitem, - transfer_type=transfer_type, - need_notify=need_notify, + # 重命名格式 + rename_format = settings.TV_RENAME_FORMAT \ + if mediainfo.type == MediaType.TV else settings.MOVIE_RENAME_FORMAT + + # 判断是否为文件夹 + if fileitem.type == "dir": + # 整理整个目录,一般为蓝光原盘 + if need_rename: + new_path = self.get_rename_path( + path=target_path, + template_string=rename_format, + rename_dict=self.get_naming_dict(meta=in_meta, + mediainfo=mediainfo) ) - return self.result - else: - new_path = target_path / fileitem.name - # 整理目录 - new_diritem, errmsg = self.__transfer_dir(fileitem=fileitem, - mediainfo=mediainfo, - source_oper=source_oper, - target_oper=target_oper, - target_storage=target_storage, - target_path=new_path, - transfer_type=transfer_type) - if not new_diritem: - logger.error(f"文件夹 {fileitem.path} 整理失败:{errmsg}") - self.__set_result(success=False, - message=errmsg, - fileitem=fileitem, - transfer_type=transfer_type, - need_notify=need_notify) - return self.result - - logger.info(f"文件夹 {fileitem.path} 整理成功") - # 计算目录下所有文件大小 - total_size = sum(file.stat().st_size for file in Path(fileitem.path).rglob('*') if file.is_file()) - # 返回整理后的路径 - self.__set_result(success=True, - fileitem=fileitem, - target_item=new_diritem, - target_diritem=new_diritem, - total_size=total_size, - need_scrape=need_scrape, - need_notify=need_notify, - transfer_type=transfer_type) - return self.result - else: - # 整理单个文件 - if mediainfo.type == MediaType.TV: - # 电视剧 - if in_meta.begin_episode is None: - logger.warn(f"文件 {fileitem.path} 整理失败:未识别到文件集数") + new_path = DirectoryHelper.get_media_root_path( + rename_format, rename_path=new_path + ) + if not new_path: + self.__set_result( + success=False, + message="重命名格式无效", + fileitem=fileitem, + transfer_type=transfer_type, + need_notify=need_notify, + ) + return self.result.copy() + else: + new_path = target_path / fileitem.name + # 整理目录 + new_diritem, errmsg = self.__transfer_dir(fileitem=fileitem, + mediainfo=mediainfo, + source_oper=source_oper, + target_oper=target_oper, + target_storage=target_storage, + target_path=new_path, + transfer_type=transfer_type) + if not new_diritem: + logger.error(f"文件夹 {fileitem.path} 整理失败:{errmsg}") self.__set_result(success=False, - message="未识别到文件集数", + message=errmsg, + fileitem=fileitem, + transfer_type=transfer_type, + need_notify=need_notify) + return self.result.copy() + + logger.info(f"文件夹 {fileitem.path} 整理成功") + # 计算目录下所有文件大小 + total_size = sum(file.stat().st_size for file in Path(fileitem.path).rglob('*') if file.is_file()) + # 返回整理后的路径 + self.__set_result(success=True, + fileitem=fileitem, + target_item=new_diritem, + target_diritem=new_diritem, + total_size=total_size, + need_scrape=need_scrape, + need_notify=need_notify, + transfer_type=transfer_type) + return self.result.copy() + else: + # 整理单个文件 + if mediainfo.type == MediaType.TV: + # 电视剧 + if in_meta.begin_episode is None: + logger.warn(f"文件 {fileitem.path} 整理失败:未识别到文件集数") + self.__set_result(success=False, + message="未识别到文件集数", + fileitem=fileitem, + fail_list=[fileitem.path], + transfer_type=transfer_type, + need_notify=need_notify) + return self.result.copy() + + # 文件结束季为空 + in_meta.end_season = None + # 文件总季数为1 + if in_meta.total_season: + in_meta.total_season = 1 + # 文件不可能超过2集 + if in_meta.total_episode > 2: + in_meta.total_episode = 1 + in_meta.end_episode = None + + # 目的文件名 + if need_rename: + new_file = self.get_rename_path( + path=target_path, + template_string=rename_format, + rename_dict=self.get_naming_dict( + meta=in_meta, + mediainfo=mediainfo, + episodes_info=episodes_info, + file_ext=f".{fileitem.extension}" + ) + ) + folder_path = DirectoryHelper.get_media_root_path( + rename_format, rename_path=new_file + ) + if not folder_path: + self.__set_result( + success=False, + message="重命名格式无效", + fileitem=fileitem, + fail_list=[fileitem.path], + transfer_type=transfer_type, + need_notify=need_notify, + ) + return self.result.copy() + else: + new_file = target_path / fileitem.name + folder_path = target_path + + # 判断是否要覆盖 + overflag = False + # 目标目录 + target_diritem = target_oper.get_folder(folder_path) + if not target_diritem: + logger.error(f"目标目录 {folder_path} 获取失败") + self.__set_result(success=False, + message=f"目标目录 {folder_path} 获取失败", fileitem=fileitem, fail_list=[fileitem.path], transfer_type=transfer_type, need_notify=need_notify) - return self.result - - # 文件结束季为空 - in_meta.end_season = None - # 文件总季数为1 - if in_meta.total_season: - in_meta.total_season = 1 - # 文件不可能超过2集 - if in_meta.total_episode > 2: - in_meta.total_episode = 1 - in_meta.end_episode = None - - # 目的文件名 - if need_rename: - new_file = self.get_rename_path( - path=target_path, - template_string=rename_format, - rename_dict=self.get_naming_dict( - meta=in_meta, - mediainfo=mediainfo, - episodes_info=episodes_info, - file_ext=f".{fileitem.extension}" - ) - ) - folder_path = DirectoryHelper.get_media_root_path( - rename_format, rename_path=new_file - ) - if not folder_path: - self.__set_result( - success=False, - message="重命名格式无效", - fileitem=fileitem, - fail_list=[fileitem.path], - transfer_type=transfer_type, - need_notify=need_notify, - ) - return self.result - else: - new_file = target_path / fileitem.name - folder_path = target_path - - # 判断是否要覆盖 - overflag = False - # 目标目录 - target_diritem = target_oper.get_folder(folder_path) - if not target_diritem: - logger.error(f"目标目录 {folder_path} 获取失败") - self.__set_result(success=False, - message=f"目标目录 {folder_path} 获取失败", - fileitem=fileitem, - fail_list=[fileitem.path], - transfer_type=transfer_type, - need_notify=need_notify) - return self.result - # 目标文件 - target_item = target_oper.get_item(new_file) - if target_item: - # 目标文件已存在 - target_file = new_file - if target_storage == "local" and new_file.is_symlink(): - target_file = new_file.readlink() - if not target_file.exists(): - overflag = True - if not overflag: + return self.result.copy() + # 目标文件 + target_item = target_oper.get_item(new_file) + if target_item: # 目标文件已存在 - logger.info(f"目的文件系统中已经存在同名文件 {target_file},当前整理覆盖模式设置为 {overwrite_mode}") - if overwrite_mode == 'always': - # 总是覆盖同名文件 - overflag = True - elif overwrite_mode == 'size': - # 存在时大覆盖小 - if target_item.size < fileitem.size: - logger.info(f"目标文件文件大小更小,将覆盖:{new_file}") + target_file = new_file + if target_storage == "local" and new_file.is_symlink(): + target_file = new_file.readlink() + if not target_file.exists(): overflag = True - else: + if not overflag: + # 目标文件已存在 + logger.info(f"目的文件系统中已经存在同名文件 {target_file},当前整理覆盖模式设置为 {overwrite_mode}") + if overwrite_mode == 'always': + # 总是覆盖同名文件 + overflag = True + elif overwrite_mode == 'size': + # 存在时大覆盖小 + if target_item.size < fileitem.size: + logger.info(f"目标文件文件大小更小,将覆盖:{new_file}") + overflag = True + else: + self.__set_result(success=False, + message=f"媒体库存在同名文件,且质量更好", + fileitem=fileitem, + target_item=target_item, + target_diritem=target_diritem, + fail_list=[fileitem.path], + transfer_type=transfer_type, + need_notify=need_notify) + return self.result.copy() + elif overwrite_mode == 'never': + # 存在不覆盖 self.__set_result(success=False, - message=f"媒体库存在同名文件,且质量更好", + message=f"媒体库存在同名文件,当前覆盖模式为不覆盖", fileitem=fileitem, target_item=target_item, target_diritem=target_diritem, fail_list=[fileitem.path], transfer_type=transfer_type, need_notify=need_notify) - return self.result - elif overwrite_mode == 'never': - # 存在不覆盖 - self.__set_result(success=False, - message=f"媒体库存在同名文件,当前覆盖模式为不覆盖", - fileitem=fileitem, - target_item=target_item, - target_diritem=target_diritem, - fail_list=[fileitem.path], - transfer_type=transfer_type, - need_notify=need_notify) - return self.result - elif overwrite_mode == 'latest': - # 仅保留最新版本 - logger.info(f"当前整理覆盖模式设置为仅保留最新版本,将覆盖:{new_file}") - overflag = True - else: - if overwrite_mode == 'latest': - # 文件不存在,但仅保留最新版本 - logger.info(f"当前整理覆盖模式设置为 {overwrite_mode},仅保留最新版本,正在删除已有版本文件 ...") - self.__delete_version_files(target_oper, new_file) - # 整理文件 - new_item, err_msg = self.__transfer_file(fileitem=fileitem, - mediainfo=mediainfo, - target_storage=target_storage, - target_file=new_file, - transfer_type=transfer_type, - over_flag=overflag, - source_oper=source_oper, - target_oper=target_oper) - if not new_item: - logger.error(f"文件 {fileitem.path} 整理失败:{err_msg}") - self.__set_result(success=False, - message=err_msg, + return self.result.copy() + elif overwrite_mode == 'latest': + # 仅保留最新版本 + logger.info(f"当前整理覆盖模式设置为仅保留最新版本,将覆盖:{new_file}") + overflag = True + else: + if overwrite_mode == 'latest': + # 文件不存在,但仅保留最新版本 + logger.info(f"当前整理覆盖模式设置为 {overwrite_mode},仅保留最新版本,正在删除已有版本文件 ...") + self.__delete_version_files(target_oper, new_file) + # 整理文件 + new_item, err_msg = self.__transfer_file(fileitem=fileitem, + mediainfo=mediainfo, + target_storage=target_storage, + target_file=new_file, + transfer_type=transfer_type, + over_flag=overflag, + source_oper=source_oper, + target_oper=target_oper) + if not new_item: + logger.error(f"文件 {fileitem.path} 整理失败:{err_msg}") + self.__set_result(success=False, + message=err_msg, + fileitem=fileitem, + fail_list=[fileitem.path], + transfer_type=transfer_type, + need_notify=need_notify) + return self.result.copy() + + logger.info(f"文件 {fileitem.path} 整理成功") + self.__set_result(success=True, fileitem=fileitem, - fail_list=[fileitem.path], + target_item=new_item, + target_diritem=target_diritem, + file_count=1, + file_list_new=[new_item.path], + total_size=new_item.size, + need_scrape=need_scrape, transfer_type=transfer_type, need_notify=need_notify) - return self.result - - logger.info(f"文件 {fileitem.path} 整理成功") - self.__set_result(success=True, - fileitem=fileitem, - target_item=new_item, - target_diritem=target_diritem, - need_scrape=need_scrape, - transfer_type=transfer_type, - need_notify=need_notify) - return self.result + return self.result.copy() + finally: + self.result = None @staticmethod def __transfer_command(fileitem: FileItem, target_storage: str,