This commit is contained in:
Robofish 2025-10-13 15:21:29 +08:00
parent e5d5afb1a8
commit b96137d807
38 changed files with 448 additions and 1339 deletions

View File

@ -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,6 +376,16 @@ class AboutInterface(QWidget):
self.update_btn.setEnabled(True)
self.manual_btn.setEnabled(True)
# 如果是平台兼容性问题,提供更友好的提示
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,
@ -284,26 +394,88 @@ class AboutInterface(QWidget):
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)
# 重新启用按钮
self.update_btn.setEnabled(True)
self.manual_btn.setEnabled(True)
if file_path and os.path.exists(file_path):
print(f"File exists: {file_path}") # 调试输出
InfoBar.success(
title="更新完成",
content="更新安装完成程序将在3秒后重启",
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
)
# 延迟重启
QTimer.singleShot(3000, self._restart_app)
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.

View File

@ -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,18 +111,33 @@ 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 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
def _get_asset_name(self, release_data: dict) -> Optional[str]:
@ -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)
self.signals.status_changed.emit("下载完成")
# 发送详细进度信息
speed = 0 # 简化版本不计算速度
remaining = 0
self.signals.download_progress.emit(downloaded_size, total_size, speed, remaining)
# 验证下载的文件
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]:

View File

@ -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"
@ -12,3 +14,61 @@ def check_update(local_version, repo="goldenfishs/MRobot"):
return None
else:
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

View 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))

View File

@ -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;
}

View File

@ -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演示完成!")

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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("测试完成!")

View File

@ -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()

View File

@ -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)

View File

@ -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")

View File

@ -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+