问题
我们的客户端程序庞大, 笨重, 更为严重的是, 当我们的程序发布给用户之后,
如果没有问题还好, 遇到了bug, 修改很成问题.
如何把更新后的程序发布给用户呢? 很多项目在做架构的时候,
选择采用web方式来绕过这样的问题. 但是很多时候还是非用客户端程序不可.
如何让客户端程序实现在线更新功能? 这里提供我的一个非常简单的解决方案.
思路
更新的业务逻辑很简单. 只需要下面几步:
- 去某个服务器, 下载一个记录有版本信息的文档.
- 与本地程序的版本号做比较, 如果不是最新的, 提示用户更新程序.
- 更新程序的方法: 去服务器下载一个压缩包, 解压覆盖本地程序.
然后是具体实现上面的逻辑.
要点
服务器必须能够被客户访问到.
如果是在外网的话, 必须有一个存放更新文件的公共服务器. 我采用 google
code 存放文件. 一个项目上传文件的限额是2G,
足够用了. 你也可以选择自己搭建一个http/ftp服务器,
或者用dropbox(被墙掉了), everbox(现在还不支持共享文件).
更新程序. 下载程序后, 需要覆盖当前程序目录, windows下面,
开启后的程序会加锁执行文件, 所以需要运行另外的一个程序来做更新.
下面是具体的逻辑.
更新程序解决方案
比如旧程序在dir_a目录下面, 可执行文件是a.exe.
星号括起来的是当前正在执行的文件.
dir_a: **a.exe**
我们把dir_a拷贝出一份, 到dir_backup, 最新的程序保存到dir_backup里面,
保存为new.tar.gz. 下载完毕后, 我们在dir_b里面建立一个文件,
叫need_update, 作为标识.
dir_a: **a.exe**
dir_backup: a.exe, new.tar.gz, need_update
用 execl
(c/c++/python/都可以用类似的函数调用), 关闭当前程序,
跳到dir_backup环境里面, 执行dir_backup/a.exe.
dir_a: a.exe
dir_backup: **a.exe**, new.tar.gz, need_update
dir_backup/a.exe启动的时候检查当前目录下面是否有need_update, 有的话,
就解压new.tar.gz, 覆盖dir_a. 这个时候程序已经更新完成了.
dir_a: new.exe
dir_backup: **a.exe**, new.tar.gz, need_update
然后再用 `execl`, 放弃当前程序, 执行dir_a/new.exe.
dir_a: **new.exe**
dir_backup: a.exe, new.tar.gz, need_update
步骤虽然有点复杂, 但是顶用, 也不需要另外加一个更新程序.
对于python发布出去的代码来说, 一个可执行文件就上M了, 少一个是一个.
下面是实际代码, 因为是从实际项目中挖出来的, 保证不能用.
都是update.py这一个源文件的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
| from qtlib import *
from qtlib.download import download
from config import add_config, conf
from tools.progresser import Progresser
import tarfile
from distutils.dir_util import copy_tree
SERVER = "http://project.googlecode.com/files/"
def update(program, version):
"""具体更新的方法"""
if not confirm(None, '开始准备更新, 是否继续?'):
return
# 检查是否有更新
if not check_update(program, version):
return
# 复制program
temp_path = '../%s.backup' % program
shutil.rmtree(temp_path, True)
shutil.copytree(os.path.join('..', program),
temp_path)
# 下载程序
tar_file = program + '.tar.gz'
local_tar_file = os.path.join(temp_path, tar_file)
with Progresser("下载中, 比较忙, 你可以离开位置休息一下..","退出",0,100, None) as p:
result = download(SERVER + tar_file,
local_tar_file,
stepper = p)
if result == False:
showMsg("无法连接到服务器, 请检查是否连上外部网络!")
return
elif result == None:
showMsg('取消下载, 不好意思, 下次还得从头开始下载..')
return
showMsg('下载完成, 现在安装新程序.')
os.chdir(temp_path)
# 解压
unzip(tar_file)
# 标示一下是新程序
with open('need_update', 'w+') as f:
f.write(program)
# 执行新程序
program_name = program+'.exe'
os.execl(program_name, program_name)
def check_update(program, version):
"""检查是否有更新"""
# 下载versions文件
if download(SERVER+'versions', 'temp/versions') == False:
showMsg("无法连接到服务器, 请检查是否连上外部网络!")
return False
# 读取里面的信息
data = open('temp/versions').read()
# 格式是: program + 空格 + 版本号
for line in data.split('\n'):
if line.startswith(program):
new_version = line.split(' ')[1]
# 把版本号当作浮点来检查
if float(version) < float(new_version):
return True
else:
showMsg("程序已经是最新版本: %s, %s" % (program, version))
def check_finish_update():
# 检查当前是否是需要更新的临时文件
if not os.path.isfile('need_update'):
return
# 读取程序名称
with open('need_update') as f:
program = f.read()
os.remove('need_update')
# 然后把新程序复制回去
old_path = '../%s'%program
copy_tree(program, old_path)
# 执行程序
os.chdir(old_path)
program_name = program + '.exe'
os.execl(program_name, program_name)
# 加载update.py模块的时候, 判断当前是否为在backup过程中执行的..
check_finish_update()
|
其他
基于上面的更新逻辑, 我们还可以根据需求, 补充一些功能:
- 启动的时候检查更新. 只需要用threading.Thread(target=check_update,
…)来用一个线程跑更新检查就可以了.
- 后台更新. 也可以用一个线程跑下载, 下载完毕后, 再通知主线程.
- 增量更新, 减少下载时间. 可以考虑用diff之类工具.
如果有人觉得这样的更新脚本有价值的话, 可以联系我,
让我整理出一份不依赖其他模块的代码出来.