This commit is contained in:
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,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.

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,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]:

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

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