247 lines
8.5 KiB
Python
247 lines
8.5 KiB
Python
|
|
import os
|
|||
|
|
from urllib.parse import urlparse
|
|||
|
|
|
|||
|
|
import requests
|
|||
|
|
from PyQt6.QtCore import QThread, pyqtSignal
|
|||
|
|
from PyQt6.QtGui import QImage, QPixmap
|
|||
|
|
|
|||
|
|
from app.core import miaoStarsBasicApi
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TextLoaderThread(QThread):
|
|||
|
|
"""文本文件加载线程"""
|
|||
|
|
|
|||
|
|
textLoaded = pyqtSignal(str)
|
|||
|
|
errorOccurred = pyqtSignal(str)
|
|||
|
|
progressUpdated = pyqtSignal(int) # 进度更新信号
|
|||
|
|
|
|||
|
|
def __init__(self, url):
|
|||
|
|
super().__init__()
|
|||
|
|
self.url = url
|
|||
|
|
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
# 2. 增加超时时间并添加重试机制
|
|||
|
|
response = miaoStarsBasicApi.returnSession().get(
|
|||
|
|
self.url,
|
|||
|
|
stream=True,
|
|||
|
|
timeout=(15, 30), # 增加超时时间:连接15秒,读取30秒
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
response.raise_for_status()
|
|||
|
|
|
|||
|
|
# 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)
|
|||
|
|
|
|||
|
|
# 更新进度(如果知道总大小)
|
|||
|
|
if total_size > 0:
|
|||
|
|
progress = int((downloaded_size / total_size) * 100)
|
|||
|
|
self.progressUpdated.emit(progress)
|
|||
|
|
|
|||
|
|
# 5. 合并内容并解码
|
|||
|
|
binary_content = b"".join(content_chunks)
|
|||
|
|
|
|||
|
|
if not binary_content:
|
|||
|
|
self.errorOccurred.emit("下载内容为空")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 6. 智能编码检测和解码
|
|||
|
|
text_content = self._decode_content(binary_content)
|
|||
|
|
|
|||
|
|
# 7. 发射加载完成的信号
|
|||
|
|
self.textLoaded.emit(text_content)
|
|||
|
|
|
|||
|
|
except requests.exceptions.Timeout:
|
|||
|
|
self.errorOccurred.emit("请求超时,请检查网络连接或尝试重新加载")
|
|||
|
|
except requests.exceptions.ConnectionError:
|
|||
|
|
self.errorOccurred.emit("网络连接错误,请检查网络设置")
|
|||
|
|
except requests.exceptions.RequestException as e:
|
|||
|
|
self.errorOccurred.emit(f"网络请求错误: {str(e)}")
|
|||
|
|
except Exception as e:
|
|||
|
|
self.errorOccurred.emit(f"文本处理错误: {str(e)}")
|
|||
|
|
|
|||
|
|
def _decode_content(self, binary_content):
|
|||
|
|
"""智能解码二进制内容"""
|
|||
|
|
# 优先尝试UTF-8
|
|||
|
|
encodings = ["utf-8", "gbk", "gb2312", "latin-1", "iso-8859-1", "cp1252"]
|
|||
|
|
|
|||
|
|
for encoding in encodings:
|
|||
|
|
try:
|
|||
|
|
return binary_content.decode(encoding)
|
|||
|
|
except UnicodeDecodeError:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 如果所有编码都失败,使用替换错误处理
|
|||
|
|
try:
|
|||
|
|
return binary_content.decode("utf-8", errors="replace")
|
|||
|
|
except:
|
|||
|
|
# 最后尝试忽略错误
|
|||
|
|
return binary_content.decode("utf-8", errors="ignore")
|
|||
|
|
|
|||
|
|
def cancel(self):
|
|||
|
|
"""取消下载"""
|
|||
|
|
if self.isRunning():
|
|||
|
|
self.terminate()
|
|||
|
|
self.wait(1000) # 等待线程结束
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ImageLoaderThread(QThread):
|
|||
|
|
"""优化的图片加载线程"""
|
|||
|
|
|
|||
|
|
imageLoaded = pyqtSignal(QPixmap)
|
|||
|
|
errorOccurred = pyqtSignal(str)
|
|||
|
|
progressUpdated = pyqtSignal(int) # 进度更新信号
|
|||
|
|
|
|||
|
|
def __init__(
|
|||
|
|
self, url, cache_dir="image_cache", max_cache_size=50 * 1024 * 1024
|
|||
|
|
): # 50MB缓存
|
|||
|
|
super().__init__()
|
|||
|
|
self.url = url
|
|||
|
|
self.cache_dir = cache_dir
|
|||
|
|
self.max_cache_size = max_cache_size
|
|||
|
|
self._setup_cache()
|
|||
|
|
|
|||
|
|
def _setup_cache(self):
|
|||
|
|
"""设置图片缓存目录"""
|
|||
|
|
if not os.path.exists(self.cache_dir):
|
|||
|
|
os.makedirs(self.cache_dir)
|
|||
|
|
|
|||
|
|
def _get_cache_filename(self):
|
|||
|
|
"""生成缓存文件名"""
|
|||
|
|
parsed_url = urlparse(self.url)
|
|||
|
|
filename = os.path.basename(parsed_url.path) or "image"
|
|||
|
|
# 添加URL哈希避免重名
|
|||
|
|
import hashlib
|
|||
|
|
|
|||
|
|
url_hash = hashlib.md5(self.url.encode()).hexdigest()[:8]
|
|||
|
|
return f"{url_hash}_{filename}"
|
|||
|
|
|
|||
|
|
def _get_cached_image(self):
|
|||
|
|
"""获取缓存图片"""
|
|||
|
|
cache_file = os.path.join(self.cache_dir, self._get_cache_filename())
|
|||
|
|
if os.path.exists(cache_file):
|
|||
|
|
try:
|
|||
|
|
pixmap = QPixmap(cache_file)
|
|||
|
|
if not pixmap.isNull():
|
|||
|
|
return pixmap
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def _save_to_cache(self, pixmap):
|
|||
|
|
"""保存图片到缓存"""
|
|||
|
|
try:
|
|||
|
|
cache_file = os.path.join(self.cache_dir, self._get_cache_filename())
|
|||
|
|
pixmap.save(cache_file, "JPG", 80) # 压缩质量80%
|
|||
|
|
self._cleanup_cache() # 清理过期缓存
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def _cleanup_cache(self):
|
|||
|
|
"""清理过期的缓存文件"""
|
|||
|
|
# noinspection PyBroadException
|
|||
|
|
try:
|
|||
|
|
files = []
|
|||
|
|
for f in os.listdir(self.cache_dir):
|
|||
|
|
filepath = os.path.join(self.cache_dir, f)
|
|||
|
|
if os.path.isfile(filepath):
|
|||
|
|
files.append((filepath, os.path.getmtime(filepath)))
|
|||
|
|
|
|||
|
|
# 按修改时间排序
|
|||
|
|
files.sort(key=lambda x: x[1])
|
|||
|
|
|
|||
|
|
# 计算总大小
|
|||
|
|
total_size = sum(os.path.getsize(f[0]) for f in files)
|
|||
|
|
|
|||
|
|
# 如果超过最大缓存大小,删除最旧的文件
|
|||
|
|
while total_size > self.max_cache_size and files:
|
|||
|
|
oldest_file = files.pop(0)
|
|||
|
|
total_size -= os.path.getsize(oldest_file[0])
|
|||
|
|
os.remove(oldest_file[0])
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def run(self):
|
|||
|
|
"""线程执行函数"""
|
|||
|
|
try:
|
|||
|
|
# 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)
|
|||
|
|
|
|||
|
|
# 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
|
|||
|
|
|
|||
|
|
# 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)
|
|||
|
|
|
|||
|
|
# 5. 从数据创建QImage(比QPixmap更快)
|
|||
|
|
image = QImage()
|
|||
|
|
image.loadFromData(image_data)
|
|||
|
|
|
|||
|
|
if image.isNull():
|
|||
|
|
raise Exception("无法加载图片数据")
|
|||
|
|
|
|||
|
|
# 6. 转换为QPixmap
|
|||
|
|
pixmap = QPixmap.fromImage(image)
|
|||
|
|
|
|||
|
|
# 7. 保存到缓存
|
|||
|
|
self._save_to_cache(pixmap)
|
|||
|
|
|
|||
|
|
# 发射加载完成的信号
|
|||
|
|
self.imageLoaded.emit(pixmap)
|
|||
|
|
|
|||
|
|
except requests.exceptions.Timeout:
|
|||
|
|
self.errorOccurred.emit("请求超时,请检查网络连接")
|
|||
|
|
except requests.exceptions.ConnectionError:
|
|||
|
|
self.errorOccurred.emit("网络连接错误")
|
|||
|
|
except requests.exceptions.RequestException as e:
|
|||
|
|
self.errorOccurred.emit(f"网络请求错误: {str(e)}")
|
|||
|
|
except Exception as e:
|
|||
|
|
self.errorOccurred.emit(f"图片处理错误: {str(e)}")
|