怎么实现软件更新?

用过那么多软件,做为一个非专业人士,你是不是和我一样好奇,软件是怎么更新的?我的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
开始下载...
下载完成!
删除老版本
更新名称
更新版本信息

好了,现在我们已经有了一个重构过的版本,功能也能满足我们需求。软件更新的初步尝试,已经完美的达到。下一篇我们将尝试把这个代码与启动结合在一起,让软件在启动时能够自查并且更新版本。

后记

服务器上的软件我会删除,大家要做尝试的就放过我的小站了。不过如果有任何问题,欢迎交流,也可以关注我的公众号追更哦。全部代码可以在公众号输入关键词 软件更新 获取。

你可能感兴趣的:(怎么实现软件更新?)