mirror of
https://github.com/goldenfishs/MRobot.git
synced 2026-03-31 21:07:14 +08:00
好
This commit is contained in:
@@ -1,65 +1,350 @@
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QMessageBox
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout
|
||||
from PyQt5.QtCore import Qt, QUrl, QTimer
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
|
||||
from qfluentwidgets import PrimaryPushSettingCard, FluentIcon
|
||||
from qfluentwidgets import InfoBar, InfoBarPosition, SubtitleLabel
|
||||
from qfluentwidgets import (
|
||||
PrimaryPushSettingCard, FluentIcon, InfoBar, InfoBarPosition,
|
||||
SubtitleLabel, BodyLabel, CaptionLabel, StrongBodyLabel,
|
||||
ElevatedCardWidget, PrimaryPushButton, PushButton,
|
||||
ProgressBar, TextEdit
|
||||
)
|
||||
|
||||
from .function_fit_interface import FunctionFitInterface
|
||||
from app.tools.check_update import check_update
|
||||
from app.tools.auto_updater import AutoUpdater, check_update_availability
|
||||
|
||||
__version__ = "1.0.5"
|
||||
__version__ = "1.0.2"
|
||||
|
||||
class AboutInterface(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setObjectName("aboutInterface")
|
||||
|
||||
|
||||
# 初始化更新相关变量
|
||||
self.updater = None
|
||||
self.update_info = None
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
"""设置用户界面"""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setAlignment(Qt.AlignTop)
|
||||
layout.setContentsMargins(20, 30, 20, 20) # 添加边距
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
layout.setContentsMargins(20, 30, 20, 20)
|
||||
|
||||
# 页面标题
|
||||
title = SubtitleLabel("MRobot 帮助页面", self)
|
||||
title.setAlignment(Qt.AlignCenter)
|
||||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.addWidget(title)
|
||||
# 添加空间隔
|
||||
layout.addSpacing(10)
|
||||
|
||||
card = PrimaryPushSettingCard(
|
||||
text="检查更新",
|
||||
icon=FluentIcon.DOWNLOAD,
|
||||
title="更新",
|
||||
content=f"MRobot_Toolbox 当前版本:{__version__}",
|
||||
)
|
||||
card.clicked.connect(self.on_check_update_clicked)
|
||||
layout.addWidget(card)
|
||||
# 版本信息卡片 - 学习AI界面风格
|
||||
version_card = ElevatedCardWidget()
|
||||
version_layout = QVBoxLayout(version_card)
|
||||
version_layout.setContentsMargins(24, 20, 24, 20)
|
||||
|
||||
version_title = StrongBodyLabel("版本信息")
|
||||
version_layout.addWidget(version_title)
|
||||
|
||||
current_version_label = BodyLabel(f"当前版本:v{__version__}")
|
||||
version_layout.addWidget(current_version_label)
|
||||
|
||||
def on_check_update_clicked(self):
|
||||
|
||||
layout.addWidget(version_card)
|
||||
|
||||
# 检查更新按钮
|
||||
self.check_update_card = PrimaryPushSettingCard(
|
||||
text="检查更新",
|
||||
icon=FluentIcon.SYNC,
|
||||
title="检查更新",
|
||||
content="检查是否有新版本可用",
|
||||
)
|
||||
self.check_update_card.clicked.connect(self.check_for_updates)
|
||||
layout.addWidget(self.check_update_card)
|
||||
|
||||
# 更新信息卡片(初始隐藏)
|
||||
self.update_info_card = ElevatedCardWidget()
|
||||
self.update_info_card.hide()
|
||||
self._setup_update_info_card()
|
||||
layout.addWidget(self.update_info_card)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
def _setup_update_info_card(self):
|
||||
"""设置更新信息卡片"""
|
||||
layout = QVBoxLayout(self.update_info_card)
|
||||
layout.setContentsMargins(24, 20, 24, 20)
|
||||
layout.setSpacing(16)
|
||||
|
||||
# 标题
|
||||
self.update_title = StrongBodyLabel("发现新版本")
|
||||
layout.addWidget(self.update_title)
|
||||
|
||||
# 版本对比
|
||||
version_layout = QHBoxLayout()
|
||||
|
||||
current_layout = QVBoxLayout()
|
||||
current_layout.addWidget(CaptionLabel("当前版本"))
|
||||
self.current_version_label = SubtitleLabel(f"v{__version__}")
|
||||
current_layout.addWidget(self.current_version_label)
|
||||
|
||||
arrow_label = SubtitleLabel("→")
|
||||
arrow_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
arrow_label.setFixedWidth(30)
|
||||
|
||||
latest_layout = QVBoxLayout()
|
||||
latest_layout.addWidget(CaptionLabel("最新版本"))
|
||||
self.latest_version_label = SubtitleLabel("v--")
|
||||
latest_layout.addWidget(self.latest_version_label)
|
||||
|
||||
version_layout.addLayout(current_layout)
|
||||
version_layout.addWidget(arrow_label)
|
||||
version_layout.addLayout(latest_layout)
|
||||
|
||||
layout.addLayout(version_layout)
|
||||
|
||||
# 更新信息
|
||||
info_layout = QHBoxLayout()
|
||||
self.file_size_label = BodyLabel("文件大小: --")
|
||||
self.release_date_label = BodyLabel("发布时间: --")
|
||||
|
||||
info_layout.addWidget(self.file_size_label)
|
||||
info_layout.addStretch()
|
||||
info_layout.addWidget(self.release_date_label)
|
||||
|
||||
layout.addLayout(info_layout)
|
||||
|
||||
# 更新说明
|
||||
layout.addWidget(CaptionLabel("更新说明:"))
|
||||
|
||||
self.notes_display = TextEdit()
|
||||
self.notes_display.setReadOnly(True)
|
||||
self.notes_display.setFixedHeight(200)
|
||||
self.notes_display.setText("暂无更新说明")
|
||||
layout.addWidget(self.notes_display)
|
||||
|
||||
# 进度条(初始隐藏)
|
||||
self.progress_widget = QWidget()
|
||||
progress_layout = QVBoxLayout(self.progress_widget)
|
||||
|
||||
self.progress_label = BodyLabel("准备更新...")
|
||||
progress_layout.addWidget(self.progress_label)
|
||||
|
||||
self.progress_bar = ProgressBar()
|
||||
self.progress_bar.setRange(0, 100)
|
||||
self.progress_bar.setValue(0)
|
||||
progress_layout.addWidget(self.progress_bar)
|
||||
|
||||
self.progress_widget.hide()
|
||||
layout.addWidget(self.progress_widget)
|
||||
|
||||
# 按钮区域
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
self.manual_btn = PushButton("手动下载")
|
||||
self.manual_btn.setIcon(FluentIcon.LINK)
|
||||
self.manual_btn.clicked.connect(self.open_manual_download)
|
||||
|
||||
self.update_btn = PrimaryPushButton("开始更新")
|
||||
self.update_btn.setIcon(FluentIcon.DOWNLOAD)
|
||||
self.update_btn.clicked.connect(self.start_update)
|
||||
|
||||
self.cancel_btn = PushButton("取消")
|
||||
self.cancel_btn.clicked.connect(self.cancel_update)
|
||||
|
||||
button_layout.addWidget(self.manual_btn)
|
||||
button_layout.addStretch()
|
||||
button_layout.addWidget(self.update_btn)
|
||||
button_layout.addWidget(self.cancel_btn)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
def check_for_updates(self):
|
||||
"""检查更新"""
|
||||
self.check_update_card.setEnabled(False)
|
||||
self.check_update_card.setContent("正在检查更新...")
|
||||
|
||||
# 延迟执行检查,避免阻塞UI
|
||||
QTimer.singleShot(100, self._perform_check)
|
||||
|
||||
def _perform_check(self):
|
||||
"""执行更新检查"""
|
||||
try:
|
||||
latest = check_update(__version__)
|
||||
if latest:
|
||||
# 直接用浏览器打开下载链接
|
||||
QDesktopServices.openUrl(QUrl("https://github.com/goldenfishs/MRobot/releases/latest"))
|
||||
InfoBar.success(
|
||||
title="发现新版本",
|
||||
content=f"检测到新版本:{latest},已为你打开下载页面。",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=5000
|
||||
)
|
||||
elif latest is None:
|
||||
InfoBar.info(
|
||||
title="已是最新版本",
|
||||
content="当前已是最新版本,无需更新。",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=3000
|
||||
)
|
||||
except Exception:
|
||||
InfoBar.error(
|
||||
title="检查更新失败",
|
||||
content="无法获取最新版本,请检查网络连接。",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=4000
|
||||
)
|
||||
self.update_info = check_update_availability(__version__)
|
||||
|
||||
if self.update_info:
|
||||
self._show_update_available()
|
||||
else:
|
||||
self._show_no_update()
|
||||
|
||||
except Exception as e:
|
||||
self._show_error(f"检查更新失败: {str(e)}")
|
||||
|
||||
def _show_update_available(self):
|
||||
"""显示发现更新"""
|
||||
# 更新按钮状态
|
||||
self.check_update_card.setEnabled(True)
|
||||
self.check_update_card.setContent("发现新版本!")
|
||||
|
||||
# 显示更新信息卡片
|
||||
self.update_info_card.show()
|
||||
|
||||
# 设置版本信息
|
||||
if self.update_info:
|
||||
version = self.update_info.get('version', 'Unknown')
|
||||
self.latest_version_label.setText(f"v{version}")
|
||||
|
||||
# 设置文件信息
|
||||
asset_size = self.update_info.get('asset_size', 0)
|
||||
file_size = self._format_file_size(asset_size)
|
||||
self.file_size_label.setText(f"文件大小: {file_size}")
|
||||
|
||||
# 设置发布时间
|
||||
release_date = self.update_info.get('release_date', '')
|
||||
formatted_date = self._format_date(release_date)
|
||||
self.release_date_label.setText(f"发布时间: {formatted_date}")
|
||||
|
||||
# 设置更新说明
|
||||
notes = self.update_info.get('release_notes', '暂无更新说明')
|
||||
self.notes_display.setText(notes[:500] + ('...' if len(notes) > 500 else ''))
|
||||
|
||||
InfoBar.success(
|
||||
title="发现新版本",
|
||||
content=f"检测到新版本 v{version}",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=3000
|
||||
)
|
||||
|
||||
def _show_no_update(self):
|
||||
"""显示无更新"""
|
||||
self.check_update_card.setEnabled(True)
|
||||
self.check_update_card.setContent("已是最新版本")
|
||||
|
||||
InfoBar.info(
|
||||
title="已是最新版本",
|
||||
content="当前已是最新版本,无需更新。",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=3000
|
||||
)
|
||||
|
||||
def _show_error(self, error_msg: str):
|
||||
"""显示错误"""
|
||||
self.check_update_card.setEnabled(True)
|
||||
self.check_update_card.setContent("检查更新失败")
|
||||
|
||||
InfoBar.error(
|
||||
title="检查更新失败",
|
||||
content=error_msg,
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=4000
|
||||
)
|
||||
|
||||
def start_update(self):
|
||||
"""开始更新"""
|
||||
if not self.update_info:
|
||||
return
|
||||
|
||||
# 显示进度UI
|
||||
self.progress_widget.show()
|
||||
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.status_changed.connect(self.update_status)
|
||||
self.updater.signals.error_occurred.connect(self.update_error)
|
||||
self.updater.signals.update_completed.connect(self.update_completed)
|
||||
|
||||
# 开始更新流程
|
||||
self.updater.start()
|
||||
|
||||
def update_progress(self, value: int):
|
||||
"""更新进度"""
|
||||
self.progress_bar.setValue(value)
|
||||
|
||||
def update_status(self, status: str):
|
||||
"""更新状态"""
|
||||
self.progress_label.setText(status)
|
||||
|
||||
def update_error(self, error_msg: str):
|
||||
"""更新错误"""
|
||||
self.progress_widget.hide()
|
||||
self.update_btn.setEnabled(True)
|
||||
self.manual_btn.setEnabled(True)
|
||||
|
||||
InfoBar.error(
|
||||
title="更新失败",
|
||||
content=error_msg,
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=4000
|
||||
)
|
||||
|
||||
def update_completed(self):
|
||||
"""更新完成"""
|
||||
self.progress_label.setText("更新完成,准备重启...")
|
||||
self.progress_bar.setValue(100)
|
||||
|
||||
InfoBar.success(
|
||||
title="更新完成",
|
||||
content="更新安装完成,程序将在3秒后重启",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=3000
|
||||
)
|
||||
|
||||
# 延迟重启
|
||||
QTimer.singleShot(3000, self._restart_app)
|
||||
|
||||
def cancel_update(self):
|
||||
"""取消更新"""
|
||||
if self.updater and self.updater.isRunning():
|
||||
self.updater.cancel_update()
|
||||
|
||||
self.update_info_card.hide()
|
||||
self.check_update_card.setContent("检查是否有新版本可用")
|
||||
|
||||
def open_manual_download(self):
|
||||
"""打开手动下载页面"""
|
||||
QDesktopServices.openUrl(QUrl("https://github.com/goldenfishs/MRobot/releases/latest"))
|
||||
|
||||
InfoBar.info(
|
||||
title="手动下载",
|
||||
content="已为您打开下载页面",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000
|
||||
)
|
||||
|
||||
def _restart_app(self):
|
||||
"""重启应用程序"""
|
||||
if self.updater:
|
||||
self.updater.restart_application()
|
||||
|
||||
def _format_file_size(self, size_bytes: int) -> str:
|
||||
"""格式化文件大小"""
|
||||
if size_bytes == 0:
|
||||
return "--"
|
||||
|
||||
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 _format_date(self, date_str: str) -> str:
|
||||
"""格式化日期"""
|
||||
if not date_str:
|
||||
return "--"
|
||||
|
||||
try:
|
||||
from datetime import datetime
|
||||
date_obj = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
|
||||
return date_obj.strftime('%Y-%m-%d')
|
||||
except:
|
||||
return date_str[:10] if len(date_str) >= 10 else date_str
|
||||
@@ -25,9 +25,8 @@ class MainWindow(FluentWindow):
|
||||
self.initInterface()
|
||||
self.initNavigation()
|
||||
|
||||
# 检查更新
|
||||
# checkUpdate(self, flag=True)
|
||||
# checkAnnouncement(self) # 检查公告
|
||||
# 后台检查更新(不弹窗,只显示通知)
|
||||
# self.check_updates_in_background()
|
||||
|
||||
def initWindow(self):
|
||||
self.setMicaEffectEnabled(False)
|
||||
@@ -74,6 +73,14 @@ class MainWindow(FluentWindow):
|
||||
None,
|
||||
NavigationItemPosition.BOTTOM
|
||||
)
|
||||
|
||||
def check_updates_in_background(self):
|
||||
"""后台检查更新"""
|
||||
try:
|
||||
# 后台更新检查已移至关于页面手动触发
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"初始化完成: {e}")
|
||||
|
||||
# main_window.py 只需修改关闭事件
|
||||
def closeEvent(self, e):
|
||||
|
||||
340
app/tools/auto_updater.py
Normal file
340
app/tools/auto_updater.py
Normal file
@@ -0,0 +1,340 @@
|
||||
"""
|
||||
自动更新模块
|
||||
实现软件的自动更新功能,包括下载、解压、安装等完整流程
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
import subprocess
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from typing import Optional, Callable
|
||||
from urllib.parse import urlparse
|
||||
|
||||
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)
|
||||
status_changed = pyqtSignal(str) # 状态变化信号
|
||||
error_occurred = pyqtSignal(str) # 错误信号
|
||||
update_completed = pyqtSignal() # 更新完成信号
|
||||
update_cancelled = pyqtSignal() # 更新取消信号
|
||||
|
||||
|
||||
class AutoUpdater(QThread):
|
||||
"""自动更新器类"""
|
||||
|
||||
def __init__(self, current_version: str, repo: str = "goldenfishs/MRobot"):
|
||||
super().__init__()
|
||||
self.current_version = current_version
|
||||
self.repo = repo
|
||||
self.signals = UpdaterSignals()
|
||||
self.cancelled = False
|
||||
|
||||
# 获取当前程序信息
|
||||
self.is_frozen = getattr(sys, 'frozen', False)
|
||||
self.app_dir = self._get_app_directory()
|
||||
self.temp_dir = None
|
||||
|
||||
def _get_app_directory(self) -> str:
|
||||
"""获取应用程序目录"""
|
||||
if self.is_frozen:
|
||||
# 如果是打包的exe,返回exe所在目录
|
||||
return os.path.dirname(sys.executable)
|
||||
else:
|
||||
# 如果是Python脚本,返回项目根目录
|
||||
return os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
def cancel_update(self):
|
||||
"""取消更新"""
|
||||
self.cancelled = True
|
||||
self.signals.update_cancelled.emit()
|
||||
|
||||
def check_for_updates(self) -> Optional[dict]:
|
||||
"""检查是否有新版本可用"""
|
||||
try:
|
||||
self.signals.status_changed.emit("正在检查更新...")
|
||||
|
||||
url = f"https://api.github.com/repos/{self.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(self.current_version):
|
||||
return {
|
||||
'version': latest_version,
|
||||
'download_url': self._get_download_url(release_data),
|
||||
'release_notes': release_data.get('body', ''),
|
||||
'release_date': release_data.get('published_at', ''),
|
||||
'asset_name': self._get_asset_name(release_data)
|
||||
}
|
||||
return None
|
||||
else:
|
||||
raise Exception(f"GitHub API请求失败: {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
self.signals.error_occurred.emit(f"检查更新失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def _get_download_url(self, release_data: dict) -> Optional[str]:
|
||||
"""从release数据中获取适合当前平台的下载链接"""
|
||||
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']
|
||||
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'):
|
||||
return asset['browser_download_url']
|
||||
elif system == 'linux':
|
||||
if name.endswith('.tar.gz') or name.endswith('.zip'):
|
||||
return asset['browser_download_url']
|
||||
|
||||
# 如果没找到特定平台的,返回第一个可用文件
|
||||
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']
|
||||
|
||||
return None
|
||||
|
||||
def _get_asset_name(self, release_data: dict) -> Optional[str]:
|
||||
"""获取资源文件名"""
|
||||
download_url = self._get_download_url(release_data)
|
||||
if download_url:
|
||||
return os.path.basename(urlparse(download_url).path)
|
||||
return None
|
||||
|
||||
def download_update(self, download_url: str, filename: str) -> Optional[str]:
|
||||
"""下载更新文件"""
|
||||
try:
|
||||
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)
|
||||
response.raise_for_status()
|
||||
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
downloaded_size = 0
|
||||
|
||||
with open(file_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if self.cancelled:
|
||||
return None
|
||||
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
|
||||
if total_size > 0:
|
||||
progress = int((downloaded_size / total_size) * 50) # 下载占50%进度
|
||||
self.signals.progress_changed.emit(progress)
|
||||
|
||||
self.signals.status_changed.emit("下载完成")
|
||||
return file_path
|
||||
|
||||
except Exception as e:
|
||||
self.signals.error_occurred.emit(f"下载失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def extract_update(self, file_path: str) -> Optional[str]:
|
||||
"""解压更新文件"""
|
||||
try:
|
||||
self.signals.status_changed.emit("正在解压文件...")
|
||||
self.signals.progress_changed.emit(50)
|
||||
|
||||
if not self.temp_dir:
|
||||
raise Exception("临时目录未初始化")
|
||||
|
||||
extract_dir = os.path.join(self.temp_dir, "extracted")
|
||||
os.makedirs(extract_dir, exist_ok=True)
|
||||
|
||||
# 根据文件扩展名选择解压方法
|
||||
if file_path.endswith('.zip'):
|
||||
with zipfile.ZipFile(file_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(extract_dir)
|
||||
elif file_path.endswith('.tar.gz'):
|
||||
import tarfile
|
||||
with tarfile.open(file_path, 'r:gz') as tar_ref:
|
||||
tar_ref.extractall(extract_dir)
|
||||
else:
|
||||
raise Exception(f"不支持的文件格式: {file_path}")
|
||||
|
||||
self.signals.progress_changed.emit(70)
|
||||
self.signals.status_changed.emit("解压完成")
|
||||
return extract_dir
|
||||
|
||||
except Exception as e:
|
||||
self.signals.error_occurred.emit(f"解压失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def install_update(self, extract_dir: str) -> bool:
|
||||
"""安装更新"""
|
||||
try:
|
||||
self.signals.status_changed.emit("正在安装更新...")
|
||||
self.signals.progress_changed.emit(80)
|
||||
|
||||
if not self.temp_dir:
|
||||
raise Exception("临时目录未初始化")
|
||||
|
||||
# 创建备份目录
|
||||
backup_dir = os.path.join(self.temp_dir, "backup")
|
||||
os.makedirs(backup_dir, exist_ok=True)
|
||||
|
||||
# 备份当前程序文件
|
||||
self._backup_current_files(backup_dir)
|
||||
|
||||
# 复制新文件
|
||||
self._copy_update_files(extract_dir)
|
||||
|
||||
self.signals.progress_changed.emit(95)
|
||||
self.signals.status_changed.emit("安装完成")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.signals.error_occurred.emit(f"安装失败: {str(e)}")
|
||||
# 尝试恢复备份
|
||||
self._restore_backup(backup_dir)
|
||||
return False
|
||||
|
||||
def _backup_current_files(self, backup_dir: str):
|
||||
"""备份当前程序文件"""
|
||||
important_files = ['MRobot.py', 'MRobot.exe', 'app/', 'assets/']
|
||||
|
||||
for item in important_files:
|
||||
src_path = os.path.join(self.app_dir, item)
|
||||
if os.path.exists(src_path):
|
||||
dst_path = os.path.join(backup_dir, item)
|
||||
if os.path.isdir(src_path):
|
||||
shutil.copytree(src_path, dst_path, dirs_exist_ok=True)
|
||||
else:
|
||||
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
|
||||
shutil.copy2(src_path, dst_path)
|
||||
|
||||
def _copy_update_files(self, extract_dir: str):
|
||||
"""复制更新文件到应用程序目录"""
|
||||
# 查找解压目录中的主要文件/文件夹
|
||||
extract_contents = os.listdir(extract_dir)
|
||||
|
||||
# 如果解压后只有一个文件夹,进入该文件夹
|
||||
if len(extract_contents) == 1 and os.path.isdir(os.path.join(extract_dir, extract_contents[0])):
|
||||
extract_dir = os.path.join(extract_dir, extract_contents[0])
|
||||
|
||||
# 复制文件到应用程序目录
|
||||
for item in os.listdir(extract_dir):
|
||||
src_path = os.path.join(extract_dir, item)
|
||||
dst_path = os.path.join(self.app_dir, item)
|
||||
|
||||
if os.path.isdir(src_path):
|
||||
if os.path.exists(dst_path):
|
||||
shutil.rmtree(dst_path)
|
||||
shutil.copytree(src_path, dst_path)
|
||||
else:
|
||||
shutil.copy2(src_path, dst_path)
|
||||
|
||||
def _restore_backup(self, backup_dir: str):
|
||||
"""恢复备份文件"""
|
||||
try:
|
||||
for item in os.listdir(backup_dir):
|
||||
src_path = os.path.join(backup_dir, item)
|
||||
dst_path = os.path.join(self.app_dir, item)
|
||||
|
||||
if os.path.isdir(src_path):
|
||||
if os.path.exists(dst_path):
|
||||
shutil.rmtree(dst_path)
|
||||
shutil.copytree(src_path, dst_path)
|
||||
else:
|
||||
shutil.copy2(src_path, dst_path)
|
||||
except Exception as e:
|
||||
print(f"恢复备份失败: {e}")
|
||||
|
||||
def restart_application(self):
|
||||
"""重启应用程序"""
|
||||
try:
|
||||
self.signals.status_changed.emit("正在重启应用程序...")
|
||||
|
||||
if self.is_frozen:
|
||||
# 如果是打包的exe
|
||||
executable = sys.executable
|
||||
else:
|
||||
# 如果是Python脚本
|
||||
executable = sys.executable
|
||||
script_path = os.path.join(self.app_dir, "MRobot.py")
|
||||
|
||||
# 启动新进程
|
||||
if platform.system() == 'Windows':
|
||||
subprocess.Popen([executable] + ([script_path] if not self.is_frozen else []))
|
||||
else:
|
||||
subprocess.Popen([executable] + ([script_path] if not self.is_frozen else []))
|
||||
|
||||
# 退出当前进程
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
self.signals.error_occurred.emit(f"重启失败: {str(e)}")
|
||||
|
||||
def cleanup(self):
|
||||
"""清理临时文件"""
|
||||
if self.temp_dir and os.path.exists(self.temp_dir):
|
||||
try:
|
||||
shutil.rmtree(self.temp_dir)
|
||||
except Exception as e:
|
||||
print(f"清理临时文件失败: {e}")
|
||||
|
||||
def run(self):
|
||||
"""执行更新流程"""
|
||||
try:
|
||||
# 检查更新
|
||||
update_info = self.check_for_updates()
|
||||
if not update_info or self.cancelled:
|
||||
return
|
||||
|
||||
# 下载更新
|
||||
downloaded_file = self.download_update(
|
||||
update_info['download_url'],
|
||||
update_info['asset_name']
|
||||
)
|
||||
if not downloaded_file or self.cancelled:
|
||||
return
|
||||
|
||||
# 解压更新
|
||||
extract_dir = self.extract_update(downloaded_file)
|
||||
if not extract_dir or self.cancelled:
|
||||
return
|
||||
|
||||
# 安装更新
|
||||
if self.install_update(extract_dir) and not self.cancelled:
|
||||
self.signals.progress_changed.emit(100)
|
||||
self.signals.update_completed.emit()
|
||||
|
||||
except Exception as e:
|
||||
self.signals.error_occurred.emit(f"更新过程中发生错误: {str(e)}")
|
||||
finally:
|
||||
# 清理临时文件
|
||||
self.cleanup()
|
||||
|
||||
|
||||
def check_update_availability(current_version: str, repo: str = "goldenfishs/MRobot") -> Optional[dict]:
|
||||
"""快速检查是否有新版本可用(不下载)"""
|
||||
updater = AutoUpdater(current_version, repo)
|
||||
return updater.check_for_updates()
|
||||
Reference in New Issue
Block a user