mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-05 05:07:34 +08:00
use aligo
This commit is contained in:
@@ -34,6 +34,7 @@ RUN apt-get update -y \
|
|||||||
rsync \
|
rsync \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
nano \
|
nano \
|
||||||
|
aria2 \
|
||||||
&& \
|
&& \
|
||||||
if [ "$(uname -m)" = "x86_64" ]; \
|
if [ "$(uname -m)" = "x86_64" ]; \
|
||||||
then ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1; \
|
then ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1; \
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ class StorageChain(ChainBase):
|
|||||||
"""
|
"""
|
||||||
return self.run_module("create_folder", fileitem=fileitem, name=name)
|
return self.run_module("create_folder", fileitem=fileitem, name=name)
|
||||||
|
|
||||||
def download_file(self, fileitem: schemas.FileItem) -> Optional[Path]:
|
def download_file(self, fileitem: schemas.FileItem, path: Path = None) -> Optional[Path]:
|
||||||
"""
|
"""
|
||||||
下载文件
|
下载文件
|
||||||
"""
|
"""
|
||||||
return self.run_module("download_file", fileitem=fileitem)
|
return self.run_module("download_file", fileitem=fileitem, path=path)
|
||||||
|
|
||||||
def upload_file(self, fileitem: schemas.FileItem, path: Path) -> Optional[bool]:
|
def upload_file(self, fileitem: schemas.FileItem, path: Path) -> Optional[bool]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ class FileManagerModule(_ModuleBase):
|
|||||||
return False
|
return False
|
||||||
return storage_oper.rename(fileitem, name)
|
return storage_oper.rename(fileitem, name)
|
||||||
|
|
||||||
def download_file(self, fileitem: FileItem) -> Optional[Path]:
|
def download_file(self, fileitem: FileItem, path: Path = None) -> Optional[Path]:
|
||||||
"""
|
"""
|
||||||
下载文件
|
下载文件
|
||||||
"""
|
"""
|
||||||
@@ -214,7 +214,7 @@ class FileManagerModule(_ModuleBase):
|
|||||||
if not storage_oper:
|
if not storage_oper:
|
||||||
logger.error(f"不支持 {fileitem.storage} 的下载处理")
|
logger.error(f"不支持 {fileitem.storage} 的下载处理")
|
||||||
return None
|
return None
|
||||||
return storage_oper.download(fileitem)
|
return storage_oper.download(fileitem, path=path)
|
||||||
|
|
||||||
def upload_file(self, fileitem: FileItem, path: Path) -> Optional[FileItem]:
|
def upload_file(self, fileitem: FileItem, path: Path) -> Optional[FileItem]:
|
||||||
"""
|
"""
|
||||||
@@ -449,7 +449,7 @@ class FileManagerModule(_ModuleBase):
|
|||||||
# 网盘到本地
|
# 网盘到本地
|
||||||
if transfer_type in ["copy", "move"]:
|
if transfer_type in ["copy", "move"]:
|
||||||
# 下载
|
# 下载
|
||||||
tmp_file = source_oper.download(fileitem)
|
tmp_file = source_oper.download(fileitem=fileitem, path=target_file.parent)
|
||||||
if tmp_file:
|
if tmp_file:
|
||||||
# 创建目录
|
# 创建目录
|
||||||
if not target_file.parent.exists():
|
if not target_file.parent.exists():
|
||||||
@@ -464,29 +464,25 @@ 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 == "move":
|
# 根据目的路径获取文件夹
|
||||||
# 移动
|
target_diritem = target_oper.get_folder(target_file.parent)
|
||||||
# 根据目的路径获取文件夹
|
if target_diritem:
|
||||||
target_diritem = target_oper.get_folder(target_file.parent)
|
# 重命名文件
|
||||||
if target_diritem:
|
if target_oper.rename(fileitem, target_file.name):
|
||||||
# 重命名文件
|
# 移动文件到新目录
|
||||||
if target_oper.rename(fileitem, target_file.name):
|
if source_oper.move(fileitem, target_diritem):
|
||||||
# 移动文件到新目录
|
ret_fileitem = copy.deepcopy(fileitem)
|
||||||
if source_oper.move(fileitem, target_diritem):
|
ret_fileitem.path = target_diritem.path + "/" + target_file.name
|
||||||
ret_fileitem = copy.deepcopy(fileitem)
|
ret_fileitem.name = target_file.name
|
||||||
ret_fileitem.path = target_diritem.path + "/" + target_file.name
|
ret_fileitem.basename = target_file.stem
|
||||||
ret_fileitem.name = target_file.name
|
ret_fileitem.parent_fileid = target_diritem.fileid
|
||||||
ret_fileitem.basename = target_file.stem
|
return ret_fileitem, ""
|
||||||
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"{fileitem.path} {target_storage} 移动文件失败"
|
||||||
else:
|
else:
|
||||||
return None, f"{target_file.parent} {target_storage} 目录获取失败"
|
return None, f"{fileitem.path} {target_storage} 重命名文件失败"
|
||||||
else:
|
else:
|
||||||
return None, f"网盘内只支持移动操作"
|
return None, f"{target_file.parent} {target_storage} 目录获取失败"
|
||||||
|
|
||||||
return None, "未知错误"
|
return None, "未知错误"
|
||||||
|
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ class StorageBase(metaclass=ABCMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def list(self, fileitm: schemas.FileItem) -> Optional[List[schemas.FileItem]]:
|
def list(self, fileitem: schemas.FileItem) -> Optional[List[schemas.FileItem]]:
|
||||||
"""
|
"""
|
||||||
浏览文件
|
浏览文件
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create_folder(self, fileitm: schemas.FileItem, name: str) -> Optional[schemas.FileItem]:
|
def create_folder(self, fileitem: schemas.FileItem, name: str) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
创建目录
|
创建目录
|
||||||
"""
|
"""
|
||||||
@@ -82,63 +82,63 @@ class StorageBase(metaclass=ABCMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def delete(self, fileitm: schemas.FileItem) -> bool:
|
def delete(self, fileitem: schemas.FileItem) -> bool:
|
||||||
"""
|
"""
|
||||||
删除文件
|
删除文件
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def rename(self, fileitm: schemas.FileItem, name: str) -> bool:
|
def rename(self, fileitem: schemas.FileItem, name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
重命名文件
|
重命名文件
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def download(self, fileitm: schemas.FileItem) -> Path:
|
def download(self, fileitem: schemas.FileItem, path: Path = None) -> Path:
|
||||||
"""
|
"""
|
||||||
下载文件,保存到本地,返回本地临时文件地址
|
下载文件,保存到本地,返回本地临时文件地址
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def upload(self, fileitm: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]:
|
def upload(self, fileitem: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
上传文件
|
上传文件
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def detail(self, fileitm: schemas.FileItem) -> Optional[schemas.FileItem]:
|
def detail(self, fileitem: schemas.FileItem) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
获取文件详情
|
获取文件详情
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def copy(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def copy(self, fileitem: schemas.FileItem, target: Union[schemas.FileItem, Path]) -> bool:
|
||||||
"""
|
"""
|
||||||
复制文件
|
复制文件
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def move(self, fileitm: schemas.FileItem, target: Union[schemas.FileItem, Path]) -> bool:
|
def move(self, fileitem: schemas.FileItem, target: Union[schemas.FileItem, Path]) -> bool:
|
||||||
"""
|
"""
|
||||||
移动文件
|
移动文件
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def link(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
"""
|
"""
|
||||||
硬链接文件
|
硬链接文件
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def softlink(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def softlink(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
"""
|
"""
|
||||||
软链接文件
|
软链接文件
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import base64
|
import base64
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import uuid
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple, List
|
from typing import Optional, Tuple, List
|
||||||
|
|
||||||
from requests import Response
|
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.log import logger
|
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 aligo import Aligo, BaseFile
|
||||||
|
|
||||||
from app.utils.string import StringUtils
|
from app.utils.string import StringUtils
|
||||||
from app.utils.system import SystemUtils
|
|
||||||
|
|
||||||
|
|
||||||
class AliPan(StorageBase):
|
class AliPan(StorageBase):
|
||||||
@@ -28,14 +27,14 @@ class AliPan(StorageBase):
|
|||||||
|
|
||||||
# 支持的整理方式
|
# 支持的整理方式
|
||||||
transtype = {
|
transtype = {
|
||||||
"move": "移动"
|
"move": "移动",
|
||||||
}
|
}
|
||||||
|
|
||||||
_X_SIGNATURE = ('f4b7bed5d8524a04051bd2da876dd79afe922b8205226d65855d02b267422adb1'
|
# 是否有aria2c
|
||||||
'e0d8a816b021eaf5c36d101892180f79df655c5712b348c2a540ca136e6b22001')
|
_has_aria2c: bool = False
|
||||||
|
|
||||||
_X_PUBLIC_KEY = ('04d9d2319e0480c840efeeb75751b86d0db0c5b9e72c6260a1d846958adceaf9d'
|
# aligo
|
||||||
'ee789cab7472741d23aafc1a9c591f72e7ee77578656e6c8588098dea1488ac2a')
|
aligo: Aligo = None
|
||||||
|
|
||||||
# 生成二维码
|
# 生成二维码
|
||||||
qrcode_url = ("https://passport.aliyundrive.com/newlogin/qrcode/generate.do?"
|
qrcode_url = ("https://passport.aliyundrive.com/newlogin/qrcode/generate.do?"
|
||||||
@@ -43,62 +42,27 @@ class AliPan(StorageBase):
|
|||||||
"&lang=zh_CN&returnUrl=&bizParams=&_bx-v=2.0.31")
|
"&lang=zh_CN&returnUrl=&bizParams=&_bx-v=2.0.31")
|
||||||
# 二维码登录确认
|
# 二维码登录确认
|
||||||
check_url = "https://passport.aliyundrive.com/newlogin/qrcode/query.do?appName=aliyun_drive&fromSite=52&_bx-v=2.0.31"
|
check_url = "https://passport.aliyundrive.com/newlogin/qrcode/query.do?appName=aliyun_drive&fromSite=52&_bx-v=2.0.31"
|
||||||
# 更新访问令牌
|
|
||||||
update_accessstoken_url = "https://auth.aliyundrive.com/v2/account/token"
|
|
||||||
# 创建会话
|
|
||||||
create_session_url = "https://api.aliyundrive.com/users/v1/users/device/create_session"
|
|
||||||
# 用户信息
|
|
||||||
user_info_url = "https://user.aliyundrive.com/v2/user/get"
|
|
||||||
# 浏览文件
|
|
||||||
list_file_url = "https://api.aliyundrive.com/adrive/v3/file/list"
|
|
||||||
# 创建目录或文件
|
|
||||||
create_folder_file_url = "https://api.aliyundrive.com/adrive/v2/file/createWithFolders"
|
|
||||||
# 文件详情
|
|
||||||
file_detail_url = "https://api.aliyundrive.com/v2/file/get"
|
|
||||||
# 删除文件
|
|
||||||
delete_file_url = " https://api.aliyundrive.com/v2/recyclebin/trash"
|
|
||||||
# 文件重命名
|
|
||||||
rename_file_url = "https://api.aliyundrive.com/v3/file/update"
|
|
||||||
# 获取下载链接
|
|
||||||
download_url = "https://api.aliyundrive.com/v2/file/get_download_url"
|
|
||||||
# 移动文件
|
|
||||||
move_file_url = "https://api.aliyundrive.com/v2/file/move"
|
|
||||||
# 上传文件完成
|
|
||||||
upload_file_complete_url = "https://api.aliyundrive.com/v2/file/complete"
|
|
||||||
# 查询存储详情
|
|
||||||
storage_info_url = "https://api.aliyundrive.com/adrive/v1/user/driveCapacityDetails"
|
|
||||||
# 播放地址
|
|
||||||
play_info_url = 'https://api.aliyundrive.com/v2/file/get_video_preview_play_info'
|
|
||||||
|
|
||||||
def __handle_error(self, res: Response, apiname: str, action: bool = True):
|
def __init__(self):
|
||||||
"""
|
super().__init__()
|
||||||
统一处理和打印错误信息
|
|
||||||
"""
|
|
||||||
if res is None:
|
|
||||||
logger.warn("无法连接到阿里云盘!")
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
result = res.json()
|
subprocess.run(['aria2c', '-h'], capture_output=True)
|
||||||
except Exception as err:
|
self._has_aria2c = True
|
||||||
logger.error(f"解析阿里云盘返回数据失败:{str(err)}")
|
logger.debug('发现 aria2c, 将使用 aria2c 下载文件')
|
||||||
return
|
except FileNotFoundError:
|
||||||
code = result.get("code")
|
logger.debug('未发现 aria2c')
|
||||||
message = result.get("message")
|
self._has_aria2c = False
|
||||||
display_message = result.get("display_message")
|
|
||||||
if code or message:
|
self.__init_aligo()
|
||||||
logger.warn(f"Aliyun {apiname}失败:{code} - {display_message or message}")
|
|
||||||
if action:
|
def __init_aligo(self):
|
||||||
if code == "DeviceSessionSignatureInvalid":
|
"""
|
||||||
logger.warn("设备已失效,正在重新建立会话...")
|
初始化 aligo
|
||||||
self.__create_session(self.__get_headers(self.__auth_params))
|
"""
|
||||||
if code == "UserDeviceOffline":
|
refresh_token = self.__auth_params.get("refreshToken")
|
||||||
logger.warn("设备已离线,尝试重新登录,如仍报错请检查阿里云盘绑定设备数量是否超限!")
|
if refresh_token:
|
||||||
self.__create_session(self.__get_headers(self.__auth_params))
|
self.aligo = Aligo(refresh_token=refresh_token, use_aria2=self._has_aria2c,
|
||||||
if code == "AccessTokenInvalid":
|
name="MoviePilot V2", level=logging.ERROR)
|
||||||
logger.warn("访问令牌已失效,正在刷新令牌...")
|
|
||||||
self.__update_accesstoken(self.__auth_params, self.__auth_params.get("refreshToken"))
|
|
||||||
else:
|
|
||||||
logger.info(f"Aliyun {apiname}成功")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __auth_params(self):
|
def __auth_params(self):
|
||||||
@@ -135,7 +99,6 @@ class AliPan(StorageBase):
|
|||||||
"t": data.get("t")
|
"t": data.get("t")
|
||||||
}, ""
|
}, ""
|
||||||
elif res is not None:
|
elif res is not None:
|
||||||
self.__handle_error(res, "生成二维码")
|
|
||||||
return {}, f"请求阿里云盘二维码失败:{res.status_code} - {res.reason}"
|
return {}, f"请求阿里云盘二维码失败:{res.status_code} - {res.reason}"
|
||||||
return {}, f"请求阿里云盘二维码失败:无法连接!"
|
return {}, f"请求阿里云盘二维码失败:无法连接!"
|
||||||
|
|
||||||
@@ -194,151 +157,62 @@ class AliPan(StorageBase):
|
|||||||
"updateTime": time.time(),
|
"updateTime": time.time(),
|
||||||
})
|
})
|
||||||
self.__update_params(data)
|
self.__update_params(data)
|
||||||
self.user_info()
|
self.__init_aligo()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {}, f"bizExt 解码失败:{str(e)}"
|
return {}, f"bizExt 解码失败:{str(e)}"
|
||||||
return data, ""
|
return data, ""
|
||||||
elif res is not None:
|
elif res is not None:
|
||||||
self.__handle_error(res, "登录确认")
|
|
||||||
return {}, f"阿里云盘登录确认失败:{res.status_code} - {res.reason}"
|
return {}, f"阿里云盘登录确认失败:{res.status_code} - {res.reason}"
|
||||||
return {}, "阿里云盘登录确认失败:无法连接!"
|
return {}, "阿里云盘登录确认失败:无法连接!"
|
||||||
|
|
||||||
def __update_accesstoken(self, params: dict, refresh_token: str) -> bool:
|
|
||||||
"""
|
|
||||||
更新阿里云盘访问令牌
|
|
||||||
"""
|
|
||||||
headers = self.__get_headers(params)
|
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(
|
|
||||||
self.update_accessstoken_url, json={
|
|
||||||
"refresh_token": refresh_token,
|
|
||||||
"grant_type": "refresh_token"
|
|
||||||
})
|
|
||||||
if res:
|
|
||||||
data = res.json()
|
|
||||||
code = data.get("code")
|
|
||||||
if code in ["RefreshTokenExpired", "InvalidParameter.RefreshToken"]:
|
|
||||||
logger.warn("刷新令牌已过期,请重新登录!")
|
|
||||||
self.__clear_params()
|
|
||||||
return False
|
|
||||||
self.__update_params({
|
|
||||||
"accessToken": data.get('access_token'),
|
|
||||||
"expiresIn": data.get('expires_in'),
|
|
||||||
"updateTime": time.time()
|
|
||||||
})
|
|
||||||
logger.info(f"阿里云盘访问令牌已更新,accessToken={data.get('access_token')}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
self.__handle_error(res, "更新令牌", action=False)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __create_session(self, headers: dict):
|
|
||||||
"""
|
|
||||||
创建会话
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __os_name():
|
|
||||||
"""
|
|
||||||
获取操作系统名称
|
|
||||||
"""
|
|
||||||
if SystemUtils.is_windows():
|
|
||||||
return 'Windows 操作系统'
|
|
||||||
elif SystemUtils.is_macos():
|
|
||||||
return 'MacOS 操作系统'
|
|
||||||
else:
|
|
||||||
return '类 Unix 操作系统'
|
|
||||||
|
|
||||||
res = RequestUtils(headers=headers, timeout=5).post_res(self.create_session_url, json={
|
|
||||||
'deviceName': 'MoviePilot',
|
|
||||||
'modelName': __os_name(),
|
|
||||||
'pubKey': self._X_PUBLIC_KEY,
|
|
||||||
})
|
|
||||||
self.__handle_error(res, "创建会话", action=False)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def __access_params(self) -> Optional[dict]:
|
|
||||||
"""
|
|
||||||
获取阿里云盘访问参数,如果超时则更新后返回
|
|
||||||
"""
|
|
||||||
params = self.__auth_params
|
|
||||||
if not params:
|
|
||||||
logger.warn("阿里云盘访问令牌不存在,请先扫码登录!")
|
|
||||||
return None
|
|
||||||
expires_in = params.get("expiresIn")
|
|
||||||
update_time = params.get("updateTime")
|
|
||||||
refresh_token = params.get("refreshToken")
|
|
||||||
if not expires_in or not update_time or not refresh_token:
|
|
||||||
logger.warn("阿里云盘访问令牌参数错误,请重新扫码登录!")
|
|
||||||
self.__clear_params()
|
|
||||||
return None
|
|
||||||
# 是否需要更新设备信息
|
|
||||||
update_device = False
|
|
||||||
# 判断访问令牌是否过期
|
|
||||||
if (time.time() - update_time) >= expires_in:
|
|
||||||
logger.info("阿里云盘访问令牌已过期,正在更新...")
|
|
||||||
if not self.__update_accesstoken(params, refresh_token):
|
|
||||||
# 更新失败
|
|
||||||
return None
|
|
||||||
update_device = True
|
|
||||||
# 生成设备ID
|
|
||||||
x_device_id = params.get("x_device_id")
|
|
||||||
if not x_device_id:
|
|
||||||
x_device_id = uuid.uuid4().hex
|
|
||||||
params['x_device_id'] = x_device_id
|
|
||||||
self.__update_params({"x_device_id": x_device_id})
|
|
||||||
update_device = True
|
|
||||||
# 更新设备信息重新创建会话
|
|
||||||
if update_device:
|
|
||||||
self.__create_session(self.__get_headers(params))
|
|
||||||
return params
|
|
||||||
|
|
||||||
def __get_headers(self, params: dict):
|
|
||||||
"""
|
|
||||||
获取请求头
|
|
||||||
"""
|
|
||||||
if not params:
|
|
||||||
return {}
|
|
||||||
return {
|
|
||||||
"Authorization": f"Bearer {params.get('accessToken')}",
|
|
||||||
"Content-Type": "application/json;charset=UTF-8",
|
|
||||||
"Accept": "application/json, text/plain, */*",
|
|
||||||
"Referer": "https://www.alipan.com/",
|
|
||||||
"User-Agent": settings.USER_AGENT,
|
|
||||||
"X-Canary": "client=web,app=adrive,version=v4.9.0",
|
|
||||||
"x-device-id": params.get('x_device_id'),
|
|
||||||
"x-signature": self._X_SIGNATURE
|
|
||||||
}
|
|
||||||
|
|
||||||
def check(self) -> bool:
|
def check(self) -> bool:
|
||||||
"""
|
"""
|
||||||
检查存储是否可用
|
检查存储是否可用
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
if not self.aligo:
|
||||||
if not params:
|
|
||||||
return False
|
return False
|
||||||
return True if self.list(schemas.FileItem(
|
return True if self.aligo.get_user() else False
|
||||||
fileid="root",
|
|
||||||
drive_id=params.get("resourceDriveId")
|
|
||||||
)) else False
|
|
||||||
|
|
||||||
def user_info(self) -> dict:
|
def user_info(self) -> dict:
|
||||||
"""
|
"""
|
||||||
获取用户信息(drive_id等)
|
获取用户信息(drive_id等)
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
return self.aligo.get_user()
|
||||||
if not params:
|
|
||||||
return {}
|
def __get_fileitem(self, fileinfo: BaseFile, parent: str = "/") -> schemas.FileItem:
|
||||||
headers = self.__get_headers(params)
|
"""
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.user_info_url)
|
获取文件信息
|
||||||
if res:
|
"""
|
||||||
result = res.json()
|
if not fileinfo:
|
||||||
self.__update_params({
|
return schemas.FileItem()
|
||||||
"resourceDriveId": result.get("resource_drive_id"),
|
if fileinfo.type == "folder":
|
||||||
"backDriveId": result.get("backup_drive_id")
|
return schemas.FileItem(
|
||||||
})
|
storage=self.schema.value,
|
||||||
return result
|
fileid=fileinfo.file_id,
|
||||||
|
parent_fileid=fileinfo.parent_file_id,
|
||||||
|
type="dir",
|
||||||
|
path=f"{parent}{fileinfo.name}" + "/",
|
||||||
|
name=fileinfo.name,
|
||||||
|
basename=fileinfo.name,
|
||||||
|
size=fileinfo.size,
|
||||||
|
modify_time=StringUtils.str_to_timestamp(fileinfo.updated_at),
|
||||||
|
drive_id=fileinfo.drive_id,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.__handle_error(res, "获取用户信息")
|
return schemas.FileItem(
|
||||||
return {}
|
storage=self.schema.value,
|
||||||
|
fileid=fileinfo.file_id,
|
||||||
|
parent_fileid=fileinfo.parent_file_id,
|
||||||
|
type="file",
|
||||||
|
path=f"{parent}{fileinfo.name}",
|
||||||
|
name=fileinfo.name,
|
||||||
|
basename=Path(fileinfo.name).stem,
|
||||||
|
size=fileinfo.size,
|
||||||
|
extension=fileinfo.file_extension,
|
||||||
|
modify_time=StringUtils.str_to_timestamp(fileinfo.updated_at),
|
||||||
|
thumbnail=fileinfo.thumbnail,
|
||||||
|
drive_id=fileinfo.drive_id,
|
||||||
|
)
|
||||||
|
|
||||||
def list(self, fileitem: schemas.FileItem = None) -> List[schemas.FileItem]:
|
def list(self, fileitem: schemas.FileItem = None) -> List[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
@@ -348,18 +222,15 @@ class AliPan(StorageBase):
|
|||||||
parent_file_id 根目录为root
|
parent_file_id 根目录为root
|
||||||
type all | file | folder
|
type all | file | folder
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
if not self.aligo:
|
||||||
if not params:
|
|
||||||
return []
|
return []
|
||||||
# 请求头
|
|
||||||
headers = self.__get_headers(params)
|
|
||||||
# 根目录处理
|
# 根目录处理
|
||||||
if not fileitem or not fileitem.drive_id:
|
if not fileitem or not fileitem.drive_id:
|
||||||
return [
|
return [
|
||||||
schemas.FileItem(
|
schemas.FileItem(
|
||||||
storage=self.schema.value,
|
storage=self.schema.value,
|
||||||
fileid=fileitem.fileid,
|
fileid=fileitem.fileid,
|
||||||
drive_id=params.get("resourceDriveId"),
|
drive_id=self.__auth_params.get("resourceDriveId"),
|
||||||
parent_fileid="root",
|
parent_fileid="root",
|
||||||
type="dir",
|
type="dir",
|
||||||
path="/资源库/",
|
path="/资源库/",
|
||||||
@@ -369,7 +240,7 @@ class AliPan(StorageBase):
|
|||||||
schemas.FileItem(
|
schemas.FileItem(
|
||||||
storage=self.schema.value,
|
storage=self.schema.value,
|
||||||
fileid=fileitem.fileid,
|
fileid=fileitem.fileid,
|
||||||
drive_id=params.get("backDriveId"),
|
drive_id=self.__auth_params.get("backDriveId"),
|
||||||
parent_fileid="root",
|
parent_fileid="root",
|
||||||
type="dir",
|
type="dir",
|
||||||
path="/备份盘/",
|
path="/备份盘/",
|
||||||
@@ -377,436 +248,126 @@ class AliPan(StorageBase):
|
|||||||
basename="备份盘"
|
basename="备份盘"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
# 如果本身是文件
|
elif fileitem.type == "file":
|
||||||
if fileitem.type == "file":
|
# 文件处理
|
||||||
return [fileitem]
|
file = self.detail(fileitem)
|
||||||
# 返回数据
|
if file:
|
||||||
ret_items = []
|
return [file]
|
||||||
# 分页获取
|
else:
|
||||||
next_marker = None
|
items = self.aligo.get_file_list(parent_file_id=fileitem.fileid, drive_id=fileitem.drive_id)
|
||||||
while True:
|
if items:
|
||||||
if not fileitem.parent_fileid or fileitem.parent_fileid == "/":
|
return [self.__get_fileitem(item) for item in items]
|
||||||
parent_file_id = "root"
|
return []
|
||||||
else:
|
|
||||||
parent_file_id = fileitem.fileid
|
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.list_file_url, json={
|
|
||||||
"drive_id": fileitem.drive_id,
|
|
||||||
"parent_file_id": parent_file_id,
|
|
||||||
"marker": next_marker
|
|
||||||
}, params={
|
|
||||||
'jsonmask': ('next_marker,items(name,file_id,drive_id,type,size,created_at,updated_at,'
|
|
||||||
'category,file_extension,parent_file_id,mime_type,starred,thumbnail,url,'
|
|
||||||
'streams_info,content_hash,user_tags,user_meta,trashed,video_media_metadata,'
|
|
||||||
'video_preview_metadata,sync_meta,sync_device_flag,sync_flag,punish_flag')
|
|
||||||
})
|
|
||||||
if res:
|
|
||||||
result = res.json()
|
|
||||||
items = result.get("items")
|
|
||||||
if not items:
|
|
||||||
break
|
|
||||||
# 合并数据
|
|
||||||
ret_items.extend(items)
|
|
||||||
next_marker = result.get("next_marker")
|
|
||||||
if not next_marker:
|
|
||||||
# 没有下一页
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
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"{fileitem.path}{fileinfo.get('name')}" + ("/" if fileinfo.get("type") == "folder" else ""),
|
|
||||||
name=fileinfo.get("name"),
|
|
||||||
basename=Path(fileinfo.get("name")).stem,
|
|
||||||
size=fileinfo.get("size"),
|
|
||||||
extension=fileinfo.get("file_extension"),
|
|
||||||
modify_time=StringUtils.str_to_timestamp(fileinfo.get("updated_at")),
|
|
||||||
thumbnail=fileinfo.get("thumbnail"),
|
|
||||||
drive_id=fileinfo.get("drive_id"),
|
|
||||||
) for fileinfo in ret_items]
|
|
||||||
|
|
||||||
def create_folder(self, fileitem: schemas.FileItem, name: str) -> Optional[schemas.FileItem]:
|
def create_folder(self, fileitem: schemas.FileItem, name: str) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
创建目录
|
创建目录
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
if not self.aligo:
|
||||||
if not params:
|
|
||||||
return None
|
return None
|
||||||
headers = self.__get_headers(params)
|
item = self.aligo.create_folder(name=name, parent_file_id=fileitem.fileid, drive_id=fileitem.drive_id)
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.create_folder_file_url, json={
|
if item:
|
||||||
"drive_id": fileitem.drive_id,
|
return self.__get_fileitem(item)
|
||||||
"parent_file_id": fileitem.parent_fileid,
|
|
||||||
"name": name,
|
|
||||||
"check_name_mode": "refuse",
|
|
||||||
"type": "folder"
|
|
||||||
})
|
|
||||||
if res:
|
|
||||||
"""
|
|
||||||
{
|
|
||||||
"parent_file_id": "root",
|
|
||||||
"type": "folder",
|
|
||||||
"file_id": "6673f2c8a88344741bd64ad192d7512b92087719",
|
|
||||||
"domain_id": "bj29",
|
|
||||||
"drive_id": "39146740",
|
|
||||||
"file_name": "test",
|
|
||||||
"encrypt_mode": "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"),
|
|
||||||
type=result.get("type"),
|
|
||||||
name=result.get("file_name"),
|
|
||||||
path=f"{fileitem.path}{result.get('file_name')}",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.__handle_error(res, "创建目录")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_folder(self, path: Path) -> Optional[schemas.FileItem]:
|
def get_folder(self, path: Path) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
根据文件路程获取目录,不存在则创建
|
根据文件路程获取目录,不存在则创建
|
||||||
"""
|
"""
|
||||||
|
if not self.aligo:
|
||||||
def __find_dir(_fileitem: schemas.FileItem, _name: str) -> Optional[schemas.FileItem]:
|
|
||||||
"""
|
|
||||||
查找下级目录中匹配名称的目录
|
|
||||||
"""
|
|
||||||
for sub_file in self.list(_fileitem):
|
|
||||||
if sub_file.type != "dir":
|
|
||||||
continue
|
|
||||||
if sub_file.name == _name:
|
|
||||||
return sub_file
|
|
||||||
return None
|
return None
|
||||||
|
item = self.aligo.get_folder_by_path(path=str(Path), create_folder=True)
|
||||||
# 逐级查找和创建目录
|
if item:
|
||||||
fileitem = schemas.FileItem(fileid="root")
|
return self.__get_fileitem(item)
|
||||||
for part in path.parts:
|
return None
|
||||||
if part == "/":
|
|
||||||
continue
|
|
||||||
dir_file = __find_dir(fileitem, part)
|
|
||||||
if dir_file:
|
|
||||||
fileitem = dir_file
|
|
||||||
else:
|
|
||||||
dir_file = self.create_folder(dir_file, part)
|
|
||||||
if not dir_file:
|
|
||||||
logger.warn(f"{self.schema.value}创建目录 {fileitem.path}{part} 失败!")
|
|
||||||
return None
|
|
||||||
fileitem = dir_file
|
|
||||||
return fileitem
|
|
||||||
|
|
||||||
def get_item(self, path: Path) -> Optional[schemas.FileItem]:
|
def get_item(self, path: Path) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
获取文件或目录,不存在返回None
|
获取文件或目录,不存在返回None
|
||||||
"""
|
"""
|
||||||
|
if not self.aligo:
|
||||||
def __find_item(_fileitem: schemas.FileItem, _name: str) -> Optional[schemas.FileItem]:
|
|
||||||
"""
|
|
||||||
查找下级目录中匹配名称的目录或文件
|
|
||||||
"""
|
|
||||||
for sub_file in self.list(_fileitem):
|
|
||||||
if sub_file.name == _name:
|
|
||||||
return sub_file
|
|
||||||
return None
|
return None
|
||||||
|
item = self.aligo.get_file_by_path(path=str(path))
|
||||||
# 逐级查找和创建目录
|
if item:
|
||||||
fileitem = schemas.FileItem(fileid="root")
|
return self.__get_fileitem(item)
|
||||||
for part in path.parts:
|
return None
|
||||||
if part == "/":
|
|
||||||
continue
|
|
||||||
item = __find_item(fileitem, part)
|
|
||||||
if not item:
|
|
||||||
return None
|
|
||||||
fileitem = item
|
|
||||||
return fileitem
|
|
||||||
|
|
||||||
def delete(self, fileitem: schemas.FileItem) -> bool:
|
def delete(self, fileitem: schemas.FileItem) -> bool:
|
||||||
"""
|
"""
|
||||||
删除文件
|
删除文件
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
if not self.aligo:
|
||||||
if not params:
|
|
||||||
return False
|
return False
|
||||||
headers = self.__get_headers(params)
|
if self.aligo.move_file_to_trash(file_id=fileitem.fileid, drive_id=fileitem.drive_id):
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.delete_file_url, json={
|
|
||||||
"drive_id": fileitem.drive_id,
|
|
||||||
"file_id": fileitem.fileid
|
|
||||||
})
|
|
||||||
if res:
|
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
self.__handle_error(res, "删除文件")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def detail(self, fileitem: schemas.FileItem) -> Optional[schemas.FileItem]:
|
def detail(self, fileitem: schemas.FileItem) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
获取文件详情
|
获取文件详情
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
if not self.aligo:
|
||||||
if not params:
|
|
||||||
return None
|
return None
|
||||||
headers = self.__get_headers(params)
|
item = self.aligo.get_file(file_id=fileitem.fileid, drive_id=fileitem.drive_id)
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.file_detail_url, json={
|
if item:
|
||||||
"drive_id": fileitem.drive_id,
|
return self.__get_fileitem(item)
|
||||||
"file_id": fileitem.fileid
|
|
||||||
})
|
|
||||||
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"),
|
|
||||||
type="file",
|
|
||||||
name=result.get("name"),
|
|
||||||
size=result.get("size"),
|
|
||||||
extension=result.get("file_extension"),
|
|
||||||
modify_time=StringUtils.str_to_timestamp(result.get("updated_at")),
|
|
||||||
thumbnail=result.get("thumbnail"),
|
|
||||||
path=f"{fileitem.path}{result.get('name')}",
|
|
||||||
url=result.get("download_url") or result.get("url")
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.__handle_error(res, "获取文件详情")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def rename(self, fileitem: schemas.FileItem, name: str) -> bool:
|
def rename(self, fileitem: schemas.FileItem, name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
重命名文件
|
重命名文件
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
if not self.aligo:
|
||||||
if not params:
|
|
||||||
return False
|
return False
|
||||||
headers = self.__get_headers(params)
|
if self.aligo.rename_file(file_id=fileitem.fileid, name=name, drive_id=fileitem.drive_id):
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.rename_file_url, json={
|
|
||||||
"drive_id": fileitem.drive_id,
|
|
||||||
"file_id": fileitem.fileid,
|
|
||||||
"name": name,
|
|
||||||
"check_name_mode": "refuse"
|
|
||||||
})
|
|
||||||
if res:
|
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
self.__handle_error(res, "重命名文件")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def download(self, fileitem: schemas.FileItem) -> Optional[Path]:
|
def download(self, fileitem: schemas.FileItem, path: Path = None) -> Optional[Path]:
|
||||||
"""
|
"""
|
||||||
下载文件,保存到本地
|
下载文件,保存到本地
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
if not self.aligo:
|
||||||
if not params:
|
|
||||||
return None
|
return None
|
||||||
headers = self.__get_headers(params)
|
local_path = self.aligo.download_file(file_id=fileitem.fileid, drive_id=fileitem.drive_id,
|
||||||
|
local_folder=str(path or settings.TEMP_PATH))
|
||||||
def __get_play_url():
|
if local_path:
|
||||||
"""
|
return Path(local_path)
|
||||||
获取播放地址
|
|
||||||
"""
|
|
||||||
play_res = RequestUtils(headers=headers, timeout=10).post_res(self.play_info_url, json={
|
|
||||||
"drive_id": fileitem.drive_id,
|
|
||||||
"file_id": fileitem.fileid
|
|
||||||
})
|
|
||||||
if play_res:
|
|
||||||
play_dict = {}
|
|
||||||
play_info = play_res.json()
|
|
||||||
if play_info.get('video_preview_play_info'):
|
|
||||||
for i in play_info['video_preview_play_info'].get('live_transcoding_task_list') or []:
|
|
||||||
if i.get('url'):
|
|
||||||
try:
|
|
||||||
play_dict[i['template_id']] = i['url']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if play_dict:
|
|
||||||
return list(play_dict.values())[-1]
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 先获取文件详情
|
|
||||||
fileinfo = self.detail(fileitem)
|
|
||||||
if not fileinfo:
|
|
||||||
logger.warn(f"{fileitem.path} 文件不存在")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 文件下载链接
|
|
||||||
download_url = None
|
|
||||||
if fileinfo.url:
|
|
||||||
# 使用文件详情中的链接
|
|
||||||
download_url = fileinfo.url
|
|
||||||
else:
|
|
||||||
# 查询文件下载链接
|
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.download_url, json={
|
|
||||||
"drive_id": fileitem.drive_id,
|
|
||||||
"file_id": fileitem.fileid,
|
|
||||||
"file_name": fileitem.name,
|
|
||||||
})
|
|
||||||
if res:
|
|
||||||
result = res.json()
|
|
||||||
download_url = result.get("url") or result.get("internal_url")
|
|
||||||
|
|
||||||
if not download_url:
|
|
||||||
# 查询播放链接
|
|
||||||
download_url = __get_play_url()
|
|
||||||
|
|
||||||
if not download_url:
|
|
||||||
logger.warn(f"{fileitem.path} 未获取到下载链接")
|
|
||||||
return None
|
|
||||||
# 下载文件到本地
|
|
||||||
res = RequestUtils(headers={
|
|
||||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
||||||
"Referer": "https://www.alipan.com/",
|
|
||||||
"Sec-Fetch-Dest": "iframe",
|
|
||||||
"Sec-Fetch-Mode": "navigate",
|
|
||||||
"Sec-Fetch-Site": "cross-site",
|
|
||||||
"User-Agent": settings.USER_AGENT
|
|
||||||
}).get_res(download_url)
|
|
||||||
if res:
|
|
||||||
path = settings.TEMP_PATH / fileitem.name
|
|
||||||
with path.open("wb") as f:
|
|
||||||
f.write(res.content)
|
|
||||||
return path
|
|
||||||
else:
|
|
||||||
self.__handle_error(res, "获取下载链接")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def upload(self, fileitem: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]:
|
def upload(self, fileitem: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
上传文件,并标记完成
|
上传文件,并标记完成
|
||||||
"""
|
"""
|
||||||
|
if not self.aligo:
|
||||||
__UPLOAD_CHUNK_SIZE: int = 10485760 # 10 MB
|
|
||||||
|
|
||||||
def __sha1(_path: Path):
|
|
||||||
"""
|
|
||||||
计算文件sha1,用于快传
|
|
||||||
"""
|
|
||||||
_sha1 = hashlib.sha1()
|
|
||||||
with open(_path, 'rb') as f:
|
|
||||||
while True:
|
|
||||||
data = f.read(8192)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
_sha1.update(data)
|
|
||||||
return _sha1.hexdigest()
|
|
||||||
|
|
||||||
params = self.__access_params
|
|
||||||
if not params:
|
|
||||||
return None
|
return None
|
||||||
headers = self.__get_headers(params)
|
item = self.aligo.upload_file(file_path=str(path.parent), parent_file_id=fileitem.fileid,
|
||||||
|
drive_id=fileitem.drive_id, name=path.name)
|
||||||
# 计算sha1
|
if item:
|
||||||
sha1 = __sha1(path)
|
return self.__get_fileitem(item)
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.create_folder_file_url, json={
|
|
||||||
"drive_id": fileitem.drive_id,
|
|
||||||
"parent_file_id": fileitem.parent_fileid,
|
|
||||||
"name": path.name,
|
|
||||||
"check_name_mode": "refuse",
|
|
||||||
"create_scene": "file_upload",
|
|
||||||
"type": "file",
|
|
||||||
"content_hash": sha1,
|
|
||||||
"content_hash_name": "sha1",
|
|
||||||
"part_info_list": [
|
|
||||||
{
|
|
||||||
"part_number": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"size": path.stat().st_size
|
|
||||||
})
|
|
||||||
if not res:
|
|
||||||
self.__handle_error(res, "创建文件")
|
|
||||||
return None
|
|
||||||
# 获取上传请求结果
|
|
||||||
result = res.json()
|
|
||||||
if result.get("exist") or result.get("rapid_upload"):
|
|
||||||
# 已存在
|
|
||||||
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"),
|
|
||||||
type="file",
|
|
||||||
name=result.get("file_name"),
|
|
||||||
path=f"{fileitem.path}{result.get('file_name')}"
|
|
||||||
)
|
|
||||||
# 上传文件
|
|
||||||
file_id = result.get("file_id")
|
|
||||||
upload_id = result.get("upload_id")
|
|
||||||
part_info_list = result.get("part_info_list")
|
|
||||||
if part_info_list:
|
|
||||||
# 上传地址
|
|
||||||
upload_url = part_info_list[0].get("upload_url")
|
|
||||||
# 上传文件
|
|
||||||
res = RequestUtils(headers={
|
|
||||||
"Content-Type": "",
|
|
||||||
"User-Agent": settings.USER_AGENT,
|
|
||||||
"Referer": "https://www.alipan.com/",
|
|
||||||
"Accept": "*/*",
|
|
||||||
}).put_res(upload_url, data=path.read_bytes())
|
|
||||||
if not res:
|
|
||||||
self.__handle_error(res, "上传文件")
|
|
||||||
return None
|
|
||||||
# 标记文件上传完毕
|
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.upload_file_complete_url, json={
|
|
||||||
"drive_id": fileitem.drive_id,
|
|
||||||
"file_id": file_id,
|
|
||||||
"upload_id": upload_id
|
|
||||||
})
|
|
||||||
if not res:
|
|
||||||
self.__handle_error(res, "标记上传状态")
|
|
||||||
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"),
|
|
||||||
type="file",
|
|
||||||
name=result.get("name"),
|
|
||||||
path=f"{fileitem.path}{result.get('name')}",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warn("阿里云盘上传文件失败:无法获取上传地址!")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def move(self, fileitem: schemas.FileItem, target: schemas.FileItem) -> bool:
|
def move(self, fileitem: schemas.FileItem, target: schemas.FileItem) -> bool:
|
||||||
"""
|
"""
|
||||||
移动文件
|
移动文件
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
if not self.aligo:
|
||||||
if not params:
|
|
||||||
return False
|
return False
|
||||||
headers = self.__get_headers(params)
|
if self.aligo.move_file(file_id=fileitem.fileid, drive_id=fileitem.drive_id,
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.move_file_url, json={
|
to_parent_file_id=target.fileid, to_drive_id=target.drive_id):
|
||||||
"drive_id": fileitem.drive_id,
|
|
||||||
"file_id": fileitem.fileid,
|
|
||||||
"to_parent_file_id": target.fileid,
|
|
||||||
"check_name_mode": "refuse"
|
|
||||||
})
|
|
||||||
if res:
|
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
self.__handle_error(res, "移动文件")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def copy(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def copy(self, fileitem: schemas.FileItem, target: schemas.FileItem) -> bool:
|
||||||
"""
|
|
||||||
复制文件
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def link(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
"""
|
"""
|
||||||
硬链接文件
|
硬链接文件
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def softlink(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def softlink(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
"""
|
"""
|
||||||
软链接文件
|
软链接文件
|
||||||
"""
|
"""
|
||||||
@@ -816,17 +377,14 @@ class AliPan(StorageBase):
|
|||||||
"""
|
"""
|
||||||
存储使用情况
|
存储使用情况
|
||||||
"""
|
"""
|
||||||
params = self.__access_params
|
if not self.aligo:
|
||||||
if not params:
|
|
||||||
return None
|
return None
|
||||||
headers = self.__get_headers(params)
|
user_capacity = self.aligo.get_user_capacity_info()
|
||||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.storage_info_url, json={})
|
if user_capacity:
|
||||||
if res:
|
drive_capacity = user_capacity.drive_capacity_details
|
||||||
result = res.json()
|
if drive_capacity:
|
||||||
return schemas.StorageUsage(
|
return schemas.StorageUsage(
|
||||||
total=result.get("drive_total_size"),
|
total=drive_capacity.drive_total_size,
|
||||||
available=result.get("drive_total_size") - result.get("drive_used_size")
|
available=drive_capacity.drive_total_size - drive_capacity.drive_used_size
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
self.__handle_error(res, "查询存储详情")
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -135,11 +135,11 @@ class LocalStorage(StorageBase):
|
|||||||
return self.__get_fileitem(path)
|
return self.__get_fileitem(path)
|
||||||
return self.__get_diritem(path)
|
return self.__get_diritem(path)
|
||||||
|
|
||||||
def detail(self, fileitm: schemas.FileItem) -> Optional[schemas.FileItem]:
|
def detail(self, fileitem: schemas.FileItem) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
获取文件详情
|
获取文件详情
|
||||||
"""
|
"""
|
||||||
path_obj = Path(fileitm.path)
|
path_obj = Path(fileitem.path)
|
||||||
if not path_obj.exists():
|
if not path_obj.exists():
|
||||||
return None
|
return None
|
||||||
return self.__get_fileitem(path_obj)
|
return self.__get_fileitem(path_obj)
|
||||||
@@ -177,7 +177,7 @@ class LocalStorage(StorageBase):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def download(self, fileitem: schemas.FileItem) -> Optional[Path]:
|
def download(self, fileitem: schemas.FileItem, path: Path = None) -> Optional[Path]:
|
||||||
"""
|
"""
|
||||||
下载文件
|
下载文件
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class Rclone(StorageBase):
|
|||||||
logger.error(f"rclone浏览文件失败:{err}")
|
logger.error(f"rclone浏览文件失败:{err}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def create_folder(self, fileitm: schemas.FileItem, name: str) -> Optional[schemas.FileItem]:
|
def create_folder(self, fileitem: schemas.FileItem, name: str) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
创建目录
|
创建目录
|
||||||
"""
|
"""
|
||||||
@@ -126,13 +126,13 @@ class Rclone(StorageBase):
|
|||||||
retcode = subprocess.run(
|
retcode = subprocess.run(
|
||||||
[
|
[
|
||||||
'rclone', 'mkdir',
|
'rclone', 'mkdir',
|
||||||
f'MP:{fileitm.path}/{name}'
|
f'MP:{fileitem.path}/{name}'
|
||||||
],
|
],
|
||||||
startupinfo=self.__get_hidden_shell()
|
startupinfo=self.__get_hidden_shell()
|
||||||
).returncode
|
).returncode
|
||||||
if retcode == 0:
|
if retcode == 0:
|
||||||
ret_fileitem = copy.deepcopy(fileitm)
|
ret_fileitem = copy.deepcopy(fileitem)
|
||||||
ret_fileitem.path = f"{fileitm.path}/{name}/"
|
ret_fileitem.path = f"{fileitem.path}/{name}/"
|
||||||
ret_fileitem.name = name
|
ret_fileitem.name = name
|
||||||
return ret_fileitem
|
return ret_fileitem
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@@ -191,7 +191,7 @@ class Rclone(StorageBase):
|
|||||||
logger.error(f"rclone获取文件失败:{err}")
|
logger.error(f"rclone获取文件失败:{err}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def delete(self, fileitm: schemas.FileItem) -> bool:
|
def delete(self, fileitem: schemas.FileItem) -> bool:
|
||||||
"""
|
"""
|
||||||
删除文件
|
删除文件
|
||||||
"""
|
"""
|
||||||
@@ -199,7 +199,7 @@ class Rclone(StorageBase):
|
|||||||
retcode = subprocess.run(
|
retcode = subprocess.run(
|
||||||
[
|
[
|
||||||
'rclone', 'deletefile',
|
'rclone', 'deletefile',
|
||||||
f'MP:{fileitm.path}'
|
f'MP:{fileitem.path}'
|
||||||
],
|
],
|
||||||
startupinfo=self.__get_hidden_shell()
|
startupinfo=self.__get_hidden_shell()
|
||||||
).returncode
|
).returncode
|
||||||
@@ -209,7 +209,7 @@ class Rclone(StorageBase):
|
|||||||
logger.error(f"rclone删除文件失败:{err}")
|
logger.error(f"rclone删除文件失败:{err}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def rename(self, fileitm: schemas.FileItem, name: str) -> bool:
|
def rename(self, fileitem: schemas.FileItem, name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
重命名文件
|
重命名文件
|
||||||
"""
|
"""
|
||||||
@@ -217,8 +217,8 @@ class Rclone(StorageBase):
|
|||||||
retcode = subprocess.run(
|
retcode = subprocess.run(
|
||||||
[
|
[
|
||||||
'rclone', 'moveto',
|
'rclone', 'moveto',
|
||||||
f'MP:{fileitm.path}',
|
f'MP:{fileitem.path}',
|
||||||
f'MP:{Path(fileitm.path).parent}/{name}'
|
f'MP:{Path(fileitem.path).parent}/{name}'
|
||||||
],
|
],
|
||||||
startupinfo=self.__get_hidden_shell()
|
startupinfo=self.__get_hidden_shell()
|
||||||
).returncode
|
).returncode
|
||||||
@@ -228,11 +228,11 @@ class Rclone(StorageBase):
|
|||||||
logger.error(f"rclone重命名文件失败:{err}")
|
logger.error(f"rclone重命名文件失败:{err}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def download(self, fileitem: schemas.FileItem) -> Optional[Path]:
|
def download(self, fileitem: schemas.FileItem, path: Path = None) -> Optional[Path]:
|
||||||
"""
|
"""
|
||||||
下载文件
|
下载文件
|
||||||
"""
|
"""
|
||||||
path = settings.TEMP_PATH / fileitem.name
|
path = (path or settings.TEMP_PATH) / fileitem.name
|
||||||
try:
|
try:
|
||||||
retcode = subprocess.run(
|
retcode = subprocess.run(
|
||||||
[
|
[
|
||||||
@@ -248,7 +248,7 @@ class Rclone(StorageBase):
|
|||||||
logger.error(f"rclone复制文件失败:{err}")
|
logger.error(f"rclone复制文件失败:{err}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def upload(self, fileitm: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]:
|
def upload(self, fileitem: schemas.FileItem, path: Path) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
上传文件
|
上传文件
|
||||||
"""
|
"""
|
||||||
@@ -256,7 +256,7 @@ class Rclone(StorageBase):
|
|||||||
retcode = subprocess.run(
|
retcode = subprocess.run(
|
||||||
[
|
[
|
||||||
'rclone', 'copyto',
|
'rclone', 'copyto',
|
||||||
fileitm.path,
|
fileitem.path,
|
||||||
f'MP:{path}'
|
f'MP:{path}'
|
||||||
],
|
],
|
||||||
startupinfo=self.__get_hidden_shell()
|
startupinfo=self.__get_hidden_shell()
|
||||||
@@ -267,7 +267,7 @@ class Rclone(StorageBase):
|
|||||||
logger.error(f"rclone上传文件失败:{err}")
|
logger.error(f"rclone上传文件失败:{err}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def detail(self, fileitm: schemas.FileItem) -> Optional[schemas.FileItem]:
|
def detail(self, fileitem: schemas.FileItem) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
获取文件详情
|
获取文件详情
|
||||||
"""
|
"""
|
||||||
@@ -275,7 +275,7 @@ class Rclone(StorageBase):
|
|||||||
ret = subprocess.run(
|
ret = subprocess.run(
|
||||||
[
|
[
|
||||||
'rclone', 'lsjson',
|
'rclone', 'lsjson',
|
||||||
f'MP:{fileitm.path}'
|
f'MP:{fileitem.path}'
|
||||||
],
|
],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
startupinfo=self.__get_hidden_shell()
|
startupinfo=self.__get_hidden_shell()
|
||||||
@@ -287,7 +287,7 @@ class Rclone(StorageBase):
|
|||||||
logger.error(f"rclone获取文件详情失败:{err}")
|
logger.error(f"rclone获取文件详情失败:{err}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def move(self, fileitm: schemas.FileItem, target: Path) -> bool:
|
def move(self, fileitem: schemas.FileItem, target: Path) -> bool:
|
||||||
"""
|
"""
|
||||||
移动文件,target_file格式:rclone:path
|
移动文件,target_file格式:rclone:path
|
||||||
"""
|
"""
|
||||||
@@ -295,7 +295,7 @@ class Rclone(StorageBase):
|
|||||||
retcode = subprocess.run(
|
retcode = subprocess.run(
|
||||||
[
|
[
|
||||||
'rclone', 'moveto',
|
'rclone', 'moveto',
|
||||||
f'MP:{fileitm.path}',
|
f'MP:{fileitem.path}',
|
||||||
f'MP:{target}'
|
f'MP:{target}'
|
||||||
],
|
],
|
||||||
startupinfo=self.__get_hidden_shell()
|
startupinfo=self.__get_hidden_shell()
|
||||||
@@ -306,13 +306,13 @@ class Rclone(StorageBase):
|
|||||||
logger.error(f"rclone移动文件失败:{err}")
|
logger.error(f"rclone移动文件失败:{err}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def copy(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def copy(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def link(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def softlink(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def softlink(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def usage(self) -> Optional[schemas.StorageUsage]:
|
def usage(self) -> Optional[schemas.StorageUsage]:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import base64
|
import base64
|
||||||
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple, List
|
from typing import Optional, Tuple, List
|
||||||
|
|
||||||
@@ -32,6 +33,19 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
|||||||
cloud: Optional[Cloud] = None
|
cloud: Optional[Cloud] = None
|
||||||
_session: QrcodeSession = None
|
_session: QrcodeSession = None
|
||||||
|
|
||||||
|
# 是否有aria2c
|
||||||
|
_has_aria2c: bool = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
try:
|
||||||
|
subprocess.run(['aria2c', '-h'], capture_output=True)
|
||||||
|
self._has_aria2c = True
|
||||||
|
logger.debug('发现 aria2c, 将使用 aria2c 下载文件')
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.debug('未发现 aria2c')
|
||||||
|
self._has_aria2c = False
|
||||||
|
|
||||||
def __init_cloud(self) -> bool:
|
def __init_cloud(self) -> bool:
|
||||||
"""
|
"""
|
||||||
初始化Cloud
|
初始化Cloud
|
||||||
@@ -260,7 +274,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
|||||||
fileitem = item
|
fileitem = item
|
||||||
return fileitem
|
return fileitem
|
||||||
|
|
||||||
def detail(self, fileitm: schemas.FileItem) -> Optional[schemas.FileItem]:
|
def detail(self, fileitem: schemas.FileItem) -> Optional[schemas.FileItem]:
|
||||||
"""
|
"""
|
||||||
获取文件详情
|
获取文件详情
|
||||||
"""
|
"""
|
||||||
@@ -292,7 +306,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
|||||||
logger.error(f"115重命名文件失败:{str(e)}")
|
logger.error(f"115重命名文件失败:{str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def download(self, fileitem: schemas.FileItem) -> Optional[Path]:
|
def download(self, fileitem: schemas.FileItem, path: Path = None) -> Optional[Path]:
|
||||||
"""
|
"""
|
||||||
获取下载链接
|
获取下载链接
|
||||||
"""
|
"""
|
||||||
@@ -301,7 +315,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
|||||||
try:
|
try:
|
||||||
ticket = self.cloud.storage().request_download(fileitem.pickcode)
|
ticket = self.cloud.storage().request_download(fileitem.pickcode)
|
||||||
if ticket:
|
if ticket:
|
||||||
path = settings.TEMP_PATH / fileitem.name
|
path = (path or settings.TEMP_PATH) / fileitem.name
|
||||||
res = RequestUtils(headers=ticket.headers).get_res(ticket.url)
|
res = RequestUtils(headers=ticket.headers).get_res(ticket.url)
|
||||||
if res:
|
if res:
|
||||||
with open(path, "wb") as f:
|
with open(path, "wb") as f:
|
||||||
@@ -374,13 +388,13 @@ class U115Pan(StorageBase, metaclass=Singleton):
|
|||||||
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, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def link(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def softlink(self, fileitm: schemas.FileItem, target_file: Path) -> bool:
|
def softlink(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def usage(self) -> Optional[schemas.StorageUsage]:
|
def usage(self) -> Optional[schemas.StorageUsage]:
|
||||||
|
|||||||
Reference in New Issue
Block a user