新增windows程序ota逻辑

This commit is contained in:
zzh 2025-09-24 16:01:16 +08:00
parent d049d9d908
commit 15bb1ce07d

View File

@ -20,6 +20,7 @@
#include <QFile>
#include <QProcess>
#include <QInputDialog>
#include <QStorageInfo>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), otaOperationPending(false), isUpgradeOperation(true)
@ -28,7 +29,7 @@ MainWindow::MainWindow(QWidget *parent)
settings = new QSettings("TuxiApp", "LightStripSN", this);
// 初始化更新相关变量
currentVersion = "1.5.1"; // 当前程序版本
currentVersion = "1.5.2"; // 当前程序版本
updateServerUrl = settings->value("updateServerUrl", "http://180.163.74.83:8001/version").toString();
updateNetworkManager = new QNetworkAccessManager(this);
updateCheckReply = nullptr;
@ -111,7 +112,7 @@ void MainWindow::showVersionUpdateInfo()
*/
void MainWindow::setupUI() {
setWindowTitle("兔喜Test1.5.1 Author:Zhangzhenghao Email:zzh9953477@gmail.com");
setWindowTitle("兔喜Test1.5.2 Author:Zhangzhenghao Email:zzh9953477@gmail.com");
// 参考qt_bak的合理尺寸设置增加竖向高度
setMinimumSize(850, 720); // 增加最小高度
@ -1479,9 +1480,10 @@ void MainWindow::showAbout()
{
QMessageBox::about(this, "关于程序",
"兔喜MQTT测试程序\n\n"
"版本: 1.2.0\n"
"版本: 1.5.2\n"
"构建日期: 2025-09-12\n\n"
"功能特性:\n"
"• 新增灯条备份恢复和程序ota升级功能\n"
"• 修复清空全部sn未生效的问题\n"
"• 修复label匹配值错误的问题\n"
"• 匹配系统颜色。修复浅色模式下字体看不清\n"
@ -1541,9 +1543,24 @@ void MainWindow::onUpdateCheckFinished()
QJsonObject obj = doc.object();
QString latestVersion = obj["version"].toString();
QString downloadUrl = obj["downloadUrl"].toString();
QString changelog = obj["changelog"].toString();
// 根据平台选择合适的下载URL
QString downloadUrl;
#ifdef Q_OS_WIN
downloadUrl = obj["downloadUrlWindows"].toString();
if (downloadUrl.isEmpty()) {
// 如果没有Windows专用URL回退到通用URL
downloadUrl = obj["downloadUrl"].toString();
}
#else
downloadUrl = obj["downloadUrl"].toString();
if (downloadUrl.isEmpty()) {
// 如果没有Linux专用URL尝试Windows URL作为备选
downloadUrl = obj["downloadUrlWindows"].toString();
}
#endif
if (compareVersions(currentVersion, latestVersion) < 0) {
showUpdateDialog(latestVersion, downloadUrl, changelog);
} else {
@ -1629,16 +1646,45 @@ void MainWindow::onUpdateDownloadFinished()
// 保存下载的文件
QByteArray data = updateDownloadReply->readAll();
QString fileName = QUrl(updateDownloadReply->url()).fileName();
// 确保文件名不为空
if (fileName.isEmpty()) {
fileName = "QtDemo.exe"; // 默认文件名
}
downloadedUpdatePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + fileName;
// 如果文件已存在,先删除
if (QFile::exists(downloadedUpdatePath)) {
QFile::remove(downloadedUpdatePath);
}
QFile file(downloadedUpdatePath);
if (file.open(QIODevice::WriteOnly)) {
file.write(data);
qint64 bytesWritten = file.write(data);
file.close();
// 验证文件是否完整写入
if (bytesWritten != data.size()) {
QMessageBox::critical(this, "下载失败",
QString("文件写入不完整\n期望:%1 字节\n实际:%2 字节")
.arg(data.size()).arg(bytesWritten));
QFile::remove(downloadedUpdatePath); // 清理不完整的文件
return;
}
// 验证下载的文件是否存在且可读
QFileInfo fileInfo(downloadedUpdatePath);
if (!fileInfo.exists() || !fileInfo.isReadable()) {
QMessageBox::critical(this, "下载失败",
QString("下载的文件无法访问:%1").arg(downloadedUpdatePath));
return;
}
QMessageBox msgBox(this);
msgBox.setWindowTitle("下载完成");
msgBox.setText("更新包下载完成,是否立即安装?");
msgBox.setText(QString("更新包下载完成(%1 MB是否立即安装")
.arg(QString::number(fileInfo.size() / 1024.0 / 1024.0, 'f', 2)));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
QAbstractButton *yesButton = msgBox.button(QMessageBox::Yes);
@ -1650,7 +1696,10 @@ void MainWindow::onUpdateDownloadFinished()
installUpdate();
}
} else {
QMessageBox::critical(this, "下载失败", "无法保存更新文件!");
QMessageBox::critical(this, "下载失败",
QString("无法保存更新文件到:%1\n错误:%2")
.arg(downloadedUpdatePath)
.arg(file.errorString()));
}
} else {
QMessageBox::warning(this, "下载失败",
@ -1681,7 +1730,31 @@ void MainWindow::installUpdate()
QString backupPath = currentPath + ".backup";
// 备份当前程序
QFile::copy(currentPath, backupPath);
if (!QFile::copy(currentPath, backupPath)) {
QString errorDetail = QFile(currentPath).errorString();
if (errorDetail.isEmpty()) {
errorDetail = "Unknown error";
}
// 在Windows上可能是因为文件正在使用中
#ifdef Q_OS_WIN
// 尝试检查文件是否被锁定
QFile testFile(currentPath);
if (!testFile.open(QIODevice::ReadOnly)) {
errorDetail += "\n可能原因:程序文件正在使用中,请确保没有其他实例在运行";
} else {
testFile.close();
// 检查目标目录权限
QFileInfo dirInfo(QFileInfo(backupPath).absolutePath());
if (!dirInfo.isWritable()) {
errorDetail += "\n可能原因:目标目录没有写入权限,请以管理员身份运行";
}
}
#endif
QMessageBox::warning(this, "安装失败", QString("无法备份当前程序:%1").arg(errorDetail));
return;
}
// 替换程序文件
QFile::remove(currentPath);
@ -1701,14 +1774,207 @@ void MainWindow::installUpdate()
QString tempPath = currentPath + ".new";
// 备份当前程序
QFile::copy(currentPath, backupPath);
#ifdef Q_OS_WIN
// Windows平台正在运行的exe文件被锁定需要特殊处理
// 先检查备份文件是否已存在,如果存在则删除
if (QFile::exists(backupPath)) {
if (!QFile::remove(backupPath)) {
QMessageBox::warning(this, "安装失败",
QString("无法删除旧的备份文件:%1\n错误:%2")
.arg(backupPath)
.arg(QFile(backupPath).errorString()));
return;
}
}
// 在Windows上尝试使用系统命令复制正在运行的文件
QProcess copyProcess;
QString copyCmd = QString("copy /Y \"%1\" \"%2\"")
.arg(QDir::toNativeSeparators(currentPath))
.arg(QDir::toNativeSeparators(backupPath));
copyProcess.start("cmd.exe", QStringList() << "/c" << copyCmd);
copyProcess.waitForFinished(5000);
if (copyProcess.exitCode() != 0 || !QFile::exists(backupPath)) {
// 如果系统命令也失败尝试使用Qt的方式但先尝试解锁文件
QFile currentFile(currentPath);
if (!currentFile.copy(backupPath)) {
QMessageBox::warning(this, "安装失败",
QString("无法备份当前程序\n当前程序:%1\n备份位置:%2\n\n这可能是因为程序正在运行被系统锁定。\n建议:\n1. 关闭所有程序实例\n2. 以管理员身份运行\n3. 或手动复制程序文件进行备份")
.arg(currentPath)
.arg(backupPath));
return;
}
}
#else
// Linux/Unix平台的备份逻辑
// 先检查备份文件是否已存在,如果存在则删除
if (QFile::exists(backupPath)) {
if (!QFile::remove(backupPath)) {
QMessageBox::warning(this, "安装失败",
QString("无法删除旧的备份文件:%1\n错误:%2")
.arg(backupPath)
.arg(QFile(backupPath).errorString()));
return;
}
}
if (!QFile::copy(currentPath, backupPath)) {
QString errorDetail = QFile(currentPath).errorString();
if (errorDetail.isEmpty()) {
errorDetail = "Unknown error";
}
// 添加Linux平台的详细诊断信息
QFileInfo currentFileInfo(currentPath);
QFileInfo backupDirInfo(QFileInfo(backupPath).absolutePath());
QString diagnosticInfo = QString("\n\n诊断信息:\n");
diagnosticInfo += QString("- 当前程序路径:%1\n").arg(currentPath);
diagnosticInfo += QString("- 备份路径:%1\n").arg(backupPath);
diagnosticInfo += QString("- 当前程序是否存在:%1\n").arg(currentFileInfo.exists() ? "" : "");
diagnosticInfo += QString("- 当前程序是否可读:%1\n").arg(currentFileInfo.isReadable() ? "" : "");
diagnosticInfo += QString("- 备份目录是否可写:%1\n").arg(backupDirInfo.isWritable() ? "" : "");
diagnosticInfo += QString("- 当前程序大小:%1 字节\n").arg(currentFileInfo.size());
// 尝试更详细的错误检查
QFile sourceFile(currentPath);
QFile targetFile(backupPath);
// 检查源文件是否可以打开
if (!sourceFile.open(QIODevice::ReadOnly)) {
diagnosticInfo += QString("- 源文件打开失败:%1\n").arg(sourceFile.errorString());
} else {
sourceFile.close();
diagnosticInfo += "- 源文件可以正常打开\n";
}
// 检查目标文件是否可以创建
if (!targetFile.open(QIODevice::WriteOnly)) {
diagnosticInfo += QString("- 目标文件创建失败:%1\n").arg(targetFile.errorString());
} else {
targetFile.close();
targetFile.remove(); // 清理测试文件
diagnosticInfo += "- 目标文件可以正常创建\n";
}
// 检查是否有权限问题
if (!backupDirInfo.isWritable()) {
diagnosticInfo += "\n可能的解决方案:\n";
diagnosticInfo += "1. 检查目录写入权限\n";
diagnosticInfo += "2. 尝试以sudo权限运行程序\n";
diagnosticInfo += "3. 更改程序安装目录的权限";
}
// 检查磁盘空间
QStorageInfo storage(backupDirInfo.absolutePath());
if (storage.isValid()) {
qint64 availableBytes = storage.bytesAvailable();
qint64 requiredBytes = currentFileInfo.size();
diagnosticInfo += QString("- 可用磁盘空间:%1 字节\n").arg(availableBytes);
if (availableBytes < requiredBytes) {
diagnosticInfo += "\n错误:磁盘空间不足!";
}
}
// 尝试使用系统命令作为备选方案
diagnosticInfo += "\n尝试使用系统命令备份...\n";
QProcess cpProcess;
QString cpCmd = QString("cp \"%1\" \"%2\"").arg(currentPath).arg(backupPath);
cpProcess.start("sh", QStringList() << "-c" << cpCmd);
cpProcess.waitForFinished(5000);
if (cpProcess.exitCode() == 0 && QFile::exists(backupPath)) {
diagnosticInfo += "系统命令备份成功!继续安装过程...\n";
// 如果系统命令成功,继续执行后续逻辑
} else {
diagnosticInfo += QString("系统命令也失败了,退出码:%1\n").arg(cpProcess.exitCode());
diagnosticInfo += QString("系统命令错误输出:%1\n").arg(QString::fromLocal8Bit(cpProcess.readAllStandardError()));
QMessageBox::warning(this, "安装失败", QString("无法备份当前程序:%1%2").arg(errorDetail).arg(diagnosticInfo));
return;
}
}
#endif
// 先将新文件复制到临时位置
// 确保临时文件不存在
if (QFile::exists(tempPath)) {
if (!QFile::remove(tempPath)) {
QMessageBox::warning(this, "安装失败",
QString("无法删除旧的临时文件:%1").arg(tempPath));
return;
}
}
if (!QFile::copy(downloadedUpdatePath, tempPath)) {
QMessageBox::warning(this, "安装失败", "无法复制更新文件!");
QString errorMsg = QString("无法复制更新文件到临时位置\n源文件:%1\n目标文件:%2\n错误:%3")
.arg(downloadedUpdatePath)
.arg(tempPath)
.arg(QFile(downloadedUpdatePath).errorString());
QMessageBox::warning(this, "安装失败", errorMsg);
return;
}
// 验证复制的文件
QFileInfo sourceInfo(downloadedUpdatePath);
QFileInfo tempInfo(tempPath);
if (!tempInfo.exists() || tempInfo.size() != sourceInfo.size()) {
QMessageBox::warning(this, "安装失败",
QString("复制的临时文件不完整\n源文件大小:%1 字节\n临时文件大小:%2 字节")
.arg(sourceInfo.size())
.arg(tempInfo.exists() ? tempInfo.size() : 0));
QFile::remove(tempPath); // 清理不完整的文件
return;
}
#ifdef Q_OS_WIN
// Windows平台的更新和重启逻辑
// 在Windows上检查目标目录是否可写
QFileInfo dirInfo(currentDir);
if (!dirInfo.isWritable()) {
QMessageBox::warning(this, "安装失败",
QString("目标目录没有写入权限:%1\n请以管理员身份运行程序或选择其他安装位置。")
.arg(currentDir));
QFile::remove(tempPath); // 清理临时文件
return;
}
QString scriptPath = currentDir + "/update_and_restart.bat";
QFile scriptFile(scriptPath);
if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&scriptFile);
out << "@echo off\r\n";
out << "timeout /t 2 /nobreak >nul\r\n"; // 等待当前进程完全退出
out << "REM 移除旧文件并重命名新文件\r\n";
out << "del /f /q \"" << QDir::toNativeSeparators(currentPath) << "\"\r\n";
out << "move \"" << QDir::toNativeSeparators(tempPath) << "\" \"" << QDir::toNativeSeparators(currentPath) << "\"\r\n";
out << "cd /d \"" << QDir::toNativeSeparators(currentDir) << "\"\r\n";
out << "REM 启动新程序\r\n";
out << "start \"\" \"" << QDir::toNativeSeparators(currentFileName) << "\"\r\n";
out << "REM 清理脚本文件\r\n";
out << "timeout /t 1 /nobreak >nul\r\n";
out << "del /f /q \"" << QDir::toNativeSeparators(scriptPath) << "\"\r\n";
scriptFile.close();
// 启动更新脚本并退出当前程序
QProcess::startDetached("cmd.exe", QStringList() << "/c" << QDir::toNativeSeparators(scriptPath));
QMessageBox::information(this, "安装完成", "更新安装完成,程序将自动重启!");
// 延迟退出,确保脚本有时间启动
QTimer::singleShot(500, []() {
QApplication::quit();
});
} else {
QMessageBox::warning(this, "安装失败",
QString("无法创建更新脚本:%1\n错误:%2")
.arg(scriptPath)
.arg(scriptFile.errorString()));
QFile::remove(tempPath); // 清理临时文件
}
#else
// Linux/Unix平台的更新和重启逻辑
// 设置临时文件权限
QFile::setPermissions(tempPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
QFile::ReadGroup | QFile::ExeGroup |
@ -1750,6 +2016,7 @@ void MainWindow::installUpdate()
QMessageBox::warning(this, "安装失败", "无法创建更新脚本!");
QFile::remove(tempPath); // 清理临时文件
}
#endif
}
}