This commit is contained in:
jxxghp
2024-07-03 07:10:46 +08:00
parent 03068778bc
commit b6800c7fda
7 changed files with 89 additions and 71 deletions

View File

@@ -1,3 +1,4 @@
import copy
import re import re
from pathlib import Path from pathlib import Path
from threading import Lock from threading import Lock
@@ -208,12 +209,15 @@ class FileManagerModule(_ModuleBase):
need_scrape = scrape need_scrape = scrape
# 是否需要重命名 # 是否需要重命名
need_rename = dir_info.renaming need_rename = dir_info.renaming
# 覆盖模式
overwrite_mode = dir_info.overwrite_mode
# 拼装媒体库一、二级子目录 # 拼装媒体库一、二级子目录
target_path = self.__get_dest_dir(mediainfo=mediainfo, target_dir=dir_info) target_path = self.__get_dest_dir(mediainfo=mediainfo, target_dir=dir_info)
elif target_path: elif target_path:
# 自定义目标路径 # 自定义目标路径
need_scrape = scrape or False need_scrape = scrape or False
need_rename = True need_rename = True
overwrite_mode = "never"
else: else:
# 未找到有效的媒体库目录 # 未找到有效的媒体库目录
logger.error( logger.error(
@@ -228,6 +232,7 @@ class FileManagerModule(_ModuleBase):
in_meta=meta, in_meta=meta,
mediainfo=mediainfo, mediainfo=mediainfo,
transfer_type=transfer_type, transfer_type=transfer_type,
overwrite_mode=overwrite_mode,
target_storage=target_storage, target_storage=target_storage,
target_path=target_path, target_path=target_path,
episodes_info=episodes_info, episodes_info=episodes_info,
@@ -274,7 +279,8 @@ class FileManagerModule(_ModuleBase):
modify_time=_path.stat().st_mtime modify_time=_path.stat().st_mtime
) )
if fileitem.storage != "local" and target_storage != "local": if (fileitem.storage != target_storage
and fileitem.storage != "local" and target_storage != "local"):
logger.error(f"不支持 {fileitem.storage}{target_storage} 的文件整理") logger.error(f"不支持 {fileitem.storage}{target_storage} 的文件整理")
return None, f"不支持 {fileitem.storage}{target_storage} 的文件整理" return None, f"不支持 {fileitem.storage}{target_storage} 的文件整理"
@@ -300,6 +306,8 @@ class FileManagerModule(_ModuleBase):
state = source_oper.softlink(fileitem, target_file) state = source_oper.softlink(fileitem, target_file)
if state: if state:
return __get_targetitem(target_file), "" return __get_targetitem(target_file), ""
else:
return None, f"{fileitem.path} {transfer_type}失败"
elif fileitem.storage == "local" and target_storage != "local": elif fileitem.storage == "local" and target_storage != "local":
# 本地到网盘 # 本地到网盘
filepath = Path(fileitem.path) filepath = Path(fileitem.path)
@@ -315,6 +323,10 @@ class FileManagerModule(_ModuleBase):
new_item = target_oper.upload(target_fileitem, filepath) new_item = target_oper.upload(target_fileitem, filepath)
if new_item: if new_item:
return new_item, "" return new_item, ""
else:
return None, f"{fileitem.path} 上传 {target_storage} 失败"
else:
return None, f"{target_file.parent} {target_storage} 目录获取失败"
elif transfer_type == "move": elif transfer_type == "move":
# 移动 # 移动
# 根据目的路径获取文件夹 # 根据目的路径获取文件夹
@@ -326,22 +338,53 @@ class FileManagerModule(_ModuleBase):
# 删除源文件 # 删除源文件
source_oper.delete(fileitem) source_oper.delete(fileitem)
return new_item, "" return new_item, ""
else:
return None, f"{fileitem.path} 上传 {target_storage} 失败"
else:
return None, f"{target_file.parent} {target_storage} 目录获取失败"
elif fileitem.storage != "local" and target_storage == "local": elif fileitem.storage != "local" and target_storage == "local":
# 检查本地是否存在 # 网盘到本地
if target_file.exists(): if target_file.exists():
logger.warn(f"文件已存在:{target_file}") logger.warn(f"文件已存在:{target_file}")
return __get_targetitem(target_file), "" return __get_targetitem(target_file), ""
# 网盘到本地 # 网盘到本地
if transfer_type == "copy": if transfer_type == "copy":
# 下载 # 下载
if target_oper.download(fileitem, target_file): if source_oper.download(fileitem, target_file):
return __get_targetitem(target_file), "" return __get_targetitem(target_file), ""
else:
return None, f"{fileitem.path} {fileitem.storage} 下载失败"
elif transfer_type == "move": elif transfer_type == "move":
# 下载 # 下载
if target_oper.download(fileitem, target_file): if source_oper.download(fileitem, target_file):
# 删除源文件 # 删除源文件
source_oper.delete(fileitem) source_oper.delete(fileitem)
return __get_targetitem(target_file), "" return __get_targetitem(target_file), ""
else:
return None, f"{fileitem.path} {fileitem.storage} 下载失败"
elif fileitem.storage == target_storage:
# 同一网盘
if transfer_type == "move":
# 移动
# 根据目的路径获取文件夹
target_diritem = target_oper.get_folder(target_file.parent)
if target_diritem:
# 重命名文件
if target_oper.rename(fileitem, target_file.name):
# 移动文件到新目录
if source_oper.move(fileitem, target_diritem):
ret_fileitem = copy.deepcopy(fileitem)
ret_fileitem.path = target_diritem.path + "/" + target_file.name
ret_fileitem.name = target_file.name
ret_fileitem.basename = target_file.stem
ret_fileitem.parent_fileid = target_diritem.fileid
return ret_fileitem, ""
else:
return None, f"{fileitem.path} {target_storage} 移动文件失败"
else:
return None, f"{fileitem.path} {target_storage} 重命名文件失败"
else:
return None, f"{target_file.parent} {target_storage} 目录获取失败"
return None, "不支持的整理操作" return None, "不支持的整理操作"
@@ -644,6 +687,7 @@ class FileManagerModule(_ModuleBase):
in_meta: MetaBase, in_meta: MetaBase,
mediainfo: MediaInfo, mediainfo: MediaInfo,
transfer_type: str, transfer_type: str,
overwrite_mode: str,
target_storage: str, target_storage: str,
target_path: Path, target_path: Path,
episodes_info: List[TmdbEpisode] = None, episodes_info: List[TmdbEpisode] = None,
@@ -658,6 +702,7 @@ class FileManagerModule(_ModuleBase):
:param target_storage: 目标存储 :param target_storage: 目标存储
:param target_path: 目标路径 :param target_path: 目标路径
:param transfer_type: 文件整理方式 :param transfer_type: 文件整理方式
:param overwrite_mode: 覆盖模式
:param episodes_info: 当前季的全部集信息 :param episodes_info: 当前季的全部集信息
:param need_scrape: 是否需要刮削 :param need_scrape: 是否需要刮削
:param need_rename: 是否需要重命名 :param need_rename: 是否需要重命名
@@ -775,8 +820,8 @@ class FileManagerModule(_ModuleBase):
overflag = True overflag = True
if not overflag: if not overflag:
# 目标文件已存在 # 目标文件已存在
logger.info(f"目标文件已存在,整理覆盖模式{settings.OVERWRITE_MODE}") logger.info(f"文件本地系统中已经存在同名文件 {target_file},当前整理覆盖模式设置为 {overwrite_mode}")
match settings.OVERWRITE_MODE: match overwrite_mode:
case 'always': case 'always':
# 总是覆盖同名文件 # 总是覆盖同名文件
overflag = True overflag = True
@@ -787,27 +832,29 @@ class FileManagerModule(_ModuleBase):
overflag = True overflag = True
else: else:
return TransferInfo(success=False, return TransferInfo(success=False,
message=f"媒体库中已存在,且质量更好", message=f"本地媒体库存在同名文件,且质量更好",
fileitem=fileitem, fileitem=fileitem,
target_fileitem=__get_targetitem(target_file), target_fileitem=__get_targetitem(target_file),
fail_list=[fileitem.path]) fail_list=[fileitem.path])
case 'never': case 'never':
# 存在不覆盖 # 存在不覆盖
return TransferInfo(success=False, return TransferInfo(success=False,
message=f"媒体库中已存在,当前设置为不覆盖", message=f"本地媒体库存在同名文件,当前整理覆盖模式设置为不覆盖",
fileitem=fileitem, fileitem=fileitem,
target_fileitem=__get_targetitem(target_file), target_fileitem=__get_targetitem(target_file),
fail_list=[fileitem.path]) fail_list=[fileitem.path])
case 'latest': case 'latest':
# 仅保留最新版本 # 仅保留最新版本
logger.info(f"仅保留最新版本,将覆盖:{new_file}") logger.info(f"当前整理覆盖模式设置为仅保留最新版本,将覆盖:{new_file}")
overflag = True overflag = True
else: else:
# FIXME if overwrite_mode == 'latest':
if settings.OVERWRITE_MODE == 'latest':
# 文件不存在,但仅保留最新版本 # 文件不存在,但仅保留最新版本
logger.info(f"整理覆盖模式{settings.OVERWRITE_MODE},仅保留最新版本") logger.info(f"当前整理覆盖模式设置为 {overwrite_mode},仅保留最新版本")
self.delete_all_version_files(new_file) self.__delete_local_version_files(new_file)
else:
# TODO 支持网盘
pass
# 整理文件 # 整理文件
new_item, err_msg = self.__transfer_file(fileitem=fileitem, new_item, err_msg = self.__transfer_file(fileitem=fileitem,
target_storage=target_storage, target_storage=target_storage,
@@ -931,19 +978,19 @@ class FileManagerModule(_ModuleBase):
def media_exists(self, mediainfo: MediaInfo, **kwargs) -> Optional[ExistMediaInfo]: def media_exists(self, mediainfo: MediaInfo, **kwargs) -> Optional[ExistMediaInfo]:
""" """
TODO 支持网盘
判断媒体文件是否存在于本地文件系统,只支持标准媒体库结构 判断媒体文件是否存在于本地文件系统,只支持标准媒体库结构
:param mediainfo: 识别的媒体信息 :param mediainfo: 识别的媒体信息
:return: 如不存在返回None存在时返回信息包括每季已存在所有集{type: movie/tv, seasons: {season: [episodes]}} :return: 如不存在返回None存在时返回信息包括每季已存在所有集{type: movie/tv, seasons: {season: [episodes]}}
""" """
# 目的路径 # 检查本地媒体库
dest_paths = DirectoryHelper().get_library_dirs() dest_dirs = DirectoryHelper().get_local_library_dirs()
# 检查每一个媒体库目录 # 检查每一个媒体库目录
for dest_path in dest_paths: for dest_dir in dest_dirs:
# 媒体分类路径 # 媒体分类路径
target_dir = self.__get_dest_dir(mediainfo=mediainfo, target_dir=dest_path) target_dir = self.__get_dest_dir(mediainfo=mediainfo, target_dir=dest_dir)
if not target_dir.exists(): if not target_dir.exists():
continue continue
# 重命名格式 # 重命名格式
rename_format = settings.TV_RENAME_FORMAT \ rename_format = settings.TV_RENAME_FORMAT \
if mediainfo.type == MediaType.TV else settings.MOVIE_RENAME_FORMAT if mediainfo.type == MediaType.TV else settings.MOVIE_RENAME_FORMAT
@@ -954,25 +1001,21 @@ class FileManagerModule(_ModuleBase):
rename_dict=self.__get_naming_dict(meta=meta, rename_dict=self.__get_naming_dict(meta=meta,
mediainfo=mediainfo) mediainfo=mediainfo)
) )
# 取相对路径的第1层目录 # 取相对路径的第1层目录
if rel_path.parts: if rel_path.parts:
media_path = target_dir / rel_path.parts[0] media_path = target_dir / rel_path.parts[0]
else: else:
continue continue
# 检查媒体文件夹是否存在 # 检查媒体文件夹是否存在
if not media_path.exists(): if not media_path.exists():
continue continue
# 检索媒体文件 # 检索媒体文件
media_files = SystemUtils.list_files(directory=media_path, extensions=settings.RMT_MEDIAEXT) media_files = SystemUtils.list_files(directory=media_path, extensions=settings.RMT_MEDIAEXT)
if not media_files: if not media_files:
continue continue
if mediainfo.type == MediaType.MOVIE: if mediainfo.type == MediaType.MOVIE:
# 电影存在任何文件为存在 # 电影存在任何文件为存在
logger.info(f"文件系统已存在:{mediainfo.title_year}") logger.info(f"{mediainfo.title_year} 在本地文件系统中找到了")
return ExistMediaInfo(type=MediaType.MOVIE) return ExistMediaInfo(type=MediaType.MOVIE)
else: else:
# 电视剧检索集数 # 电视剧检索集数
@@ -987,15 +1030,16 @@ class FileManagerModule(_ModuleBase):
seasons[season_index] = [] seasons[season_index] = []
seasons[season_index].append(episode_index) seasons[season_index].append(episode_index)
# 返回剧集情况 # 返回剧集情况
logger.info(f"{mediainfo.title_year} 文件系统已存在{seasons}") logger.info(f"{mediainfo.title_year} 在本地文件系统中找到了这些季集{seasons}")
return ExistMediaInfo(type=MediaType.TV, seasons=seasons) return ExistMediaInfo(type=MediaType.TV, seasons=seasons)
# 不存在 # 不存在
return None return None
@staticmethod @staticmethod
def delete_all_version_files(path: Path) -> bool: def __delete_local_version_files(path: Path) -> bool:
""" """
删除目录下的所有版本文件 TODO 支持网盘
删除目录下的所有版本文件(仅本地)
:param path: 目录路径 :param path: 目录路径
""" """
# 识别文件中的季集信息 # 识别文件中的季集信息

View File

@@ -1,6 +1,6 @@
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from pathlib import Path from pathlib import Path
from typing import Optional, List from typing import Optional, List, Union
from app import schemas from app import schemas
from app.helper.storage import StorageHelper from app.helper.storage import StorageHelper
@@ -111,7 +111,7 @@ class StorageBase(metaclass=ABCMeta):
pass pass
@abstractmethod @abstractmethod
def move(self, fileitm: schemas.FileItem, target_file: Path) -> bool: def move(self, fileitm: schemas.FileItem, target: Union[schemas.FileItem, Path]) -> bool:
""" """
移动文件 移动文件
""" """

View File

@@ -663,7 +663,7 @@ class AliPan(StorageBase):
logger.warn("上传文件失败:无法获取上传地址!") logger.warn("上传文件失败:无法获取上传地址!")
return None return None
def move(self, fileitem: schemas.FileItem, target_dir: schemas.FileItem) -> bool: def move(self, fileitem: schemas.FileItem, target: schemas.FileItem) -> bool:
""" """
移动文件 移动文件
""" """
@@ -674,7 +674,7 @@ class AliPan(StorageBase):
res = RequestUtils(headers=headers, timeout=10).post_res(self.move_file_url, json={ res = RequestUtils(headers=headers, timeout=10).post_res(self.move_file_url, json={
"drive_id": fileitem.drive_id, "drive_id": fileitem.drive_id,
"file_id": fileitem.fileid, "file_id": fileitem.fileid,
"to_parent_file_id": target_dir.fileid, "to_parent_file_id": target.fileid,
"check_name_mode": "refuse" "check_name_mode": "refuse"
}) })
if res: if res:

View File

@@ -176,19 +176,7 @@ class LocalStorage(StorageBase):
""" """
上传文件 上传文件
""" """
filepath = Path(fileitem.path) pass
if not filepath.exists():
logger.warn(f"文件不存在:{filepath}")
return None
if not path.exists():
try:
filepath.rename(path)
except Exception as e:
logger.error(f"移动文件失败:{e}")
return None
if path.exists():
return self.__get_fileitem(path)
return None
def copy(self, fileitem: schemas.FileItem, target_file: Path) -> bool: def copy(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
""" """
@@ -223,12 +211,12 @@ class LocalStorage(StorageBase):
return False return False
return True return True
def move(self, fileitem: schemas.FileItem, target_file: Path) -> bool: def move(self, fileitem: schemas.FileItem, target: Path) -> bool:
""" """
移动文件 移动文件
""" """
file_path = Path(fileitem.path) file_path = Path(fileitem.path)
code, message = SystemUtils.move(file_path, target_file) code, message = SystemUtils.move(file_path, target)
if code != 0: if code != 0:
logger.error(f"移动文件失败:{message}") logger.error(f"移动文件失败:{message}")
return False return False

View File

@@ -250,7 +250,7 @@ class Rclone(StorageBase):
logger.error(f"获取文件详情失败:{err}") logger.error(f"获取文件详情失败:{err}")
return None return None
def move(self, fileitm: schemas.FileItem, target_file: Path) -> bool: def move(self, fileitm: schemas.FileItem, target: Path) -> bool:
""" """
移动文件target_file格式rclone:path 移动文件target_file格式rclone:path
""" """
@@ -259,7 +259,7 @@ class Rclone(StorageBase):
[ [
'rclone', 'moveto', 'rclone', 'moveto',
f'MP:{fileitm.path}', f'MP:{fileitm.path}',
f'MP:{target_file}' f'MP:{target}'
], ],
startupinfo=self.__get_hidden_shell() startupinfo=self.__get_hidden_shell()
).returncode ).returncode
@@ -270,23 +270,7 @@ class Rclone(StorageBase):
return False return False
def copy(self, fileitm: schemas.FileItem, target_file: Path) -> bool: def copy(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
""" pass
复制文件target_file格式rclone:path
"""
try:
retcode = subprocess.run(
[
'rclone', 'copyto',
f'MP:{fileitm.path}',
f'MP:{target_file}'
],
startupinfo=self.__get_hidden_shell()
).returncode
if retcode == 0:
return True
except Exception as err:
logger.error(f"复制文件失败:{err}")
return False
def link(self, fileitm: schemas.FileItem, target_file: Path) -> bool: def link(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
pass pass

View File

@@ -228,7 +228,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
else: else:
dir_file = self.create_folder(dir_file, part) dir_file = self.create_folder(dir_file, part)
if not dir_file: if not dir_file:
logger.warn(f"创建 115 目录 {fileitem.path}{part} 失败!") logger.warn(f"115创建目录 {fileitem.path}{part} 失败!")
return None return None
fileitem = dir_file fileitem = dir_file
return None return None
@@ -249,7 +249,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
self.cloud.storage().delete(fileitem.fileid) self.cloud.storage().delete(fileitem.fileid)
return True return True
except Exception as e: except Exception as e:
logger.error(f"删除115文件失败{str(e)}") logger.error(f"115删除文件失败:{str(e)}")
return False return False
def rename(self, fileitem: schemas.FileItem, name: str) -> bool: def rename(self, fileitem: schemas.FileItem, name: str) -> bool:
@@ -262,7 +262,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
self.cloud.storage().rename(fileitem.fileid, name) self.cloud.storage().rename(fileitem.fileid, name)
return True return True
except Exception as e: except Exception as e:
logger.error(f"重命名115文件失败:{str(e)}") logger.error(f"115重命名文件失败:{str(e)}")
return False return False
def download(self, fileitem: schemas.FileItem, path: Path) -> bool: def download(self, fileitem: schemas.FileItem, path: Path) -> bool:
@@ -328,20 +328,20 @@ class U115Pan(StorageBase, metaclass=Singleton):
logger.warn(f"115上传文件失败{por.resp.response.text}") logger.warn(f"115上传文件失败{por.resp.response.text}")
return None return None
except Exception as e: except Exception as e:
logger.error(f"上传115文件失败{str(e)}") logger.error(f"115上传文件失败:{str(e)}")
return None return None
def move(self, fileitem: schemas.FileItem, target_dir: schemas.FileItem) -> bool: def move(self, fileitem: schemas.FileItem, target: schemas.FileItem) -> bool:
""" """
移动文件 移动文件
""" """
if not self.__init_cloud(): if not self.__init_cloud():
return False return False
try: try:
self.cloud.storage().move(fileitem.fileid, target_dir.fileid) self.cloud.storage().move(fileitem.fileid, target.fileid)
return True return True
except Exception as e: except Exception as e:
logger.error(f"移动115文件失败{str(e)}") logger.error(f"115移动文件失败:{str(e)}")
return False return False
def copy(self, fileitm: schemas.FileItem, target_file: Path) -> bool: def copy(self, fileitm: schemas.FileItem, target_file: Path) -> bool:

View File

@@ -83,6 +83,8 @@ class TransferDirectoryConf(BaseModel):
monitor_type: Optional[str] = None monitor_type: Optional[str] = None
# 整理方式 move/copy/link/softlink # 整理方式 move/copy/link/softlink
transfer_type: Optional[str] = None transfer_type: Optional[str] = None
# 文件覆盖模式 always/size/never/latest
overwrite_mode: Optional[str] = None
# 整理到媒体库目录 # 整理到媒体库目录
library_path: Optional[str] = None library_path: Optional[str] = None
# 媒体库目录存储 # 媒体库目录存储