diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9c14552..7533857 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -20,6 +20,7 @@ #include #include #include +#include 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 } }