基于esky实现python应用的自动升级

一、esky介绍

Esky is an auto-update framework for frozen Python applications. It provides a simple API through which apps can find, fetch and install updates, and a bootstrapping mechanism that keeps the app safe in the face of failed or partial updates. Updates can also be sent as differential patches.

Esky is currently capable of freezing apps with py2exe, py2app, cxfreeze and bbfreeze. Adding support for other freezer programs should be easy; patches will be gratefully accepted.

We are tested and running on Python 2.7 Py2app will work on python3 fine, the other freezers not so much.

Esky是一个python编译程序的自动升级框架,提供简单的api实现应用的自动更新(包括比较版本、更新版本),esky支持py2exe,py2app,cxfreeze以及bbfreeze等多种python打包框架。

二、esky安装及说明

1、pip安装

pip install esky

2、esky说明

https://github.com/cloudmatrix/esky/

3、esky教学视频

http://pyvideo.org/pycon-au-2010/pyconau-2010--esky--keep-your-frozen-apps-fresh.html

三、esky用法示例

esky用起来比较简单,我们这里以常用的基于wx的windows应用举例。

wxpython下有个wx.lib.softwareupdate 类,对wxpython应用的esky升级进行了二次封装。

网上有个现成的示范例子,具体网址:http://www.blog.pythonlibrary.org/2013/07/12/wxpython-updating-your-application-with-esky/

代码很简单,对其中的关键部分进行注释说明(红色字体部分):

# ----------------------------------------
# image_viewer2.py
#
# Created 03-20-2010
#
# Author: Mike Driscoll
# ----------------------------------------

import glob
import os
import wx
from wx.lib.pubsub import setuparg1
from wx.lib.pubsub import  pub as Publisher 
#申明语句
from wx.lib.softwareupdate import SoftwareUpdate
import version

########################################################################
class ViewerPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)

        width, height = wx.DisplaySize()
        self.picPaths = []
        self.currentPicture = 0
        self.totalPictures = 0
        self.photoMaxSize = height - 200
        Publisher.subscribe(self.updateImages, ("update images"))

        self.slideTimer = wx.Timer(None)
        self.slideTimer.Bind(wx.EVT_TIMER, self.update)

        self.layout()

    #----------------------------------------------------------------------
    def layout(self):
        """
        Layout the widgets on the panel
        """

        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)

        img = wx.EmptyImage(self.photoMaxSize,self.photoMaxSize)
        self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY,
                                         wx.BitmapFromImage(img))
        self.mainSizer.Add(self.imageCtrl, 0, wx.ALL|wx.CENTER, 5)
        self.imageLabel = wx.StaticText(self, label="")
        self.mainSizer.Add(self.imageLabel, 0, wx.ALL|wx.CENTER, 5)

        btnData = [("Previous", btnSizer, self.onPrevious),
                   ("Slide Show", btnSizer, self.onSlideShow),
                   ("Next", btnSizer, self.onNext)]
        for data in btnData:
            label, sizer, handler = data
            self.btnBuilder(label, sizer, handler)

        self.mainSizer.Add(btnSizer, 0, wx.CENTER)
        self.SetSizer(self.mainSizer)

    #----------------------------------------------------------------------
    def btnBuilder(self, label, sizer, handler):
        """
        Builds a button, binds it to an event handler and adds it to a sizer
        """
        btn = wx.Button(self, label=label)
        btn.Bind(wx.EVT_BUTTON, handler)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)

    #----------------------------------------------------------------------
    def loadImage(self, image):
        """"""
        image_name = os.path.basename(image)
        img = wx.Image(image, wx.BITMAP_TYPE_ANY)
        # scale the image, preserving the aspect ratio
        W = img.GetWidth()
        H = img.GetHeight()
        if W > H:
            NewW = self.photoMaxSize
            NewH = self.photoMaxSize * H / W
        else:
            NewH = self.photoMaxSize
            NewW = self.photoMaxSize * W / H
        img = img.Scale(NewW,NewH)

        self.imageCtrl.SetBitmap(wx.BitmapFromImage(img))
        self.imageLabel.SetLabel(image_name)
        self.Refresh()
        Publisher.sendMessage("resize", "")

    #----------------------------------------------------------------------
    def nextPicture(self):
        """
        Loads the next picture in the directory
        """
        if self.currentPicture == self.totalPictures-1:
            self.currentPicture = 0
        else:
            self.currentPicture += 1
        self.loadImage(self.picPaths[self.currentPicture])

    #----------------------------------------------------------------------
    def previousPicture(self):
        """
        Displays the previous picture in the directory
        """
        if self.currentPicture == 0:
            self.currentPicture = self.totalPictures - 1
        else:
            self.currentPicture -= 1
        self.loadImage(self.picPaths[self.currentPicture])

    #----------------------------------------------------------------------
    def update(self, event):
        """
        Called when the slideTimer‘s timer event fires. Loads the next
        picture from the folder by calling th nextPicture method
        """
        self.nextPicture()

    #----------------------------------------------------------------------
    def updateImages(self, msg):
        """
        Updates the picPaths list to contain the current folder‘s images
        """
        self.picPaths = msg.data
        self.totalPictures = len(self.picPaths)
        self.loadImage(self.picPaths[0])

    #----------------------------------------------------------------------
    def onNext(self, event):
        """
        Calls the nextPicture method
        """
        self.nextPicture()

    #----------------------------------------------------------------------
    def onPrevious(self, event):
        """
        Calls the previousPicture method
        """
        self.previousPicture()

    #----------------------------------------------------------------------
    def onSlideShow(self, event):
        """
        Starts and stops the slideshow
        """
        btn = event.GetEventObject()
        label = btn.GetLabel()
        if label == "Slide Show":
            self.slideTimer.Start(3000)
            btn.SetLabel("Stop")
        else:
            self.slideTimer.Stop()
            btn.SetLabel("Slide Show")

########################################################################
class ViewerFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        title = ‘Image Viewer %s‘ %(version.VERSION)

        wx.Frame.__init__(self, None, title=title)
        panel = ViewerPanel(self)
        self.folderPath = ""
        Publisher.subscribe(self.resizeFrame, ("resize"))

        self.initToolbar()
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(panel, 1, wx.EXPAND)
        self.SetSizer(self.sizer)

        self.Show()
        self.sizer.Fit(self)
        self.Center()

    #----------------------------------------------------------------------
    def initToolbar(self):
        """
        Initialize the toolbar
        """
        self.toolbar = self.CreateToolBar()
        self.toolbar.SetToolBitmapSize((16,16))

        open_ico = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (16,16))
        openTool = self.toolbar.AddSimpleTool(wx.ID_ANY, open_ico, "Open", "Open an Image Directory")
        self.Bind(wx.EVT_MENU, self.onOpenDirectory, openTool)

        self.toolbar.Realize()

    #----------------------------------------------------------------------
    def onOpenDirectory(self, event):
        """
        Opens a DirDialog to allow the user to open a folder with pictures
        """
        dlg = wx.DirDialog(self, "Choose a directory",
                           style=wx.DD_DEFAULT_STYLE)

        if dlg.ShowModal() == wx.ID_OK:
            self.folderPath = dlg.GetPath()
            print self.folderPath
            picPaths = glob.glob(self.folderPath + "\\*.jpg")
            print picPaths
            Publisher.sendMessage("update images", picPaths)

    #----------------------------------------------------------------------
    def resizeFrame(self, msg):
        """"""
        self.sizer.Fit(self)

#########################################################################注意基类是两个
class ImageApp(wx.App, SoftwareUpdate):
    """"""

    #----------------------------------------------------------------------
    def OnInit(self):
        """Constructor"""
        BASEURL = "http://127.0.0.1:8000"        #升级初始化,参数1:检查升级包的网页地址,参数2:升级说明文件,升级网页地址与升级说明文件可以不在一个目录。
        self.InitUpdates(BASEURL,BASEURL + ‘ChangeLog.txt‘)        #启动升级检查,参数:是否显示升级提示,默认显示提示。显然该语句可以放到按钮或者菜单中触发。
        self.CheckForUpdate(silentUnlessUpdate=False)
        frame = ViewerFrame()
        self.SetTopWindow(frame)
        self.SetAppDisplayName(‘Image Viewer‘)
        #ViewerPanel..SetValue(‘Image Viewer %s‘ %(version.VERSION))

        return True

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = ViewerFrame()
    app.MainLoop()
    

总结:

1、先声明类

from wx.lib.softwareupdate import SoftwareUpdate

2、在app中调用声明的类,做为基类之一

class UpApp(wx.App, SoftwareUpdate):

3、在app的中初始化softwareupate,一般放在OnInit()中

wx.GetApp().InitUpdates(‘http://127.0.0.1/update.html‘,  ‘http://127.0.0.1/ChangeLog.txt‘)

4、在窗口事件中调用升级检查,可以放到菜单或者按钮中

wx.GetApp().CheckForUpdate()

四、esky编译脚本编写

esky本身不支持编译,所以必须调用cx_freeze或者py2exe之类进行python编译,由于本人比较熟悉cx_freeze,所以……以下例子均是基于cx_freeze。

其编译脚本跟cx_freeze的setup.py有点类似,先来一个简单例子:

#coding=utf-8
#---------------------------------------------------------------------------
# This setup file serves as a model for how to structure your
# distutils setup files for making self-updating applications using
# Esky.  When you run this script use
#
#    python setup.py bdist_esky
#
# Esky will then use py2app or py2exe as appropriate to create the
# bundled application and also its own shell that will help manage
# doing the updates.  See wx.lib.softwareupdate for the class you can
# use to add self-updates to your applications, and you can see how
# that code is used here in the superdoodle.py module.
#---------------------------------------------------------------------------

from esky import bdist_esky
from setuptools import setup

# Common settings
exeICON = ‘mondrian.ico‘
NAME = "wxImageViewer"#明确调用cx_freeze进行编译
FREEZER =‘cx_freeze‘
#cx_freeze的编译options
FREEZER_OPTIONS = {
    "excludes": ["tkinter","collections.sys",‘collections._weakref‘]#,                #剔除wx里tkinter包
};

APP = [bdist_esky.Executable("image_viewer.py",
                             gui_only=True,
                             icon=exeICON,
                             )]

DATA_FILES = [ ‘mondrian.ico‘ ]

ESKY_OPTIONS = dict( freezer_module     = FREEZER,
                     freezer_options    = FREEZER_OPTIONS,
                     enable_appdata_dir = True,
                     bundle_msvcrt      = False,
                     )    

# Build the app and the esky bundle
setup( name       = NAME,
       version    = ‘1.0‘,
       scripts    = APP,
       data_files = DATA_FILES,
       options    = dict(bdist_esky=ESKY_OPTIONS),
       )

这个是编译脚本,具体的编译命令,如下。

五、编译命令

注意setup.py中的version=1.0就是版本定义,若是要发布升级版,只要把version修改成1.1或者2.0,程序就会判断为升级包,进行更新。

编译分两种方式,一种是编译完整包,一种是编译增量补丁包。

特别说明一下补丁包的生成机制:先编译完整包,再比较老版本完整包、新版本完整包,生成差异补丁包。

1、编译完整包

python setup.py bdist_esky

编译之后会在dist目录生成名为wxImageViewer-1.0.win-amd64.zip的打包文件,注意这个文件名本身就包含了版本信息:

1)、wxImageViewer是应用名,对应setup.py中的name定义

2)、1.0是版本号,对应setup.py中version定义

3)、amd64代表64位编译版本,跟python的版本一致。

2、编译增量补丁包

python setup.py bdist_esky_path

注意每次重新编译,需要修改version,会自动生成会自动增量包。

譬如第二次编译,修改version=2.0,则增量包为:wxImageViewer-1.0.win-amd64.from-2.0.patch

1)增量包文件基本很小

2)升级时会自动判断是下载全新包,还是下载增量包。

譬如本地程序是1.0版本,服务器端发了2.0版本的升级文件:wxImageViewer-2.0.win-amd64.zip、wxImageViewer-1.0.win-amd64.from-2.0.patch,esky会自动只下载patch文件。

六、复杂的esky编译脚本

1、实现目录打包

2、实现应用程序版本信息设置

#coding=utf-8
#---------------------------------------------------------------------------
‘‘‘
Create by: joshua zou 2016.10.08
Purpose: 调用esky打包成执行文件,支持自动升级。
Example: python setup.py bdist_esky / python setup.py bdist_esky_patch
‘‘‘
#---------------------------------------------------------------------------

from esky import bdist_esky
from setuptools import setupimport distutils

# Dependencies are automatically detected, but it might need fine tuning.

VER = ‘1.16.1102.1‘# Common settings
exeICON = ‘et.ico‘
NAME = "eTaxMain.exe"
FREEZER =‘cx_freeze‘

ESKY_VERSION ={ "version": VER,
                "company": u"****公司",
                "description": u"****程序",
                "product": u"****系统",
                ‘copyright‘:u"***@版权所有 2016-2020"
                }

#版本申明部分
metadata= distutils.dist.DistributionMetadata()
metadata.version = ESKY_VERSION[‘version‘]
metadata.description = ESKY_VERSION[‘description‘]
metadata.copyright = ESKY_VERSION[‘copyright‘]
metadata.name = ESKY_VERSION[‘product‘]
#版本申明结束

FREEZER_OPTIONS = {
    "packages": ["os","wx","requests","lxml","lxml.etree"],                #包含package
    "includes": ["PIL","traceback",‘HTMLParser‘,‘appdirs‘,‘pyDes‘],
    "excludes": [‘MSVCP90.dll‘,
                 ‘mswsock.dll‘,
                 ‘powrprof.dll‘,
                 ‘USP10.dll‘,
                 ‘_gtkagg‘, ‘_tkagg‘,
                 ‘bsddb‘, ‘curses‘,
                 ‘pywin.debugger‘,‘pywin.debugger.dbgcon‘,
                 ‘pywin.dialogs‘,‘tcl‘,
                 ‘Tkconstants‘, ‘Tkinter‘,
                 ‘wx.tools.*‘,‘wx.py.*‘,
                 "collections.sys",‘collections._weakref‘], #剔除wx里tkinter包
    "metadata":metadata
};

APP = [bdist_esky.Executable("eTaxMain.py",
                             gui_only=True,
                             icon=exeICON,
                             )]
#打包et.ico,helpinfo.txt放到应用目录下#打包.\lang\zh_CN\LC_MESSAGES\eTaxMain.mo到lang\zh_CN下。
DATA_FILES=[(‘‘, [‘et.ico‘,‘helpinfo.txt‘]),
            (‘lang\zh_CN‘, [‘.\lang\zh_CN\LC_MESSAGES\eTaxMain.mo‘,‘.\lang\zh_CN\LC_MESSAGES\eTaxMain.po‘])
            ]

ESKY_OPTIONS = dict( freezer_module     = FREEZER,
                     freezer_options    = FREEZER_OPTIONS,
                     enable_appdata_dir = True,
                     bundle_msvcrt      = False,
                     )     

# Build the app and the esky bundle
setup( name       = NAME,
       version    = VER,
       scripts    = APP,
       data_files = DATA_FILES,
       options    = dict(bdist_esky=ESKY_OPTIONS),
       )

前前后后,为了这个esky,折腾了快2个礼拜,总算圆满成功,写的比较简单,感兴趣的留言交流。

时间: 2024-10-15 01:21:38

基于esky实现python应用的自动升级的相关文章

【开源下载】基于TCP网络通信的自动升级程序c#源码

本程序使用开源的来自英国的networkcomms2.3.1网络通讯框架(c#语言编写) [http://www.networkcomms.net] 使用networkcomms框架作为工作中的主要编程框架1年多的时间了,networkcomms的有优美.稳定深深打动了我,基于此框架开发了不少程序,特别的稳定. networkcomms框架由英国剑桥的2位工程师开发,支持.net2.0以上平台,原生态的支持xamarion.android(安卓),xamarin.ios,以及蓝牙等多平台开发.

基于TCP网络通信的自动升级程序源码分析-客户端请求服务器上的升级信息

每次升级,客户端都会获取服务器端存放在upgradefile文件夹下的需要升级的文件和升级信息配置文件(即upgradeconfig.xml文件) 我们来看一下代码 //升级信息配置文件相对应的类 ( 升级信息配置文件是由这个类转化成的) private UpgradeConfig upgradeConfig = null; //客户端存储升级配置文件的地址 是放在客户端根目录下的 (就是把服务器 upgradefile/upgradeconfig.xml下载到客户端存放的位置) string

基于TCP网络通信的自动升级程序源码分析-启动升级文件下载程序

升级程序启动后,首先会连接服务器 private void Connect() { try { int port = int.Parse(System.Configuration.ConfigurationManager.AppSettings["Port"]); connnectionInfo = new ConnectionInfo(IPAddress, port); connection = TCPConnection.GetConnection(connnectionInfo)

基于TCP网络通信的自动升级程序源码分析-客户端接收文件

升级程序客户端接收文件 /// <summary> /// 文件数据缓存 索引是 ConnectionInfo对象 数据包的顺序号 值是数据 /// </summary> Dictionary<ConnectionInfo, Dictionary<long, byte[]>> incomingDataCache = new Dictionary<ConnectionInfo, Dictionary<long, byte[]>>();

基于Sae和Python的flask实现的金山快盘自动签到

第一次写博客园,各位大大请多加照顾哦. 进入正题 功能介绍 架到Sae后,每天定点Sae的服务器就会帮你自动签到金山快盘和自动转盘抽奖.一些记录如下: 金山快盘的签到记录: Sae的Cron的日记: 核心代码 本来想写抓包的过程的,但是感觉很小儿科(如果大家觉得有需要,我再写上来),就不献丑了,直接po上代码. 1 #!/usr/bin/python 2 #-*-coding:utf-8-*- 3 import urllib, urllib2, cookielib, sys 4 5 class

Ionic实战 自动升级APP(Android版)

Ionic 框架介绍 Ionic是一个基于Angularjs.可以使用HTML5构建混合移动应用的用户界面框架,它自称为是"本地与HTML5的结合".该框架提供了很多基本的移动用户界面范例,例如像列表(lists).标签页栏(tab bars)和触发开关(toggle switches)这样的简单条目.它还提供了更加复杂的可视化布局示例,例如在下面显示内容的滑出式菜单. Ionic 自动升级APP一.准备工作 1.Cordova插件: cordova plugin add https:

DIY一个基于树莓派和Python的无人机视觉跟踪系统

DIY一个基于树莓派和Python的无人机视觉跟踪系统 无人机通过图传将航拍到的图像存储并实时传送回地面站几乎已经是标配.如果想来点高级的--在无人机上直接处理拍摄的图像并实现自动控制要怎么实现呢?其实视觉跟踪已经在一些高端的消费级无人机上有了应用,不过玩现成的永远没有自己动手来劲;).前段时间DIY了一个无人机三轴云台的视觉跟踪系统,除去云台花了¥370,本文将设计思路与实验效果分享出来. 一.基本配置 1.1 硬件 计算平台:树莓派3 (¥219.00) 摄像头:USB网络摄像头(¥108.

自动更新(自动升级)组件分享

原文:自动更新(自动升级)组件分享 自从接触安装部署以来就有软件升级的需求,最简单的就是clickonce,但无法做到深入控制,本寄希望于WIX可以自己实现,但现在还没有找到例子.然后才自己实现. 要声明一下,这是在圣殿骑士AutoUpdater基础上改动过来的.基于他分享的精神,我也继续分享.我主要做了以下改动. 1.加入客服端安装版本和下载版本检测. 2.加入更新提示. 3.做了一些异常处理,增加了接口方法. 4.加入了皮肤. 按照国际惯例,先上图:    原理简介 最基本原理还是借助于圣殿

SNF开发平台WinForm之八-自动升级程序部署使用说明-SNF快速开发平台3.3-Spring.Net.Framework

9.1运行效果: 9.2开发实现: 1.首先配置服务器端,把“SNFAutoUpdate2.0\服务器端部署“目录按网站程序进行发布到IIS服务器上. 2.粘贴语句,生成程序 需要调用的应用程序的Load事件或者Program入口的Main方法第一行代码加上如下代码: 注意:是主程序的 Load事件要加上调整自动更新程序的代码.要以模式打开窗口.如果没有差异会自动关闭升级窗口显示主窗口. 3.把下面目录里的文件拷贝到 应用程序的同级目录下: 4.配置WINFORMS应用程序目录下Updateli