基于Qt 和python 的自动升级功能

news2024/11/23 1:07:11

需求:

公司内部的一个客户端工具,想加上一个自动升级功能。

服务端:

1,服务端使用python3.7 ,搭配 fastapi  和uvicorn 写一个简单的服务,开出一个get接口,用于客户端读取安装包的版本,描述,和路径。

2,使用 python 自带的 http.server  启动一个文件服务器,将安装包存入,并将地址写到步骤1的json文件中。

json文件长这个样子,每次客户端都解析这个文件,如果最新的版本大于当前的版本,则从url下载文件,并自动执行文件。

{
    "ver":"1.0.1",
    "desc":"1.优化界面\n2.删除了什么东西\n3.增加了什么东西把\n4.添加了什么东西\n5.特别好用 试试吧",
    "file":"test_1_0.exe",
    "url":"http://xxx.xxx.xxx:8002/pkg/test/WinSCP.exe"
}

服务很简单, 主要就是提供一个get接口。

from fastapi import FastAPI
import json

class MyApp:
    def __init__(self, title: str = "UpgradeServer", version: str = "1.0.0"):
        self.app = FastAPI(title=title, version=version)

        # Additional initialization or configuration can be done here

    def configure_routes(self):
        @self.app.get("/")
        def root():
            return {"不为无益之事,何以遣有涯之生!"}
        
        @self.app.get("/cur_ver/{item}")
        def cur_ver(item:str=None):
            path = "pkg/"+item+"/"+item+".json"

            with open(path, 'r') as file:
                # 从文件中加载JSON数据
                data = json.load(file)
                print(data['ver'])
                return data

    def run(self, host: str = "0.0.0.0", port: int = 8001):
        import uvicorn
        uvicorn.run(self.app, host=host, port=port)

if __name__ == "__main__":
    my_app = MyApp()
    my_app.configure_routes()
    my_app.run()

客户端:

1,客户端是一个 QDialog,每次启动时 从服务端获取最新的版本号,大于则开始下载安装包,下载完成后,则执行安装包。

2,使用的时候 将客户端放到main函数中,并传入当前的版本号。

//.h 文件
#ifndef UPGRADECLIENT_H
#define UPGRADECLIENT_H

#include <QDialog>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QThread>
#include <QMutex>
#include <QEventLoop>
#include <QFile>

namespace Ui {
class UpgradeClient;
}

enum class Status{
    Init,
    Error,
    NeedUpgrade,
    NoUpgradde,
    Abandon,
    DownloadFinished,
};

class UpgradeClient : public QDialog
{
    Q_OBJECT

public:
    explicit UpgradeClient(const QString& ver,QWidget *parent = nullptr);
    ~UpgradeClient();

    int start();

private slots:
    void on_laterBtn_clicked();
    void on_nowBtn_clicked();

private:
    Ui::UpgradeClient *ui;
    QNetworkAccessManager manager;
    QNetworkReply *verReply{nullptr};
    QNetworkReply *downloadReply{nullptr};

    //当前版本
    QString curVer;

    //最新版本 描述 名称 url
    QString latestVer;
    QString pkgDesc;
    QString pkgName;
    QString pkgUrl;

    //判断当前状态
    Status curStatus{Status::Init};

    //安装包存储文件
    QFile pkgFile;

    //事件循环 用于等待版本检擦
    QEventLoop eventLoop;

private:
    //检查当前版本
    void checkVer();
    //下载安装包
    void downloadPkg(const QString& _name,const QString& _url);
    //解析json数据
    void parseJson(const QByteArray &jsonData);
    //比较版本
    bool compareVer(int lMajor,int lMinor,int lPath,int cMajor,int cMinor,int cPath);
    //运行安装包
    bool runPkg(const QString& filename);

protected:
    void closeEvent(QCloseEvent *event) override;

};

#endif // UPGRADECLIENT_H


//cpp 文件
#include "upgradeclient.h"
#include "ui_upgradeclient.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QtConcurrent>
#include <chrono>

//检查版本 url
const QString checkVerUrl{"http://xxx.xxx.xxx:8001/cur_ver/test"};

UpgradeClient::UpgradeClient(const QString& ver,QWidget *parent) :QDialog (parent),
    ui(new Ui::UpgradeClient),
    curVer(ver)
{
    ui->setupUi(this);
    this->setWindowTitle(QStringLiteral("检测到需要升级"));
    ui->progressBar->setHidden(true);
}

UpgradeClient::~UpgradeClient()
{
    qDebug()<<"~UpgradeClient()";
    delete ui;
}

int UpgradeClient::start()
{
    checkVer();
    eventLoop.exec();

    if(curStatus==Status::NeedUpgrade){
        this->exec();
        if(curStatus==Status::DownloadFinished){
            return 0;
        }
    }else{
        this->reject();
    }
    return -1;
}

void UpgradeClient::on_laterBtn_clicked()
{
    curStatus = Status::Abandon;
    this->reject();
}

void UpgradeClient::on_nowBtn_clicked()
{
    if(pkgName.isEmpty())
        return;
    downloadPkg(pkgName,pkgUrl);
    ui->laterBtn->setEnabled(false);
    ui->nowBtn->setEnabled(false);
}

void UpgradeClient::checkVer()
{
    curStatus = Status::Init;
    QUrl url;
    url.setUrl(checkVerUrl);
    QNetworkRequest request(url);
    verReply = manager.get(request);

    QObject::connect(verReply, &QNetworkReply::finished, this,[&]() {
        if (verReply->error() == QNetworkReply::NoError) {
            QByteArray responseData = verReply->readAll();
            qDebug() << "Response:" << responseData;
            parseJson(responseData);
        } else {
            qDebug() << "Error:" << verReply->errorString();
            curStatus = Status::Error;
        }
        eventLoop.quit();
    });
}

void UpgradeClient::downloadPkg(const QString& _name,const QString& _url)
{
    QUrl url;
    url.setUrl(_url);
    QNetworkRequest request(url);

    QString currentPath = QCoreApplication::applicationDirPath();
    QString filename = currentPath+"/"+_name;
    pkgFile.setFileName(filename);
    if (pkgFile.open(QIODevice::WriteOnly)){
        downloadReply = manager.get(request);

        connect(downloadReply, &QNetworkReply::downloadProgress, this, [&](qint64 bytesReceived, qint64 bytesTotal){
            if(bytesTotal!=0){
                int progress = static_cast<int>((bytesReceived * 100) / bytesTotal);
                qDebug()<<"process "<<progress;
                ui->progressBar->setHidden(false);
                ui->progressBar->setValue(progress);
            }
        });

        connect(downloadReply,&QNetworkReply::readyRead,this,[&](){
            pkgFile.write(downloadReply->readAll());
        });

        connect(downloadReply, &QNetworkReply::finished, this, [&,filename](){
            if(curStatus==Status::Abandon)
                return;

            if (verReply->error() == QNetworkReply::NoError){
                pkgFile.flush();
                pkgFile.close();

                if(ui->progressBar->value()<98){
                    curStatus = Status::Error;
                    ui->logLabel->setText(QStringLiteral("下载安装包出错!"));
                }else{
                    if(!this->runPkg(filename)){
                        curStatus = Status::Error;
                        ui->logLabel->setText(QStringLiteral("安装程序执行失败!"));
                    }else{
                        curStatus = Status::DownloadFinished;
                        this->accept();
                    }
                }

            }else{
                curStatus = Status::Error;
                qDebug() << "Error:" << downloadReply->errorString();
                ui->logLabel->setText(QStringLiteral("下载出错:")+downloadReply->errorString());
            }
        });
    }else {
        qDebug() << "Error: Could not open file for writing";
        curStatus = Status::Error;
        this->reject();
    }
}

void UpgradeClient::parseJson(const QByteArray &jsonData)
{
    QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData);
    if (!jsonDocument.isNull()) {
        if (jsonDocument.isObject()) {
            QJsonObject jsonObject = jsonDocument.object();
            latestVer = jsonObject["ver"].toString();
            pkgDesc = jsonObject["desc"].toString();
            pkgName = jsonObject["file"].toString();
            pkgUrl = jsonObject["url"].toString();
            qDebug()<<"curVer:"<<curVer<<" "<<"latestVer:"<<latestVer;

            QStringList latestV = latestVer.split(".");
            QStringList curV = curVer.split(".");

            if(latestV.size()==3&&curV.size()==3){
                int  lMajorV = latestV.at(0).toUInt();
                int  lMinorV = latestV.at(1).toUInt();
                int  lPathV = latestV.at(2).toUInt();

                int  cMajorV = curV.at(0).toUInt();
                int  cMinorV = curV.at(1).toUInt();
                int  cPathV = curV.at(2).toUInt();

                if(compareVer(lMajorV,lMinorV,lPathV,cMajorV,cMinorV,cPathV)){
                    ui->textEdit->append(QStringLiteral("最新版本:%1\n").arg(latestVer));
                    ui->textEdit->append(pkgDesc);
                    curStatus = Status::NeedUpgrade;

                }else{
                    curStatus = Status::NoUpgradde;
                }

            }else{
                curStatus = Status::Error;
            }
        }
        else{
            curStatus = Status::Error;
        }
    } else {
        qDebug() << "Error: Failed to parse JSON data";
        curStatus = Status::Error;
    }
}

bool UpgradeClient::compareVer(int  lMajor, int  lMinor, int  lPath, int  cMajor, int  cMinor, int  cPath)
{
    int  localVersion[3]{cMajor,cMinor,cPath};
    int  latestVersion[3]{lMajor,lMinor,lPath};
    int k = memcmp(localVersion,latestVersion,sizeof(int)*3);
    qDebug()<<"compareVer "<<k;
    if(k==0){
        return false;
    }else if(k<0){
        return true;
    }else{
        return false;
    }
}

bool UpgradeClient::runPkg(const QString &filename)
{
    QStringList arguments;
    bool success = QProcess::startDetached(filename,arguments);

    if (success) {
        qDebug() << "External program started as a detached process.";
    } else {
        qDebug() << "Failed to start external program.";
    }
    return success;
}

void UpgradeClient::closeEvent(QCloseEvent *event)
{
    qDebug()<<"closeEvent";
    curStatus = Status::Abandon;

    if(verReply){
        verReply->close();
        verReply->deleteLater();
    }

    if(downloadReply){
        downloadReply->close();
        downloadReply->deleteLater();
    }

    if(pkgFile.isOpen()){
        pkgFile.close();
    }

    QDialog::closeEvent(event);
}

//ui文件
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>UpgradeClient</class>
 <widget class="QWidget" name="UpgradeClient">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>409</width>
    <height>240</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QGridLayout" name="gridLayout">
   <item row="0" column="0">
    <widget class="QTextEdit" name="textEdit">
     <property name="readOnly">
      <bool>true</bool>
     </property>
    </widget>
   </item>
   <item row="1" column="0">
    <widget class="QProgressBar" name="progressBar">
     <property name="value">
      <number>0</number>
     </property>
     <property name="textVisible">
      <bool>true</bool>
     </property>
     <property name="invertedAppearance">
      <bool>false</bool>
     </property>
     <property name="format">
      <string>%p%</string>
     </property>
    </widget>
   </item>
   <item row="2" column="0">
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QLabel" name="logLabel">
       <property name="text">
        <string/>
       </property>
      </widget>
     </item>
     <item>
      <spacer name="horizontalSpacer">
       <property name="orientation">
        <enum>Qt::Horizontal</enum>
       </property>
       <property name="sizeHint" stdset="0">
        <size>
         <width>40</width>
         <height>20</height>
        </size>
       </property>
      </spacer>
     </item>
     <item>
      <widget class="QPushButton" name="nowBtn">
       <property name="text">
        <string>现在</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="laterBtn">
       <property name="text">
        <string>稍后</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>



 效果:

1,启动检测升级。

2, 点击 【现在】 开始下载 安装包。

docker 部署一下服务端:

1. 下载镜像:docker pull python3.7
2. 启动容器:docker run -it -p 8001:8001 -p 8002:8002 --name upgrade python:3.7 /bin/bash
3. 安装环境:pip3.7 install fastapi &pip3.7 install uvicorn
4. 拷贝文件:docker cp  upgrade upgrade:/home
5. 退出容器:Ctrl+P+Q

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1512807.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何采集电商网站数据之抖音商品搜索商品详情数据采集

电商网站的数据包含了丰富的市场和用户信息&#xff0c;对于商业决策和竞争分析至关重要。数据采集是获取这些有价值信息的关键步骤。下面将详细介绍如何采集电商网站数据&#xff0c;帮助你掌握这一重要技能。 1. 设定数据采集目标 在开始采集之前&#xff0c;明确你的数据采…

初识进程状态

&#x1f30e;进程状态【上】 文章目录&#xff1a; 进程状态 发现进程的状态 运行队列 进程排队 进程状态的表述       状态在代码中的表示       运行状态       阻塞状态       挂起状态 总结 前言&#xff1a; 为了搞明白正在运行的进程是什么意思…

优思学院|为什么企业要做质量管理体系认证?

在二战后的美国&#xff0c;公司对自己的产品质量颇为自满。市场需求旺盛&#xff0c;产品销售状况良好&#xff0c;即便产品存在质量缺陷&#xff0c;消费者似乎也能接受。这种态度导致了一种现象&#xff1a;即使在生产结束时发现了一定比例的缺陷&#xff0c;公司也能通过加…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Span)

作为Text组件的子组件&#xff0c;用于显示行内文本的组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 该组件从API Version 10开始支持继承父组件Text的属性&#xff0c;即如果子组件未设置…

解决 mmseg/models/decode_heads/mask2former_head.py 中__init__()关键字参数错误

目录 【1 - 问题描述】 【2 - 报错位置检查】 【3 - 问题解决】 【4 - 解决的过程】 【1 - 问题描述】 在mmsegmentation中运行mask2former模型的分割任务遇到报错缺少关键字的错误&#xff1a; TypeError: class Mask2FormerHead in mmseg/models/decode_heads/mask2form…

【gpt实践】同时让chatgpt和claude开发俄罗斯方块

最近chatgpt和claude都在使用&#xff0c;其实大部分日常使用场景表现都没有相差太多&#xff0c;想搞一个有趣的小实验&#xff0c;如果同时让chatgpt和claude开发俄罗斯方块谁会表现的更好呢&#xff0c;说干就干&#xff01; prompt 我选择了用英文描述&#xff0c;毕竟英…

大模型应用开发-大模型数据侧总结

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径及一点个人思考大模型应用开发实用开源项目汇总大模型问答项目…

使用 Amazon Bedrock 和 RAG 构建 Text2SQL 行业数据查询助手

背景 随着企业数据量的持续增长&#xff0c;如何让非技术人员也能轻松分析数据、获得商业洞察成为了当前的痛点。本文将介绍如何使用亚马逊云科技的大语言模型服务 Amazon Bedrock 以及 RAG (Retrieval Augmented Generation)&#xff0c;实现 Text2SQL 功能&#xff0c;以此为…

【深度学习笔记】5_12稠密连接网络(DenseNet)

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 5.12 稠密连接网络&#xff08;DenseNet&#xff09; ResNet中的跨层连接设计引申出了数个后续工作。本节我们介绍其中的一个&#xf…

【论文速读】| DeepGo:预测式定向灰盒模糊测试

本次分享论文为&#xff1a;DeepGo: Predictive Directed Greybox Fuzzing 基本信息 原文作者&#xff1a;Peihong Lin, Pengfei Wang, Xu Zhou, Wei Xie, Gen Zhang, Kai Lu 作者单位&#xff1a;国防科技大学计算机学院 关键词&#xff1a;Directed Greybox Fuzzing, Path…

Postman请求API接口测试步骤和说明

Postman请求API接口测试步骤 本文测试的接口是国内数智客&#xff08;www.shuzike.com&#xff09;的API接口手机三要素验证&#xff0c;验证个人的姓名&#xff0c;身份证号码&#xff0c;手机号码是否一致。 1、设置接口的Headers参数。 Content-Type&#xff1a;applicati…

2024蓝桥杯每日一题(区间合并)

一、第一题&#xff1a;挤牛奶 解题思路&#xff1a;区间合并 区间合并模板题 【Python程序代码】 n int(input()) a [] for i in range(n):l,r map(int,input().split())a.append([l,r]) def cmp(x):return x[0],x[1] a.sort(keycmp) res1,res20,0 st,ed a[0][0…

JS-12-关键字this、apply()、call()

一、对象的方法 在一个对象中绑定函数&#xff0c;称为这个对象的方法。 示例&#xff1a; 1、对象&#xff1a; var xiaoming {name: 小明,birth: 1990 }; 2、给xiaoming绑定一个函数。比如&#xff0c;写个age()方法&#xff0c;返回xiaoming的年龄&#xff1a; var x…

亲测抖音小程序备案流程,抖音小程序如何备案,抖音小程序备案所需准备资料

抖音小程序为什么要备案&#xff0c;抖音官方给出如下说明&#xff1a; 1、2024年3月15日后提交备案的小程序将不保证2024年3月31日前平台可初审通过&#xff1b; 2、2024年3月31日后未完成备案小程序将被下架处理。 一&#xff0c;备案前需准备资料 &#xff08;一&#xff0…

Python 导入Excel三维坐标数据 生成三维曲面地形图(面) 1、线条折线曲面

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 代码: import pandas as pd import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D from matplotlib.colors import ListedColor…

k8s+wordpress+zabbix+elastic+filebeat+kibana服务搭建以及测试

一&#xff0c;环境&#xff1a;docker&#xff0c;k8s&#xff0c;zabbix&#xff0c;以及搭建worpdress&#xff0c;elasticsearch&#xff0c;filebeat&#xff0c;kibana 二&#xff0c;主机分配&#xff1a; 名称host详述个人博客3192.168.142.133 搭配mysql8.0.36的数据…

Stable Diffusion 模型:从噪声中生成逼真图像

你好&#xff0c;我是郭震 简介 Stable Diffusion 模型是一种生成式模型&#xff0c;可以从噪声中生成逼真的图像。它由 Google AI 研究人员于 2022 年提出&#xff0c;并迅速成为图像生成领域的热门模型。 数学基础 Stable Diffusion模型基于一种称为扩散概率模型(Diffusion P…

【QT】文件流操作(QTextStream/QDataStream)

文本流/数据流&#xff08;二级制格式&#xff09; 文本流 &#xff08;依赖平台&#xff0c;不同平台可能乱码&#xff09;涉及文件编码 #include <QTextStream>操作的都是基础数据类型&#xff1a;int float string //Image Qpoint QRect就不可以操作 需要下面的 …

ES分片均衡策略分析与改进

从故障说起 某日早高峰收到 Elasticsearch 大量查询超时告警&#xff0c;不同于以往&#xff0c;查看 Elasticsearch 查询队列监控后发现&#xff0c;仅123节点存在大量查询请求堆积。 各节点查询队列堆积情况 查看节点监控发现&#xff0c;123节点的 IO 占用远高于其他节点。…

喜报!聚铭网络实力入选2024年度扬州市网络安全技术支撑服务机构

近日&#xff0c;中共扬州市委网络安全和信息化委员会办公室正式公布了“2024年度扬州市网络安全技术支撑服务机构”名单&#xff0c;聚铭网络凭借其卓越的技术实力与优质的安服能力&#xff0c;在众多竞争者中脱颖而出&#xff0c;光荣上榜&#xff01; 为了健全扬州市网络安…