jiateng_ws/utils/version_manager.py

262 lines
11 KiB
Python
Raw Normal View History

2025-06-07 10:45:09 +08:00
import os
import json
import logging
import requests
import shutil
import tempfile
import zipfile
from pathlib import Path
import sys
import datetime
from PySide6.QtWidgets import QMessageBox, QProgressDialog
from PySide6.QtCore import Qt
# 版本检查服务器地址
VERSION_CHECK_URL = "http://your-server.com/api/version_check"
UPDATE_DOWNLOAD_URL = "http://your-server.com/api/download_update"
class VersionManager:
"""版本管理工具类"""
VERSION = "1.0.0"
@staticmethod
def get_current_version():
"""获取当前版本号"""
return VersionManager.VERSION
@staticmethod
def check_for_updates(parent_widget=None):
"""
检查是否有更新可用
Args:
parent_widget: 父窗口用于显示消息框
Returns:
dict: 包含更新信息的字典如果没有更新或检查失败则为None
"""
try:
current_version = VersionManager.get_current_version()
logging.info(f"正在检查更新,当前版本: {current_version}")
# 从服务器获取最新版本信息
response = requests.get(VERSION_CHECK_URL, params={"current_version": current_version}, timeout=5)
response.raise_for_status() # 如果HTTP请求返回了不成功的状态码将引发HTTPError异常
data = response.json()
if not data.get("success"):
logging.warning(f"版本检查失败: {data.get('message')}")
return None
if data.get("update_available"):
logging.info(f"发现新版本: {data.get('latest_version')}")
return {
"latest_version": data.get("latest_version"),
"update_url": data.get("update_url"),
"release_notes": data.get("release_notes"),
"is_mandatory": data.get("is_mandatory", False)
}
else:
logging.info("当前已是最新版本")
return None
except requests.RequestException as e:
logging.error(f"检查更新时网络错误: {e}")
if parent_widget:
QMessageBox.warning(parent_widget, "更新检查失败", f"无法连接到更新服务器: {e}")
return None
except Exception as e:
logging.error(f"检查更新时发生错误: {e}")
if parent_widget:
QMessageBox.warning(parent_widget, "更新检查失败", f"检查更新时发生错误: {e}")
return None
@staticmethod
def download_and_install_update(update_info, parent_widget=None):
"""
下载并安装更新
Args:
update_info: 包含更新信息的字典
parent_widget: 父窗口用于显示进度对话框
Returns:
bool: 更新是否成功
"""
if not update_info or not update_info.get("update_url"):
logging.error("无效的更新信息")
return False
update_url = update_info.get("update_url")
version = update_info.get("latest_version")
try:
# 创建进度对话框
progress = None
if parent_widget:
progress = QProgressDialog("正在下载更新...", "取消", 0, 100, parent_widget)
progress.setWindowTitle(f"下载更新 v{version}")
progress.setWindowModality(Qt.WindowModal)
progress.setAutoClose(True)
progress.setMinimumDuration(0)
progress.setValue(0)
progress.show()
# 下载更新文件
logging.info(f"开始下载更新: {update_url}")
# 使用临时目录
with tempfile.TemporaryDirectory() as temp_dir:
# 下载文件
update_file = os.path.join(temp_dir, "update.zip")
# 使用流式下载以显示进度
with requests.get(update_url, stream=True) as r:
r.raise_for_status()
total_size = int(r.headers.get('content-length', 0))
downloaded = 0
with open(update_file, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
if progress and progress.wasCanceled():
logging.info("用户取消了下载")
return False
if chunk:
f.write(chunk)
downloaded += len(chunk)
if total_size and progress:
percent = int((downloaded / total_size) * 100)
progress.setValue(percent)
if progress:
progress.setValue(100)
progress.setLabelText("正在安装更新...")
# 解压更新文件
logging.info("正在解压更新文件")
extract_dir = os.path.join(temp_dir, "extracted")
os.makedirs(extract_dir, exist_ok=True)
with zipfile.ZipFile(update_file, 'r') as zip_ref:
zip_ref.extractall(extract_dir)
# 获取应用程序根目录
app_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 备份当前版本
current_version = VersionManager.get_current_version()
backup_dir = os.path.join(app_dir, f"backup_v{current_version}")
logging.info(f"备份当前版本到: {backup_dir}")
if os.path.exists(backup_dir):
shutil.rmtree(backup_dir)
os.makedirs(backup_dir, exist_ok=True)
# 复制当前文件到备份目录
for item in os.listdir(app_dir):
if not item.startswith("backup_v"):
s = os.path.join(app_dir, item)
d = os.path.join(backup_dir, item)
if os.path.isdir(s):
shutil.copytree(s, d, dirs_exist_ok=True)
else:
shutil.copy2(s, d)
# 复制更新文件到应用目录
logging.info("正在安装更新文件")
for item in os.listdir(extract_dir):
s = os.path.join(extract_dir, item)
d = os.path.join(app_dir, item)
if os.path.isdir(s):
shutil.copytree(s, d, dirs_exist_ok=True)
else:
shutil.copy2(s, d)
# 更新版本文件
version_file = os.path.join(app_dir, "version.json")
with open(version_file, 'w') as f:
json.dump({
"version": version,
"updated_at": str(datetime.datetime.now()),
"release_notes": update_info.get("release_notes", "")
}, f, indent=4)
logging.info(f"更新完成,新版本: {version}")
if parent_widget:
QMessageBox.information(parent_widget, "更新完成",
f"已成功更新到版本 {version}\n应用程序需要重启才能应用更新。")
# 提示用户重启应用
if parent_widget and QMessageBox.question(
parent_widget, "重启应用",
"需要重启应用程序以完成更新。是否现在重启?",
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
logging.info("用户选择重启应用")
# 重启应用
python = sys.executable
os.execl(python, python, *sys.argv)
return True
except Exception as e:
logging.error(f"下载或安装更新时发生错误: {e}", exc_info=True)
if parent_widget:
QMessageBox.critical(parent_widget, "更新失败", f"下载或安装更新时发生错误: {e}")
return False
finally:
if progress:
progress.close()
@staticmethod
def check_and_prompt_update(parent_widget=None):
"""检查更新并提示用户"""
# 这里只是一个示例,实际应该连接到服务器检查更新
logging.info(f"检查更新,当前版本: {VersionManager.VERSION}")
return False # 返回是否有更新
@staticmethod
def check_and_prompt_update(parent_widget):
"""
检查更新并提示用户
Args:
parent_widget: 父窗口
Returns:
bool: 是否有可用更新
"""
update_info = VersionManager.check_for_updates(parent_widget)
if not update_info:
return False
# 获取更新信息
latest_version = update_info.get("latest_version", "未知")
release_notes = update_info.get("release_notes", "无更新说明")
is_mandatory = update_info.get("is_mandatory", False)
message = f"发现新版本: {latest_version}\n\n"
message += "更新内容:\n"
message += release_notes
if is_mandatory:
message += "\n\n这是一个必须安装的更新。"
result = QMessageBox.information(parent_widget, "发现必要更新", message,
QMessageBox.Ok | QMessageBox.Cancel)
if result == QMessageBox.Ok:
return VersionManager.download_and_install_update(update_info, parent_widget)
else:
# 如果是必要更新但用户取消,则退出应用
if parent_widget:
QMessageBox.critical(parent_widget, "更新取消",
"这是一个必要更新,应用程序将退出。")
sys.exit(0)
else:
message += "\n\n是否现在更新?"
result = QMessageBox.question(parent_widget, "发现新版本", message,
QMessageBox.Yes | QMessageBox.No)
if result == QMessageBox.Yes:
return VersionManager.download_and_install_update(update_info, parent_widget)
return False