新增qt程序ota

This commit is contained in:
zzh 2025-09-24 10:45:23 +08:00
parent 5bf62c410a
commit faae203eb1
5 changed files with 642 additions and 5 deletions

View File

@ -20,12 +20,14 @@ set(SOURCES
src/mainwindow.cpp
src/mqttclient.cpp
src/lightstripmanager.cpp
src/updatesettingsdialog.cpp
)
set(HEADERS
src/mainwindow.h
src/mqttclient.h
src/lightstripmanager.h
src/updatesettingsdialog.h
)
add_executable(QtDemo ${SOURCES} ${HEADERS}

View File

@ -1,12 +1,13 @@
#include "mainwindow.h"
#include "lightstripmanager.h"
#include "updatesettingsdialog.h"
#include <QDateTime>
#include <QJsonObject>
#include <QJsonDocument>
//#include <QNetworkAccessManager>
//#include <QNetworkRequest>
//#include <QNetworkReply>
//#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QFileInfo>
#include <QDir>
#include <QStandardPaths>
#include <QMessageBox>
@ -15,12 +16,24 @@
#include <QDebug>
#include <QScreen>
#include <QGuiApplication>
#include <QProgressDialog>
#include <QFile>
#include <QProcess>
#include <QInputDialog>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), otaOperationPending(false), isUpgradeOperation(true)
{
// 初始化设置对象
settings = new QSettings("TuxiApp", "LightStripSN", this);
// 初始化更新相关变量
currentVersion = "1.3.0"; // 当前程序版本
updateServerUrl = settings->value("updateServerUrl", "http://180.163.74.83:8001/version").toString();
updateNetworkManager = new QNetworkAccessManager(this);
updateCheckReply = nullptr;
updateDownloadReply = nullptr;
downloadedUpdatePath = "";
setupUI();
@ -98,7 +111,7 @@ void MainWindow::showVersionUpdateInfo()
*/
void MainWindow::setupUI() {
setWindowTitle("兔喜Test1.2 Author:Zhangzhenghao Email:zzh9953477@gmail.com");
setWindowTitle("兔喜Test1.5 Author:Zhangzhenghao Email:zzh9953477@gmail.com");
// 参考qt_bak的合理尺寸设置增加竖向高度
setMinimumSize(850, 720); // 增加最小高度
@ -333,6 +346,27 @@ void MainWindow::setupUI() {
"}"
);
snHeaderLayout->addWidget(searchLightStripBtn);
// 添加清除按钮
clearSnBtn = new QPushButton("清除列表", this);
clearSnBtn->setMinimumHeight(26);
clearSnBtn->setMinimumWidth(80);
clearSnBtn->setStyleSheet(
"QPushButton { "
" background-color: #f44336; "
" color: white; "
" padding: 4px 10px; "
" font-weight: bold; "
" border-radius: 6px; "
" font-size: 12px; "
" border: none; "
"} "
"QPushButton:hover { "
" background-color: #d32f2f; "
"}"
);
snHeaderLayout->addWidget(clearSnBtn);
snLayout->addLayout(snHeaderLayout);
// 打开管理器按钮
@ -1415,6 +1449,17 @@ void MainWindow::createMenus()
QAction *useGuideAction = helpMenu->addAction("使用说明(&U)");
connect(useGuideAction, &QAction::triggered, this, &MainWindow::showUseGuide);
// 添加分隔线
helpMenu->addSeparator();
// 添加手动检查更新菜单项
QAction *checkUpdateAction = helpMenu->addAction("检查更新(&C)");
connect(checkUpdateAction, &QAction::triggered, this, &MainWindow::checkForUpdates);
// 添加更新设置菜单项
QAction *updateSettingsAction = helpMenu->addAction("更新设置(&S)");
connect(updateSettingsAction, &QAction::triggered, this, &MainWindow::onUpdateSettingsClicked);
}
void MainWindow::showAbout()
@ -1457,3 +1502,272 @@ QString MainWindow::getTextColorForTheme() const {
// 如果背景较暗亮度小于0.5),使用白色文字;否则使用黑色文字
return (luminance < 0.5) ? "white" : "black";
}
// 更新功能实现
void MainWindow::checkForUpdates()
{
if (updateCheckReply) {
updateCheckReply->abort();
updateCheckReply = nullptr;
}
QNetworkRequest request{QUrl(updateServerUrl)};
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
updateCheckReply = updateNetworkManager->get(request);
connect(updateCheckReply, &QNetworkReply::finished, this, &MainWindow::onUpdateCheckFinished);
}
void MainWindow::onUpdateCheckFinished()
{
if (!updateCheckReply) return;
if (updateCheckReply->error() == QNetworkReply::NoError) {
QByteArray data = updateCheckReply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject obj = doc.object();
QString latestVersion = obj["version"].toString();
QString downloadUrl = obj["downloadUrl"].toString();
QString changelog = obj["changelog"].toString();
if (compareVersions(currentVersion, latestVersion) < 0) {
showUpdateDialog(latestVersion, downloadUrl, changelog);
} else {
QMessageBox::information(this, "检查更新", "当前已是最新版本!");
}
} else {
QMessageBox::warning(this, "检查更新失败",
QString("无法连接到更新服务器:%1").arg(updateCheckReply->errorString()));
}
updateCheckReply->deleteLater();
updateCheckReply = nullptr;
}
void MainWindow::showUpdateDialog(const QString &version, const QString &downloadUrl, const QString &changelog)
{
QMessageBox msgBox(this);
msgBox.setWindowTitle("发现新版本");
msgBox.setText(QString("发现新版本 %1").arg(version));
msgBox.setDetailedText(changelog);
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
QAbstractButton *yesButton = msgBox.button(QMessageBox::Yes);
QAbstractButton *noButton = msgBox.button(QMessageBox::No);
if (yesButton) yesButton->setText("立即更新");
if (noButton) noButton->setText("稍后更新");
if (msgBox.exec() == QMessageBox::Yes) {
downloadUpdate(downloadUrl);
}
}
void MainWindow::downloadUpdate(const QString &downloadUrl)
{
if (updateDownloadReply) {
updateDownloadReply->abort();
updateDownloadReply = nullptr;
}
QNetworkRequest request{QUrl(downloadUrl)};
updateDownloadReply = updateNetworkManager->get(request);
connect(updateDownloadReply, &QNetworkReply::downloadProgress,
this, &MainWindow::onUpdateDownloadProgress);
connect(updateDownloadReply, &QNetworkReply::finished,
this, &MainWindow::onUpdateDownloadFinished);
// 显示下载进度对话框
QProgressDialog *progressDialog = new QProgressDialog("正在下载更新...", "取消", 0, 100, this);
progressDialog->setWindowModality(Qt::WindowModal);
progressDialog->show();
connect(progressDialog, &QProgressDialog::canceled, [this]() {
if (updateDownloadReply) {
updateDownloadReply->abort();
}
});
}
void MainWindow::onUpdateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
if (bytesTotal > 0) {
int progress = static_cast<int>((bytesReceived * 100) / bytesTotal);
// 更新进度条
QProgressDialog *progressDialog = findChild<QProgressDialog*>();
if (progressDialog) {
progressDialog->setValue(progress);
}
}
}
void MainWindow::onUpdateDownloadFinished()
{
if (!updateDownloadReply) return;
QProgressDialog *progressDialog = findChild<QProgressDialog*>();
if (progressDialog) {
progressDialog->close();
progressDialog->deleteLater();
}
if (updateDownloadReply->error() == QNetworkReply::NoError) {
// 保存下载的文件
QByteArray data = updateDownloadReply->readAll();
QString fileName = QUrl(updateDownloadReply->url()).fileName();
downloadedUpdatePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + fileName;
QFile file(downloadedUpdatePath);
if (file.open(QIODevice::WriteOnly)) {
file.write(data);
file.close();
QMessageBox msgBox(this);
msgBox.setWindowTitle("下载完成");
msgBox.setText("更新包下载完成,是否立即安装?");
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
QAbstractButton *yesButton = msgBox.button(QMessageBox::Yes);
QAbstractButton *noButton = msgBox.button(QMessageBox::No);
if (yesButton) yesButton->setText("立即安装");
if (noButton) noButton->setText("稍后安装");
if (msgBox.exec() == QMessageBox::Yes) {
installUpdate();
}
} else {
QMessageBox::critical(this, "下载失败", "无法保存更新文件!");
}
} else {
QMessageBox::warning(this, "下载失败",
QString("下载更新失败:%1").arg(updateDownloadReply->errorString()));
}
updateDownloadReply->deleteLater();
updateDownloadReply = nullptr;
}
void MainWindow::installUpdate()
{
if (downloadedUpdatePath.isEmpty() || !QFile::exists(downloadedUpdatePath)) {
QMessageBox::warning(this, "安装失败", "找不到更新文件!");
return;
}
// 根据文件类型执行不同的安装逻辑
if (downloadedUpdatePath.endsWith(".deb")) {
// Debian包安装
QProcess::startDetached("pkexec", QStringList() << "dpkg" << "-i" << downloadedUpdatePath);
} else if (downloadedUpdatePath.endsWith(".rpm")) {
// RPM包安装
QProcess::startDetached("pkexec", QStringList() << "rpm" << "-i" << downloadedUpdatePath);
} else if (downloadedUpdatePath.endsWith(".AppImage")) {
// AppImage替换
QString currentPath = QCoreApplication::applicationFilePath();
QString backupPath = currentPath + ".backup";
// 备份当前程序
QFile::copy(currentPath, backupPath);
// 替换程序文件
QFile::remove(currentPath);
QFile::copy(downloadedUpdatePath, currentPath);
QFile::setPermissions(currentPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
QFile::ReadGroup | QFile::ExeGroup |
QFile::ReadOther | QFile::ExeOther);
QMessageBox::information(this, "安装完成", "更新安装完成,请重启程序!");
QApplication::quit();
} else {
// 普通可执行文件替换如QtDemo
QString currentPath = QCoreApplication::applicationFilePath();
QString currentDir = QFileInfo(currentPath).absolutePath();
QString currentFileName = QFileInfo(currentPath).fileName();
QString backupPath = currentPath + ".backup";
QString tempPath = currentPath + ".new";
// 备份当前程序
QFile::copy(currentPath, backupPath);
// 先将新文件复制到临时位置
if (!QFile::copy(downloadedUpdatePath, tempPath)) {
QMessageBox::warning(this, "安装失败", "无法复制更新文件!");
return;
}
// 设置临时文件权限
QFile::setPermissions(tempPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
QFile::ReadGroup | QFile::ExeGroup |
QFile::ReadOther | QFile::ExeOther);
// 创建更新脚本
QString scriptPath = currentDir + "/update_and_restart.sh";
QFile scriptFile(scriptPath);
if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&scriptFile);
out << "#!/bin/bash\n";
out << "sleep 2\n"; // 等待当前进程完全退出
out << "# 移除旧文件并重命名新文件\n";
out << "rm -f \"" << currentPath << "\"\n";
out << "mv \"" << tempPath << "\" \"" << currentPath << "\"\n";
out << "chmod +x \"" << currentPath << "\"\n";
out << "cd \"" << currentDir << "\"\n";
out << "# 启动新程序\n";
out << "nohup ./" << currentFileName << " > /dev/null 2>&1 &\n";
out << "# 清理脚本文件\n";
out << "sleep 1\n";
out << "rm -f \"" << scriptPath << "\"\n";
scriptFile.close();
// 设置脚本可执行权限
QFile::setPermissions(scriptPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
QFile::ReadGroup | QFile::ExeGroup |
QFile::ReadOther | QFile::ExeOther);
// 启动更新脚本并退出当前程序
QProcess::startDetached("/bin/bash", QStringList() << scriptPath);
QMessageBox::information(this, "安装完成", "更新安装完成,程序将自动重启!");
// 延迟退出,确保脚本有时间启动
QTimer::singleShot(500, []() {
QApplication::quit();
});
} else {
QMessageBox::warning(this, "安装失败", "无法创建更新脚本!");
QFile::remove(tempPath); // 清理临时文件
}
}
}
int MainWindow::compareVersions(const QString &version1, const QString &version2)
{
QStringList v1Parts = version1.split('.');
QStringList v2Parts = version2.split('.');
int maxParts = qMax(v1Parts.size(), v2Parts.size());
for (int i = 0; i < maxParts; ++i) {
int v1Part = (i < v1Parts.size()) ? v1Parts[i].toInt() : 0;
int v2Part = (i < v2Parts.size()) ? v2Parts[i].toInt() : 0;
if (v1Part < v2Part) return -1;
if (v1Part > v2Part) return 1;
}
return 0;
}
void MainWindow::onUpdateSettingsClicked()
{
UpdateSettingsDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
// 更新本地设置
updateServerUrl = dialog.getUpdateServerUrl();
settings->setValue("updateServerUrl", updateServerUrl);
settings->setValue("autoCheckUpdates", dialog.isAutoCheckEnabled());
settings->setValue("checkInterval", dialog.getCheckInterval());
QMessageBox::information(this, "设置保存", "更新设置已保存!");
}
}

View File

@ -62,6 +62,13 @@ private slots:
void onClearSnListClicked();
void onLightStripManagerClosed();
void onDeviceSnChanged(); // 新增设备SN变化槽函数
// 更新相关槽函数
void checkForUpdates();
void onUpdateCheckFinished();
void onUpdateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onUpdateDownloadFinished();
void onUpdateSettingsClicked();
private:
void setupUI();
@ -77,6 +84,12 @@ private:
void sendSearchLightStripCommand();
void openLightStripManager(); // 添加这个声明
// 更新相关函数
void showUpdateDialog(const QString &version, const QString &downloadUrl, const QString &changelog);
void downloadUpdate(const QString &downloadUrl);
void installUpdate();
int compareVersions(const QString &version1, const QString &version2);
// 新增:公共接口
QString getDeviceSn() const;
QString getTextColorForTheme() const;
@ -142,6 +155,14 @@ private:
// MQTT客户端
MqttClient *mqttClient;
// 更新相关成员变量
QString currentVersion;
QString updateServerUrl;
QNetworkAccessManager *updateNetworkManager;
QNetworkReply *updateCheckReply;
QNetworkReply *updateDownloadReply;
QString downloadedUpdatePath;
// 数据管理
QSet<QString> uniqueSnSet;
QList<QCheckBox*> lightStripCheckBoxes; // 添加复选框列表

View File

@ -0,0 +1,249 @@
#include "updatesettingsdialog.h"
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QTimer>
#include <QSpinBox>
UpdateSettingsDialog::UpdateSettingsDialog(QWidget *parent)
: QDialog(parent),
serverUrlEdit(nullptr),
autoCheckBox(nullptr),
checkIntervalEdit(nullptr),
testBtn(nullptr),
resetBtn(nullptr),
okBtn(nullptr),
cancelBtn(nullptr),
settings(nullptr)
{
settings = new QSettings("TuxiApp", "LightStripSN", this);
setupUI();
loadSettings();
}
UpdateSettingsDialog::~UpdateSettingsDialog()
{
}
void UpdateSettingsDialog::setupUI()
{
qDebug() << "开始设置UpdateSettingsDialog UI...";
setWindowTitle("更新设置");
setFixedSize(400, 300);
setModal(true);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
if (!mainLayout) {
qDebug() << "错误: 无法创建主布局";
return;
}
// 服务器设置组
QGroupBox *serverGroup = new QGroupBox("服务器设置", this);
if (!serverGroup) {
qDebug() << "错误: 无法创建服务器设置组";
return;
}
QFormLayout *serverLayout = new QFormLayout(serverGroup);
if (!serverLayout) {
qDebug() << "错误: 无法创建服务器布局";
return;
}
serverUrlEdit = new QLineEdit(this);
if (!serverUrlEdit) {
qDebug() << "错误: 无法创建服务器URL编辑框";
return;
}
serverUrlEdit->setPlaceholderText("http://180.163.74.83:8001/version");
serverLayout->addRow("更新服务器地址:", serverUrlEdit);
QHBoxLayout *testLayout = new QHBoxLayout();
testBtn = new QPushButton("测试连接", this);
resetBtn = new QPushButton("恢复默认", this);
if (!testBtn || !resetBtn) {
qDebug() << "错误: 无法创建测试或重置按钮";
return;
}
testLayout->addWidget(testBtn);
testLayout->addWidget(resetBtn);
testLayout->addStretch();
serverLayout->addRow("", testLayout);
mainLayout->addWidget(serverGroup);
// 自动检查设置组
QGroupBox *autoGroup = new QGroupBox("自动检查设置", this);
QFormLayout *autoLayout = new QFormLayout(autoGroup);
autoCheckBox = new QCheckBox("启用自动检查更新", this);
autoLayout->addRow(autoCheckBox);
checkIntervalEdit = new QLineEdit(this);
checkIntervalEdit->setPlaceholderText("24");
QLabel *intervalLabel = new QLabel("检查间隔(小时):", this);
autoLayout->addRow(intervalLabel, checkIntervalEdit);
// 当自动检查被禁用时,禁用间隔设置
connect(autoCheckBox, &QCheckBox::toggled, [this, intervalLabel](bool checked) {
checkIntervalEdit->setEnabled(checked);
intervalLabel->setEnabled(checked);
});
mainLayout->addWidget(autoGroup);
// 按钮布局
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addStretch();
okBtn = new QPushButton("确定", this);
cancelBtn = new QPushButton("取消", this);
buttonLayout->addWidget(okBtn);
buttonLayout->addWidget(cancelBtn);
mainLayout->addLayout(buttonLayout);
// 连接信号槽
connect(okBtn, &QPushButton::clicked, this, &UpdateSettingsDialog::onOkClicked);
connect(cancelBtn, &QPushButton::clicked, this, &UpdateSettingsDialog::onCancelClicked);
connect(testBtn, &QPushButton::clicked, this, &UpdateSettingsDialog::onTestConnectionClicked);
connect(resetBtn, &QPushButton::clicked, this, &UpdateSettingsDialog::onResetToDefaultClicked);
qDebug() << "UpdateSettingsDialog UI设置完成";
}
void UpdateSettingsDialog::loadSettings()
{
QString defaultUrl = "http://180.163.74.83:8001/version";
serverUrlEdit->setText(settings->value("updateServerUrl", defaultUrl).toString());
autoCheckBox->setChecked(settings->value("autoCheckUpdates", true).toBool());
checkIntervalEdit->setText(settings->value("checkInterval", 24).toString());
// 触发复选框状态变化以更新UI
emit autoCheckBox->toggled(autoCheckBox->isChecked());
}
void UpdateSettingsDialog::saveSettings()
{
settings->setValue("updateServerUrl", serverUrlEdit->text().trimmed());
settings->setValue("autoCheckUpdates", autoCheckBox->isChecked());
settings->setValue("checkInterval", checkIntervalEdit->text().toInt());
settings->sync();
}
QString UpdateSettingsDialog::getUpdateServerUrl() const
{
return serverUrlEdit->text().trimmed();
}
bool UpdateSettingsDialog::isAutoCheckEnabled() const
{
return autoCheckBox->isChecked();
}
int UpdateSettingsDialog::getCheckInterval() const
{
return checkIntervalEdit->text().toInt();
}
void UpdateSettingsDialog::onOkClicked()
{
// 验证输入
QString url = serverUrlEdit->text().trimmed();
if (url.isEmpty()) {
QMessageBox::warning(this, "输入错误", "请输入更新服务器地址!");
return;
}
if (!url.startsWith("http://") && !url.startsWith("https://")) {
QMessageBox::warning(this, "输入错误", "服务器地址必须以 http:// 或 https:// 开头!");
return;
}
int interval = checkIntervalEdit->text().toInt();
if (autoCheckBox->isChecked() && (interval < 1 || interval > 168)) {
QMessageBox::warning(this, "输入错误", "检查间隔必须在1-168小时之间");
return;
}
saveSettings();
accept();
}
void UpdateSettingsDialog::onCancelClicked()
{
reject();
}
void UpdateSettingsDialog::onTestConnectionClicked()
{
QString url = serverUrlEdit->text().trimmed();
if (url.isEmpty()) {
QMessageBox::warning(this, "测试失败", "请先输入服务器地址!");
return;
}
testConnection();
}
void UpdateSettingsDialog::onResetToDefaultClicked()
{
int ret = QMessageBox::question(this, "确认重置",
"确定要恢复默认设置吗?",
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) {
serverUrlEdit->setText("http://180.163.74.83:8001/version");
autoCheckBox->setChecked(true);
checkIntervalEdit->setText("24");
}
}
void UpdateSettingsDialog::testConnection()
{
testBtn->setEnabled(false);
testBtn->setText("测试中...");
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
QNetworkRequest request;
request.setUrl(QUrl(serverUrlEdit->text().trimmed()));
request.setRawHeader("User-Agent", "LightStripSN-Updater/1.0");
// 设置超时
QTimer *timer = new QTimer(this);
timer->setSingleShot(true);
QNetworkReply *reply = manager->get(request);
connect(timer, &QTimer::timeout, [this, reply]() {
reply->abort();
testBtn->setEnabled(true);
testBtn->setText("测试连接");
QMessageBox::warning(this, "连接超时", "连接服务器超时,请检查网络或服务器地址!");
});
connect(reply, &QNetworkReply::finished, [this, reply, timer]() {
timer->stop();
testBtn->setEnabled(true);
testBtn->setText("测试连接");
if (reply->error() == QNetworkReply::NoError) {
QMessageBox::information(this, "连接成功", "服务器连接正常!");
} else {
QMessageBox::warning(this, "连接失败",
QString("连接失败: %1").arg(reply->errorString()));
}
reply->deleteLater();
timer->deleteLater();
});
timer->start(10000); // 10秒超时
}

View File

@ -0,0 +1,51 @@
#ifndef UPDATESETTINGSDIALOG_H
#define UPDATESETTINGSDIALOG_H
#include <QDialog>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QLabel>
#include <QGroupBox>
#include <QCheckBox>
#include <QSettings>
#include <QSpinBox>
class UpdateSettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit UpdateSettingsDialog(QWidget *parent = nullptr);
~UpdateSettingsDialog();
QString getUpdateServerUrl() const;
bool isAutoCheckEnabled() const;
int getCheckInterval() const;
private slots:
void onOkClicked();
void onCancelClicked();
void onTestConnectionClicked();
void onResetToDefaultClicked();
private:
void setupUI();
void loadSettings();
void saveSettings();
void testConnection();
QLineEdit *serverUrlEdit;
QCheckBox *autoCheckBox;
QLineEdit *checkIntervalEdit;
QPushButton *testBtn;
QPushButton *resetBtn;
QPushButton *okBtn;
QPushButton *cancelBtn;
QSettings *settings;
};
#endif // UPDATESETTINGSDIALOG_H