diff --git a/app/api/endpoints/aliyun.py b/app/api/endpoints/aliyun.py index bd0f97c2..fe585faa 100644 --- a/app/api/endpoints/aliyun.py +++ b/app/api/endpoints/aliyun.py @@ -9,7 +9,6 @@ from app.chain.transfer import TransferChain from app.core.config import settings from app.core.metainfo import MetaInfoPath from app.core.security import verify_token, verify_uri_token -from app.modules.filetransfer.storage.alipan import AliyunHelper from app.helper.progress import ProgressHelper from app.schemas.types import ProgressKey diff --git a/app/api/endpoints/u115.py b/app/api/endpoints/u115.py index 2c68b8cb..9a6ae198 100644 --- a/app/api/endpoints/u115.py +++ b/app/api/endpoints/u115.py @@ -10,7 +10,6 @@ from app.core.config import settings from app.core.metainfo import MetaInfoPath from app.core.security import verify_token, verify_uri_token from app.helper.progress import ProgressHelper -from app.modules.filetransfer.storage.u115 import U115Helper from app.schemas.types import ProgressKey from app.utils.http import RequestUtils diff --git a/app/modules/filetransfer/__init__.py b/app/modules/filetransfer/__init__.py index 195eff8c..347546aa 100644 --- a/app/modules/filetransfer/__init__.py +++ b/app/modules/filetransfer/__init__.py @@ -181,7 +181,7 @@ class FileTransferModule(_ModuleBase): pass def __transfer_command(self, fileitem: FileItem, target_storage: str, - target_file: Path, transfer_type: str) -> int: + target_file: Path, transfer_type: str) -> bool: """ 使用系统命令处理单个文件 :param fileitem: 源文件 @@ -192,29 +192,60 @@ class FileTransferModule(_ModuleBase): if fileitem.storage != "local" and target_storage != "local": logger.error(f"不支持 {fileitem.storage} 到 {target_storage} 的文件整理") - return 1 - retcode = 0 + return False # 源操作对象 source_oper = self.__get_storage_oper(fileitem.storage) # 目的操作对象 target_oper = self.__get_storage_oper(target_storage) - with lock: if fileitem.storage == "local" and target_storage == "local": # 本地到本地 if transfer_type == "copy": - retcode = source_oper.copy(fileitem, target_file) + return source_oper.copy(fileitem, target_file) elif transfer_type == "move": - retcode = source_oper.move(fileitem, target_file) + return source_oper.move(fileitem, target_file) elif transfer_type == "link": - retcode = source_oper.link(fileitem, target_file) + return source_oper.link(fileitem, target_file) elif transfer_type == "softlink": - retcode = source_oper.softlink(fileitem, target_file) - # TODO 本地到网盘 - - # TODO 网盘到本地 - - return retcode + return source_oper.softlink(fileitem, target_file) + elif fileitem.storage == "local" and target_storage != "local": + # 本地到网盘 + if transfer_type == "copy": + # 复制 + 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: + # 上传文件 + return target_oper.upload(target_fileitem, filepath) + 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: + # 上传文件 + result = target_oper.upload(target_fileitem, filepath) + if result: + # 删除源文件 + return source_oper.delete(fileitem) + elif fileitem.storage != "local" and target_storage == "local": + # 网盘到本地 + if transfer_type == "copy": + # 下载 + return target_oper.download(fileitem, target_file) + elif transfer_type == "move": + # 下载 + if target_oper.download(fileitem, target_file): + # 删除源文件 + return source_oper.delete(fileitem) + return False def __transfer_other_files(self, fileitem: FileItem, target_storage: str, target_file: Path, transfer_type: str) -> int: diff --git a/app/modules/filetransfer/storage/__init__.py b/app/modules/filetransfer/storage/__init__.py index 79ee2fea..72388415 100644 --- a/app/modules/filetransfer/storage/__init__.py +++ b/app/modules/filetransfer/storage/__init__.py @@ -45,6 +45,13 @@ class StorageBase(metaclass=ABCMeta): """ pass + @abstractmethod + def get_folder(self, path: Path) -> Optional[schemas.FileItem]: + """ + 获取目录 + """ + pass + @abstractmethod def delete(self, fileitm: schemas.FileItem) -> bool: """ @@ -60,9 +67,9 @@ class StorageBase(metaclass=ABCMeta): pass @abstractmethod - def download(self, fileitm: schemas.FileItem) -> Any: + def download(self, fileitm: schemas.FileItem, path: Path): """ - 下载链接 + 下载文件,保存到本地 """ pass diff --git a/app/modules/filetransfer/storage/alipan.py b/app/modules/filetransfer/storage/alipan.py index 4f16389a..f4989cfd 100644 --- a/app/modules/filetransfer/storage/alipan.py +++ b/app/modules/filetransfer/storage/alipan.py @@ -452,6 +452,12 @@ class AliPan(StorageBase): self.__handle_error(res, "创建目录") return None + def get_folder(self, path: Path) -> Optional[schemas.FileItem]: + """ + TODO 获取目录,不存在则创建 + """ + pass + def delete(self, fileitem: schemas.FileItem) -> bool: """ 删除文件 @@ -520,27 +526,35 @@ class AliPan(StorageBase): self.__handle_error(res, "重命名文件") return False - def download(self, fileitem: schemas.FileItem) -> Optional[str]: + def download(self, fileitem: schemas.FileItem, path: Path) -> bool: """ - 获取下载链接 + 下载文件,保存到本地 """ params = self.__access_params if not params: - return None + return False headers = self.__get_headers(params) res = RequestUtils(headers=headers, timeout=10).post_res(self.download_url, json={ "drive_id": fileitem.drive_id, "file_id": fileitem.fileid }) if res: - return res.json().get("url") + download_url = res.json().get("url") + if not download_url: + return False + res = RequestUtils().get_res(download_url) + if res: + with path.open("wb") as f: + f.write(res.content) + return True else: self.__handle_error(res, "获取下载链接") - return None + return False def upload(self, fileitem: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]: """ 上传文件,并标记完成 + TODO 上传文件分片、秒传 """ params = self.__access_params if not params: @@ -634,10 +648,19 @@ class AliPan(StorageBase): return False def copy(self, fileitm: schemas.FileItem, target_file: Path) -> bool: + """ + 复制文件 + """ pass def link(self, fileitm: schemas.FileItem, target_file: Path) -> bool: + """ + 硬链接文件 + """ pass def softlink(self, fileitm: schemas.FileItem, target_file: schemas.FileItem) -> bool: + """ + 软链接文件 + """ pass diff --git a/app/modules/filetransfer/storage/local.py b/app/modules/filetransfer/storage/local.py index 9b9d874b..26ba64e5 100644 --- a/app/modules/filetransfer/storage/local.py +++ b/app/modules/filetransfer/storage/local.py @@ -2,8 +2,6 @@ import shutil from pathlib import Path from typing import Optional, List -from starlette.responses import FileResponse, Response - from app import schemas from app.log import logger from app.modules.filetransfer.storage import StorageBase @@ -117,6 +115,12 @@ class LocalStorage(StorageBase): modify_time=path_obj.stat().st_mtime, ) + def get_folder(self, path: Path) -> Optional[schemas.FileItem]: + """ + 获取目录 + """ + pass + def detail(self, fileitm: schemas.FileItem) -> Optional[schemas.FileItem]: """ 获取文件详情 @@ -156,45 +160,17 @@ class LocalStorage(StorageBase): return False path_obj.rename(path_obj.parent / name) - def download(self, fileitem: schemas.FileItem) -> Optional[Response]: + def download(self, fileitem: schemas.FileItem, path: Path) -> bool: """ 下载文件 """ - if not fileitem.path: - return None - path_obj = Path(fileitem.path) - if not path_obj.exists(): - return None - if path_obj.is_file(): - # 做为文件流式下载 - return FileResponse(path_obj) - else: - # 做为压缩包下载 - shutil.make_archive(base_name=path_obj.stem, format="zip", root_dir=path_obj) - reponse = Response(content=path_obj.read_bytes(), media_type="application/zip") - # 删除压缩包 - Path(f"{path_obj.stem}.zip").unlink() - return reponse + return False def upload(self, fileitem: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]: """ 上传文件 """ - if not fileitem.path: - return None - path_obj = Path(fileitem.path) - if not path_obj.exists(): - return None - shutil.copy(path, path_obj / path.name) - return schemas.FileItem( - type="file", - path=str(path_obj / path.name).replace("\\", "/"), - name=path.name, - basename=path.stem, - extension=path.suffix[1:], - size=path.stat().st_size, - modify_time=path.stat().st_mtime, - ) + return None def copy(self, fileitem: schemas.FileItem, target_file: Path) -> bool: """ diff --git a/app/modules/filetransfer/storage/rclone.py b/app/modules/filetransfer/storage/rclone.py index 2cc2de23..f419276d 100644 --- a/app/modules/filetransfer/storage/rclone.py +++ b/app/modules/filetransfer/storage/rclone.py @@ -1,9 +1,12 @@ +import subprocess from pathlib import Path -from typing import Optional, Any, List +from typing import Optional, List from app import schemas +from app.log import logger from app.modules.filetransfer.storage import StorageBase from app.schemas.types import StorageSchema +from app.utils.system import SystemUtils class Rclone(StorageBase): @@ -19,6 +22,16 @@ class Rclone(StorageBase): "copy": "复制" } + @staticmethod + def __get_hidden_shell(): + if SystemUtils.is_windows(): + st = subprocess.STARTUPINFO() + st.dwFlags = subprocess.STARTF_USESHOWWINDOW + st.wShowWindow = subprocess.SW_HIDE + return st + else: + return None + def check(self) -> bool: pass @@ -28,13 +41,19 @@ class Rclone(StorageBase): def create_folder(self, fileitm: schemas.FileItem, name: str) -> Optional[schemas.FileItem]: pass + def get_folder(self, path: Path) -> Optional[schemas.FileItem]: + """ + 获取目录 + """ + pass + def delete(self, fileitm: schemas.FileItem) -> bool: pass def rename(self, fileitm: schemas.FileItem, name: str) -> bool: pass - def download(self, fileitm: schemas.FileItem) -> Any: + def download(self, fileitm: schemas.FileItem, path: Path) -> bool: pass def upload(self, fileitm: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]: @@ -43,11 +62,43 @@ class Rclone(StorageBase): def detail(self, fileitm: schemas.FileItem) -> Optional[schemas.FileItem]: pass - def move(self, fileitm: schemas.FileItem, target_dir: schemas.FileItem) -> bool: - pass + def move(self, fileitm: schemas.FileItem, target_file: schemas.FileItem) -> bool: + """ + 移动文件 + """ + try: + retcode = subprocess.run( + [ + 'rclone', 'moveto', + 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 copy(self, fileitm: schemas.FileItem, target_file: Path) -> bool: - pass + """ + 复制文件 + """ + try: + retcode = subprocess.run( + [ + 'rclone', 'copyto', + 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: pass diff --git a/app/modules/filetransfer/storage/u115.py b/app/modules/filetransfer/storage/u115.py index 1d7aaf28..071a4083 100644 --- a/app/modules/filetransfer/storage/u115.py +++ b/app/modules/filetransfer/storage/u115.py @@ -5,13 +5,14 @@ from typing import Optional, Tuple, List import oss2 import py115 from py115 import Cloud -from py115.types import LoginTarget, QrcodeSession, QrcodeStatus, Credential, DownloadTicket +from py115.types import LoginTarget, QrcodeSession, QrcodeStatus, Credential from app import schemas from app.db.systemconfig_oper import SystemConfigOper from app.log import logger from app.modules.filetransfer.storage import StorageBase from app.schemas.types import SystemConfigKey, StorageSchema +from app.utils.http import RequestUtils from app.utils.singleton import Singleton @@ -196,6 +197,12 @@ class U115Pan(StorageBase, metaclass=Singleton): logger.error(f"创建115目录失败:{str(e)}") return None + def get_folder(self, path: Path) -> Optional[schemas.FileItem]: + """ + TODO 获取目录,不存在则创建 + """ + pass + def detail(self, fileitm: schemas.FileItem) -> Optional[schemas.FileItem]: """ 获取文件详情 @@ -228,17 +235,23 @@ class U115Pan(StorageBase, metaclass=Singleton): logger.error(f"重命名115文件失败:{str(e)}") return False - def download(self, fileitem: schemas.FileItem) -> Optional[DownloadTicket]: + def download(self, fileitem: schemas.FileItem, path: Path) -> bool: """ 获取下载链接 """ if not self.__init_cloud(): - return None + return False try: - return self.cloud.storage().request_download(fileitem.pickcode) + ticket = self.cloud.storage().request_download(fileitem.pickcode) + if ticket: + res = RequestUtils(headers=ticket.headers).get_res(ticket.url) + if res: + with open(path, "wb") as f: + f.write(res.content) + return True except Exception as e: logger.error(f"115下载失败:{str(e)}") - return None + return False def upload(self, fileitem: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]: """ diff --git a/app/utils/system.py b/app/utils/system.py index 3266a79b..b2e486f3 100644 --- a/app/utils/system.py +++ b/app/utils/system.py @@ -3,7 +3,6 @@ import os import platform import re import shutil -import subprocess import sys from pathlib import Path from typing import List, Union, Tuple @@ -144,54 +143,6 @@ class SystemUtils: print(str(err)) return -1, str(err) - @staticmethod - def rclone_move(src: Path, dest: Path): - """ - Rclone移动 - """ - try: - retcode = subprocess.run( - [ - 'rclone', 'moveto', - str(src), - f'MP:{dest}' - ], - startupinfo=SystemUtils.__get_hidden_shell() - ).returncode - return retcode, "" - except Exception as err: - print(str(err)) - return -1, str(err) - - @staticmethod - def rclone_copy(src: Path, dest: Path): - """ - Rclone复制 - """ - try: - retcode = subprocess.run( - [ - 'rclone', 'copyto', - str(src), - f'MP:{dest}' - ], - startupinfo=SystemUtils.__get_hidden_shell() - ).returncode - return retcode, "" - except Exception as err: - print(str(err)) - return -1, str(err) - - @staticmethod - def __get_hidden_shell(): - if SystemUtils.is_windows(): - st = subprocess.STARTUPINFO() - st.dwFlags = subprocess.STARTF_USESHOWWINDOW - st.wShowWindow = subprocess.SW_HIDE - return st - else: - return None - @staticmethod def list_files(directory: Path, extensions: list, min_filesize: int = 0) -> List[Path]: """