fix lot of thing

This commit is contained in:
2025-11-01 20:14:35 +08:00
parent 39dfb62cbf
commit f006729311
16 changed files with 23303 additions and 3996 deletions

View File

@@ -140,22 +140,22 @@ class DeleteFileThread(QThread):
successDelete = pyqtSignal()
errorDelete = pyqtSignal(str)
def __init__(self, fileId: str, fileType: str):
def __init__(self, fileUri: str, fileType: str):
super().__init__()
logger.debug(f"初始化删除文件线程 - ID: {fileId}, 类型: {fileType}")
self.fileId = fileId
logger.debug(f"初始化删除文件线程 - URI: {fileUri}, 类型: {fileType}")
self.fileUri = fileUri
self.fileType = fileType
def run(self):
logger.info(f"开始删除文件 - ID: {self.fileId}, 类型: {self.fileType}")
logger.info(f"开始删除文件 - URI: {self.fileUri}, 类型: {self.fileType}")
try:
response = miaoStarsBasicApi.deleteFile(self.fileId, self.fileType)
response = miaoStarsBasicApi.deleteFile(self.fileUri, self.fileType)
if response["code"] == 0:
logger.info(f"文件删除成功 - ID: {self.fileId}")
logger.info(f"文件删除成功 - URI: {self.fileUri}")
self.successDelete.emit()
else:
logger.error(
f"文件删除失败 - ID: {self.fileId}, 错误: {response.get('msg', '未知错误')}"
f"文件删除失败 - URI: {self.fileUri}, 错误: {response.get('msg', '未知错误')}"
)
self.errorDelete.emit(f"删除失败: {response.get('msg', '未知错误')}")
@@ -237,7 +237,11 @@ class UploadThread(QThread):
self.applicationUrl = "/file/upload"
self.current_path = policyConfig.returnCurrentPath()
self.policy = policyConfig.returnPolicy().get("id")
# 获取存储策略如果为None则使用默认值PqI8
self.policy = policyConfig.returnPolicy().get("id", "PqI8")
if self.policy is None:
self.policy = "PqI8"
logger.info("存储策略ID为None已设置默认值PqI8")
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
@@ -262,54 +266,99 @@ class UploadThread(QThread):
return mime_type if mime_type else "application/octet-stream"
def _prepareUploadData(self) -> Dict[str, Any]:
"""准备上传数据"""
"""准备上传数据符合Cloudreve V4 API规范"""
try:
modification_time = os.path.getmtime(self.file_path)
size = os.path.getsize(self.file_path)
# 构建符合API要求的URI格式: cloudreve://my{path}/{name}
# 处理路径,确保不会有重复斜杠和前缀
# 清理路径确保不包含cloudreve://my前缀
clean_current_path = self.current_path.replace("cloudreve://my", "")
# 处理路径,确保不会有重复斜杠
path_part = clean_current_path if clean_current_path.startswith('/') else f'/{clean_current_path}'
uri = f"cloudreve://my{path_part}/{self.file_name}"
# 确保路径格式正确,移除重复的前缀
uri = uri.replace("cloudreve://my/cloudreve://my", "cloudreve://my")
# 更健壮地处理重复文件名的情况
# 分割路径并去重
path_parts = uri.split('/')
if len(path_parts) > 1:
# 检查最后一个部分是否是文件名
if path_parts[-1] == self.file_name:
# 检查倒数第二个部分是否也是文件名
if len(path_parts) > 2 and path_parts[-2] == self.file_name:
# 移除重复的文件名部分
path_parts.pop(-2)
uri = '/'.join(path_parts)
logger.info(f"构建上传URI: {uri}")
return {
"path": self.current_path,
"uri": uri,
"size": size,
"name": self.file_name,
"policy_id": self.policy,
"last_modified": int(modification_time * 1000), # 转换为毫秒
"mime_type": self.getMimeType(self.file_path),
}
except (OSError, ValueError) as e:
logger.error(f"准备请求失败: {e}")
raise
def _uploadWithProgress(self, upload_url: str, credential: str, total_size: int):
"""带进度显示的上传方法"""
try:
logger.info("_uploadWithProgress方法开始执行")
logger.info(f"参数检查 - URL: {upload_url}, Credential是否存在: {credential is not None}")
# 处理credential可能为None的情况
if credential is None:
logger.warning("Credential为None尝试不使用Authorization头进行上传")
# 对于本地存储策略可能不需要credential
auth_header = {}
else:
auth_header = {"Authorization": credential}
# 打开文件
logger.info(f"开始打开文件: {self.file_path}")
self._file_obj = open(self.file_path, "rb")
logger.info("文件打开成功")
# 准备上传头信息
upload_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36",
"Accept": "*/*",
"Authorization": credential,
"Accept-Language": "zh-CN,zh;q=0.9",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Content-Type": self.getMimeType(self.file_path), # 使用正确的MIME类型
"Content-Length": str(total_size), # 添加必需的Content-Length头
# 删除硬编码的Origin和Referer使用miaoStarsBasicApi中已配置的请求头
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "cross-site",
**auth_header, # 只在credential不为None时添加Authorization头
}
logger.info(f"上传头信息准备完成: {upload_headers}")
# 发送预检请求
options_result = miaoStarsBasicApi.returnSession().options(
upload_url, headers=upload_headers
)
options_result.raise_for_status()
logger.info("OPTIONS预检请求完成")
# 尝试发送预检请求,但不阻塞主流程
try:
logger.info("尝试发送OPTIONS预检请求")
options_result = miaoStarsBasicApi.returnSession().options(
upload_url, headers=upload_headers, timeout=10
)
options_result.raise_for_status()
logger.info("OPTIONS预检请求完成")
except Exception as e:
logger.warning(f"OPTIONS预检请求失败但继续上传: {str(e)}")
# 创建自定义请求体以支持进度监控和取消
class ProgressFileObject:
def __init__(
self, file_obj, total_size, progress_callback, cancel_check
):
logger.info("创建ProgressFileObject对象")
self.file_obj = file_obj
self.total_size = total_size
self.progress_callback = progress_callback
@@ -319,7 +368,8 @@ class UploadThread(QThread):
def read(self, size=-1):
# 检查是否取消
if self.cancel_check():
return
logger.info("检测到取消上传请求")
raise InterruptedError("Upload cancelled by user")
data = self.file_obj.read(size)
if data:
@@ -330,12 +380,14 @@ class UploadThread(QThread):
else 0
)
self.progress_callback(progress, self.uploaded, self.total_size)
logger.debug(f"上传进度: {progress:.2f}% ({self.uploaded}/{self.total_size} 字节)")
return data
def __len__(self):
return self.total_size
# 创建带进度监控的文件对象
logger.info("创建进度监控文件对象")
progress_file = ProgressFileObject(
self._file_obj,
total_size,
@@ -347,17 +399,60 @@ class UploadThread(QThread):
# 执行实际上传
logger.info(f"开始上传文件,总大小: {total_size} 字节")
upload_result = miaoStarsBasicApi.returnSession().post(
upload_url,
data=progress_file,
headers=upload_headers,
timeout=60,
)
logger.info(f"上传完成,响应状态: {upload_result.status_code}")
upload_result.raise_for_status()
return upload_result
logger.info(f"上传URL: {upload_url}")
logger.info(f"上传头信息(已脱敏): {{\n 'User-Agent': '...', \n 'Content-Type': '{upload_headers.get("Content-Type")}',\n 'Content-Length': '{upload_headers.get("Content-Length")}',\n 'Authorization': '***' if 'Authorization' in upload_headers else 'None'\n }}")
# 修复优化上传逻辑先尝试PUT方法Cloudreve V4本地存储通常使用PUT
try:
# 首先尝试使用PUT方法
logger.info("尝试使用PUT方法上传")
upload_result = miaoStarsBasicApi.returnSession().put(
upload_url,
data=progress_file,
headers=upload_headers,
timeout=120,
stream=True
)
logger.info(f"PUT方法上传完成响应状态: {upload_result.status_code}")
upload_result.raise_for_status()
return upload_result
except InterruptedError:
# 用户取消上传
raise
except Exception as e:
logger.error(f"PUT方法上传失败: {str(e)}")
# 尝试使用POST方法
logger.info("尝试使用POST方法重新上传...")
# 重新打开文件
if self._file_obj:
self._file_obj.close()
self._file_obj = open(self.file_path, "rb")
# 重新创建进度文件对象
progress_file = ProgressFileObject(
self._file_obj,
total_size,
lambda progress, uploaded, total: self.uploadProgress.emit(
progress, uploaded, total
),
lambda: self._is_cancelled,
)
# 使用POST方法上传
upload_result = miaoStarsBasicApi.returnSession().post(
upload_url,
data=progress_file,
headers=upload_headers,
timeout=120,
stream=True,
)
logger.info(f"POST方法上传完成响应状态: {upload_result.status_code}")
upload_result.raise_for_status()
return upload_result
except Exception as e:
logger.error(f"上传过程中发生错误: {e}")
@@ -379,6 +474,24 @@ class UploadThread(QThread):
# 准备上传数据
upload_data = self._prepareUploadData()
# 检查policy_id是否为None如果是则尝试获取默认策略
if upload_data.get('policy_id') is None:
logger.warning("存储策略ID为None尝试获取默认存储策略")
try:
# 获取用户的存储策略列表
policies_response = miaoStarsBasicApi.getPolicy()
if policies_response.get('code') == 0 and policies_response.get('data'):
# 使用第一个可用的策略
default_policy = policies_response['data'][0]
upload_data['policy_id'] = default_policy.get('id')
logger.info(f"已设置默认存储策略: {default_policy.get('name')} (ID: {upload_data['policy_id']})")
else:
logger.warning("无法获取存储策略列表使用硬编码默认策略PqI8")
upload_data['policy_id'] = 'PqI8'
except Exception as e:
logger.error(f"获取存储策略失败: {str(e)}使用硬编码默认策略PqI8")
upload_data['policy_id'] = 'PqI8'
# 检查是否取消
if self._is_cancelled:
@@ -387,41 +500,126 @@ class UploadThread(QThread):
# 执行上传请求获取上传URL
logger.info("请求上传URL")
# 从miaoStarsBasicApi获取完整URL(去掉/api/v4后缀
base_url = miaoStarsBasicApi.basicApi.rstrip('/api/v4') # 使用API类中定义的基础URL
full_url = f"{base_url}{self.applicationUrl}"
# 直接使用basicApi构建完整URL,保留/api/v4后缀
# Cloudreve V4 API要求上传接口路径为/api/v4/file/upload
full_url = f"{miaoStarsBasicApi.basicApi}{self.applicationUrl}"
logger.info(f"构建的上传请求URL: {full_url}")
logger.info(f"上传请求数据: {upload_data}")
response = miaoStarsBasicApi.returnSession().put(
full_url, json=upload_data, headers=self.headers, timeout=30
)
# 记录响应状态码和内容
logger.info(f"上传URL请求响应状态码: {response.status_code}")
response_text = response.text
logger.info(f"上传URL请求响应内容长度: {len(response_text)} 字符")
# 如果响应内容较长只记录前100个字符
if len(response_text) > 100:
logger.info(f"上传URL请求响应内容预览: {response_text[:100]}...")
else:
logger.info(f"上传URL请求响应内容: {response_text}")
response.raise_for_status()
result = response.json()
# 添加错误处理检查响应是否为有效的JSON
try:
result = response.json()
logger.info(f"JSON解析成功响应code: {result.get('code', '未找到code字段')}")
except ValueError as e:
logger.error(f"JSON解析失败: {e}")
logger.error(f"无法解析的响应内容: {response_text}")
# 抛出异常让上层处理
raise ValueError(f"无效的响应格式: {response_text}")
if result.get("code") == 0:
self.uploadApplicationApprovedSignal.emit()
upload_urls = result.get("data").get("uploadURLs")
credential = result.get("data").get("credential")
data = result.get("data", {})
upload_urls = data.get("upload_urls") # 使用正确的字段名符合API规范
credential = data.get("credential")
session_id = data.get("session_id")
storage_policy = data.get("storage_policy", {})
policy_type = storage_policy.get("type", "")
logger.info(f"获取到上传URL数量: {len(upload_urls) if upload_urls else 0}")
logger.debug(f"存储策略类型: {policy_type}, Session ID: {session_id}")
self.uploadUrl = upload_urls[0] + "?chunk=0"
logger.info("获取到上传URL")
# 优先检查是否有session_id因为这是必须的
if not session_id:
logger.error("未获取到session_id无法构建上传URL")
self.uploadFailed.emit("未获取到session_id无法构建上传URL")
return
# 根据存储策略类型和API响应构建正确的上传URL
if upload_urls and len(upload_urls) > 0:
# 对于远程存储策略使用返回的upload_urls并正确设置chunk参数
# 注意根据URL情况决定是否添加?或&
if "?" in upload_urls[0]:
self.uploadUrl = upload_urls[0] + "&chunk=0"
else:
self.uploadUrl = upload_urls[0] + "?chunk=0"
logger.info(f"使用远程存储上传URL: {self.uploadUrl}")
else:
# 对于本地存储策略或没有提供upload_urls的情况使用session_id构建上传URL
# 正确的API路径格式: /file/upload/{sessionId}/{index}
# 修复分块索引应该从0开始符合Cloudreve V4 API规范
self.uploadUrl = f"{miaoStarsBasicApi.basicApi}/file/upload/{session_id}/0"
logger.info(f"使用本地存储分块上传路径: {self.uploadUrl}")
logger.info(f"获取到上传URL: {self.uploadUrl}")
# 获取文件总大小
total_size = os.path.getsize(self.file_path)
# 执行带进度显示的上传
upload_result = self._uploadWithProgress(
self.uploadUrl, credential, total_size
)
logger.info("开始调用_uploadWithProgress方法执行上传")
try:
upload_result = self._uploadWithProgress(
self.uploadUrl, credential, total_size
)
logger.info("_uploadWithProgress方法调用完成")
# 检查是否取消
if self._is_cancelled:
self.uploadCancelled.emit()
# 检查是否取消
if self._is_cancelled:
self.uploadCancelled.emit()
return
# 处理上传结果
logger.info(f"上传响应状态码: {upload_result.status_code}")
# 限制日志长度,避免日志过大
response_text = upload_result.text
if len(response_text) > 100:
logger.info(f"上传响应内容预览: {response_text[:100]}...")
else:
logger.info(f"上传响应内容: {response_text}")
# 确保上传成功
upload_result.raise_for_status()
except Exception as e:
logger.error(f"上传过程中发生异常: {str(e)}")
logger.error(f"异常类型: {type(e).__name__}")
self.uploadFailed.emit(f"上传过程中发生错误: {str(e)}")
return
# 处理上传结果
logger.info(f"上传响应: {upload_result}")
self.uploadFinished.emit()
try:
# 解析上传响应
upload_response = upload_result.json()
logger.info(f"上传响应JSON解析成功: {upload_response}")
# 检查上传是否成功完成
if upload_response.get('code') == 0:
logger.info("文件上传成功完成")
self.uploadFinished.emit()
else:
error_msg = upload_response.get('msg', '上传失败')
logger.error(f"上传失败: {error_msg}")
self.uploadFailed.emit(error_msg)
except ValueError as e:
# 如果响应不是有效的JSON只要状态码成功也视为上传成功
logger.warning(f"上传响应不是有效的JSON: {e}")
logger.info("基于成功状态码,假设上传成功")
self.uploadFinished.emit()
else:
error_msg = result.get("msg", "上传失败")