欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

PyQt5程序自動更新的實現(xiàn)代碼

 更新時間:2025年05月07日 10:22:27   作者:乾巫宇宙國監(jiān)察特使  
開發(fā)的QT工具需要給不同的部門間使用,工具版本迭代需要經(jīng)歷打包->壓縮->上傳到共享目錄->下載解壓,協(xié)作十分不爽,且PyQt5不像原生的Qt有自己的更新框架,因此需要自己實現(xiàn)一套更新邏輯,所以本文介紹了PyQt5程序自動更新的實現(xiàn),需要的朋友可以參考下

一、背景

開發(fā)的QT工具需要給不同的部門間使用,工具版本迭代需要經(jīng)歷打包->壓縮->上傳到共享目錄->下載解壓,協(xié)作十分不爽。且PyQt5不像原生的Qt有自己的更新框架,因此需要自己實現(xiàn)一套更新邏輯。

二、需要關(guān)注的核心問題

  • Qt應(yīng)用需要運行起來后,才能有可交互的界面,即優(yōu)先執(zhí)行app = QApplication(sys.argv)
  • 更新時,需要替換掉有修改的模塊,但是在Qt程序運行時,某些模塊是被占用的,無法被替換刪除,因此需要在Qt程序停止后才能替換。

三、實現(xiàn)思路

  • 另啟動一個監(jiān)控程序,專門負(fù)責(zé)更新。
  • 通過編寫執(zhí)行.bat文件,負(fù)責(zé)Qt程序停止運行后的更新、替換、重新啟動等一系列操作。 很明顯,第二種方法看起來更簡潔,迅速。

四、具體實現(xiàn)

# main.py
import argparse  
import os  
import sys  
  
from PyQt5.QtWidgets import QApplication, QMessageBox  
from packaging import version  
  
from main import MainForm  
from updates_scripts.updater import update_main  
  
  
def check_is_need_update():  
    """檢查應(yīng)用程序是否需要更新"""  
    try:  
        remote_path = r'\\10.10.10.10' # 我這里是共享文件服務(wù)器,根據(jù)需求自己替換  

        # 讀取本地版本  
        with open('version.bin', 'r', encoding='utf-8') as f:  
            local_version = f.readline().strip()  

        # 遍歷遠(yuǎn)程文件夾查找最新版本  
        max_version = version.parse('0')  
        update_path = ''  

        for root, dirs, files in os.walk(remote_path):  
            for file in files:  
                if file.startswith('DebugTool_V') and file.endswith('.zip'):  
                    remote_version = file[file.find('_V') + 2:file.rfind('.')]  
                    try:  
                        remote_ver = version.parse(remote_version)  
                        if remote_ver > max_version:  
                            max_version = remote_ver  
                            update_path = os.path.join(root, file)  
                    except version.InvalidVersion:  
                        continue  

        # 比較版本  
        local_ver = version.parse(local_version)  
        if max_version > local_ver:  
            # 必須在主線程中顯示Qt對話框  
            reply = QMessageBox.question(  
            None,  
            "更新提示",  
            f"發(fā)現(xiàn)新版本 {max_version} (當(dāng)前版本 {local_version}),是否更新?",  
            QMessageBox.Yes | QMessageBox.No,  
            QMessageBox.Yes  
            )  

            if reply == QMessageBox.Yes:  
                print("用戶選擇更新")  
                root_path = os.path.dirname(os.path.abspath(sys.argv[0]))  
                return {  
                'update': True,  
                'root_dir': root_path,  
                'version': f"DebugTool_V{str(max_version)}",  
                'remote_file': update_path  
                }  
            else:  
                print("用戶取消更新")  
       return {'update': False}  

    except Exception as e:  
        print(f"檢查更新時出錯: {str(e)}")  
        return {'update': False, 'error': str(e)}  
  
  
if __name__ == "__main__":  
# 初始化Qt應(yīng)用  
app = QApplication(sys.argv)  
parser = argparse.ArgumentParser()  
parser.add_argument('--check', action='store_true', help='是否需要檢查更新')  
args = parser.parse_args()  
# 檢查更新  
if not args.check:  
    update_info = check_is_need_update()  

    if not args.check and update_info.get('update'):  
    # 執(zhí)行更新邏輯  
    print(f"準(zhǔn)備更新到: {update_info['version']}")  
    # 這里添加您的更新邏輯  
    if update_main(root_dir=update_info['root_dir'], version=update_info['version'],  
        remote_file=update_info['remote_file']):  
        root_path = update_info['root_dir']  
        bat_content = f"""@echo off  
        REM 等待主程序退出  
        :loop  
        tasklist /FI "IMAGENAME eq DebugTool.exe" 2>NUL | find /I "DebugTool.exe" >NUL  
        if "%ERRORLEVEL%"=="0" (  
            timeout /t 1 /nobreak >NUL  
            goto loop  
        )  

        REM 刪除舊文件  
        del /F /Q "{root_path}\*.*"  
        for /D %%p in ("{root_path}\*") do (  
            if /I not "%%~nxp"=="DebugTool" (  
                if /I not "%%~nxp"=="plans" (  
                    if /I not "%%~nxp"=="Logs" (  
                        if /I not "%%~nxp"=="results" (  
                            rmdir "%%p" /s /q  
                        )  
                    )  
                )  
            )  
        )  

        REM 拷貝新版本文件(使用robocopy實現(xiàn)可靠拷貝)  
        robocopy {os.path.join(root_path, 'DebugTool')} {root_path} /E  

        REM 啟動新版本  
        start {root_path}\DebugTool\DebugTool.exe --check  

        REM 刪除臨時文件  
        del "%~f0"  
        """  
        # 將批處理腳本寫入臨時文件  
        bat_path = os.path.join(root_path, 'DebugTool', "update.bat")  
        with open(bat_path, 'w') as f:  
            f.write(bat_content)  
        # 啟動批處理腳本  
        os.startfile(bat_path)  

        # 退出當(dāng)前程序  
        QApplication.instance().quit()  
    else:  
        # 彈出更新失敗提示框  
        QMessageBox.critical(None, "更新失敗", "更新失敗,請檢查或重新打開選擇不更新")  
        sys.exit(0)  
else:  
    # 正常啟動應(yīng)用  
    win = MainForm()  
    win.show()  
    sys.exit(app.exec_())
updater.py
# -*- coding: utf-8 -*-  
"""  
Time : 2025/5/6 20:50  
Author : jiaqi.wang  
"""  
# -*- coding: utf-8 -*-  
"""  
Time : 2025/5/6 9:39  
Author : jiaqi.wang  
"""  
import json  
import os  
import shutil  
import sys  
import zipfile  
from datetime import datetime  
  
from PyQt5.QtCore import QSettings, Qt  
from PyQt5.QtWidgets import (QApplication, QLabel, QMessageBox, QProgressDialog)  
  
  
def get_resource_path(relative_path):  
    """獲取資源的絕對路徑,兼容開發(fā)模式和打包后模式"""  
    if hasattr(sys, '_MEIPASS'):  
    return os.path.join(sys._MEIPASS, relative_path)  
    return os.path.join(os.path.abspath("."), relative_path)  
  
  
class UpdateConfig:  
    """更新系統(tǒng)配置"""  

    def __init__(self):  
        self.settings = QSettings("DebugTool", "DebugTool")  

        # 修改為共享文件夾路徑  
        self.config = {  
        'update_url': r"\\10.10.10.10",  
        'auto_check': self.settings.value("auto_update", True, bool),  
        'allow_incremental': True,  
        'max_rollback_versions': 3,  
        'app_name': 'DebugTool',  
        'main_executable': 'DebugTool.exe' if sys.platform == 'win32' else 'DebugTool'  
        }  

    def __getitem__(self, key):  
        return self.config[key]  

    def set_auto_update(self, enabled):  
        self.config['auto_check'] = enabled  
        self.settings.setValue("auto_update", enabled)  
  
  
class SharedFileDownloader:  
    """從Windows共享文件夾下載文件的下載器"""  

    def __init__(self, config, parent=None):  
        self.config = config  
        self.parent = parent  
        self.temp_dir = os.path.join(os.path.expanduser("~"), "temp", f"{self.config['app_name']}_updates")  
        os.makedirs(self.temp_dir, exist_ok=True)  

    def download_update(self, update_info, progress_callback=None):  
        """從共享文件夾下載更新包"""  
        try:  
            if update_info['update_type'] == 'incremental':  
                # 增量更新  
                return self.download_incremental(update_info, progress_callback)  
            else:  
                # 全量更新  
                return self.download_full(update_info, progress_callback)  
        except Exception as e:  
            raise Exception(f"從共享文件夾下載失敗: {str(e)}")  

    def download_full(self, update_info, progress_callback):  
        """下載完整更新包"""  
        local_file = os.path.join(self.temp_dir, f"{update_info['version']}.zip")  

        self._copy_from_share(update_info['remote_file'], local_file, progress_callback)  
        return local_file  

    def download_incremental(self, update_info, progress_callback):  
        """下載增量更新包"""  
        incremental = update_info['incremental']  
        remote_file = f"incremental/{incremental['file']}"  
        local_file = os.path.join(self.temp_dir, f"{update_info['version']}.zip")  

        self._copy_from_share(remote_file, local_file, progress_callback)  

        # 下載差異文件清單  
        remote_manifest = f"incremental/{incremental['manifest']}"  
        local_manifest = os.path.join(self.temp_dir, f"inc_{update_info['version']}.json")  
        self._copy_from_share(remote_manifest, local_manifest, None)  

        return local_file  

    def _copy_from_share(self, remote_path, local_path, progress_callback):  
        """從共享文件夾復(fù)制文件"""  
        # 構(gòu)造完整的共享路徑  
        source_path = remote_path  

        # 確保使用UNC路徑格式  
        if not source_path.startswith('\\\\'):  
            source_path = '\\\\' + source_path.replace('/', '\\')  

        # 標(biāo)準(zhǔn)化路徑  
        source_path = os.path.normpath(source_path)  
        local_path = os.path.normpath(local_path)  

        # 確保本地目錄存在  
        os.makedirs(os.path.dirname(local_path), exist_ok=True)  

        if progress_callback:  
            progress_callback(0, f"準(zhǔn)備從共享文件夾 {source_path} 復(fù)制...")  
            QApplication.processEvents()  

        try:  
            # 獲取文件大小用于進(jìn)度顯示  
            total_size = os.path.getsize(source_path)  

            # 模擬進(jìn)度更新(文件復(fù)制是原子操作)  
            if progress_callback:  
                progress_callback(30, "正在從共享文件夾復(fù)制文件...")  
                QApplication.processEvents()  

            # 執(zhí)行文件復(fù)制  
            shutil.copy2(source_path, local_path)  

            # 驗證文件  
            if os.path.getsize(local_path) != total_size:  
                raise Exception("文件大小不匹配,復(fù)制可能不完整")  

            if progress_callback:  
                progress_callback(100, "文件復(fù)制完成!")  
        except Exception as e:  
        # 清理可能已部分復(fù)制的文件  
        if os.path.exists(local_path):  
            try:  
                os.remove(local_path)  
            except:  
                pass  
                raise e  
  
  
class UpdateApplier:  
"""更新應(yīng)用器"""  
  
def __init__(self, config, backup_dir):  
    self.config = config  
    self.backup_dir = backup_dir  
    os.makedirs(self.backup_dir, exist_ok=True)  
  
def apply_update(self, update_file, update_info, progress_callback=None):  
    """應(yīng)用更新"""  
    try:  
        if progress_callback:  
            progress_callback(10, "開始應(yīng)用更新...")  

        if update_info['update_type'] == 'incremental':  
            self._apply_incremental_update(update_file, update_info)  
        else:  
            self._apply_full_update(update_file, update_info['root_dir'])  

        if progress_callback:  
            progress_callback(90, "更新版本信息...")  

        self._update_version_file(update_info['version'], update_info['root_dir'])  

        if progress_callback:  
            progress_callback(100, "更新完成!")  

        return True  
    except Exception as e:  
        if progress_callback:  
            progress_callback(0, f"更新失敗: {str(e)}")  
            raise Exception(f"更新失敗: {str(e)}")  
  
def _create_backup(self):  
    """創(chuàng)建當(dāng)前版本的備份"""  
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")  
    backup_path = os.path.join(self.backup_dir, f"backup_{timestamp}")  
    os.makedirs(backup_path, exist_ok=True)  

    # 備份整個應(yīng)用目錄  
    app_dir = r'xxx'  
    for item in os.listdir(app_dir):  
        src = os.path.join(app_dir, item)  
        if os.path.isfile(src):  
            shutil.copy2(src, os.path.join(backup_path, item))  
        elif os.path.isdir(src) and not item.startswith('_'):  
            shutil.copytree(src, os.path.join(backup_path, item))  

    return backup_path  
  
def _apply_full_update(self, update_file, root_dir):  
    """應(yīng)用完整更新"""  

    # 解壓更新包  
    with zipfile.ZipFile(update_file, 'r') as zip_ref:  
        zip_ref.extractall(root_dir)  

    # 解壓到臨時目錄  
    temp_dir = os.path.join(os.path.dirname(update_file), "temp_full_update")  
    with zipfile.ZipFile(update_file, 'r') as zip_ref:  
        zip_ref.extractall(temp_dir)  

    # 刪除解壓后的目錄  
    shutil.rmtree(os.path.dirname(os.path.dirname(update_file)))  
    shutil.rmtree(self.backup_dir)  

def _apply_incremental_update(self, update_file, update_info):  
    """應(yīng)用增量更新"""  
    root_dir = update_info['root_dir']  

    # 解析差異文件清單  
    manifest_file = os.path.join(  
    os.path.dirname(update_file),  
    f"inc_{update_info['version']}.json"  
    )  

    with open(manifest_file, 'r') as f:  
        manifest = json.load(f)  

    # 解壓增量包  
    temp_dir = os.path.join(os.path.dirname(update_file), "temp_inc_update")  
    with zipfile.ZipFile(update_file, 'r') as zip_ref:  
        zip_ref.extractall(temp_dir)  

    # 應(yīng)用更新  
    for action in manifest['actions']:  
        src = os.path.join(temp_dir, action['path'])  
        dst = os.path.join(root_dir, action['path'])  

        if action['type'] == 'add' or action['type'] == 'modify':  
            os.makedirs(os.path.dirname(dst), exist_ok=True)  
            if os.path.exists(dst):  
                os.remove(dst)  
            shutil.move(src, dst)  
        elif action['type'] == 'delete':  
            if os.path.exists(dst):  
                if os.path.isfile(dst):  
                    os.remove(dst)  
                else:  
                    shutil.rmtree(dst)  
  
def _update_version_file(self, new_version, root_dir):  
    """更新版本號文件"""  
    version_file = os.path.join(root_dir, 'version.txt')  
    with open(version_file, 'w') as f:  
        f.write(new_version)  

def _rollback_update(self, backup_path):  
    """回滾到備份版本"""  
    if not os.path.exists(backup_path):  
        return False  

    app_dir = r'xxx'  

    # 恢復(fù)備份  
    for item in os.listdir(backup_path):  
        src = os.path.join(backup_path, item)  
        dst = os.path.join(app_dir, item)  

        if os.path.exists(dst):  
            if os.path.isfile(dst):  
                os.remove(dst)  
            else:  
                shutil.rmtree(dst)  

        if os.path.isfile(src):  
            shutil.copy2(src, dst)  
        else:  
            shutil.copytree(src, dst)  

    return True  
  
  
class UpdateProgressDialog(QProgressDialog):  
    """更新進(jìn)度對話框"""  
  
def __init__(self, parent=None):  
    super().__init__("", "取消", 0, 100, parent)  
    self.setWindowTitle("正在更新")  
    self.setWindowModality(Qt.WindowModal)  
    self.setFixedSize(500, 150)  

    self.detail_label = QLabel()  
    self.setLabel(self.detail_label)  
  
def update_progress(self, value, message):  
    self.setValue(value)  
    self.detail_label.setText(message)  
    QApplication.processEvents()  
  
  
def update_main(root_dir: str, version: str, remote_file: str):  
    try:  
    config = UpdateConfig()  

    update_info = {  
        "version": version,  
        "update_type": "full",  
        "changelog": "修復(fù)了一些bug",  
        'root_dir': root_dir,  
        'remote_file': remote_file  
    }  

    progress = UpdateProgressDialog()  
    downloader = SharedFileDownloader(config)  
    applier = UpdateApplier(config, os.path.join(os.path.expanduser("~"), f"{config['app_name']}_backups"))  
    # 下載更新  
    update_file = downloader.download_update(  
        update_info,  
        progress.update_progress  
    )  

    # 應(yīng)用更新  
    if progress.wasCanceled():  
        sys.exit(1)  

    success = applier.apply_update(  
        update_file,  
        update_info,  
        progress.update_progress  
    )  

    if success:  
        QMessageBox.information(  
        None,  
        "更新完成",  
        "更新已成功安裝,點擊OK后,即將重新啟動應(yīng)用程序(10s內(nèi))。"  
        )  
        return True  
    return True  
except:  
    return False

五、總結(jié)

到此這篇關(guān)于PyQt5程序自動更新的實現(xiàn)代碼的文章就介紹到這了,更多相關(guān)PyQt5程序自動更新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • wxpython中利用線程防止假死的實現(xiàn)方法

    wxpython中利用線程防止假死的實現(xiàn)方法

    上午抽空學(xué)習(xí)了一下在wxpython中啟用線程的方法,將GUI和功能的執(zhí)行分開,果然程序運行起來杠杠滴。因為我那個軟件的代碼暫時不能公開,這里專門寫個小程序,作為今天的筆記吧
    2014-08-08
  • python獲取當(dāng)前時間對應(yīng)unix時間戳的方法

    python獲取當(dāng)前時間對應(yīng)unix時間戳的方法

    這篇文章主要介紹了python獲取當(dāng)前時間對應(yīng)unix時間戳的方法,涉及Python時間操作的相關(guān)技巧,非常簡單實用,需要的朋友可以參考下
    2015-05-05
  • Python中常見的導(dǎo)入方式總結(jié)

    Python中常見的導(dǎo)入方式總結(jié)

    這篇文章主要介紹了Python中常見的導(dǎo)入方式總結(jié),文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)python的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-05-05
  • Python3爬蟲學(xué)習(xí)之將爬取的信息保存到本地的方法詳解

    Python3爬蟲學(xué)習(xí)之將爬取的信息保存到本地的方法詳解

    這篇文章主要介紹了Python3爬蟲學(xué)習(xí)之將爬取的信息保存到本地的方法,結(jié)合實例形式詳細(xì)分析了Python3信息爬取、文件讀寫、圖片存儲等相關(guān)操作技巧,需要的朋友可以參考下
    2018-12-12
  • 淺談Python處理PDF的方法

    淺談Python處理PDF的方法

    這篇文章主要介紹了Python處理PDF的兩種方法代碼示例,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • 對python捕獲ctrl+c手工中斷程序的兩種方法詳解

    對python捕獲ctrl+c手工中斷程序的兩種方法詳解

    今天小編就為大家分享一篇對python捕獲ctrl+c手工中斷程序的兩種方法詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-12-12
  • Python中的@cache巧妙用法

    Python中的@cache巧妙用法

    緩存是一種空間換時間的策略,緩存的設(shè)置可以提高計算機系統(tǒng)的性能,這篇文章主要介紹了Python中的@cache巧妙用法,需要的朋友可以參考下
    2023-04-04
  • Python常用標(biāo)準(zhǔn)庫詳解(pickle序列化和JSON序列化)

    Python常用標(biāo)準(zhǔn)庫詳解(pickle序列化和JSON序列化)

    這篇文章主要介紹了Python常用標(biāo)準(zhǔn)庫,主要包括pickle序列化和JSON序列化模塊,通過使用場景分析給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-05-05
  • 使用IDLE的Python shell窗口實例詳解

    使用IDLE的Python shell窗口實例詳解

    在本篇文章里小編給各位整理的是關(guān)于使用IDLE的Python shell窗口實例詳解內(nèi)容,有興趣的朋友們學(xué)習(xí)下。
    2019-11-11
  • python字典setdefault方法和get方法使用實例

    python字典setdefault方法和get方法使用實例

    這篇文章主要介紹了python字典setdefault方法和get方法使用實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12

最新評論