更新程序只需要替换部分需要更新的文件,不必整个程序重新安装。
主程序在运行的时候会一直占用需要的依赖文件。无法进行覆盖替换,所以当主程序将文件下载完后。启动另一个可执行程序解压下载的zip包,完成后在启动主程序。
所以需要编写另一个解压zip的可执行程序,zip是从服务器下载的更新文件,下载到主程序目录下将zip 解压后直接替换主程序的文件,有时候需要删除新增文件,也由这个程序完成。
- 检查是否需要更新
- 编写更新程序替换旧文件
解压zip 的可执行文件 需要在.por 文件中加入 QT += gui-private
这里 char *argv[] 会接受主程序传过来的一些路径,这里的约束是
- 参数是主程序路径
- zip包的路径
- 解压后的目的地路径
Release编译后生成update.exe可执行文件, 解包后会无提示覆盖之前重复的文件。
#include <private/qzipreader_p.h>
#include <iostream>
#include <cstring>
#include <ShlObj_core.h>
int main(int argc, char *argv[]) {
// 主程序路径
const char *host = nullptr;
// zip路径
const char *zip = nullptr;
// 解压路径
const char *dest = nullptr;
// -m 主程序完整路径
// -z 下载的zip包完整路径
// -d zip包解压的目的地
// 这里自己约定参数,得到-m 后面的路径等
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-m") == 0) {
host = argv[++i];
} else if (strcmp(argv[i], "-z") == 0) {
zip = argv[++i];
} else if (strcmp(argv[i], "-d") == 0) {
dest = argv[++i];
}
}
QZipReader reader(zip);
reader.extractAll(dest);
reader.close();
std::remove(zip);
// 完成后启动主程序
ShellExecuteA(nullptr, "open" , host, nullptr, nullptr, SW_SHOWNORMAL);
return 0;
}
如何使用:
- 向服务器发起请求,查看服务器是否有新版本。
- 如果有新版本,下载新的版本需要的文件.zip,建议将文件下载到 Downloads下,避免权限
- 在运行update.exe之前检查是否有读写权限,如果没有就用管理员方式运行update.exe。
- 退出主程序运行update.exe解压zip覆盖之前的文件。
在检查是否有读写权限的时候,根据QT官翻:
出于性能原因,默认情况下,、 和相关类不会对 NTFS 文件系统执行完全所有权和权限 (ACL) 检查。在此类的任何实例的生存期内,将覆盖该默认值并执行高级检查。这提供了一种安全简便的方法来管理启用和禁用默认行为的此更改。
此类仅在 Windows 上可用。
qt_ntfs_permission_lookup
在 Qt 6.6 之前,用户必须直接操作全局变量。然而,这是一个非原子的全局变量,因此它容易出现数据争用。
qt_ntfs_permission_lookup
因此,该变量从 Qt 6.6 开始被弃用。
qt_ntfs_permission_lookup
需要用到QT 的网络请求模块在.pro中 QT+=network 如果是CMake Qt::Network
#ifndef UPDATE_H
#define UPDATE_H
#define VERSION_MAJOR 1
#define VERSION_MINOR 1
#define VERSION_PATCH 0
#define VERSION_CHECK(major, minor, patch) ((major<<16)|(minor<<8)|(patch))
#define VERSION VERSION_CHECK(VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH)
#define MAJOR(version) ((version >> 16) & 0xFF)
#define MINOR(version) ((version >> 8) & 0xFF)
#define PATCH(version) (version & 0xFF)
#include <ShlObj_core.h>
#pragma comment (lib, "Shell32.lib")
#include <Windows.h>
#include <QApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QJsonParseError>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
#include <QProcess>
#include <QPushButton>
#include <QObject>
class CheckVersion : public QObject {
public:
explicit CheckVersion(QObject *parent = nullptr) : QObject(parent) {}
void check() {
QNetworkRequest request(QUrl("http://127.0.0.1:8080/update/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
manager->post(request, QByteArray("可以向服务器反馈一些问题."));
connect(manager, &QNetworkAccessManager::finished, this, [this](QNetworkReply *r) {
if (r->error() == QNetworkReply::NoError) {
QJsonParseError error;
const QJsonDocument &document = QJsonDocument::fromJson(r->readAll(), &error);
if (error.error == QJsonParseError::NoError) {
const QJsonObject &object = document.object();
int version = object["version"].toInt();
if (version > VERSION) {
const QJsonArray &content = object["content"].toArray();
for (int i = 0; i < content.size(); ++i) {
qDebug() << QString::number(i + 1) + ": " + content[i].toString();
}
qDebug() << QString("当前版本:%1.%2.%3 发现新版本:%4.%5.%6").arg(VERSION_MAJOR).arg(
VERSION_MINOR).arg(VERSION_PATCH).arg(MAJOR(version)).arg(MINOR(version)).arg(
PATCH(version));
const QString &zip = object["zip"].toString();
QNetworkReply *reply = manager->get(QNetworkRequest(zip));
// 准备下载路径
QFileInfo info(zip);
const QString &location =
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" +
info.fileName();
file->setFileName(location);
file->open(QIODevice::WriteOnly);//打开文件
connect(reply, &QIODevice::readyRead, this, [this, reply] { file->write(reply->readAll()); });
connect(reply, &QNetworkReply::downloadProgress, [=](qint64 bytesRead, qint64 totalBytes) {
//下载进度
qreal progress = qreal(bytesRead) / qreal(totalBytes);
qDebug() << QString::number(progress * 100, 'f', 2) << "% "
<< bytesRead / (1024 * 1024)
<< "MB" << "/" << totalBytes / (1024 * 1024) << "MB";
});
// 下载完成后 启动更新程序 解压下载的zip 并删除文件
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, location] {
file->reset();// 重置
file->close();
if (reply->error() == QNetworkReply::NoError) {//启动更新程序
const QString &target = QCoreApplication::applicationDirPath();
bool writable;
QFileInfo info(target);
// 检查是否有写入权限
#if QT_VERSION > QT_VERSION_CHECK(6, 6, 0)
{
QNtfsPermissionCheckGuard permissionGuard;
writable = info.isWritable();
}
#else
qt_ntfs_permission_lookup++;
writable = info.isWritable();
qt_ntfs_permission_lookup--;
#endif
// 退出主程序
QApplication::exit(0);
// 此处是传递给更新程序的参数,注意:在传递路径的时候防止路径有空格特殊字符,需要加双引号。
// 在update.exe中得到约束
// -m 主程序完整路径
// -z 下载的zip包完整路径
// -d zip包解压的目的地
const std::string &args = QString(R"(-m "%1" -z "%2" -d "%3")").arg(
QCoreApplication::applicationFilePath(), location, target).toStdString();
// 启动更新程序,如果没有写入权限就申请管理员方式运行
ShellExecuteA(nullptr,
writable ? "open" : "runas",
"update.exe",
args.c_str(),
nullptr, SW_SHOWNORMAL);
}
delete reply;
});
}
}
}
});
}
private:
QNetworkAccessManager *manager{new QNetworkAccessManager(this)};
QFile *file{new QFile()};
};
#endif // UPDATE_H
// main.cpp
#include <QApplication>
#include "update.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QPushButton button;
auto*check = new CheckVersion(&button);
button.resize(300,300);
button.show();
QObject::connect(&button,&QPushButton::clicked,check,&CheckVersion::check);
return QApplication::exec();
}
运行结果:
"1: 优化标题栏样式"
"2: 修复一些已知的BUG"
"3: 新增Application"
"当前版本:1.1.0 发现新版本:2.0.0"
"18.27" % 30 MB / 167 MB
"43.98" % 73 MB / 167 MB
"67.97" % 114 MB / 167 MB
"92.13" % 154 MB / 167 MB
"100.00" % 167 MB / 167 MB
Django 服务器搭建:
服务器的搭建一般会有专业人士搭建,这里用python Django 简单的搭建一个服务器。
Pycharm 新建一个Project
这里需要修改3个地方 ,打开 djangoProject-settings.py
注册:这里对应的就是url 的请求头,这里的update就是 "http://127.0.0.1:8080/update/"
如果是请求 plugin url就是 "http://127.0.0.1:8080/plugin /"
views.update views.plugin 是函数具体实现。
来到 app-views.py 实现函数。当发起对应的请求时会触发对应的函数。
然后 在终端 启动 django:
python manage.py runserver 0.0.0.0:8080
接下来用python 请求一下
import requests
result = requests.post("http://127.0.0.1:8080/update/").json()
print(result, "\n")
zip = result["zip"]
print(zip)
response = requests.get(zip)
with open("Testing.zip", "wb") as f:
f.write(response.content)
成功请求: