pyinstaller 自动更新版本

自动更新版本流程

1. 检测新版本,弹窗提醒更新
2.下载更新文件压缩包
3.将旧的配置文件移到新配置文件夹中
4.关闭当前进程,移除旧文件夹里面的文件,将新文件移到当前位置
5.启动新程序
# -*- coding: utf-8 -*-
import json
import subprocess
import sys
import textwrap
import threading
# import webbrowser
import zipfile
from pathlib import os
# import os
import requests
import shutil
import semver
import tkinter
import tkinter.messagebox
from tqdm.tk import tqdm
from tkinter import Tk

"""
pyinstaller -D unit_test.py -n '软件测试'
"""


class Unit_Test_Version():
    def __init__(self, root=None):
        # 当前版本号
        self.current_version = '1.0.0'
        # 请求软件资源信息
        self.version_url = 'http://127.0.0.1:28082/search_version'
        # 软件名
        self.app_name = '软件测试'
        # 软件类型(同一个表存多个软件的版本)
        self.soft_type = 4
        self.root = root

    def upzip_file(self, zip_path=None, unzip_path=None):
        """
        :zip_path 压缩文件路径
        :unzip_path 解压文件路径
        :return 解压 zip 文件,返回所有解压文件夹下的路径
        """
        zip_file = zipfile.ZipFile(zip_path)
        if not os.path.isdir(unzip_path):
            os.mkdir(unzip_path)
        for names in zip_file.namelist():
            zip_file.extract(names, unzip_path)
        zip_file.close()
        return [os.path.join(unzip_path, i).replace('\\', '/') for i in zip_file.namelist()]

    def upzip_file_new(self, zip_path=None, unzip_path=None):
        paths = []
        if not os.path.exists(unzip_path):
            os.mkdir(unzip_path)
        with zipfile.ZipFile(file=zip_path, mode='r') as zf:
            for old_name in zf.namelist():
                file_size = zf.getinfo(old_name).file_size
                # 由于源码遇到中文是cp437方式,所以解码成gbk,windows即可正常
                new_name = old_name.encode('cp437').decode('gbk')
                # 拼接文件的保存路径
                new_path = os.path.join(unzip_path, new_name)
                paths.append(new_path)
                # 判断文件是文件夹还是文件
                if file_size > 0:
                    # 是文件,通过open创建文件,写入数据
                    with open(file=new_path, mode='wb') as f:
                        # zf.read 是读取压缩包里的文件内容
                        f.write(zf.read(old_name))
                else:
                    # 是文件夹,就创建
                    os.mkdir(new_path)
        return paths

    def get_version_info(self):
        # 获取版本信息
        try:
            data = json.dumps({'soft_type': self.soft_type})
            res = requests.post(self.version_url, data=data, timeout=15)
            response = json.loads(res.text)
        except:
            response = {}
        return response

    def del_current_pid(self):
        pid = os.getpid()  # 关闭后台进程
        try:
            os.system('taskkill -f -pid %s' % pid)
        except:
            pass

    def check_version(self, version_info):
        self.root = Tk()
        self.root.withdraw()  # 隐藏主窗口
        t = threading.Thread(target=lambda: self.check_for_update(version_info), name='update_thread')
        t.daemon = True  # 守护为True,设置True线程会随着进程一同关闭
        t.start()
        self.root.mainloop()

    # def browser_update(self, ver, window):
    #     webbrowser.open(ver.get('updateUrl'))
    #     window.destroy()

    def check_for_update(self, version_info):
        version = version_info['version']
        ver = semver.compare(version, self.current_version)
        if ver:
            # 更新的内容
            publish_notes = version_info['remark']
            message = f'当前版本[{self.current_version}], 有新版本[{version}]\n' \
                      f'更新内容:\n{publish_notes}\n请选择立即去下载更新[确定],暂不更新[取消]?'
            result = tkinter.messagebox.askokcancel(title='更新提示', message=message)
            if result:
                self.is_down_update = True
                self.down_zip(version_info)
                self.root.destroy()
            else:
                self.root.destroy()

    def down_zip(self, version_info):
        zip_url, file_name = version_info['zip_url'], version_info['file_name']
        res = requests.get(zip_url)
        # 当前文件夹路径
        # current_path = os.path.dirname(os.path.realpath(sys.argv[0]))
        # 当前文件夹上一级路径
        current_path = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
        zip_path = os.path.join(current_path, f'{zip_url.split("/")[-1]}')
        unzip_path = os.path.join(current_path, file_name+'_new')
        print('zip_path:', zip_path)
        print('unzip_path:', unzip_path)
        # with open(zip_path, 'wb') as f:
        #     f.write(res.content)

        chunk_size = 1024 * 100
        total_size = int(res.headers.get('content-length', 0))
        progress_bar = tqdm(iterable=res.iter_content(chunk_size=chunk_size), tk_parent=None,
                            leave=False, total=total_size, unit='B', unit_scale=True)
        # 设置下载进度条位置
        screenWidth = progress_bar._tk_window.winfo_screenwidth()
        screenHeight = progress_bar._tk_window.winfo_screenheight()
        progress_bar._tk_window.geometry(f"300x160+{screenWidth - 300}+{screenHeight - 400}")
        try:
            with open(zip_path, 'wb') as f:
                for chunk in res.iter_content(chunk_size):
                    if chunk:
                        f.write(chunk)
                        progress_bar.update(len(chunk))
        except Exception as ee:
            progress_bar.close()
            return '下载异常,检查链接'
        progress_bar.close()
        filepath = self.upzip_file_new(zip_path, unzip_path)[0]
        print('filepath:', filepath)
        # 删除压缩包
        if os.path.exists(zip_path):
            os.remove(zip_path)
        # 老版本配置文件
        old_config = os.path.join(current_path, 'config', 'config.ini')
        # 新版本的配置文件路径
        new_config = os.path.join(filepath, '客户端配置文件.ini')
        print('new_config:', new_config)
        print('old_config:', old_config)
        if os.path.exists(new_config) and os.path.exists(old_config):
            os.remove(new_config)
        if os.path.exists(old_config):
            if not os.path.exists(os.path.join(filepath, 'config')):
                os.makedirs(os.path.join(filepath, 'config'))
            shutil.copy(old_config, new_config)
        self.auto_update_setup(filepath)

    def make_updater_bat(self, filepath):
        filepath = os.path.abspath(filepath)
        # 当前进程ID
        pid = os.getpid()
        current_path = os.path.dirname(os.path.realpath(sys.argv[0]))
        print('current_path:', current_path)
        # 删除临时文件夹路径
        new_file_dir = os.path.abspath(os.path.join(filepath, ".."))

        # old_file = filepath.replace(new_file_dir.replace('\\', '/'), current_path.replace('\\', '/'))  # 旧的文件夹
        # old_file = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
        old_file = current_path

        old_file_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))  # 上一级文件夹
        new_file = filepath      # 新版本文件夹

        # 运行exe路径
        # new_file_exe = os.path.join(filepath, f'{self.app_name}.exe').replace('\\', '/')
        # new_file_exe = new_file_exe.replace(new_file_dir.replace('\\', '/'), old_file_dir.replace('\\', '/'))
        new_file_exe = os.path.join(current_path, f'{self.app_name}.exe').replace('\\', '/')

        print('filepath:', filepath)
        print('new_file_dir:', new_file_dir.replace('\\', '/'))
        print('current_path:', current_path.replace('\\', '/'))
        # 自动更新脚本
        """
        关闭当前进程,删除所有文件除了updater.bat,将新版本下的所有文件移到当前文件夹下,访问新exe文件
        """
        bat_name = 'updater.bat'
        bat_path = os.path.join(old_file, bat_name)
        print('bat_path:', bat_path)
        with open(bat_path, 'w', encoding='gbk') as updater:
            updater.write(textwrap.dedent(f'''\
            @echo off
            echo 正在更新[{self.app_name}]最新版本,请勿关闭窗口...
            ping -n 2 127.0.0.1
            taskkill -f -pid {pid}
            echo 正在复制[{self.app_name}],请勿关闭窗口...
            for /D %%i in (*) DO (
                if not %%i=={bat_name} (
                echo 删除文件:%%i 
                rmdir /s /q "%%i"
                )
            )
            for %%i in (*) DO (
                if not %%i=={bat_name} (
                echo 删除文件:%%i 
                del /s /q "%%i"
                )
            )
            ping -n 2 127.0.0.1
            xcopy /s /e /y /q "{new_file}\*" "{current_path}"
            ping -n 2 127.0.0.1
            if exist "{new_file_dir}" rd /s /q "{new_file_dir}"
            echo 更新完成,等待自动启动{self.app_name}...
            ping -n 2 127.0.0.1
            "{new_file_exe}"
            exit
            '''))
            updater.flush()
        return bat_path

    def auto_update_setup(self, filepath):
        # 自动更新
        bat_path = self.make_updater_bat(filepath)
        subprocess.Popen(bat_path, encoding="gbk", shell=True)

    def run(self):
        version_info = self.get_version_info()
        if version_info and version_info["code"] == 200:
            if version_info['is_force_update'] == 0:
                # 可选择更新
                self.check_version(version_info)
            else:
                # 强制更新
                self.down_zip(version_info)


if __name__ == '__main__':
    start = Unit_Test_Version()
    start.run()
# -*- coding: utf-8 -*-
import time
from datetime import datetime
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from pydantic import BaseModel
import asyncio
from fastapi import FastAPI, File, UploadFile, Form
from link_sql import Link_Sql
from tools import log_count
from starlette.responses import FileResponse

"""
http://127.0.0.1:19315/search_version
post请求
参数:soft_type 软件类型
data={
"soft_type":1
}
返回结果:
{
'version': '1.0.2', 
'zip_url': 'https://127.0.0.1:19315/zipfiles/xxxx.zip', 
'file_name': '软件测试', 
'remark': '版本测试',  # 更新内容
'is_force_update': 0 # 0.可选择是否更新 1. 强制更新
}

"""

class Comment_Task():
    def __init__(self):
        self.loggings = log_count()
        self.db = Link_Sql(self.loggings)

    def search_version(self, item):
        """
        查询版本信息
        """
        soft_type = item.soft_type
        sql = 'select version,zip_url,file_name,remark,is_force_update from plugin_version where soft_type=%s ORDER BY create_time desc'
        result_data = self.db.one_search(sql % soft_type, to_json=True)
        if result_data:
            result_data["code"] = 200
        else:
            result_data["code"] = 500
        print('result_data:', result_data)
        return result_data


app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 表示允许任何源
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


class Update_Status(BaseModel):
    soft_type: int


method_name_dic = {
    # 查询版本信息
    "search_version": Comment_Task().search_version,
}


@app.post('/search_version')
async def search_version(item: Update_Status):
    method_name = "search_version"
    try:
        loop = asyncio.get_event_loop()
        method_ = method_name_dic[method_name]
        data = await loop.run_in_executor(None, method_, item)
        return data
    except:
        return {"code": 500, "message": "查询失败"}


@app.get("/zipfiles/{file_path}/{file_name}")
async def download(file_path: str, file_name: str):
    # 下载压缩包 zipfiles为在当前文件 建立的文件夹放置压缩包
    filename = f"zipfiles/{file_path}/{file_name}"
    print('filename:', filename)
    return FileResponse(filename, filename=f"{file_name}")  #展示的下载的文件名


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=19315)

你可能感兴趣的:(1024程序员节,python)