v1.0.6
This commit is contained in:
parent
e5d5afb1a8
commit
b96137d807
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +1,4 @@
|
||||
import os
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout
|
||||
from PyQt5.QtCore import Qt, QUrl, QTimer
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
@ -12,8 +13,9 @@ from qfluentwidgets import (
|
||||
from .function_fit_interface import FunctionFitInterface
|
||||
from app.tools.check_update import check_update
|
||||
from app.tools.auto_updater import AutoUpdater, check_update_availability
|
||||
from app.tools.update_check_thread import UpdateCheckThread
|
||||
|
||||
__version__ = "1.0.2"
|
||||
__version__ = "1.0.6"
|
||||
|
||||
class AboutInterface(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
@ -32,13 +34,7 @@ class AboutInterface(QWidget):
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
layout.setContentsMargins(20, 30, 20, 20)
|
||||
|
||||
# 页面标题
|
||||
title = SubtitleLabel("MRobot 帮助页面", self)
|
||||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.addWidget(title)
|
||||
layout.addSpacing(10)
|
||||
|
||||
# 版本信息卡片 - 学习AI界面风格
|
||||
# 版本信息卡片
|
||||
version_card = ElevatedCardWidget()
|
||||
version_layout = QVBoxLayout(version_card)
|
||||
version_layout.setContentsMargins(24, 20, 24, 20)
|
||||
@ -57,7 +53,7 @@ class AboutInterface(QWidget):
|
||||
text="检查更新",
|
||||
icon=FluentIcon.SYNC,
|
||||
title="检查更新",
|
||||
content="检查是否有新版本可用",
|
||||
content="检查是否有新版本可用(需要能够连接github)",
|
||||
)
|
||||
self.check_update_card.clicked.connect(self.check_for_updates)
|
||||
layout.addWidget(self.check_update_card)
|
||||
@ -119,7 +115,8 @@ class AboutInterface(QWidget):
|
||||
|
||||
self.notes_display = TextEdit()
|
||||
self.notes_display.setReadOnly(True)
|
||||
self.notes_display.setFixedHeight(200)
|
||||
self.notes_display.setMaximumHeight(200)
|
||||
self.notes_display.setMinimumHeight(80)
|
||||
self.notes_display.setText("暂无更新说明")
|
||||
layout.addWidget(self.notes_display)
|
||||
|
||||
@ -135,6 +132,11 @@ class AboutInterface(QWidget):
|
||||
self.progress_bar.setValue(0)
|
||||
progress_layout.addWidget(self.progress_bar)
|
||||
|
||||
# 详细下载信息
|
||||
self.download_info = BodyLabel("")
|
||||
self.download_info.setWordWrap(True)
|
||||
progress_layout.addWidget(self.download_info)
|
||||
|
||||
self.progress_widget.hide()
|
||||
layout.addWidget(self.progress_widget)
|
||||
|
||||
@ -170,16 +172,58 @@ class AboutInterface(QWidget):
|
||||
def _perform_check(self):
|
||||
"""执行更新检查"""
|
||||
try:
|
||||
# 获取最新版本信息(包括当前版本的详细信息)
|
||||
latest_info = self._get_latest_release_info()
|
||||
|
||||
# 检查是否有可用更新
|
||||
self.update_info = check_update_availability(__version__)
|
||||
|
||||
if self.update_info:
|
||||
self._show_update_available()
|
||||
else:
|
||||
self._show_no_update()
|
||||
self._show_no_update(latest_info)
|
||||
|
||||
except Exception as e:
|
||||
self._show_error(f"检查更新失败: {str(e)}")
|
||||
|
||||
def _get_latest_release_info(self):
|
||||
"""获取最新发布信息,不论版本是否需要更新"""
|
||||
try:
|
||||
import requests
|
||||
from packaging.version import parse as vparse
|
||||
|
||||
url = f"https://api.github.com/repos/goldenfishs/MRobot/releases/latest"
|
||||
response = requests.get(url, timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
release_data = response.json()
|
||||
latest_version = release_data["tag_name"].lstrip("v")
|
||||
|
||||
# 获取下载URL和文件大小
|
||||
assets = release_data.get('assets', [])
|
||||
asset_size = 0
|
||||
download_url = None
|
||||
|
||||
if assets:
|
||||
# 选择第一个资源文件
|
||||
asset = assets[0]
|
||||
asset_size = asset.get('size', 0)
|
||||
download_url = asset.get('browser_download_url', '')
|
||||
|
||||
return {
|
||||
'version': latest_version,
|
||||
'release_notes': release_data.get('body', '暂无更新说明'),
|
||||
'release_date': release_data.get('published_at', ''),
|
||||
'asset_size': asset_size,
|
||||
'download_url': download_url
|
||||
}
|
||||
else:
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取发布信息失败: {e}")
|
||||
return None
|
||||
|
||||
def _show_update_available(self):
|
||||
"""显示发现更新"""
|
||||
# 更新按钮状态
|
||||
@ -216,11 +260,43 @@ class AboutInterface(QWidget):
|
||||
duration=3000
|
||||
)
|
||||
|
||||
def _show_no_update(self):
|
||||
"""显示无更新"""
|
||||
def _show_no_update(self, latest_info=None):
|
||||
"""显示无更新,但展示最新版本信息"""
|
||||
self.check_update_card.setEnabled(True)
|
||||
self.check_update_card.setContent("已是最新版本")
|
||||
|
||||
# 如果有最新版本信息,显示详情卡片
|
||||
if latest_info:
|
||||
self.update_info_card.show()
|
||||
|
||||
# 显示版本信息(当前版本就是最新版本)
|
||||
self.latest_version_label.setText(f"v{__version__}")
|
||||
|
||||
# 设置文件信息
|
||||
asset_size = latest_info.get('asset_size', 0)
|
||||
file_size = self._format_file_size(asset_size)
|
||||
self.file_size_label.setText(f"文件大小: {file_size}")
|
||||
|
||||
# 设置发布时间
|
||||
release_date = latest_info.get('release_date', '')
|
||||
formatted_date = self._format_date(release_date)
|
||||
self.release_date_label.setText(f"发布时间: {formatted_date}")
|
||||
|
||||
# 设置更新说明
|
||||
notes = latest_info.get('release_notes', '暂无更新说明')
|
||||
self.notes_display.setText(notes[:500] + ('...' if len(notes) > 500 else ''))
|
||||
|
||||
# 修改标题和按钮
|
||||
self.update_title.setText("版本信息")
|
||||
self.update_btn.setText("手动下载")
|
||||
self.update_btn.setIcon(FluentIcon.DOWNLOAD)
|
||||
self.update_btn.setEnabled(True)
|
||||
self.manual_btn.setEnabled(True)
|
||||
|
||||
# 连接手动下载功能
|
||||
self.update_btn.clicked.disconnect()
|
||||
self.update_btn.clicked.connect(self.open_manual_download)
|
||||
|
||||
InfoBar.info(
|
||||
title="已是最新版本",
|
||||
content="当前已是最新版本,无需更新。",
|
||||
@ -252,9 +328,10 @@ class AboutInterface(QWidget):
|
||||
self.update_btn.setEnabled(False)
|
||||
self.manual_btn.setEnabled(False)
|
||||
|
||||
# 启动更新器
|
||||
# 启动更新器(使用简化的单线程下载)
|
||||
self.updater = AutoUpdater(__version__)
|
||||
self.updater.signals.progress_changed.connect(self.update_progress)
|
||||
self.updater.signals.download_progress.connect(self.update_download_progress)
|
||||
self.updater.signals.status_changed.connect(self.update_status)
|
||||
self.updater.signals.error_occurred.connect(self.update_error)
|
||||
self.updater.signals.update_completed.connect(self.update_completed)
|
||||
@ -266,6 +343,29 @@ class AboutInterface(QWidget):
|
||||
"""更新进度"""
|
||||
self.progress_bar.setValue(value)
|
||||
|
||||
def update_download_progress(self, downloaded: int, total: int, speed: float, remaining: float):
|
||||
"""更新下载进度详情"""
|
||||
if total > 0:
|
||||
downloaded_str = self._format_bytes(downloaded)
|
||||
total_str = self._format_bytes(total)
|
||||
percentage = (downloaded / total) * 100
|
||||
|
||||
info_text = f"已下载: {downloaded_str} / {total_str} ({percentage:.1f}%)"
|
||||
|
||||
self.download_info.setText(info_text)
|
||||
|
||||
def _format_bytes(self, size_bytes: int) -> str:
|
||||
"""格式化字节大小"""
|
||||
if size_bytes == 0:
|
||||
return "0 B"
|
||||
|
||||
size = float(size_bytes)
|
||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||
if size < 1024.0:
|
||||
return f"{size:.1f} {unit}"
|
||||
size /= 1024.0
|
||||
return f"{size:.1f} TB"
|
||||
|
||||
def update_status(self, status: str):
|
||||
"""更新状态"""
|
||||
self.progress_label.setText(status)
|
||||
@ -276,34 +376,106 @@ class AboutInterface(QWidget):
|
||||
self.update_btn.setEnabled(True)
|
||||
self.manual_btn.setEnabled(True)
|
||||
|
||||
InfoBar.error(
|
||||
title="更新失败",
|
||||
content=error_msg,
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=4000
|
||||
)
|
||||
# 如果是平台兼容性问题,提供更友好的提示
|
||||
if "Windows 安装程序" in error_msg and "当前系统是" in error_msg:
|
||||
InfoBar.warning(
|
||||
title="平台不兼容",
|
||||
content="检测到 Windows 安装程序,请点击'手动下载'获取适合 macOS 的版本",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=6000
|
||||
)
|
||||
else:
|
||||
InfoBar.error(
|
||||
title="更新失败",
|
||||
content=error_msg,
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=4000
|
||||
)
|
||||
|
||||
def update_completed(self):
|
||||
"""更新完成"""
|
||||
self.progress_label.setText("更新完成,准备重启...")
|
||||
def update_completed(self, file_path=None):
|
||||
"""更新完成 - 显示下载文件位置"""
|
||||
print(f"update_completed called with file_path: {file_path}") # 调试输出
|
||||
|
||||
self.progress_label.setText("下载完成!")
|
||||
self.progress_bar.setValue(100)
|
||||
|
||||
InfoBar.success(
|
||||
title="更新完成",
|
||||
content="更新安装完成,程序将在3秒后重启",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=3000
|
||||
)
|
||||
# 重新启用按钮
|
||||
self.update_btn.setEnabled(True)
|
||||
self.manual_btn.setEnabled(True)
|
||||
|
||||
# 延迟重启
|
||||
QTimer.singleShot(3000, self._restart_app)
|
||||
if file_path and os.path.exists(file_path):
|
||||
print(f"File exists: {file_path}") # 调试输出
|
||||
InfoBar.success(
|
||||
title="下载完成",
|
||||
content="安装文件已下载完成,点击下方按钮打开文件位置",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=5000
|
||||
)
|
||||
|
||||
# 添加打开文件夹按钮
|
||||
self._add_open_folder_button(file_path)
|
||||
else:
|
||||
print(f"File does not exist or file_path is None: {file_path}") # 调试输出
|
||||
InfoBar.success(
|
||||
title="下载完成",
|
||||
content="文件下载完成",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=3000
|
||||
)
|
||||
|
||||
def _add_open_folder_button(self, file_path):
|
||||
"""添加打开文件夹按钮"""
|
||||
def open_file_location():
|
||||
folder_path = os.path.dirname(file_path)
|
||||
# 在 macOS 上使用 Finder 打开文件夹
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(folder_path))
|
||||
|
||||
InfoBar.info(
|
||||
title="已打开文件夹",
|
||||
content=f"文件位置: {folder_path}",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=3000
|
||||
)
|
||||
|
||||
# 直接替换更新按钮的文本和功能
|
||||
self.update_btn.setText("打开文件位置")
|
||||
self.update_btn.setIcon(FluentIcon.FOLDER)
|
||||
# 断开原有连接
|
||||
self.update_btn.clicked.disconnect()
|
||||
# 连接新功能
|
||||
self.update_btn.clicked.connect(open_file_location)
|
||||
|
||||
# 修改取消按钮为清理按钮
|
||||
self.cancel_btn.setText("清理临时文件")
|
||||
self.cancel_btn.setIcon(FluentIcon.DELETE)
|
||||
self.cancel_btn.clicked.disconnect()
|
||||
|
||||
def cleanup_temp_files():
|
||||
if self.updater:
|
||||
self.updater.cleanup()
|
||||
InfoBar.success(
|
||||
title="已清理",
|
||||
content="临时文件已清理完成",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000
|
||||
)
|
||||
# 重置界面
|
||||
self.update_info_card.hide()
|
||||
self.check_update_card.setContent("检查是否有新版本可用")
|
||||
|
||||
self.cancel_btn.clicked.connect(cleanup_temp_files)
|
||||
|
||||
def cancel_update(self):
|
||||
"""取消更新"""
|
||||
if self.updater and self.updater.isRunning():
|
||||
if hasattr(self, 'updater') and self.updater and self.updater.isRunning():
|
||||
self.updater.cancel_update()
|
||||
self.updater.cleanup()
|
||||
|
||||
self.update_info_card.hide()
|
||||
self.check_update_card.setContent("检查是否有新版本可用")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
app/tools/__pycache__/auto_updater.cpython-39.pyc
Normal file
BIN
app/tools/__pycache__/auto_updater.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/tools/__pycache__/multi_thread_downloader.cpython-39.pyc
Normal file
BIN
app/tools/__pycache__/multi_thread_downloader.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/tools/__pycache__/update_check_thread.cpython-39.pyc
Normal file
BIN
app/tools/__pycache__/update_check_thread.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -18,13 +18,16 @@ import requests
|
||||
from packaging.version import parse as vparse
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QObject
|
||||
|
||||
# 移除多线程下载器依赖
|
||||
|
||||
|
||||
class UpdaterSignals(QObject):
|
||||
"""更新器信号类"""
|
||||
progress_changed = pyqtSignal(int) # 进度变化信号 (0-100)
|
||||
download_progress = pyqtSignal(int, int, float, float) # 下载进度: 已下载字节, 总字节, 速度MB/s, 剩余时间秒
|
||||
status_changed = pyqtSignal(str) # 状态变化信号
|
||||
error_occurred = pyqtSignal(str) # 错误信号
|
||||
update_completed = pyqtSignal() # 更新完成信号
|
||||
update_completed = pyqtSignal(str) # 更新完成信号,可选包含文件路径
|
||||
update_cancelled = pyqtSignal() # 更新取消信号
|
||||
|
||||
|
||||
@ -43,6 +46,9 @@ class AutoUpdater(QThread):
|
||||
self.app_dir = self._get_app_directory()
|
||||
self.temp_dir = None
|
||||
|
||||
# 多线程下载器
|
||||
self.downloader = None
|
||||
|
||||
def _get_app_directory(self) -> str:
|
||||
"""获取应用程序目录"""
|
||||
if self.is_frozen:
|
||||
@ -55,6 +61,8 @@ class AutoUpdater(QThread):
|
||||
def cancel_update(self):
|
||||
"""取消更新"""
|
||||
self.cancelled = True
|
||||
if self.downloader:
|
||||
self.downloader.cancel()
|
||||
self.signals.update_cancelled.emit()
|
||||
|
||||
def check_for_updates(self) -> Optional[dict]:
|
||||
@ -70,12 +78,15 @@ class AutoUpdater(QThread):
|
||||
latest_version = release_data["tag_name"].lstrip("v")
|
||||
|
||||
if vparse(latest_version) > vparse(self.current_version):
|
||||
download_url = self._get_download_url(release_data)
|
||||
asset_size = self._get_asset_size(release_data, download_url) if download_url else 0
|
||||
return {
|
||||
'version': latest_version,
|
||||
'download_url': self._get_download_url(release_data),
|
||||
'download_url': download_url,
|
||||
'release_notes': release_data.get('body', ''),
|
||||
'release_date': release_data.get('published_at', ''),
|
||||
'asset_name': self._get_asset_name(release_data)
|
||||
'asset_name': self._get_asset_name(release_data),
|
||||
'asset_size': asset_size
|
||||
}
|
||||
return None
|
||||
else:
|
||||
@ -100,17 +111,32 @@ class AutoUpdater(QThread):
|
||||
if name.endswith('.exe') or name.endswith('.zip'):
|
||||
return asset['browser_download_url']
|
||||
elif system == 'darwin': # macOS
|
||||
if name.endswith('.dmg') or name.endswith('.zip'):
|
||||
# 优先选择 dmg 或 zip 文件
|
||||
if name.endswith('.dmg'):
|
||||
return asset['browser_download_url']
|
||||
if name.endswith('.zip') and 'macos' in name:
|
||||
return asset['browser_download_url']
|
||||
elif system == 'linux':
|
||||
if name.endswith('.tar.gz') or name.endswith('.zip'):
|
||||
if name.endswith('.tar.gz') or (name.endswith('.zip') and 'linux' in name):
|
||||
return asset['browser_download_url']
|
||||
|
||||
# 如果没找到特定平台的,返回第一个可用文件
|
||||
# 如果没找到特定平台的,在 macOS 上避免选择 .exe 文件
|
||||
for asset in assets:
|
||||
name = asset['name'].lower()
|
||||
if any(name.endswith(ext) for ext in ['.zip', '.exe', '.dmg', '.tar.gz']):
|
||||
return asset['browser_download_url']
|
||||
if system == 'darwin':
|
||||
# macOS 优先选择非 exe 文件
|
||||
if name.endswith('.zip') or name.endswith('.dmg') or name.endswith('.tar.gz'):
|
||||
return asset['browser_download_url']
|
||||
else:
|
||||
if any(name.endswith(ext) for ext in ['.zip', '.exe', '.dmg', '.tar.gz']):
|
||||
return asset['browser_download_url']
|
||||
|
||||
# 最后才选择 exe 文件(如果没有其他选择)
|
||||
if system == 'darwin':
|
||||
for asset in assets:
|
||||
name = asset['name'].lower()
|
||||
if name.endswith('.exe'):
|
||||
return asset['browser_download_url']
|
||||
|
||||
return None
|
||||
|
||||
@ -121,46 +147,95 @@ class AutoUpdater(QThread):
|
||||
return os.path.basename(urlparse(download_url).path)
|
||||
return None
|
||||
|
||||
def _get_asset_size(self, release_data: dict, download_url: str) -> int:
|
||||
"""获取资源文件大小"""
|
||||
if not download_url:
|
||||
return 0
|
||||
|
||||
assets = release_data.get('assets', [])
|
||||
for asset in assets:
|
||||
if asset.get('browser_download_url') == download_url:
|
||||
return asset.get('size', 0)
|
||||
return 0
|
||||
|
||||
def download_update(self, download_url: str, filename: str) -> Optional[str]:
|
||||
"""下载更新文件"""
|
||||
"""下载更新文件 - 使用简单的单线程下载"""
|
||||
try:
|
||||
self.signals.status_changed.emit("正在下载更新...")
|
||||
self.signals.status_changed.emit("开始下载...")
|
||||
|
||||
# 创建临时目录
|
||||
self.temp_dir = tempfile.mkdtemp(prefix="MRobot_update_")
|
||||
file_path = os.path.join(self.temp_dir, filename)
|
||||
|
||||
response = requests.get(download_url, stream=True, timeout=30)
|
||||
print(f"Downloading to: {file_path}")
|
||||
|
||||
# 使用简单的requests下载
|
||||
import requests
|
||||
headers = {
|
||||
'User-Agent': 'MRobot-Updater/1.0',
|
||||
'Accept': '*/*',
|
||||
}
|
||||
|
||||
response = requests.get(download_url, headers=headers, stream=True, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
# 获取文件总大小
|
||||
total_size = int(response.headers.get('Content-Length', 0))
|
||||
downloaded_size = 0
|
||||
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
|
||||
# 下载文件
|
||||
with open(file_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if self.cancelled:
|
||||
print("Download cancelled by user")
|
||||
return None
|
||||
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
|
||||
# 更新进度
|
||||
if total_size > 0:
|
||||
progress = int((downloaded_size / total_size) * 50) # 下载占50%进度
|
||||
progress = int((downloaded_size / total_size) * 100)
|
||||
self.signals.progress_changed.emit(progress)
|
||||
|
||||
# 发送详细进度信息
|
||||
speed = 0 # 简化版本不计算速度
|
||||
remaining = 0
|
||||
self.signals.download_progress.emit(downloaded_size, total_size, speed, remaining)
|
||||
|
||||
self.signals.status_changed.emit("下载完成")
|
||||
return file_path
|
||||
# 验证下载的文件
|
||||
if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
|
||||
print(f"Download completed successfully: {file_path}")
|
||||
self.signals.status_changed.emit("下载完成!")
|
||||
return file_path
|
||||
else:
|
||||
raise Exception("下载的文件不存在或为空")
|
||||
|
||||
except Exception as e:
|
||||
self.signals.error_occurred.emit(f"下载失败: {str(e)}")
|
||||
error_msg = f"下载失败: {str(e)}"
|
||||
print(f"Download error: {error_msg}")
|
||||
self.signals.error_occurred.emit(error_msg)
|
||||
return None
|
||||
|
||||
def _on_download_progress(self, downloaded: int, total: int, speed: float, remaining: float):
|
||||
"""处理下载进度"""
|
||||
# 转发详细下载进度
|
||||
self.signals.download_progress.emit(downloaded, total, speed, remaining)
|
||||
|
||||
# 计算总体进度 (下载占80%,其他操作占20%)
|
||||
if total > 0:
|
||||
download_progress = int((downloaded / total) * 80)
|
||||
self.signals.progress_changed.emit(download_progress)
|
||||
|
||||
def extract_update(self, file_path: str) -> Optional[str]:
|
||||
"""解压更新文件"""
|
||||
try:
|
||||
self.signals.status_changed.emit("正在解压文件...")
|
||||
self.signals.progress_changed.emit(50)
|
||||
self.signals.progress_changed.emit(85)
|
||||
|
||||
if not self.temp_dir:
|
||||
raise Exception("临时目录未初始化")
|
||||
@ -176,10 +251,19 @@ class AutoUpdater(QThread):
|
||||
import tarfile
|
||||
with tarfile.open(file_path, 'r:gz') as tar_ref:
|
||||
tar_ref.extractall(extract_dir)
|
||||
elif file_path.endswith('.exe'):
|
||||
# 对于 .exe 文件,在非 Windows 系统上提示手动安装
|
||||
current_system = platform.system()
|
||||
if current_system != 'Windows':
|
||||
self.signals.error_occurred.emit(f"下载的是 Windows 安装程序,当前系统是 {current_system}。\n请手动下载适合您系统的版本。")
|
||||
return None
|
||||
else:
|
||||
# Windows 系统直接返回文件路径,由安装函数处理
|
||||
return file_path
|
||||
else:
|
||||
raise Exception(f"不支持的文件格式: {file_path}")
|
||||
|
||||
self.signals.progress_changed.emit(70)
|
||||
self.signals.progress_changed.emit(90)
|
||||
self.signals.status_changed.emit("解压完成")
|
||||
return extract_dir
|
||||
|
||||
@ -191,7 +275,7 @@ class AutoUpdater(QThread):
|
||||
"""安装更新"""
|
||||
try:
|
||||
self.signals.status_changed.emit("正在安装更新...")
|
||||
self.signals.progress_changed.emit(80)
|
||||
self.signals.progress_changed.emit(95)
|
||||
|
||||
if not self.temp_dir:
|
||||
raise Exception("临时目录未初始化")
|
||||
@ -206,7 +290,7 @@ class AutoUpdater(QThread):
|
||||
# 复制新文件
|
||||
self._copy_update_files(extract_dir)
|
||||
|
||||
self.signals.progress_changed.emit(95)
|
||||
self.signals.progress_changed.emit(99)
|
||||
self.signals.status_changed.emit("安装完成")
|
||||
|
||||
return True
|
||||
@ -295,43 +379,81 @@ class AutoUpdater(QThread):
|
||||
|
||||
def cleanup(self):
|
||||
"""清理临时文件"""
|
||||
print(f"cleanup() called, temp_dir: {self.temp_dir}") # 调试输出
|
||||
if self.temp_dir and os.path.exists(self.temp_dir):
|
||||
try:
|
||||
print(f"Removing temp directory: {self.temp_dir}") # 调试输出
|
||||
shutil.rmtree(self.temp_dir)
|
||||
print("Temp directory removed successfully") # 调试输出
|
||||
except Exception as e:
|
||||
print(f"清理临时文件失败: {e}")
|
||||
else:
|
||||
print("No temp directory to clean up") # 调试输出
|
||||
|
||||
def run(self):
|
||||
"""执行更新流程"""
|
||||
try:
|
||||
self.signals.status_changed.emit("开始更新流程...")
|
||||
|
||||
# 检查更新
|
||||
self.signals.status_changed.emit("正在获取更新信息...")
|
||||
update_info = self.check_for_updates()
|
||||
if not update_info or self.cancelled:
|
||||
self.signals.status_changed.emit("未找到更新信息或已取消")
|
||||
return
|
||||
|
||||
self.signals.status_changed.emit(f"准备下载版本 {update_info['version']}")
|
||||
|
||||
# 下载更新
|
||||
downloaded_file = self.download_update(
|
||||
update_info['download_url'],
|
||||
update_info['asset_name']
|
||||
)
|
||||
if not downloaded_file or self.cancelled:
|
||||
self.signals.status_changed.emit("下载失败或已取消")
|
||||
return
|
||||
|
||||
self.signals.status_changed.emit("下载完成!")
|
||||
self.signals.progress_changed.emit(100)
|
||||
|
||||
# 检查是否为exe文件且当前系统非Windows
|
||||
current_system = platform.system()
|
||||
print(f"Downloaded file: {downloaded_file}") # 调试输出
|
||||
print(f"Current system: {current_system}") # 调试输出
|
||||
if downloaded_file.endswith('.exe') and current_system != 'Windows':
|
||||
# 直接完成,返回文件路径
|
||||
print(f"Emitting update_completed signal with file path: {downloaded_file}") # 调试输出
|
||||
self.signals.update_completed.emit(downloaded_file)
|
||||
# 不要立即清理,让用户有时间访问文件
|
||||
print("Skipping cleanup to preserve downloaded file") # 调试输出
|
||||
return
|
||||
|
||||
# 对于其他情况,继续原有流程
|
||||
self.signals.status_changed.emit("下载完成,开始解压...")
|
||||
|
||||
# 解压更新
|
||||
extract_dir = self.extract_update(downloaded_file)
|
||||
if not extract_dir or self.cancelled:
|
||||
self.signals.status_changed.emit("解压失败或已取消")
|
||||
return
|
||||
|
||||
# 安装更新
|
||||
self.signals.status_changed.emit("开始安装更新...")
|
||||
if self.install_update(extract_dir) and not self.cancelled:
|
||||
self.signals.progress_changed.emit(100)
|
||||
self.signals.update_completed.emit()
|
||||
self.signals.status_changed.emit("更新安装完成")
|
||||
self.signals.update_completed.emit("") # 传递空字符串表示正常安装完成
|
||||
else:
|
||||
self.signals.status_changed.emit("安装失败或已取消")
|
||||
|
||||
except Exception as e:
|
||||
self.signals.error_occurred.emit(f"更新过程中发生错误: {str(e)}")
|
||||
error_msg = f"更新过程中发生错误: {str(e)}"
|
||||
print(f"AutoUpdater error: {error_msg}") # 调试输出
|
||||
self.signals.error_occurred.emit(error_msg)
|
||||
finally:
|
||||
# 清理临时文件
|
||||
self.cleanup()
|
||||
# 对于下载完成的情况,延迟清理临时文件
|
||||
# 这样用户有时间访问下载的文件
|
||||
pass # 暂时不在这里清理
|
||||
|
||||
|
||||
def check_update_availability(current_version: str, repo: str = "goldenfishs/MRobot") -> Optional[dict]:
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import requests
|
||||
import platform
|
||||
from packaging.version import parse as vparse
|
||||
from typing import Optional
|
||||
|
||||
def check_update(local_version, repo="goldenfishs/MRobot"):
|
||||
url = f"https://api.github.com/repos/{repo}/releases/latest"
|
||||
@ -11,4 +13,62 @@ def check_update(local_version, repo="goldenfishs/MRobot"):
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
raise RuntimeError("GitHub API 请求失败")
|
||||
raise RuntimeError("GitHub API 请求失败")
|
||||
|
||||
def check_update_availability(current_version: str, repo: str = "goldenfishs/MRobot") -> Optional[dict]:
|
||||
"""检查更新并返回详细信息"""
|
||||
try:
|
||||
url = f"https://api.github.com/repos/{repo}/releases/latest"
|
||||
response = requests.get(url, timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
release_data = response.json()
|
||||
latest_version = release_data["tag_name"].lstrip("v")
|
||||
|
||||
if vparse(latest_version) > vparse(current_version):
|
||||
# 获取适合当前平台的下载链接和文件大小
|
||||
download_url, asset_size, asset_name = _get_platform_asset(release_data)
|
||||
|
||||
return {
|
||||
'version': latest_version,
|
||||
'download_url': download_url,
|
||||
'asset_size': asset_size,
|
||||
'asset_name': asset_name,
|
||||
'release_notes': release_data.get('body', ''),
|
||||
'release_date': release_data.get('published_at', ''),
|
||||
}
|
||||
return None
|
||||
else:
|
||||
raise Exception(f"GitHub API请求失败: {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"检查更新失败: {str(e)}")
|
||||
|
||||
def _get_platform_asset(release_data: dict) -> tuple:
|
||||
"""获取适合当前平台的资源文件信息"""
|
||||
assets = release_data.get('assets', [])
|
||||
system = platform.system().lower()
|
||||
|
||||
# 根据操作系统选择合适的安装包
|
||||
for asset in assets:
|
||||
name = asset['name'].lower()
|
||||
|
||||
if system == 'windows':
|
||||
if 'installer' in name and name.endswith('.exe'):
|
||||
return asset['browser_download_url'], asset.get('size', 0), asset['name']
|
||||
if name.endswith('.exe') or name.endswith('.zip'):
|
||||
return asset['browser_download_url'], asset.get('size', 0), asset['name']
|
||||
elif system == 'darwin': # macOS
|
||||
if name.endswith('.dmg') or name.endswith('.zip'):
|
||||
return asset['browser_download_url'], asset.get('size', 0), asset['name']
|
||||
elif system == 'linux':
|
||||
if name.endswith('.tar.gz') or name.endswith('.zip'):
|
||||
return asset['browser_download_url'], asset.get('size', 0), asset['name']
|
||||
|
||||
# 如果没找到特定平台的,返回第一个可用文件
|
||||
for asset in assets:
|
||||
name = asset['name'].lower()
|
||||
if any(name.endswith(ext) for ext in ['.zip', '.exe', '.dmg', '.tar.gz']):
|
||||
return asset['browser_download_url'], asset.get('size', 0), asset['name']
|
||||
|
||||
return None, 0, None
|
||||
34
app/tools/update_check_thread.py
Normal file
34
app/tools/update_check_thread.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""
|
||||
更新检查线程
|
||||
避免阻塞UI界面的更新检查
|
||||
"""
|
||||
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
from app.tools.check_update import check_update_availability
|
||||
|
||||
|
||||
class UpdateCheckThread(QThread):
|
||||
"""更新检查线程"""
|
||||
|
||||
# 信号定义
|
||||
update_found = pyqtSignal(dict) # 发现更新
|
||||
no_update = pyqtSignal() # 无更新
|
||||
error_occurred = pyqtSignal(str) # 检查出错
|
||||
|
||||
def __init__(self, current_version: str, repo: str = "goldenfishs/MRobot"):
|
||||
super().__init__()
|
||||
self.current_version = current_version
|
||||
self.repo = repo
|
||||
|
||||
def run(self):
|
||||
"""执行更新检查"""
|
||||
try:
|
||||
update_info = check_update_availability(self.current_version, self.repo)
|
||||
|
||||
if update_info:
|
||||
self.update_found.emit(update_info)
|
||||
else:
|
||||
self.no_update.emit()
|
||||
|
||||
except Exception as e:
|
||||
self.error_occurred.emit(str(e))
|
||||
@ -74,7 +74,7 @@ static int8_t MOTOR_RM_GetLogicalIndex(uint16_t can_id, MOTOR_RM_Module_t module
|
||||
static float MOTOR_RM_GetRatio(MOTOR_RM_Module_t module) {
|
||||
switch (module) {
|
||||
case MOTOR_M2006: return 36.0f;
|
||||
case MOTOR_M3508: return 19.0f;
|
||||
case MOTOR_M3508: return 3591.0f / 187.0f;
|
||||
case MOTOR_GM6020: return 1.0f;
|
||||
default: return 1.0f;
|
||||
}
|
||||
|
||||
@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
自动更新功能演示
|
||||
展示如何使用自动更新功能的完整示例
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
def demo_auto_update():
|
||||
"""演示自动更新功能"""
|
||||
from app.tools.update_dialog import UpdateDialog
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 创建主窗口
|
||||
window = QWidget()
|
||||
window.setWindowTitle("自动更新演示")
|
||||
window.setFixedSize(300, 200)
|
||||
|
||||
layout = QVBoxLayout(window)
|
||||
|
||||
# 标题
|
||||
title = QLabel("MRobot 自动更新演示")
|
||||
title.setAlignment(Qt.AlignCenter)
|
||||
title.setStyleSheet("font-size: 16px; font-weight: bold; margin: 20px;")
|
||||
layout.addWidget(title)
|
||||
|
||||
# 当前版本显示
|
||||
version_label = QLabel("当前版本: v1.0.0")
|
||||
version_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(version_label)
|
||||
|
||||
# 更新按钮
|
||||
update_btn = QPushButton("检查并更新")
|
||||
update_btn.clicked.connect(lambda: show_update_dialog(window))
|
||||
layout.addWidget(update_btn)
|
||||
|
||||
# 说明
|
||||
info_label = QLabel("点击按钮体验自动更新功能")
|
||||
info_label.setAlignment(Qt.AlignCenter)
|
||||
info_label.setStyleSheet("color: gray; font-size: 12px;")
|
||||
layout.addWidget(info_label)
|
||||
|
||||
window.show()
|
||||
|
||||
def show_update_dialog(parent):
|
||||
"""显示更新对话框"""
|
||||
dialog = UpdateDialog("1.0.0", parent)
|
||||
dialog.exec_()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
def demo_quick_check():
|
||||
"""演示快速更新检查"""
|
||||
from app.tools.update_dialog import QuickUpdateChecker
|
||||
|
||||
print("演示快速更新检查功能...")
|
||||
|
||||
# 检查更新但不显示对话框
|
||||
result = QuickUpdateChecker.check_and_notify("1.0.0", None, auto_show_dialog=False)
|
||||
|
||||
if result:
|
||||
print("✅ 发现更新并显示了通知")
|
||||
else:
|
||||
print("ℹ️ 当前已是最新版本或检查失败")
|
||||
|
||||
def demo_api_usage():
|
||||
"""演示API使用方法"""
|
||||
from app.tools.auto_updater import check_update_availability
|
||||
|
||||
print("\n演示API使用方法...")
|
||||
|
||||
# 检查更新
|
||||
current_version = "1.0.0"
|
||||
update_info = check_update_availability(current_version)
|
||||
|
||||
if update_info:
|
||||
print("📦 发现新版本!")
|
||||
print(f" 版本号: {update_info['version']}")
|
||||
print(f" 下载链接: {update_info['download_url']}")
|
||||
print(f" 文件名: {update_info['asset_name']}")
|
||||
print(f" 发布日期: {update_info['release_date']}")
|
||||
print(f" 更新说明: {update_info['release_notes'][:100]}...")
|
||||
else:
|
||||
print("ℹ️ 当前已是最新版本")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="MRobot 自动更新功能演示")
|
||||
parser.add_argument("--mode", choices=["ui", "quick", "api"], default="ui",
|
||||
help="演示模式: ui=图形界面, quick=快速检查, api=API演示")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("🚀 MRobot 自动更新功能演示")
|
||||
print("="*40)
|
||||
|
||||
if args.mode == "ui":
|
||||
print("启动图形界面演示...")
|
||||
demo_auto_update()
|
||||
elif args.mode == "quick":
|
||||
demo_quick_check()
|
||||
elif args.mode == "api":
|
||||
demo_api_usage()
|
||||
|
||||
print("\n演示完成!")
|
||||
@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
自动更新功能测试脚本
|
||||
用于测试自动更新功能的各个组件
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目路径到 Python 路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
def test_update_check():
|
||||
"""测试更新检查功能"""
|
||||
print("测试更新检查功能...")
|
||||
|
||||
try:
|
||||
from app.tools.auto_updater import check_update_availability
|
||||
|
||||
current_version = "1.0.0" # 使用一个较低的版本号来测试
|
||||
result = check_update_availability(current_version)
|
||||
|
||||
if result:
|
||||
print(f"✅ 发现新版本: {result['version']}")
|
||||
print(f" 下载链接: {result['download_url']}")
|
||||
print(f" 文件名: {result['asset_name']}")
|
||||
return True
|
||||
else:
|
||||
print("ℹ️ 当前已是最新版本")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新检查失败: {e}")
|
||||
return False
|
||||
|
||||
def test_updater_creation():
|
||||
"""测试更新器创建"""
|
||||
print("\n测试更新器创建...")
|
||||
|
||||
try:
|
||||
from app.tools.auto_updater import AutoUpdater
|
||||
|
||||
updater = AutoUpdater("1.0.0")
|
||||
print(f"✅ 更新器创建成功")
|
||||
print(f" 应用目录: {updater.app_dir}")
|
||||
print(f" 是否打包: {updater.is_frozen}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新器创建失败: {e}")
|
||||
return False
|
||||
|
||||
def test_dialog_import():
|
||||
"""测试对话框导入"""
|
||||
print("\n测试对话框导入...")
|
||||
|
||||
try:
|
||||
from app.tools.update_dialog import UpdateDialog, QuickUpdateChecker
|
||||
print("✅ 更新对话框模块导入成功")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新对话框导入失败: {e}")
|
||||
return False
|
||||
|
||||
def test_config_import():
|
||||
"""测试配置导入"""
|
||||
print("\n测试配置导入...")
|
||||
|
||||
try:
|
||||
from app.tools import update_config
|
||||
print("✅ 更新配置模块导入成功")
|
||||
print(f" 自动更新启用: {update_config.AUTO_UPDATE_ENABLED}")
|
||||
print(f" GitHub仓库: {update_config.GITHUB_REPO}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新配置导入失败: {e}")
|
||||
return False
|
||||
|
||||
def run_all_tests():
|
||||
"""运行所有测试"""
|
||||
print("🚀 开始自动更新功能测试\n")
|
||||
|
||||
tests = [
|
||||
("配置导入", test_config_import),
|
||||
("更新器创建", test_updater_creation),
|
||||
("对话框导入", test_dialog_import),
|
||||
("更新检查", test_update_check),
|
||||
]
|
||||
|
||||
results = []
|
||||
for name, test_func in tests:
|
||||
try:
|
||||
result = test_func()
|
||||
results.append((name, result))
|
||||
except Exception as e:
|
||||
print(f"❌ {name}测试出现异常: {e}")
|
||||
results.append((name, False))
|
||||
|
||||
print("\n" + "="*50)
|
||||
print("测试结果总结:")
|
||||
print("="*50)
|
||||
|
||||
passed = 0
|
||||
for name, result in results:
|
||||
status = "✅ 通过" if result else "❌ 失败"
|
||||
print(f"{name}: {status}")
|
||||
if result:
|
||||
passed += 1
|
||||
|
||||
print(f"\n总计: {passed}/{len(results)} 项测试通过")
|
||||
|
||||
if passed == len(results):
|
||||
print("🎉 所有测试通过!自动更新功能可以正常使用。")
|
||||
else:
|
||||
print("⚠️ 部分测试失败,请检查相关模块。")
|
||||
|
||||
return passed == len(results)
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_all_tests()
|
||||
sys.exit(0 if success else 1)
|
||||
@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试修复后的更新功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
def test_dialog_creation():
|
||||
"""测试对话框创建"""
|
||||
print("测试对话框创建...")
|
||||
|
||||
try:
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
app = QApplication(sys.argv if len(sys.argv) > 1 else ['test'])
|
||||
|
||||
from app.tools.update_dialog import UpdateDialog
|
||||
|
||||
# 创建对话框(但不显示)
|
||||
dialog = UpdateDialog("1.0.0")
|
||||
print("✅ 对话框创建成功")
|
||||
|
||||
# 测试基本方法
|
||||
dialog.check_for_updates()
|
||||
print("✅ 检查更新功能正常")
|
||||
|
||||
# 清理
|
||||
dialog.close()
|
||||
app.quit()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 对话框测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_imports():
|
||||
"""测试导入"""
|
||||
print("测试模块导入...")
|
||||
|
||||
try:
|
||||
from app.tools.update_dialog import UpdateDialog, QuickUpdateChecker
|
||||
from app.tools.auto_updater import AutoUpdater, check_update_availability
|
||||
print("✅ 所有模块导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 导入失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🧪 测试修复后的更新功能\n")
|
||||
|
||||
tests = [
|
||||
("模块导入", test_imports),
|
||||
("对话框创建", test_dialog_creation),
|
||||
]
|
||||
|
||||
results = []
|
||||
for name, test_func in tests:
|
||||
print(f"🔍 {name}...")
|
||||
try:
|
||||
result = test_func()
|
||||
results.append((name, result))
|
||||
except Exception as e:
|
||||
print(f"❌ {name}异常: {e}")
|
||||
results.append((name, False))
|
||||
print()
|
||||
|
||||
print("="*40)
|
||||
print("测试结果:")
|
||||
passed = 0
|
||||
for name, result in results:
|
||||
status = "✅ 通过" if result else "❌ 失败"
|
||||
print(f"{name}: {status}")
|
||||
if result:
|
||||
passed += 1
|
||||
|
||||
print(f"\n总计: {passed}/{len(results)} 项测试通过")
|
||||
|
||||
if passed == len(results):
|
||||
print("🎉 修复成功!可以运行主程序了。")
|
||||
else:
|
||||
print("⚠️ 仍有问题需要解决。")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,182 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试基于Fluent Design的现代化更新对话框
|
||||
完全遵循qfluentwidgets设计规范,支持明暗主题
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
def test_fluent_dialog():
|
||||
"""测试Fluent Design更新对话框"""
|
||||
from qfluentwidgets import (
|
||||
setThemeColor, Theme, setTheme,
|
||||
PrimaryPushButton, PushButton, BodyLabel, SubtitleLabel,
|
||||
CardWidget, VBoxLayout, HBoxLayout, isDarkTheme
|
||||
)
|
||||
from app.tools.fluent_design_update import FluentUpdateDialog
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 设置主题
|
||||
setThemeColor('#f18cb9')
|
||||
setTheme(Theme.AUTO)
|
||||
|
||||
# 创建测试窗口
|
||||
window = QWidget()
|
||||
window.setWindowTitle("Fluent Design 更新对话框测试")
|
||||
window.setFixedSize(500, 400)
|
||||
|
||||
# 应用主题样式
|
||||
if isDarkTheme():
|
||||
window.setStyleSheet("background-color: #202020; color: white;")
|
||||
else:
|
||||
window.setStyleSheet("background-color: #FAFAFA; color: black;")
|
||||
|
||||
layout = VBoxLayout(window)
|
||||
layout.setContentsMargins(40, 40, 40, 40)
|
||||
layout.setSpacing(24)
|
||||
|
||||
# 创建测试卡片
|
||||
test_card = CardWidget()
|
||||
test_card.setFixedHeight(280)
|
||||
|
||||
card_layout = VBoxLayout(test_card)
|
||||
card_layout.setContentsMargins(32, 24, 32, 24)
|
||||
card_layout.setSpacing(20)
|
||||
|
||||
# 标题
|
||||
title = SubtitleLabel("MRobot 现代化更新测试")
|
||||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
card_layout.addWidget(title)
|
||||
|
||||
# 说明
|
||||
info = BodyLabel("这是基于QFluentWidgets设计系统的现代化自动更新界面测试。\n\n"
|
||||
"新界面特点:\n"
|
||||
"• 完全遵循Fluent Design规范\n"
|
||||
"• 自动适应明暗主题\n"
|
||||
"• 流畅的动画效果\n"
|
||||
"• 现代化的卡片设计\n"
|
||||
"• 清晰的视觉层次")
|
||||
info.setWordWrap(True)
|
||||
card_layout.addWidget(info)
|
||||
|
||||
# 主题切换提示
|
||||
theme_info = BodyLabel(f"当前主题: {'暗色模式' if isDarkTheme() else '亮色模式'}")
|
||||
theme_info.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
card_layout.addWidget(theme_info)
|
||||
|
||||
layout.addWidget(test_card)
|
||||
|
||||
# 按钮区域
|
||||
btn_layout = HBoxLayout()
|
||||
|
||||
test_btn = PrimaryPushButton("测试更新对话框")
|
||||
test_btn.clicked.connect(lambda: show_update_dialog(window))
|
||||
|
||||
theme_btn = PushButton("切换主题")
|
||||
theme_btn.clicked.connect(lambda: toggle_theme_and_refresh(window, theme_info))
|
||||
|
||||
btn_layout.addWidget(theme_btn)
|
||||
btn_layout.addStretch()
|
||||
btn_layout.addWidget(test_btn)
|
||||
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
def show_update_dialog(parent):
|
||||
"""显示更新对话框"""
|
||||
dialog = FluentUpdateDialog("1.0.0", parent) # 使用低版本触发更新
|
||||
dialog.exec_()
|
||||
|
||||
def toggle_theme_and_refresh(window, label):
|
||||
"""切换主题并刷新"""
|
||||
from qfluentwidgets import toggleTheme, isDarkTheme
|
||||
toggleTheme()
|
||||
|
||||
# 更新窗口样式
|
||||
if isDarkTheme():
|
||||
window.setStyleSheet("background-color: #202020; color: white;")
|
||||
label.setText("当前主题: 暗色模式")
|
||||
else:
|
||||
window.setStyleSheet("background-color: #FAFAFA; color: black;")
|
||||
label.setText("当前主题: 亮色模式")
|
||||
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
def test_components():
|
||||
"""测试组件导入"""
|
||||
tests = [
|
||||
("Fluent Design更新对话框", lambda: __import__('app.tools.fluent_design_update')),
|
||||
("qfluentwidgets组件", test_qfluentwidgets_components),
|
||||
("自动更新器", lambda: __import__('app.tools.auto_updater')),
|
||||
]
|
||||
|
||||
print("🎨 测试Fluent Design组件...")
|
||||
print("-" * 50)
|
||||
|
||||
for name, test_func in tests:
|
||||
try:
|
||||
test_func()
|
||||
print(f"✅ {name}: 导入成功")
|
||||
except Exception as e:
|
||||
print(f"❌ {name}: 导入失败 - {e}")
|
||||
|
||||
print("-" * 50)
|
||||
print("测试完成!")
|
||||
|
||||
def test_qfluentwidgets_components():
|
||||
"""测试qfluentwidgets组件"""
|
||||
from qfluentwidgets import (
|
||||
Dialog, CardWidget, SimpleCardWidget, ElevatedCardWidget,
|
||||
PrimaryPushButton, PushButton, TransparentPushButton,
|
||||
ProgressBar, ProgressRing, IndeterminateProgressBar,
|
||||
SubtitleLabel, BodyLabel, CaptionLabel, StrongBodyLabel, DisplayLabel,
|
||||
FluentIcon, InfoBar, ScrollArea, VBoxLayout, HBoxLayout,
|
||||
setTheme, Theme, isDarkTheme, toggleTheme
|
||||
)
|
||||
return True
|
||||
|
||||
def test_theme_switching():
|
||||
"""测试主题切换"""
|
||||
from qfluentwidgets import setTheme, Theme, isDarkTheme, toggleTheme
|
||||
|
||||
print("🌓 测试主题切换...")
|
||||
|
||||
# 测试设置主题
|
||||
setTheme(Theme.LIGHT)
|
||||
print(f"设置亮色主题 - 当前是否暗色: {isDarkTheme()}")
|
||||
|
||||
setTheme(Theme.DARK)
|
||||
print(f"设置暗色主题 - 当前是否暗色: {isDarkTheme()}")
|
||||
|
||||
setTheme(Theme.AUTO)
|
||||
print(f"设置自动主题 - 当前是否暗色: {isDarkTheme()}")
|
||||
|
||||
print("主题切换测试完成!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Fluent Design更新对话框测试")
|
||||
parser.add_argument("--mode", choices=["dialog", "components", "theme"],
|
||||
default="components", help="测试模式")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("🎨 Fluent Design 现代化更新对话框测试")
|
||||
print("=" * 60)
|
||||
|
||||
if args.mode == "dialog":
|
||||
print("启动图形界面测试...")
|
||||
test_fluent_dialog()
|
||||
elif args.mode == "components":
|
||||
test_components()
|
||||
elif args.mode == "theme":
|
||||
test_theme_switching()
|
||||
@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试基于QFluentWidgets的自动更新对话框
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
def test_fluent_update_dialog():
|
||||
"""测试Fluent风格更新对话框"""
|
||||
from qfluentwidgets import setThemeColor, Theme, setTheme
|
||||
from app.tools.fluent_update_dialog import FluentUpdateDialog
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 设置主题
|
||||
setThemeColor('#f18cb9')
|
||||
setTheme(Theme.AUTO)
|
||||
|
||||
# 创建对话框
|
||||
dialog = FluentUpdateDialog("1.0.0") # 使用较低版本来触发更新
|
||||
|
||||
# 显示对话框
|
||||
dialog.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
def test_import():
|
||||
"""测试导入"""
|
||||
try:
|
||||
from app.tools.fluent_update_dialog import FluentUpdateDialog, QuickUpdateNotification
|
||||
print("✅ Fluent更新对话框导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 导入失败: {e}")
|
||||
return False
|
||||
|
||||
def test_components():
|
||||
"""测试组件"""
|
||||
try:
|
||||
from qfluentwidgets import (
|
||||
CardWidget, PrimaryPushButton, ProgressBar,
|
||||
SubtitleLabel, BodyLabel, InfoBar, FluentIcon
|
||||
)
|
||||
print("✅ QFluentWidgets组件导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ QFluentWidgets组件导入失败: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="测试Fluent风格更新对话框")
|
||||
parser.add_argument("--mode", choices=["import", "components", "dialog"],
|
||||
default="import", help="测试模式")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.mode == "import":
|
||||
test_import()
|
||||
elif args.mode == "components":
|
||||
test_components()
|
||||
elif args.mode == "dialog":
|
||||
test_fluent_update_dialog()
|
||||
@ -1,116 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试现代化QFluentWidgets自动更新对话框
|
||||
展示完整的Fluent Design风格界面
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
def test_modern_dialog():
|
||||
"""测试现代化更新对话框"""
|
||||
from qfluentwidgets import setThemeColor, Theme, setTheme, PrimaryPushButton, BodyLabel
|
||||
from app.tools.modern_update_dialog import ModernUpdateDialog
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 设置主题
|
||||
setThemeColor('#f18cb9')
|
||||
setTheme(Theme.AUTO)
|
||||
|
||||
# 创建测试窗口
|
||||
window = QWidget()
|
||||
window.setWindowTitle("现代化更新对话框测试")
|
||||
window.setFixedSize(400, 300)
|
||||
|
||||
layout = QVBoxLayout(window)
|
||||
layout.setSpacing(20)
|
||||
layout.setContentsMargins(40, 40, 40, 40)
|
||||
|
||||
# 标题
|
||||
title = BodyLabel("MRobot 现代化更新测试")
|
||||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
title.setStyleSheet("font-size: 18px; font-weight: bold;")
|
||||
layout.addWidget(title)
|
||||
|
||||
# 说明
|
||||
info = BodyLabel("点击下方按钮测试基于QFluentWidgets的现代化更新界面")
|
||||
info.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
info.setWordWrap(True)
|
||||
layout.addWidget(info)
|
||||
|
||||
# 测试按钮
|
||||
test_btn = PrimaryPushButton("打开更新对话框")
|
||||
test_btn.clicked.connect(lambda: show_update_dialog(window))
|
||||
layout.addWidget(test_btn)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
def show_update_dialog(parent):
|
||||
dialog = ModernUpdateDialog("1.0.0", parent) # 使用低版本触发更新
|
||||
dialog.exec_()
|
||||
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
def test_components():
|
||||
"""测试组件导入"""
|
||||
tests = [
|
||||
("现代化对话框", lambda: __import__('app.tools.modern_update_dialog')),
|
||||
("Fluent组件", lambda: __import__('app.tools.fluent_components')),
|
||||
("自动更新器", lambda: __import__('app.tools.auto_updater')),
|
||||
]
|
||||
|
||||
print("🧪 测试组件导入...")
|
||||
print("-" * 40)
|
||||
|
||||
for name, test_func in tests:
|
||||
try:
|
||||
test_func()
|
||||
print(f"✅ {name}: 导入成功")
|
||||
except Exception as e:
|
||||
print(f"❌ {name}: 导入失败 - {e}")
|
||||
|
||||
print("-" * 40)
|
||||
print("测试完成!")
|
||||
|
||||
def test_qfluentwidgets():
|
||||
"""测试QFluentWidgets组件"""
|
||||
try:
|
||||
from qfluentwidgets import (
|
||||
Dialog, CardWidget, PrimaryPushButton, ProgressBar,
|
||||
SubtitleLabel, BodyLabel, InfoBar, FluentIcon,
|
||||
ElevatedCardWidget, SimpleCardWidget, HeaderCardWidget,
|
||||
TransparentToolButton, ProgressRing, PillPushButton
|
||||
)
|
||||
print("✅ QFluentWidgets高级组件导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ QFluentWidgets组件导入失败: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="现代化更新对话框测试")
|
||||
parser.add_argument("--mode", choices=["dialog", "components", "qfw"],
|
||||
default="components", help="测试模式")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("🚀 现代化自动更新对话框测试")
|
||||
print("=" * 50)
|
||||
|
||||
if args.mode == "dialog":
|
||||
print("启动图形界面测试...")
|
||||
test_modern_dialog()
|
||||
elif args.mode == "components":
|
||||
test_components()
|
||||
elif args.mode == "qfw":
|
||||
test_qfluentwidgets()
|
||||
@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from app.tools.fluent_design_update import FluentUpdateDialog
|
||||
|
||||
def test_ui():
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
try:
|
||||
dialog = FluentUpdateDialog("1.0.2")
|
||||
print("FluentUpdateDialog created successfully")
|
||||
dialog.show()
|
||||
app.exec_()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_ui()
|
||||
@ -1,89 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试简化的自动更新对话框
|
||||
确保稳定性和兼容性
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
def test_simple_dialog():
|
||||
"""测试简化对话框"""
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from qfluentwidgets import setThemeColor, Theme, setTheme
|
||||
from app.tools.simple_update_dialog import SimpleUpdateDialog
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 设置主题
|
||||
setThemeColor('#f18cb9')
|
||||
setTheme(Theme.AUTO)
|
||||
|
||||
# 创建对话框
|
||||
dialog = SimpleUpdateDialog("1.0.0") # 使用较低版本触发更新
|
||||
dialog.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
def test_imports():
|
||||
"""测试导入"""
|
||||
try:
|
||||
from app.tools.simple_update_dialog import SimpleUpdateDialog, QuickNotifier
|
||||
print("✅ 简化更新对话框导入成功")
|
||||
|
||||
from app.tools.auto_updater import AutoUpdater, check_update_availability
|
||||
print("✅ 自动更新器导入成功")
|
||||
|
||||
from qfluentwidgets import (
|
||||
CardWidget, PrimaryPushButton, ProgressBar,
|
||||
SubtitleLabel, BodyLabel, InfoBar, FluentIcon
|
||||
)
|
||||
print("✅ QFluentWidgets基础组件导入成功")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 导入失败: {e}")
|
||||
return False
|
||||
|
||||
def test_update_check():
|
||||
"""测试更新检查"""
|
||||
try:
|
||||
from app.tools.auto_updater import check_update_availability
|
||||
|
||||
result = check_update_availability("1.0.0")
|
||||
if result:
|
||||
print(f"✅ 检测到更新: v{result['version']}")
|
||||
else:
|
||||
print("ℹ️ 当前已是最新版本")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 更新检查失败: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="简化更新对话框测试")
|
||||
parser.add_argument("--mode", choices=["dialog", "imports", "check"],
|
||||
default="imports", help="测试模式")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("🧪 简化自动更新对话框测试")
|
||||
print("=" * 40)
|
||||
|
||||
if args.mode == "dialog":
|
||||
print("启动对话框测试...")
|
||||
test_simple_dialog()
|
||||
elif args.mode == "imports":
|
||||
test_imports()
|
||||
elif args.mode == "check":
|
||||
test_update_check()
|
||||
|
||||
print("测试完成!")
|
||||
@ -1,94 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试稳定的更新对话框
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
def test_stable_imports():
|
||||
"""测试稳定组件导入"""
|
||||
try:
|
||||
from app.tools.simple_update_components import (
|
||||
SimpleUpdateStatusCard, SimpleVersionCard, SimpleActionButtons
|
||||
)
|
||||
print("✅ 简化组件导入成功")
|
||||
|
||||
from app.tools.stable_update_dialog import StableUpdateDialog, SimpleUpdateNotifier
|
||||
print("✅ 稳定对话框导入成功")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 导入失败: {e}")
|
||||
return False
|
||||
|
||||
def test_stable_dialog():
|
||||
"""测试稳定对话框"""
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from qfluentwidgets import setThemeColor, Theme, setTheme
|
||||
from app.tools.stable_update_dialog import StableUpdateDialog
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 设置主题
|
||||
setThemeColor('#f18cb9')
|
||||
setTheme(Theme.AUTO)
|
||||
|
||||
# 创建对话框
|
||||
dialog = StableUpdateDialog("1.0.0") # 使用较低版本来触发更新
|
||||
|
||||
# 显示对话框
|
||||
dialog.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
def test_about_interface():
|
||||
"""测试关于页面集成"""
|
||||
try:
|
||||
from app.about_interface import AboutInterface
|
||||
print("✅ 关于页面导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 关于页面导入失败: {e}")
|
||||
return False
|
||||
|
||||
def run_all_tests():
|
||||
"""运行所有测试"""
|
||||
print("🧪 测试稳定的更新功能\n")
|
||||
|
||||
tests = [
|
||||
("稳定组件导入", test_stable_imports),
|
||||
("关于页面集成", test_about_interface),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
for name, test_func in tests:
|
||||
try:
|
||||
if test_func():
|
||||
print(f"✅ {name}: 通过")
|
||||
passed += 1
|
||||
else:
|
||||
print(f"❌ {name}: 失败")
|
||||
except Exception as e:
|
||||
print(f"❌ {name}: 异常 - {e}")
|
||||
|
||||
print(f"\n📊 测试结果: {passed}/{len(tests)} 通过")
|
||||
return passed == len(tests)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="测试稳定更新对话框")
|
||||
parser.add_argument("--mode", choices=["test", "dialog"],
|
||||
default="test", help="运行模式")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.mode == "test":
|
||||
success = run_all_tests()
|
||||
sys.exit(0 if success else 1)
|
||||
elif args.mode == "dialog":
|
||||
test_stable_dialog()
|
||||
@ -1,111 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试修复后的自动更新功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
def test_simple_dialog_import():
|
||||
"""测试简化对话框导入"""
|
||||
try:
|
||||
from app.tools.update_dialog_simple import SimpleUpdateDialog, QuickUpdateChecker
|
||||
print("✅ 简化对话框导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 简化对话框导入失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def test_dialog_creation():
|
||||
"""测试对话框创建(不显示)"""
|
||||
try:
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from app.tools.update_dialog_simple import SimpleUpdateDialog
|
||||
|
||||
# 创建应用程序实例(如果还没有的话)
|
||||
if not QApplication.instance():
|
||||
app = QApplication([])
|
||||
|
||||
# 创建对话框但不显示
|
||||
dialog = SimpleUpdateDialog("1.0.0")
|
||||
print("✅ 对话框创建成功")
|
||||
|
||||
# 清理
|
||||
dialog.deleteLater()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 对话框创建失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def test_about_interface_import():
|
||||
"""测试关于界面导入"""
|
||||
try:
|
||||
from app.about_interface import AboutInterface
|
||||
print("✅ 关于界面导入成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 关于界面导入失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def run_tests():
|
||||
"""运行所有测试"""
|
||||
print("🔧 测试修复后的自动更新功能\n")
|
||||
|
||||
tests = [
|
||||
("简化对话框导入", test_simple_dialog_import),
|
||||
("对话框创建", test_dialog_creation),
|
||||
("关于界面导入", test_about_interface_import),
|
||||
]
|
||||
|
||||
results = []
|
||||
for name, test_func in tests:
|
||||
print(f"测试 {name}...")
|
||||
try:
|
||||
result = test_func()
|
||||
results.append((name, result))
|
||||
except Exception as e:
|
||||
print(f"❌ {name} 测试异常: {e}")
|
||||
results.append((name, False))
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print("测试结果:")
|
||||
print("=" * 50)
|
||||
|
||||
passed = 0
|
||||
for name, result in results:
|
||||
status = "✅ 通过" if result else "❌ 失败"
|
||||
print(f"{name}: {status}")
|
||||
if result:
|
||||
passed += 1
|
||||
|
||||
print(f"\n总计: {passed}/{len(results)} 项测试通过")
|
||||
|
||||
if passed == len(results):
|
||||
print("🎉 所有测试通过!修复成功,自动更新功能应该不会再闪退。")
|
||||
else:
|
||||
print("⚠️ 部分测试失败,可能还有问题需要修复。")
|
||||
|
||||
return passed == len(results)
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_tests()
|
||||
|
||||
if success:
|
||||
print("\n💡 使用提示:")
|
||||
print("1. 现在可以在'关于'页面点击'自动更新'按钮")
|
||||
print("2. 更新过程中会显示详细的进度条和状态")
|
||||
print("3. 更新完成后程序会自动重启")
|
||||
print("4. 如果更新失败会显示错误信息并允许重试")
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
@ -1,44 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试版本检查逻辑
|
||||
"""
|
||||
|
||||
from packaging.version import parse as vparse
|
||||
|
||||
def test_version_comparison():
|
||||
"""测试版本比较"""
|
||||
current_version = "1.0.2"
|
||||
|
||||
# 测试不同的版本情况
|
||||
test_versions = ["1.0.1", "1.0.2", "1.0.3", "1.0.5", "1.1.0", "2.0.0"]
|
||||
|
||||
print(f"当前版本: {current_version}")
|
||||
print("-" * 40)
|
||||
|
||||
for version in test_versions:
|
||||
is_newer = vparse(version) > vparse(current_version)
|
||||
status = "有更新" if is_newer else "无更新"
|
||||
print(f"版本 {version}: {status}")
|
||||
|
||||
|
||||
def simulate_check_update(local_version, remote_version):
|
||||
"""模拟更新检查"""
|
||||
print(f"\n模拟检查: 本地版本 {local_version} vs 远程版本 {remote_version}")
|
||||
|
||||
if vparse(remote_version) > vparse(local_version):
|
||||
print("✓ 发现新版本")
|
||||
return remote_version
|
||||
else:
|
||||
print("✗ 已是最新版本")
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_version_comparison()
|
||||
|
||||
# 模拟你遇到的情况
|
||||
print("\n" + "="*50)
|
||||
print("模拟实际情况:")
|
||||
simulate_check_update("1.0.2", "1.0.5")
|
||||
simulate_check_update("1.0.2", "1.0.2")
|
||||
simulate_check_update("1.0.2", "1.0.1")
|
||||
224
自动更新功能说明.md
224
自动更新功能说明.md
@ -1,224 +0,0 @@
|
||||
# 自动更新功能使用说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
MRobot 现在支持完整的自动更新功能,包括:
|
||||
|
||||
- ✅ 自动检查更新
|
||||
- ✅ 自动下载更新包
|
||||
- ✅ 自动解压安装
|
||||
- ✅ 自动重启程序
|
||||
- ✅ 更新失败回滚
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 启动时自动检查更新
|
||||
|
||||
程序启动 3 秒后会自动在后台检查更新。如果发现新版本,会显示通知提示。
|
||||
|
||||
### 2. 手动检查更新
|
||||
|
||||
在"关于"页面点击:
|
||||
- **自动更新**: 打开完整的自动更新对话框
|
||||
- **手动检查**: 检查更新并在浏览器中打开下载页面
|
||||
|
||||
### 3. 自动更新流程
|
||||
|
||||
1. 点击"自动更新"按钮
|
||||
2. 系统自动检查是否有新版本
|
||||
3. 如有更新,显示版本信息和更新说明
|
||||
4. 点击"开始更新"确认更新
|
||||
5. 系统自动完成:
|
||||
- 下载更新包
|
||||
- 备份当前文件
|
||||
- 解压安装新版本
|
||||
- 重启程序
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心组件
|
||||
|
||||
1. **AutoUpdater** (`auto_updater.py`)
|
||||
- 主要更新逻辑
|
||||
- 多线程下载和安装
|
||||
- 错误处理和回滚
|
||||
|
||||
2. **UpdateDialog** (`update_dialog.py`)
|
||||
- 用户界面
|
||||
- 进度显示
|
||||
- 用户交互
|
||||
|
||||
3. **QuickUpdateChecker**
|
||||
- 后台更新检查
|
||||
- 通知显示
|
||||
|
||||
### 更新检查逻辑
|
||||
|
||||
```python
|
||||
# 检查 GitHub Releases API
|
||||
url = f"https://api.github.com/repos/{repo}/releases/latest"
|
||||
response = requests.get(url, timeout=10)
|
||||
release_data = response.json()
|
||||
|
||||
# 比较版本号
|
||||
from packaging.version import parse as vparse
|
||||
if vparse(latest_version) > vparse(current_version):
|
||||
# 有新版本可用
|
||||
```
|
||||
|
||||
### 下载逻辑
|
||||
|
||||
```python
|
||||
# 根据操作系统选择合适的安装包
|
||||
system = platform.system().lower()
|
||||
if system == 'windows':
|
||||
# 查找 .exe 或 .zip 文件
|
||||
elif system == 'darwin': # macOS
|
||||
# 查找 .dmg 或 .zip 文件
|
||||
elif system == 'linux':
|
||||
# 查找 .tar.gz 或 .zip 文件
|
||||
```
|
||||
|
||||
### 安装逻辑
|
||||
|
||||
1. **备份当前文件**
|
||||
```python
|
||||
# 备份重要文件到临时目录
|
||||
backup_files = ['MRobot.py', 'app/', 'assets/']
|
||||
```
|
||||
|
||||
2. **解压新版本**
|
||||
```python
|
||||
# 支持 .zip 和 .tar.gz 格式
|
||||
with zipfile.ZipFile(file_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(extract_dir)
|
||||
```
|
||||
|
||||
3. **复制文件**
|
||||
```python
|
||||
# 替换应用程序文件
|
||||
shutil.copytree(src_path, dst_path)
|
||||
```
|
||||
|
||||
4. **重启程序**
|
||||
```python
|
||||
# 启动新进程并退出当前进程
|
||||
subprocess.Popen([executable])
|
||||
sys.exit(0)
|
||||
```
|
||||
|
||||
## 配置选项
|
||||
|
||||
在 `update_config.py` 中可以配置:
|
||||
|
||||
```python
|
||||
# 基本设置
|
||||
AUTO_UPDATE_ENABLED = True # 启用自动更新
|
||||
AUTO_UPDATE_ON_STARTUP = True # 启动时检查
|
||||
UPDATE_CHECK_INTERVAL = 3000 # 检查延迟(毫秒)
|
||||
|
||||
# 下载设置
|
||||
DOWNLOAD_TIMEOUT = 300 # 下载超时
|
||||
CHUNK_SIZE = 8192 # 下载块大小
|
||||
|
||||
# 备份设置
|
||||
CREATE_BACKUP = True # 创建备份
|
||||
BACKUP_IMPORTANT_FILES = [...] # 备份文件列表
|
||||
```
|
||||
|
||||
## 安全考虑
|
||||
|
||||
1. **数字签名验证** (未实现,可扩展)
|
||||
- 验证下载文件的数字签名
|
||||
- 确保文件来源可信
|
||||
|
||||
2. **备份和回滚**
|
||||
- 更新前自动备份
|
||||
- 更新失败自动回滚
|
||||
|
||||
3. **用户确认**
|
||||
- 所有更新操作需要用户确认
|
||||
- 不会自动静默更新
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **网络连接问题**
|
||||
- 检查网络连接
|
||||
- 检查防火墙设置
|
||||
- 尝试手动更新
|
||||
|
||||
2. **权限问题**
|
||||
- 确保程序有写入权限
|
||||
- 以管理员身份运行(Windows)
|
||||
|
||||
3. **文件被占用**
|
||||
- 关闭其他实例
|
||||
- 重启计算机后再试
|
||||
|
||||
### 错误日志
|
||||
|
||||
更新过程中的错误会显示在界面上,并可在控制台查看详细信息。
|
||||
|
||||
### 手动回滚
|
||||
|
||||
如果自动回滚失败,可以:
|
||||
1. 从临时目录恢复备份文件
|
||||
2. 重新下载完整安装包
|
||||
3. 联系技术支持
|
||||
|
||||
## 开发者信息
|
||||
|
||||
### 扩展功能
|
||||
|
||||
可以在现有基础上添加:
|
||||
|
||||
1. **增量更新**
|
||||
- 只下载变更的文件
|
||||
- 减少下载时间和流量
|
||||
|
||||
2. **多源下载**
|
||||
- 支持多个下载镜像
|
||||
- 提高下载成功率
|
||||
|
||||
3. **版本回退**
|
||||
- 支持回退到指定版本
|
||||
- 版本历史管理
|
||||
|
||||
4. **自定义更新源**
|
||||
- 支持企业内部更新服务器
|
||||
- 自定义更新策略
|
||||
|
||||
### API 接口
|
||||
|
||||
```python
|
||||
# 检查更新
|
||||
update_info = check_update_availability(current_version)
|
||||
|
||||
# 启动自动更新
|
||||
dialog = UpdateDialog(current_version, parent)
|
||||
dialog.exec_()
|
||||
|
||||
# 后台检查
|
||||
QuickUpdateChecker.check_and_notify(version, parent)
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保 GitHub Releases 中有对应平台的安装包
|
||||
2. 安装包命名要规范,便于识别平台
|
||||
3. 更新说明要写在 Release Notes 中
|
||||
4. 版本号要遵循语义化版本规范 (如: v1.0.5)
|
||||
|
||||
## 版本兼容性
|
||||
|
||||
- PyQt5 5.x+
|
||||
- Python 3.7+
|
||||
- requests 2.x+
|
||||
- packaging 20.x+
|
||||
|
||||
支持的操作系统:
|
||||
- Windows 7+
|
||||
- macOS 10.12+
|
||||
- Ubuntu 18.04+
|
||||
Loading…
Reference in New Issue
Block a user