fix lot of thing
This commit is contained in:
@@ -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", "上传失败")
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import os
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
from PyQt6.QtCore import QThread, pyqtSignal
|
||||
from PyQt6.QtGui import QImage, QPixmap
|
||||
|
||||
from app.core import miaoStarsBasicApi
|
||||
from app.core.api import basicApi, miaoStarsBasicApi
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TextLoaderThread(QThread):
|
||||
@@ -15,54 +18,88 @@ class TextLoaderThread(QThread):
|
||||
errorOccurred = pyqtSignal(str)
|
||||
progressUpdated = pyqtSignal(int) # 进度更新信号
|
||||
|
||||
def __init__(self, url):
|
||||
def __init__(self, url, fileId=None):
|
||||
super().__init__()
|
||||
self.url = url
|
||||
self.url = url # 原始URL,可能作为fallback
|
||||
self.fileId = fileId # 文件ID,优先使用
|
||||
|
||||
def run(self):
|
||||
"""线程执行函数"""
|
||||
try:
|
||||
# 1. 设置网络请求参数 - 优化连接参数
|
||||
session = requests.Session()
|
||||
adapter = requests.adapters.HTTPAdapter(
|
||||
pool_connections=20,
|
||||
pool_maxsize=20,
|
||||
max_retries=5, # 增加重试次数
|
||||
pool_block=False,
|
||||
)
|
||||
session.mount("http://", adapter)
|
||||
session.mount("https://", adapter)
|
||||
binary_content = None
|
||||
content_chunks = [] # 预先初始化content_chunks,避免在任何执行路径下出现未定义错误
|
||||
|
||||
# 如果提供了fileId(实际上是URI),优先使用getFileContent直接获取内容
|
||||
if self.fileId:
|
||||
self.progressUpdated.emit(0) # 发送初始进度
|
||||
logger.info(f"使用文件URI {self.fileId} 直接获取文件内容")
|
||||
response = basicApi.getFileContent(self.fileId)
|
||||
|
||||
if response.get('code') == 0 and 'data' in response:
|
||||
# 检查response['data']的类型,确保只有二进制数据才直接使用
|
||||
if isinstance(response['data'], bytes):
|
||||
binary_content = response['data']
|
||||
logger.info(f"成功获取文件内容,大小: {len(binary_content)} 字节")
|
||||
self.progressUpdated.emit(100) # 直接发送完成进度
|
||||
else:
|
||||
# 如果不是二进制数据(可能是字典等其他类型),记录警告并使用URL作为fallback
|
||||
logger.warning(f"获取到的文件内容不是二进制数据,类型: {type(response['data']).__name__},将使用URL作为fallback")
|
||||
binary_content = None
|
||||
else:
|
||||
error_msg = response.get('msg', '获取文件内容失败')
|
||||
logger.warning(f"获取文件内容失败: {error_msg},将使用原始URL作为fallback")
|
||||
|
||||
# 如果直接获取内容失败,尝试使用URL
|
||||
if binary_content is None:
|
||||
if not self.url:
|
||||
self.errorOccurred.emit("没有可用的URL来加载文本内容")
|
||||
return
|
||||
|
||||
# 1. 设置网络请求参数 - 优化连接参数
|
||||
session = requests.Session()
|
||||
adapter = requests.adapters.HTTPAdapter(
|
||||
pool_connections=20,
|
||||
pool_maxsize=20,
|
||||
max_retries=5, # 增加重试次数
|
||||
pool_block=False,
|
||||
)
|
||||
session.mount("http://", adapter)
|
||||
session.mount("https://", adapter)
|
||||
|
||||
# 2. 增加超时时间并添加重试机制
|
||||
response = miaoStarsBasicApi.returnSession().get(
|
||||
self.url,
|
||||
stream=True,
|
||||
timeout=(15, 30), # 增加超时时间:连接15秒,读取30秒
|
||||
)
|
||||
# 2. 增加超时时间并添加重试机制
|
||||
response = basicApi.returnSession().get(
|
||||
self.url,
|
||||
stream=True,
|
||||
timeout=(15, 30), # 增加超时时间:连接15秒,读取30秒
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
response.raise_for_status()
|
||||
|
||||
# 3. 获取文件大小(如果服务器支持)
|
||||
total_size = int(response.headers.get("content-length", 0))
|
||||
downloaded_size = 0
|
||||
# 3. 获取文件大小(如果服务器支持)
|
||||
total_size = int(response.headers.get("content-length", 0))
|
||||
downloaded_size = 0
|
||||
|
||||
# 4. 分块读取并处理 - 使用二进制读取提高速度
|
||||
content_chunks = []
|
||||
for chunk in response.iter_content(chunk_size=16384): # 增大块大小
|
||||
if chunk:
|
||||
content_chunks.append(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
# 4. 分块读取并处理 - 使用二进制读取提高速度
|
||||
content_chunks = []
|
||||
for chunk in response.iter_content(chunk_size=16384): # 增大块大小
|
||||
if chunk:
|
||||
content_chunks.append(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
|
||||
# 更新进度(如果知道总大小)
|
||||
if total_size > 0:
|
||||
progress = int((downloaded_size / total_size) * 100)
|
||||
self.progressUpdated.emit(progress)
|
||||
# 更新进度(如果知道总大小)
|
||||
if total_size > 0:
|
||||
progress = int((downloaded_size / total_size) * 100)
|
||||
self.progressUpdated.emit(progress)
|
||||
|
||||
# 5. 合并内容并解码
|
||||
binary_content = b"".join(content_chunks)
|
||||
# 5. 合并内容 - 使用正确的二进制合并语法
|
||||
binary_content = b"" .join(content_chunks)
|
||||
|
||||
if not binary_content:
|
||||
self.errorOccurred.emit("下载内容为空")
|
||||
# 确保binary_content是bytes类型且不为空
|
||||
if not binary_content or not isinstance(binary_content, bytes):
|
||||
if not binary_content:
|
||||
self.errorOccurred.emit("下载内容为空")
|
||||
else:
|
||||
self.errorOccurred.emit(f"下载内容类型错误,应为bytes,实际为: {type(binary_content).__name__}")
|
||||
return
|
||||
|
||||
# 6. 智能编码检测和解码
|
||||
@@ -113,10 +150,11 @@ class ImageLoaderThread(QThread):
|
||||
progressUpdated = pyqtSignal(int) # 进度更新信号
|
||||
|
||||
def __init__(
|
||||
self, url, cache_dir="image_cache", max_cache_size=50 * 1024 * 1024
|
||||
self, url, cache_dir="image_cache", max_cache_size=50 * 1024 * 1024, fileId=None
|
||||
): # 50MB缓存
|
||||
super().__init__()
|
||||
self.url = url
|
||||
self.url = url # 原始URL,可能作为fallback
|
||||
self.fileId = fileId # 文件ID,优先使用
|
||||
self.cache_dir = cache_dir
|
||||
self.max_cache_size = max_cache_size
|
||||
self._setup_cache()
|
||||
@@ -184,48 +222,108 @@ class ImageLoaderThread(QThread):
|
||||
def run(self):
|
||||
"""线程执行函数"""
|
||||
try:
|
||||
# 1. 首先检查缓存
|
||||
cached_pixmap = self._get_cached_image()
|
||||
if cached_pixmap:
|
||||
self.imageLoaded.emit(cached_pixmap)
|
||||
return
|
||||
image_data = None
|
||||
|
||||
# 如果提供了fileId(实际上是URI),优先使用getFileContent直接获取内容
|
||||
if self.fileId:
|
||||
logger.info(f"使用文件URI {self.fileId} 直接获取图片内容")
|
||||
response = miaoStarsBasicApi.getFileContent(self.fileId)
|
||||
|
||||
if response.get('code') == 0 and 'data' in response:
|
||||
# 检查response['data']的类型,确保只有二进制数据才直接使用
|
||||
if isinstance(response['data'], bytes):
|
||||
image_data = response['data']
|
||||
logger.info(f"成功获取图片内容,大小: {len(image_data)} 字节")
|
||||
self.progressUpdated.emit(100) # 直接发送完成进度
|
||||
else:
|
||||
# 如果不是二进制数据(可能是字典等其他类型),记录警告并使用URL作为fallback
|
||||
logger.warning(f"获取到的图片内容不是二进制数据,类型: {type(response['data']).__name__},将使用URL作为fallback")
|
||||
else:
|
||||
error_msg = response.get('msg', '获取图片内容失败')
|
||||
logger.warning(f"获取图片内容失败: {error_msg},将使用原始URL作为fallback")
|
||||
|
||||
# 如果直接获取内容失败,尝试使用URL
|
||||
if image_data is None:
|
||||
if not self.url:
|
||||
self.errorOccurred.emit("没有可用的URL来加载图片内容")
|
||||
return
|
||||
|
||||
# 确保URL是字符串类型
|
||||
if isinstance(self.url, dict):
|
||||
# 处理字典类型的URL,通常包含urls数组
|
||||
if 'urls' in self.url and isinstance(self.url['urls'], list) and len(self.url['urls']) > 0:
|
||||
# 获取第一个URL信息
|
||||
url_info = self.url['urls'][0]
|
||||
if isinstance(url_info, dict) and 'url' in url_info:
|
||||
download_url = url_info['url']
|
||||
logger.info(f"从字典中提取URL: {download_url}")
|
||||
self.url = download_url
|
||||
else:
|
||||
logger.error("URL字典格式不正确,缺少有效的URL信息")
|
||||
self.errorOccurred.emit("URL格式错误: 缺少有效的URL信息")
|
||||
return
|
||||
else:
|
||||
logger.error(f"URL字典格式不正确,缺少urls数组或数组为空: {self.url}")
|
||||
self.errorOccurred.emit("URL格式错误: 缺少有效的URL信息")
|
||||
return
|
||||
elif not isinstance(self.url, str):
|
||||
logger.error(f"URL类型错误,应为字符串或字典,实际为: {type(self.url).__name__}")
|
||||
self.errorOccurred.emit(f"URL类型错误: 应为字符串")
|
||||
return
|
||||
|
||||
# 1. 首先检查缓存
|
||||
cached_pixmap = self._get_cached_image()
|
||||
if cached_pixmap:
|
||||
self.imageLoaded.emit(cached_pixmap)
|
||||
return
|
||||
|
||||
# 2. 设置更短的超时时间
|
||||
session = requests.Session()
|
||||
adapter = requests.adapters.HTTPAdapter(
|
||||
pool_connections=10, pool_maxsize=10, max_retries=5 # 重试2次
|
||||
)
|
||||
session.mount("http://", adapter)
|
||||
session.mount("https://", adapter)
|
||||
# 2. 设置更短的超时时间
|
||||
session = requests.Session()
|
||||
adapter = requests.adapters.HTTPAdapter(
|
||||
pool_connections=10, pool_maxsize=10, max_retries=5 # 重试2次
|
||||
)
|
||||
session.mount("http://", adapter)
|
||||
session.mount("https://", adapter)
|
||||
|
||||
# 3. 流式下载,支持进度显示
|
||||
response = miaoStarsBasicApi.returnSession().get(
|
||||
self.url, stream=True, timeout=(20, 30) # 连接超时5秒,读取超时10秒
|
||||
)
|
||||
response.raise_for_status()
|
||||
# 3. 流式下载,支持进度显示
|
||||
response = miaoStarsBasicApi.returnSession().get(
|
||||
self.url, stream=True, timeout=(20, 30) # 连接超时5秒,读取超时10秒
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# 获取文件大小(如果服务器支持)
|
||||
total_size = int(response.headers.get("content-length", 0))
|
||||
downloaded_size = 0
|
||||
# 获取文件大小(如果服务器支持)
|
||||
total_size = int(response.headers.get("content-length", 0))
|
||||
downloaded_size = 0
|
||||
|
||||
# 4. 分块读取并处理
|
||||
image_data = b""
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
image_data += chunk
|
||||
downloaded_size += len(chunk)
|
||||
# 4. 分块读取并处理
|
||||
image_data = b""
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
image_data += chunk
|
||||
downloaded_size += len(chunk)
|
||||
|
||||
# 更新进度(如果知道总大小)
|
||||
if total_size > 0:
|
||||
progress = int((downloaded_size / total_size) * 100)
|
||||
self.progressUpdated.emit(progress)
|
||||
# 更新进度(如果知道总大小)
|
||||
if total_size > 0:
|
||||
# 确保进度不超过100%
|
||||
progress = min(int((downloaded_size / total_size) * 100), 100)
|
||||
self.progressUpdated.emit(progress)
|
||||
|
||||
# 5. 从数据创建QImage(比QPixmap更快)
|
||||
image = QImage()
|
||||
image.loadFromData(image_data)
|
||||
|
||||
if image.isNull():
|
||||
raise Exception("无法加载图片数据")
|
||||
|
||||
# 检查数据是否为空或类型是否正确
|
||||
if not image_data:
|
||||
raise Exception("图片数据为空")
|
||||
|
||||
if not isinstance(image_data, bytes):
|
||||
logger.error(f"图片数据类型错误,应为bytes,实际为: {type(image_data).__name__}")
|
||||
raise Exception(f"图片数据类型错误: 应为bytes,实际为{type(image_data).__name__}")
|
||||
|
||||
# 尝试加载图片数据
|
||||
load_success = image.loadFromData(image_data)
|
||||
|
||||
if not load_success or image.isNull():
|
||||
raise Exception("无法加载图片数据或图片格式不支持")
|
||||
|
||||
# 6. 转换为QPixmap
|
||||
pixmap = QPixmap.fromImage(image)
|
||||
|
||||
Reference in New Issue
Block a user