diff --git a/app/modules/filemanager/__init__.py b/app/modules/filemanager/__init__.py index 608289bc..c120a242 100644 --- a/app/modules/filemanager/__init__.py +++ b/app/modules/filemanager/__init__.py @@ -14,6 +14,7 @@ from app.helper.message import MessageHelper from app.helper.module import ModuleHelper from app.log import logger from app.modules import _ModuleBase +from app.modules.filemanager.storage import StorageBase from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode, MediaDirectory, FileItem from app.schemas.types import MediaType from app.utils.system import SystemUtils @@ -230,12 +231,12 @@ class FileManagerModule(_ModuleBase): episodes_info=episodes_info, need_scrape=need_scrape) - def __get_storage_oper(self, storage: str): + def __get_storage_oper(self, _storage: str): """ 获取存储操作对象 """ for storage_schema in self._storage_schemas: - if storage_schema.schema == storage: + if storage_schema.schema == _storage: return storage_schema() return None @@ -259,9 +260,9 @@ class FileManagerModule(_ModuleBase): logger.error(f"不支持 {fileitem.storage} 到 {target_storage} 的文件整理") return False # 源操作对象 - source_oper = self.__get_storage_oper(fileitem.storage) + source_oper: StorageBase = self.__get_storage_oper(fileitem.storage) # 目的操作对象 - target_oper = self.__get_storage_oper(target_storage) + target_oper: StorageBase = self.__get_storage_oper(target_storage) with lock: if fileitem.storage == "local" and target_storage == "local": # 本地到本地 @@ -281,18 +282,19 @@ class FileManagerModule(_ModuleBase): if not filepath.exists(): logger.error(f"文件 {filepath} 不存在") return False - # TODO 根据目的路径创建文件夹 + # 根据目的路径创建文件夹 target_fileitem = target_oper.get_folder(target_file.parent) if target_fileitem: # 上传文件 - return target_oper.upload(target_fileitem, filepath) + if target_oper.upload(target_fileitem, filepath): + return True elif transfer_type == "move": # 移动 filepath = Path(fileitem.path) if not filepath.exists(): logger.error(f"文件 {filepath} 不存在") return False - # TODO 根据目的路径获取文件夹 + # 根据目的路径获取文件夹 target_fileitem = target_oper.get_folder(target_file.parent) if target_fileitem: # 上传文件 diff --git a/app/modules/filemanager/storage/__init__.py b/app/modules/filemanager/storage/__init__.py index 72388415..062af698 100644 --- a/app/modules/filemanager/storage/__init__.py +++ b/app/modules/filemanager/storage/__init__.py @@ -1,6 +1,6 @@ from abc import ABCMeta, abstractmethod from pathlib import Path -from typing import Optional, List, Any +from typing import Optional, List from app import schemas @@ -109,7 +109,7 @@ class StorageBase(metaclass=ABCMeta): pass @abstractmethod - def softlink(self, fileitm: schemas.FileItem, target_file: schemas.FileItem) -> bool: + def softlink(self, fileitm: schemas.FileItem, target_file: Path) -> bool: """ 软链接文件 """ diff --git a/app/modules/filemanager/storage/alipan.py b/app/modules/filemanager/storage/alipan.py index b13b2098..7d188f1c 100644 --- a/app/modules/filemanager/storage/alipan.py +++ b/app/modules/filemanager/storage/alipan.py @@ -331,8 +331,7 @@ class AliPan(StorageBase): self.__handle_error(res, "获取用户信息") return {} - def list(self, drive_id: str = None, parent_file_id: str = 'root', list_type: str = None, - limit: int = 100, order_by: str = 'updated_at', path: str = "/") -> List[schemas.FileItem]: + def list(self, fileitem: schemas.FileItem = None) -> List[schemas.FileItem]: """ 浏览文件 limit 返回文件数量,默认 50,最大 100 @@ -346,10 +345,11 @@ class AliPan(StorageBase): # 请求头 headers = self.__get_headers(params) # 根目录处理 - if not drive_id: + if not fileitem or not fileitem.drive_id: return [ schemas.FileItem( - fileid=parent_file_id, + storage=self.schema.value, + fileid=fileitem.fileid, drive_id=params.get("resourceDriveId"), parent_fileid="root", type="dir", @@ -357,7 +357,8 @@ class AliPan(StorageBase): name="资源库" ), schemas.FileItem( - fileid=parent_file_id, + storage=self.schema.value, + fileid=fileitem.fileid, drive_id=params.get("backDriveId"), parent_fileid="root", type="dir", @@ -370,13 +371,12 @@ class AliPan(StorageBase): # 分页获取 next_marker = None while True: - if not parent_file_id or parent_file_id == "/": + if not fileitem.parent_fileid or fileitem.parent_fileid == "/": parent_file_id = "root" + else: + parent_file_id = fileitem.fileid res = RequestUtils(headers=headers, timeout=10).post_res(self.list_file_url, json={ - "drive_id": drive_id, - "type": list_type, - "limit": limit, - "order_by": order_by, + "drive_id": fileitem.drive_id, "parent_file_id": parent_file_id, "marker": next_marker }, params={ @@ -400,10 +400,11 @@ class AliPan(StorageBase): self.__handle_error(res, "浏览文件") break return [schemas.FileItem( + storage=self.schema.value, fileid=fileinfo.get("file_id"), parent_fileid=fileinfo.get("parent_file_id"), type="dir" if fileinfo.get("type") == "folder" else "file", - path=f"{path}{fileinfo.get('name')}" + ("/" if fileinfo.get("type") == "folder" else ""), + path=f"{fileitem.path}{fileinfo.get('name')}" + ("/" if fileinfo.get("type") == "folder" else ""), name=fileinfo.get("name"), size=fileinfo.get("size"), extension=fileinfo.get("file_extension"), @@ -441,6 +442,7 @@ class AliPan(StorageBase): """ result = res.json() return schemas.FileItem( + storage=self.schema.value, fileid=result.get("file_id"), drive_id=result.get("drive_id"), parent_fileid=result.get("parent_file_id"), @@ -454,9 +456,36 @@ class AliPan(StorageBase): def get_folder(self, path: Path) -> Optional[schemas.FileItem]: """ - TODO 获取目录,不存在则创建 + 根据文件路程获取目录,不存在则创建 """ - pass + + def __find_dir_name(_fileitem: schemas.FileItem, _name: str) -> Optional[schemas.FileItem]: + """ + 查找下级目录中匹配名称的目录 + """ + sub_files = self.list(_fileitem) + for sub_file in sub_files: + if sub_file.type != "dir": + continue + if sub_file.name == _name: + return sub_file + return None + + # 逐级查找和创建目录 + fileitem = schemas.FileItem(fileid="root") + for part in path.parts: + if part == "/": + continue + dir_file = __find_dir_name(fileitem, part) + if dir_file: + return dir_file + else: + dir_file = self.create_folder(dir_file, part) + if not dir_file: + logger.warn(f"创建 aplipan 目录 {fileitem.path}{part} 失败!") + return None + fileitem = dir_file + return None def delete(self, fileitem: schemas.FileItem) -> bool: """ @@ -491,6 +520,7 @@ class AliPan(StorageBase): if res: result = res.json() return schemas.FileItem( + storage=self.schema.value, fileid=result.get("file_id"), drive_id=result.get("drive_id"), parent_fileid=result.get("parent_file_id"), @@ -582,6 +612,7 @@ class AliPan(StorageBase): if result.get("exist"): logger.info(f"文件{result.get('file_name')}已存在,无需上传") return schemas.FileItem( + storage=self.schema.value, drive_id=result.get("drive_id"), fileid=result.get("file_id"), parent_fileid=result.get("parent_file_id"), @@ -616,6 +647,7 @@ class AliPan(StorageBase): return None result = res.json() return schemas.FileItem( + storage=self.schema.value, fileid=result.get("file_id"), drive_id=result.get("drive_id"), parent_fileid=result.get("parent_file_id"), @@ -659,7 +691,7 @@ class AliPan(StorageBase): """ pass - def softlink(self, fileitm: schemas.FileItem, target_file: schemas.FileItem) -> bool: + def softlink(self, fileitm: schemas.FileItem, target_file: Path) -> bool: """ 软链接文件 """ diff --git a/app/modules/filemanager/storage/local.py b/app/modules/filemanager/storage/local.py index 289ea9f5..21addf75 100644 --- a/app/modules/filemanager/storage/local.py +++ b/app/modules/filemanager/storage/local.py @@ -42,6 +42,7 @@ class LocalStorage(StorageBase): partitions = SystemUtils.get_windows_drives() or ["C:/"] for partition in partitions: ret_items.append(schemas.FileItem( + storage=self.schema.value, type="dir", path=partition + "/", name=partition, @@ -65,6 +66,7 @@ class LocalStorage(StorageBase): # 如果是文件 if path_obj.is_file(): ret_items.append(schemas.FileItem( + storage=self.schema.value, type="file", path=str(path_obj).replace("\\", "/"), name=path_obj.name, @@ -78,6 +80,7 @@ class LocalStorage(StorageBase): # 扁历所有目录 for item in SystemUtils.list_sub_directory(path_obj): ret_items.append(schemas.FileItem( + storage=self.schema.value, type="dir", path=str(item).replace("\\", "/") + "/", name=item.name, @@ -88,6 +91,7 @@ class LocalStorage(StorageBase): # 遍历所有文件,不含子目录 for item in SystemUtils.list_sub_all(path_obj): ret_items.append(schemas.FileItem( + storage=self.schema.value, type="file", path=str(item).replace("\\", "/"), name=item.name, @@ -108,6 +112,7 @@ class LocalStorage(StorageBase): if not path_obj.exists(): path_obj.mkdir(parents=True, exist_ok=True) return schemas.FileItem( + storage=self.schema.value, type="dir", path=str(path_obj).replace("\\", "/") + "/", name=name, @@ -127,6 +132,7 @@ class LocalStorage(StorageBase): """ path_obj = Path(fileitm.path) return schemas.FileItem( + storage=self.schema.value, type="file", path=str(path_obj).replace("\\", "/"), name=path_obj.name, @@ -178,6 +184,7 @@ class LocalStorage(StorageBase): filepath.rename(path) if path.exists(): return schemas.FileItem( + storage=self.schema.value, type="file", path=str(path).replace("\\", "/"), name=path.name, diff --git a/app/modules/filemanager/storage/rclone.py b/app/modules/filemanager/storage/rclone.py index c288d8a2..c9ad871f 100644 --- a/app/modules/filemanager/storage/rclone.py +++ b/app/modules/filemanager/storage/rclone.py @@ -103,5 +103,5 @@ class Rclone(StorageBase): def link(self, fileitm: schemas.FileItem, target_file: Path) -> bool: pass - def softlink(self, fileitm: schemas.FileItem, target_file: schemas.FileItem) -> bool: + def softlink(self, fileitm: schemas.FileItem, target_file: Path) -> bool: pass diff --git a/app/modules/filemanager/storage/u115.py b/app/modules/filemanager/storage/u115.py index 4abdb230..7ba0e708 100644 --- a/app/modules/filemanager/storage/u115.py +++ b/app/modules/filemanager/storage/u115.py @@ -162,8 +162,9 @@ class U115Pan(StorageBase, metaclass=Singleton): if not self.__init_cloud(): return None try: - items = self.cloud.storage().list(dir_id=fileitem.parent_fileid) + items = self.cloud.storage().list(dir_id=fileitem.fileid) return [schemas.FileItem( + storage=self.schema.value, fileid=item.file_id, parent_fileid=item.parent_id, type="dir" if item.is_dir else "file", @@ -187,6 +188,7 @@ class U115Pan(StorageBase, metaclass=Singleton): try: result = self.cloud.storage().make_dir(fileitem.parent_fileid, name) return schemas.FileItem( + storage=self.schema.value, fileid=result.file_id, parent_fileid=result.parent_id, type="dir", @@ -201,9 +203,36 @@ class U115Pan(StorageBase, metaclass=Singleton): def get_folder(self, path: Path) -> Optional[schemas.FileItem]: """ - TODO 获取目录,不存在则创建 + 根据文件路程获取目录,不存在则创建 """ - pass + + def __find_dir_name(_fileitem: schemas.FileItem, _name: str) -> Optional[schemas.FileItem]: + """ + 查找下级目录中匹配名称的目录 + """ + sub_files = self.list(_fileitem) + for sub_file in sub_files: + if sub_file.type != "dir": + continue + if sub_file.name == _name: + return sub_file + return None + + # 逐级查找和创建目录 + fileitem = schemas.FileItem(fileid="0") + for part in path.parts: + if part == "/": + continue + dir_file = __find_dir_name(fileitem, part) + if dir_file: + return dir_file + else: + dir_file = self.create_folder(dir_file, part) + if not dir_file: + logger.warn(f"创建 115 目录 {fileitem.path}{part} 失败!") + return None + fileitem = dir_file + return None def detail(self, fileitm: schemas.FileItem) -> Optional[schemas.FileItem]: """ @@ -286,6 +315,7 @@ class U115Pan(StorageBase, metaclass=Singleton): fileitem = result.get('data') logger.info(f"115上传文件成功:{fileitem}") return schemas.FileItem( + storage=self.schema.value, fileid=fileitem.get('file_id'), parent_fileid=fileitem.fileid, type="file", @@ -321,5 +351,5 @@ class U115Pan(StorageBase, metaclass=Singleton): def link(self, fileitm: schemas.FileItem, target_file: Path) -> bool: pass - def softlink(self, fileitm: schemas.FileItem, target_file: schemas.FileItem) -> bool: + def softlink(self, fileitm: schemas.FileItem, target_file: Path) -> bool: pass diff --git a/app/schemas/file.py b/app/schemas/file.py index 00f09505..15ec4508 100644 --- a/app/schemas/file.py +++ b/app/schemas/file.py @@ -9,7 +9,7 @@ class FileItem(BaseModel): # 类型 dir/file type: Optional[str] = None # 文件路径 - path: Optional[str] = None + path: Optional[str] = "/" # 文件名 name: Optional[str] = None # 文件名