This commit is contained in:
jxxghp
2024-11-21 12:13:44 +08:00
parent 824c3ac5d6
commit 4f1d3a7572
9 changed files with 180 additions and 146 deletions
+6 -2
View File
@@ -393,10 +393,14 @@ class TransferChain(ChainBase):
if src_match: if src_match:
# 按源目录匹配,以便找到更合适的目录配置 # 按源目录匹配,以便找到更合适的目录配置
target_directory = self.directoryhelper.get_dir(file_mediainfo, target_directory = self.directoryhelper.get_dir(file_mediainfo,
storage=file_item.storage, src_path=file_path) storage=file_item.storage,
src_path=file_path,
target_storage=target_storage)
else: else:
# 未指定目标路径,根据媒体信息获取目标目录 # 未指定目标路径,根据媒体信息获取目标目录
target_directory = self.directoryhelper.get_dir(file_mediainfo) target_directory = self.directoryhelper.get_dir(file_mediainfo,
storage=target_storage,
target_storage=target_storage)
# 执行整理 # 执行整理
transferinfo: TransferInfo = self.transfer(fileitem=file_item, transferinfo: TransferInfo = self.transfer(fileitem=file_item,
+17 -13
View File
@@ -48,16 +48,18 @@ class DirectoryHelper:
""" """
return [d for d in self.get_library_dirs() if d.library_storage == "local"] return [d for d in self.get_library_dirs() if d.library_storage == "local"]
def get_dir(self, media: MediaInfo, storage: str = "local", def get_dir(self, media: MediaInfo,
src_path: Path = None, dest_path: Path = None, fileitem: schemas.FileItem = None storage: str = "local", fileitem: schemas.FileItem = None, src_path: Path = None,
target_storage: str = "local", dest_path: Path = None
) -> Optional[schemas.TransferDirectoryConf]: ) -> Optional[schemas.TransferDirectoryConf]:
""" """
根据媒体信息获取下载目录、媒体库目录配置 根据媒体信息获取下载目录、媒体库目录配置
:param media: 媒体信息 :param media: 媒体信息
:param storage: 存储类型 :param storage: 存储类型
:param target_storage: 目标存储类型
:param fileitem: 文件项,使用文件路径匹配
:param src_path: 源目录,有值时直接匹配 :param src_path: 源目录,有值时直接匹配
:param dest_path: 目标目录,有值时直接匹配 :param dest_path: 目标目录,有值时直接匹配
:param fileitem: 文件项,使用文件路径匹配
""" """
# 处理类型 # 处理类型
if not media: if not media:
@@ -70,21 +72,23 @@ class DirectoryHelper:
# 没有启用整理的目录 # 没有启用整理的目录
if not d.monitor_type: if not d.monitor_type:
continue continue
# 存储类型不匹配 # 存储类型不匹配
if storage and d.storage != storage: if storage and d.storage != storage:
continue continue
# 下载目录 # 目标存储类型不匹配
download_path = Path(d.download_path) if target_storage and d.library_storage != target_storage:
# 媒体库目录 continue
library_path = Path(d.library_path) # 有文件项时,源存储不匹配
# 有源目录时,源目录不匹配下载目录 if fileitem and fileitem.storage != d.storage:
if src_path and not src_path.is_relative_to(download_path):
continue continue
# 有文件项时,文件项不匹配下载目录 # 有文件项时,文件项不匹配下载目录
if fileitem and not Path(fileitem.path).is_relative_to(download_path): if fileitem and not Path(fileitem.path).is_relative_to(d.download_path):
continue
# 有源目录时,源目录不匹配下载目录
if src_path and not src_path.is_relative_to(d.download_path):
continue continue
# 有目标目录时,目标目录不匹配媒体库目录 # 有目标目录时,目标目录不匹配媒体库目录
if dest_path and not dest_path.is_relative_to(library_path): if dest_path and not dest_path.is_relative_to(d.library_path):
continue continue
# 目录类型为全部的,符合条件 # 目录类型为全部的,符合条件
if not d.media_type: if not d.media_type:
+18 -23
View File
@@ -1,4 +1,3 @@
import copy
import re import re
from pathlib import Path from pathlib import Path
from threading import Lock from threading import Lock
@@ -463,9 +462,9 @@ class FileManagerModule(_ModuleBase):
target_file.parent.mkdir(parents=True) target_file.parent.mkdir(parents=True)
# 本地到本地 # 本地到本地
if transfer_type == "copy": if transfer_type == "copy":
state = source_oper.copy(fileitem, target_file) state = source_oper.copy(fileitem, target_file.parent, target_file.name)
elif transfer_type == "move": elif transfer_type == "move":
state = source_oper.move(fileitem, target_file) state = source_oper.move(fileitem, target_file.parent, target_file.name)
elif transfer_type == "link": elif transfer_type == "link":
state = source_oper.link(fileitem, target_file) state = source_oper.link(fileitem, target_file)
elif transfer_type == "softlink": elif transfer_type == "softlink":
@@ -493,7 +492,7 @@ class FileManagerModule(_ModuleBase):
else: else:
return None, f"{fileitem.path} 上传 {target_storage} 失败" return None, f"{fileitem.path} 上传 {target_storage} 失败"
else: else:
return None, f"{target_file.parent} {target_storage} 目录获取失败" return None, f"{target_storage}{target_file.parent} 目录获取失败"
elif transfer_type == "move": elif transfer_type == "move":
# 移动 # 移动
# 根据目的路径获取文件夹 # 根据目的路径获取文件夹
@@ -508,7 +507,7 @@ class FileManagerModule(_ModuleBase):
else: else:
return None, f"{fileitem.path} 上传 {target_storage} 失败" return None, f"{fileitem.path} 上传 {target_storage} 失败"
else: else:
return None, f"{target_file.parent} {target_storage} 目录获取失败" return None, f"{target_storage}{target_file.parent} 目录获取失败"
elif fileitem.storage != "local" and target_storage == "local": elif fileitem.storage != "local" and target_storage == "local":
# 网盘到本地 # 网盘到本地
if target_file.exists(): if target_file.exists():
@@ -532,25 +531,20 @@ class FileManagerModule(_ModuleBase):
return None, f"{fileitem.path} {fileitem.storage} 下载失败" return None, f"{fileitem.path} {fileitem.storage} 下载失败"
elif fileitem.storage == target_storage: elif fileitem.storage == target_storage:
# 同一网盘 # 同一网盘
# 根据目的路径获取文件夹 if transfer_type == "copy":
target_diritem = target_oper.get_folder(target_file.parent) # 移动文件到新目录
if target_diritem: if source_oper.move(fileitem, target_file.parent, target_file.name):
# 重命名文件 return target_oper.get_item(target_file), ""
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: else:
return None, f"{fileitem.path} {target_storage} 重命名文件失败" return None, f"{target_storage}{fileitem.path} 移动文件失败"
elif transfer_type == "move":
# 移动文件到新目录
if source_oper.move(fileitem, target_file.parent, target_file.name):
return target_oper.get_item(target_file), ""
else:
return None, f"{target_storage}{fileitem.path} 移动文件失败"
else: else:
return None, f"{target_file.parent} {target_storage} 目录获取失败" return None, f"不支持的整理方式:{transfer_type}"
return None, "未知错误" return None, "未知错误"
@@ -815,7 +809,8 @@ class FileManagerModule(_ModuleBase):
else: else:
logger.info(f"正在删除已存在的文件:{target_file}") logger.info(f"正在删除已存在的文件:{target_file}")
target_file.unlink() target_file.unlink()
logger.info(f"正在整理文件:【{fileitem.storage}{fileitem.path} 到 【{target_storage}{target_file}") logger.info(f"正在整理文件:【{fileitem.storage}{fileitem.path} 到 【{target_storage}{target_file}"
f"操作类型:{transfer_type}")
new_item, errmsg = self.__transfer_command(fileitem=fileitem, new_item, errmsg = self.__transfer_command(fileitem=fileitem,
target_storage=target_storage, target_storage=target_storage,
target_file=target_file, target_file=target_file,
+8 -3
View File
@@ -122,7 +122,6 @@ class StorageBase(metaclass=ABCMeta):
下载文件,保存到本地,返回本地临时文件地址 下载文件,保存到本地,返回本地临时文件地址
:param fileitem: 文件项 :param fileitem: 文件项
:param path: 文件保存路径 :param path: 文件保存路径
""" """
pass pass
@@ -144,16 +143,22 @@ class StorageBase(metaclass=ABCMeta):
pass pass
@abstractmethod @abstractmethod
def copy(self, fileitem: schemas.FileItem, target: Union[schemas.FileItem, Path]) -> bool: def copy(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
""" """
复制文件 复制文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
""" """
pass pass
@abstractmethod @abstractmethod
def move(self, fileitem: schemas.FileItem, target: Union[schemas.FileItem, Path]) -> bool: def move(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
""" """
移动文件 移动文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
""" """
pass pass
+30 -4
View File
@@ -61,6 +61,7 @@ class AliPan(StorageBase, metaclass=Singleton):
""" """
初始化 aligo 初始化 aligo
""" """
def show_qrcode(qr_link: str): def show_qrcode(qr_link: str):
""" """
显示二维码 显示二维码
@@ -370,6 +371,9 @@ class AliPan(StorageBase, metaclass=Singleton):
def upload(self, fileitem: schemas.FileItem, path: Path, new_name: str = None) -> Optional[schemas.FileItem]: def upload(self, fileitem: schemas.FileItem, path: Path, new_name: str = None) -> Optional[schemas.FileItem]:
""" """
上传文件,并标记完成 上传文件,并标记完成
:param fileitem: 上传目录项
:param path: 目标目录
:param new_name: 新文件名
""" """
if not self.aligo: if not self.aligo:
return None return None
@@ -383,19 +387,41 @@ class AliPan(StorageBase, metaclass=Singleton):
return self.__get_fileitem(item) return self.__get_fileitem(item)
return None return None
def move(self, fileitem: schemas.FileItem, target: schemas.FileItem) -> bool: def move(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
""" """
移动文件 移动文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
""" """
if not self.aligo: if not self.aligo:
return False return False
target = self.get_folder(path)
if not target:
return False
if self.aligo.move_file(file_id=fileitem.fileid, drive_id=fileitem.drive_id, if self.aligo.move_file(file_id=fileitem.fileid, drive_id=fileitem.drive_id,
to_parent_file_id=target.fileid, to_drive_id=target.drive_id): to_parent_file_id=target.fileid, to_drive_id=target.drive_id,
new_name=new_name):
return True return True
return False return False
def copy(self, fileitem: schemas.FileItem, target: schemas.FileItem) -> bool: def copy(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
pass """
复制文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
"""
if not self.aligo:
return False
target = self.get_folder(path)
if not target:
return False
if self.aligo.copy_file(file_id=fileitem.fileid, drive_id=fileitem.drive_id,
to_parent_file_id=target.fileid, to_drive_id=target.drive_id,
new_name=new_name):
return True
return False
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool: def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
""" """
+31 -65
View File
@@ -2,10 +2,10 @@ import json
import logging import logging
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Optional, Tuple, List, Dict, Union from typing import Optional, List, Dict
from requests import Response
from cachetools import cached, TTLCache from cachetools import cached, TTLCache
from requests import Response
from app import schemas from app import schemas
from app.core.config import settings from app.core.config import settings
@@ -13,10 +13,11 @@ from app.log import logger
from app.modules.filemanager.storages import StorageBase from app.modules.filemanager.storages import StorageBase
from app.schemas.types import StorageSchema from app.schemas.types import StorageSchema
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
from app.utils.singleton import Singleton
from app.utils.url import UrlUtils from app.utils.url import UrlUtils
class Alist(StorageBase): class Alist(StorageBase, metaclass=Singleton):
""" """
Alist相关操作 Alist相关操作
api文档:https://alist.nn.ci/zh/guide/api api文档:https://alist.nn.ci/zh/guide/api
@@ -348,7 +349,7 @@ class Alist(StorageBase):
result = resp.json() result = resp.json()
if result["code"] != 200: if result["code"] != 200:
logging.warning(f'获取文件 {path} 失败,错误信息:{result["message"]}') logging.debug(f'获取文件 {path} 失败,错误信息:{result["message"]}')
return return
return schemas.FileItem( return schemas.FileItem(
@@ -576,51 +577,21 @@ class Alist(StorageBase):
""" """
return self.get_item(Path(fileitem.path)) return self.get_item(Path(fileitem.path))
@staticmethod def copy(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
def __get_copy_and_move_data(
fileitem: schemas.FileItem, target: Union[schemas.FileItem, Path]
) -> Tuple[str, str, List[str], bool]:
"""
获取复制或移动文件需要的数据
:param fileitem: 文件项
:param target: 目标文件项或目标路径
:return: 源目录,目标目录,文件名列表,是否有效
"""
name = Path(target).name
if fileitem.name != name:
return "", "", [], False
src_dir = Path(fileitem.path).parent.as_posix()
if isinstance(target, schemas.FileItem):
traget_dir = Path(target.path).parent.as_posix()
else:
traget_dir = target.parent.as_posix()
return src_dir, traget_dir, [name], True
def copy(
self, fileitem: schemas.FileItem, target: Union[schemas.FileItem, Path]
) -> bool:
""" """
复制文件 复制文件
:param fileitem: 文件项
源文件名和目标文件名必须相同 :param path: 目标目录
:param new_name: 新文件名
""" """
src_dir, dst_dir, names, is_valid = self.__get_copy_and_move_data(
fileitem, target
)
if not is_valid:
return False
resp: Response = RequestUtils( resp: Response = RequestUtils(
headers=self.__get_header_with_token() headers=self.__get_header_with_token()
).post_res( ).post_res(
self.__get_api_url("/api/fs/copy"), self.__get_api_url("/api/fs/copy"),
json={ json={
"src_dir": src_dir, "src_dir": Path(fileitem.path).parent.as_posix(),
"dst_dir": dst_dir, "dst_dir": path.as_posix(),
"names": names, "names": [fileitem.name],
}, },
) )
""" """
@@ -655,28 +626,31 @@ class Alist(StorageBase):
f'复制文件 {fileitem.path} 失败,错误信息:{result["message"]}' f'复制文件 {fileitem.path} 失败,错误信息:{result["message"]}'
) )
return False return False
# 重命名
if fileitem.name != new_name:
self.rename(
self.get_item(path / fileitem.name), new_name
)
return True return True
def move( def move(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
self, fileitem: schemas.FileItem, target: Union[schemas.FileItem, Path]
) -> bool:
""" """
移动文件 移动文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
""" """
src_dir, dst_dir, names, is_valid = self.__get_copy_and_move_data( # 先重命名
fileitem, target if fileitem.name != new_name:
) self.rename(fileitem, new_name)
if not is_valid:
return False
resp: Response = RequestUtils( resp: Response = RequestUtils(
headers=self.__get_header_with_token() headers=self.__get_header_with_token()
).post_res( ).post_res(
self.__get_api_url("/api/fs/move"), self.__get_api_url("/api/fs/move"),
json={ json={
"src_dir": src_dir, "src_dir": Path(fileitem.path).parent.as_posix(),
"dst_dir": dst_dir, "dst_dir": path.as_posix(),
"names": names, "names": [new_name],
}, },
) )
""" """
@@ -757,15 +731,7 @@ class Alist(StorageBase):
@staticmethod @staticmethod
def __parse_timestamp(time_str: str) -> float: def __parse_timestamp(time_str: str) -> float:
# try: """
# # 尝试解析带微秒的时间格式 直接使用 ISO 8601 格式解析时间
# dt = datetime.strptime(time_str[:26], '%Y-%m-%dT%H:%M:%S.%f') """
# except ValueError: return datetime.fromisoformat(time_str).timestamp()
# # 如果失败,尝试解析不带微秒的时间格式
# dt = datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%SZ')
# 直接使用 ISO 8601 格式解析时间
dt = datetime.fromisoformat(time_str)
# 返回时间戳
return dt.timestamp()
+22 -16
View File
@@ -37,7 +37,7 @@ class LocalStorage(StorageBase):
""" """
return True return True
def __get_fileitem(self, path: Path): def __get_fileitem(self, path: Path) -> schemas.FileItem:
""" """
获取文件项 获取文件项
""" """
@@ -52,7 +52,7 @@ class LocalStorage(StorageBase):
modify_time=path.stat().st_mtime, modify_time=path.stat().st_mtime,
) )
def __get_diritem(self, path: Path): def __get_diritem(self, path: Path) -> schemas.FileItem:
""" """
获取目录项 获取目录项
""" """
@@ -201,17 +201,6 @@ class LocalStorage(StorageBase):
return None return None
return self.get_item(target_path) return self.get_item(target_path)
def copy(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
"""
复制文件
"""
file_path = Path(fileitem.path)
code, message = SystemUtils.copy(file_path, target_file)
if code != 0:
logger.error(f"复制文件失败:{message}")
return False
return True
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool: def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
""" """
硬链接文件 硬链接文件
@@ -234,12 +223,29 @@ class LocalStorage(StorageBase):
return False return False
return True return True
def move(self, fileitem: schemas.FileItem, target: Path) -> bool: def copy(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
""" """
移动文件 复制文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
""" """
file_path = Path(fileitem.path) file_path = Path(fileitem.path)
code, message = SystemUtils.move(file_path, target) code, message = SystemUtils.copy(file_path, path / new_name)
if code != 0:
logger.error(f"复制文件失败:{message}")
return False
return True
def move(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
"""
移动文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
"""
file_path = Path(fileitem.path)
code, message = SystemUtils.move(file_path, path / new_name)
if code != 0: if code != 0:
logger.error(f"移动文件失败:{message}") logger.error(f"移动文件失败:{message}")
return False return False
+27 -5
View File
@@ -306,16 +306,19 @@ class Rclone(StorageBase):
logger.error(f"rclone获取文件详情失败:{err}") logger.error(f"rclone获取文件详情失败:{err}")
return None return None
def move(self, fileitem: schemas.FileItem, target: Path) -> bool: def move(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
""" """
移动文件target_file格式:rclone:path 移动文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
""" """
try: try:
retcode = subprocess.run( retcode = subprocess.run(
[ [
'rclone', 'moveto', 'rclone', 'moveto',
f'MP:{fileitem.path}', f'MP:{fileitem.path}',
f'MP:{target}' f'MP:{path / new_name}'
], ],
startupinfo=self.__get_hidden_shell() startupinfo=self.__get_hidden_shell()
).returncode ).returncode
@@ -325,8 +328,27 @@ class Rclone(StorageBase):
logger.error(f"rclone移动文件失败:{err}") logger.error(f"rclone移动文件失败:{err}")
return False return False
def copy(self, fileitem: schemas.FileItem, target_file: Path) -> bool: def copy(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
pass """
复制文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
"""
try:
retcode = subprocess.run(
[
'rclone', 'copyto',
f'MP:{fileitem.path}',
f'MP:{path / new_name}'
],
startupinfo=self.__get_hidden_shell()
).returncode
if retcode == 0:
return True
except Exception as err:
logger.error(f"rclone复制文件失败:{err}")
return False
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool: def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
pass pass
+21 -15
View File
@@ -358,32 +358,38 @@ class U115Pan(StorageBase, metaclass=Singleton):
logger.error(f"115上传文件失败:{str(e)}") logger.error(f"115上传文件失败:{str(e)}")
return None return None
def move(self, fileitem: schemas.FileItem, target: schemas.FileItem) -> bool: def copy(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
"""
移动文件
"""
if not self.client:
return False
try:
self.client.fs.move(fileitem.path, target.path)
return True
except Exception as e:
logger.error(f"115移动文件失败:{str(e)}")
return False
def copy(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
""" """
复制文件 复制文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
""" """
if not self.client: if not self.client:
return False return False
try: try:
self.client.fs.copy(fileitem.path, target_file) self.client.fs.copy(fileitem.path, path / new_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 move(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
"""
移动文件
:param fileitem: 文件项
:param path: 目标目录
:param new_name: 新文件名
"""
if not self.client:
return False
try:
self.client.fs.move(fileitem.path, path / new_name)
return True
except Exception as e:
logger.error(f"115移动文件失败:{str(e)}")
return False
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool: def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
pass pass