用过那么多软件,做为一个非专业人士,你是不是和我一样好奇,软件是怎么更新的?我的0.5版本怎么更新到1.0了?
一、思考
用我们已有的知识分解一个软件更新到底是怎么回事,这个神秘的面纱能不能被我们摘下来呢? 先看看我们现在拥有什么? 一个已经有的软件,当然是我自己做的了,小工具,为了实验,我们就排除其他干扰。
那么怎么才能更新它呢? 更新的实际情况是什么?其实是用另外一个软件代替这现有的这个,假设我们已经有另外一个版本了,叫什么好呢?就叫敦煌工具 v1.0.exe
好了。这个1.0
肯定是放在一个地方的。更新就是从某个存储1.0
的地方把他取过来放到原来版本存放的目录里。这样目录里就有两个文件,我们还要把原来的版本给删除掉。这样就只有一个新版本存在了。逻辑合不合理?内心窃笑。 好了,貌似我们的思路十分合理,那么我们就可以开始了吗? 慢着,我们还有问题,我们怎么知道现有版本不是最新的呢? 我们需要有一个存储现有版本信息的文件,方便我们知道现有版本信息,并且应该在我们更新了版本也现时更新版本信息。 那么那个存储新版本软件的地方呢? 其实我们完全可以放到不同的文件夹来模拟,可是这样是不是太low了,显示不出功力不说,也没有那种真实的体验呀。怎么办?有了,我们可以放到服务器上,最好是有一个域名地址可以直接访问。这下应该齐活了,我们开工吧。
二、预备工作
经过前面的思考,我们已经知道有一些工作要提前做好了,我们先把这些工作做好。 - 把新版本软件放到服务器 - 制作一个储存版本信息的文件
远程服务器
我这里直接用了一个现有的服务器,我有一个博客网站,正好可以放这个文件,有一个网站多重要,哈哈。 打开mobaxterm,使用sftp,把软件上传。
原谅我涂的这么难看,不过高手太多,不这么涂的话,小站抗不住呀。 我们看软件已经上传好了。域名地址测试下:https://geekala.com/敦煌工具.exe
可以看到,在浏览器里输入这个网址 https://geekala.com/敦煌工具.exe, 直接就弹出了下载保存的对话框,说明我们的文件可以正常使用了,到时只要从这里下载更新我们本地的文件就好了。
版本信息
接下来我们还要先制作好一个版本信息文件,一般大家都偏爱xml
,可是现在json
这么流行,没理由不用呀,我们就直接使用json
文件。 创建一个update.json
文件: 内容如下:
{
"name": "敦煌工具",
"version": 0.5,
"version_url": "https://geekala.com/dhgate-tool.json",
"download_url": "http://geekala.com/%E6%95%A6%E7%85%8C%E5%B7%A5%E5%85%B7.exe"
}
简单解释下: name
代表工具名称 version
代表版本号 version
代表远程服务里存储的版本信息,对的,我们肯定也需要在服务器里储存一个版本信息,不然怎么知道是否是新版本呢,当然也可以使用一个简单方式,就是直接在服务器里的软件名称上写上版本好就像是v1.0
这样。 download_url
代表去那儿下载,就是我们刚刚验证的那个网址,这里从网址上复制下来被转码了,直接用上面的地址 https://geekala.com/敦煌工具.exe 也能用。 本地的已经做好了,服务器里的版本信息其实可以和本地一样,方便,当然有些内容不要也可以,但是一定要有一个关键信息就是version
,这样就可以获取和本地version
进行对比,知道有没有更新版本。
三、开工了
我们先写上思路,看看有没有还需要补充的。
整个程序大概就是这样,现在可以开始写了。当然我们在写的过程中还可以再整理思路进行重构。
# 获取本地版本信息
with open('update.json', 'r', encoding='utf-8') as f:
content = json.loads(f.read())
print(content)
# 输出
(base) E:测试软件更新>python update.py
{
'name': '敦煌工具',
'version': 0.5,
'version_url': 'https://geekala.com/dhgate-tool.json',
'download_url': 'http://geekala.com/%E6%95%A6%E7%85%8C%E5%B7%A5%E5%85%B7.exe'
}
获取本地版本信息,用json
库读取文件内容,正常输出内容,这里要注意的是,open
方法里要传入encoding='utf-8'
,因为我们的文件内容里有中文,如果不传入这个会出现乱码。
# 获取服务器版本信息
r_content = requests.get(content['version_url']).json()
print(r_content)
# 输出
(base) E:测试软件更新>python update.py
{
'name': '敦煌工具',
'version': 1.0,
'url': 'http://geekala.com/%E6%95%A6%E7%85%8C%E5%B7%A5%E5%85%B7.exe'
}
服务器版本信息,这里因为我没在服务版本文件里写下载地址,因些比本地少了一项,这里可以看到我把服务器版本信息里的version
值设为了1.0
,比本地的0.5
要大,也就是有新版本。
# 对比版本信息
updated = False
if content['version'] < r_content['version']:
updated = True
# 输出
(base) E:测试软件更新>python update.py
True
这里是比较版本信息,设置了一个默认值为False
,如果有新版本就改为True
.
# 检查本地文件是否存在
exist_file = path.exists(content['name'] + '.exe')
print(exist_file)
# 输出
True
为什么要做这个检测呢,主要是有可能会有人误删除文件,导致本地文件不存在了。当然我们后期是要把更新文件和启动做到一起的,那时候启动就会先做更新,当然是不存在文件不在的情况,那我为什么还要写?当然是因为我在写思路的时候写上了,不好意思说自己想错了呀,被人叫大神了,还会出错!(心虚,冷汗ing)
# 如果有更新,下载服务器上的新版本
if updated:
appname = content['name'] + '_new.exe'
try:
urlretrieve(content['download_url'], appname)
except (RuntimeError, ConnectionError):
urlretrieve(content['download_url'], appname)
如果有更新,就下载新版本,这里引入了from urllib.request import urlretrieve
, 这个可以直接下载服务器上的文件,非常好用,强烈推荐,我以前下载妹子图的时候也经常用,(嗯 ,好像说了什么不该说的...)。 try
一下刚刚好,下载一般会遇到各种意外,遇到了我们再重新下载下。 当然,如果这里直接使用requests下载二进制,再写入文件也是可以用的。不过麻烦了点。 此时,我们的目录下应该会多出一个文件。
下面就是要把原来的文件给删除掉。
# 删除本地版本
if exist_file:
remove(content['name']+'.exe')
这时前面的检查文件是否存在就可以用上了。如果老版本存在,就删除老版本。 到了这里,我们先做一定的重构,方便后面使用:
# 检查文件是否存在
def check_exists(file):
return path.exists(file)
# 如果有更新,并且本地没有新版本
# 下载服务器上的新版本
appname = content['name'] + '_new.exe'
if updated and not check_exists(appname):
try:
urlretrieve(content['download_url'], appname)
except (RuntimeError, ConnectionError):
urlretrieve(content['download_url'], appname)
# 删除本地版本
old_name = content['name'] + '.exe'
if check_exists(old_name):
remove(old_name)
这样删除老版本就方便了,当然我这里是一步一步写下来,还有测试,所以做了这些修改,如果后面还有重构,可能就不需要再做检测。
删除老版本后,目录里的新版本是这样的名字,我们还要修改名称变成原来的名字。
# 更改新版本名称
if check_exists(appname) and not check_exists(old_name):
rename(appname, old_name)
成功修改。到这时一切都很完美。
四、更新本地版本信息
经过上面的步骤,其实我们的目的已经基本上全部达成,可是还有一个最重要的步骤还没做,那就是更新本地版本信息文件,如果不做这一步,每次启动都会重新下载一次,更新一次,这显然不合理,我们应该只有在有新版本时才做更新。
# 更新本地版本信息
# 把远程版本号更新到本地
content['version'] = r_content['version']
with open(update_file, 'w', encoding='utf-8') as f:
json.dump(content, f, ensure_ascii=False)
查看一下更新后的update.json
:
{
"name": "敦煌工具",
"version": 1.0,
"version_url": "https://geekala.com/dhgate-tool.json",
"download_url": "http://geekala.com/%E6%95%A6%E7%85%8C%E5%B7%A5%E5%85%B7.exe"
}
可以看到,版本号已经更新,如果我们这时再运行脚本,程序将不会运行。
五、第二次重构
经过前面的初步尝试,我们能够达到目的,接下来让我们稍稍重构下。
def main():
# 版本文件
update_file = 'update.json'
# 本地版本信息
content = get_json(update_file)
# 获取服务器版本信息
r_content = get_json(content['version_url'])
# 老版本号与新版本号
old = content['version']
new = r_content['version']
# 零时名称
appname = content['name'] + '_new.exe'
# 是否有新版本
updated = is_updated(old, new)
# 有新版本并且没有下载好的文件
if updated and not check_exists(appname):
print("开始下载...")
download(content['download_url'], appname)
print("下载完成!")
# 删除本地版本
old_name = content['name'] + '.exe'
if updated and check_exists(old_name):
print("删除老版本")
remove(old_name)
# 更改新版本名称
if updated and check_exists(appname) and not check_exists(old_name):
print("更新名称")
rename(appname, old_name)
# 更新本地版本信息
# 把远程版本号更新到本地
if updated:
content['version'] = r_content['version']
with open(update_file, 'w', encoding='utf-8') as f:
print("更新版本信息")
json.dump(content, f, ensure_ascii=False)
# 输出
(base) E:测试软件更新>python update.py
开始下载...
下载完成!
删除老版本
更新名称
更新版本信息
好了,现在我们已经有了一个重构过的版本,功能也能满足我们需求。软件更新的初步尝试,已经完美的达到。下一篇我们将尝试把这个代码与启动结合在一起,让软件在启动时能够自查并且更新版本。
后记
服务器上的软件我会删除,大家要做尝试的就放过我的小站了。不过如果有任何问题,欢迎交流,也可以关注我的公众号追更哦。全部代码可以在公众号输入关键词 软件更新
获取。