前言
目前常见的中小型软件/开源软件的更新模式大致有以下几种:
指引用户前往站点下载安装包自行覆盖安装。用户体验不太好,感觉很麻烦。代表有DBeaver/VirtualBox等;
提醒用户执行更新,经过同意后,下载安装包并运行。用户体验稍微好了一些,代表有Bandizip/Notepad++等;
软件在后台自动下载安装包,适时提醒用户执行更新,经过同意后运行安装包。这样能够节省用户的等待时间,代表有PotPlayer/FileZilla等。
一个完善的软件应当有着合理的更新机制。为了让用户获得更好的体验,更新过程应当是简便的、无痛的;为了提高开发者本身的效率,更新模块应当是通用的,可以快速地扩展到不同的项目中去。
同时,我认为软件应当是「绿色」的,即尽可能不提权,不写注册表,删除目录即卸载,不留垃圾文件。注意到上述的几种更新模式都使用了安装包作为最终媒介,这并不符合我的需求,因此我需要另行设计。如果你的项目符合我对「绿色」的定义,那么可以接着向下阅读。
文末将附上本项目源码(.NET4.5以上)。
特点使用简单
整个模块包含两个可执行文件Uploader与Updater。开发者方面,编写一个简单的配置文件并使用Uploader执行,就可以将新版本与相关信息上传到服务器(需要具有用FTP与HTTP访问服务器上文件的权限);客户端方面,只需引用Updater并调用一个方法,即可自动完成检查/通知/下载/安装更新的整个过程。
节省空间和流量
上传的内容经过压缩(可选),节省服务器空间。同时,客户端在更新时仅下载有变化的文件,相比下载整个安装包的做法更加节省服务器流量与用户的时间。
可完全自定义的更新内容展示
通过XAML代码向用户展示更新内容。不仅能做出漂亮的展示界面,还可以在其中添加图片和视频。
可对自身进行更新
更新程序在临时文件夹中运行,不占用项目根目录中任何一个文件。
流程
开发者:
编写配置文件(包含更新内容描述等);
用Uploader打开配置文件;
等待上传完毕。
客户端:
在项目中引用Updater;
客户端程序运行时,调用Updater的检查更新方法,将在后台下载并检验更新信息;
若有新版本,把新版本描述展示给用户;
用户同意更新后,将Updater复制到临时目录并启动;
Updater将当前版本文件内容与新版本进行对比,仅下载有变化的文件,显示下载进度;
下载完成后将新文件覆盖到项目根目录,启动主程序并退出Updater,完成更新。
配置文件解析
以下是一个配置文件的例子:
//必需区段,指定服务器地址。更新模块与下载模块将从此地址检测更新和下载新版本。 [DownloadRootAddress] www.example.com/content //必需区段,指定协议类型。取决于域名,在http与https中选择。 [DownloadSchema] http //必需区段,指定FTP服务器地址。 [FtpServerAddress] www.example.com //必需区段,指定FTP服务器端口。 [FtpServerPort] 21 //必需区段,连接FTP服务器的账号。该账号必须具有完整的读写权限。 [FtpServerAccount] example_account //必需区段,连接FTP服务器的密码。 [FtpServerPassword] example_password //必需区段,指定软件名称。在FTP根目录中将创建一个以[AppName]为名的文件夹,所有的更新内容都将存放在这一文件夹中。 [AppName] example_app //必需区段,指定软件版本号。如果在后面的[File]区段中指定了VERSIONSOURCE::True选项则此项无效。 [AppVersion] 1.0.0.0 //必需区段,指定强制更新。若用户的软件版本与最新版本之间有任何一个版本要求强制更新,则用户将不能忽略这次更新。 [ForceUpdate] False //必需区段,用XAML代码对更新的内容进行描述。向用户展示更新提示时,这段XAML将被解析为具体的UI。 [UpdateDescription] <StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <StackPanel.Resources> <Style TargetType="TextBlock"> <Setter Property="Margin" Value="20,4,4,4" /> </Style> <Style TargetType="Image"> <Setter Property="Margin" Value="20,4" /> </Style> </StackPanel.Resources> <TextBlock Text="新增" FontWeight="Bold" /> <TextBlock Text="1. xxxxx" /> <TextBlock Text="修复" FontWeight="Bold" /> <TextBlock Text="1. yyyy" /> <TextBlock Text="2. zzzz" /> </StackPanel> //可选区段,指定一个项目中的文件,该文件将被上传。 //第一行:文件在本地计算机中的绝对路径; //第二行:文件相对**项目**根目录的路径(可以指定其他文件名); //第三行:文件相对**FTP**根目录的路径(可以指定其他文件名)。 //COMPRESS选项:是否在上传时对这个文件进行压缩,默认为True; //COVERABLE选项:更新时本地若已存在此文件,是否允许覆盖,默认为True; //VERSIONSOURCE选项:是否将此文件作为版本号的来源,仅限对exe、dll文件使用,默认为False; //RUNAFTER选项:是否在更新完成时运行此文件,一般在主程序上使用,默认为False。 [File] D:/AnimeWallpaper/Release/AnimeWallpaper.exe /AnimeWallpaper.exe /AnimeWallpaper.exe COMPRESS::True COVERABLE::True VERSIONSOURCE::True RUNAFTER::True //可选区段,指定一个项目中的文件夹,该文件夹与其中文件(取决于后续选项)将被上传。 //第一行:文件夹在本地计算机中的绝对路径; //第二行:文件夹相对**项目**根目录的路径(不可指定文件夹名称); //第三行:文件夹相对**FTP**根目录的路径(不可指定文件夹名称)。 //INCLUDE选项:要包含的文件夹中的文件,可使用通配符(*),用分号隔开。若不指定此选项则包含全部文件; //COMPRESS选项:是否在上传时对这个文件夹中的文件进行压缩,默认为True; //COVERABLE选项:更新时本地若已存在此文件夹(与其中的文件),是否允许覆盖,默认为True; //SUBFOLDER选项:是否包含全部的子文件夹与其中的文件,默认为True; //RANDOMNAME选项:是否将文件夹中的文件名随机化(可避免服务器禁止上传某类文件等问题),默认为False; [Folder] D:/AnimeWallpaper/Release / / INCLUDE::*.dll;EnneaCode.*.exe COMPRESS::True COVERABLE::True SUBFOLDER::False RANDOMNAME::True [Folder] D:/AnimeWallpaper/Release/x64 /x64 /x64 INCLUDE::*.dll COMPRESS::True COVERABLE::True SUBFOLDER::False [Folder] D:/AnimeWallpaper/Release/x86 /x86 /x86 INCLUDE::*.dll COMPRESS::True COVERABLE::True SUBFOLDER::False
将配置文件保存为纯文本文档,拖动到Uploader上以执行。
客户端调用方法
using EnneaCode.UniversalUpdater; private async void Window_Loaded(object sender, RoutedEventArgs e) { var currentVersion = this.GetType().Assembly.GetName().Version; /* * public static Task<ecCheckUpdateState> CheckUpdateAsync(string appUpdateBaseUri, string appName, Version currentAppVersion) * appUpdateBaseUri:为配置文件中的[DownloadSchema] + "://" + [DownloadRootAddress]; * appName:为配置文件中的[AppName]; * currentAppVersion:当前程序版本。 */ var result = await ecUpdater.CheckUpdateAsync("http://www.example.com/content", "example_app", currentVersion); switch (result) { case ecCheckUpdateState.UpdateConfirmed: /* * 更新确认时会尝试通过关闭主窗口的方式退出程序,如果程序不能这样退出, * 则必须在此执行退出程序的逻辑 */ //Environment.Exit(0); break; case ecCheckUpdateState.UpToDate: MessageBox.Show("已经是最新版本"); break; case ecCheckUpdateState.Error: MessageBox.Show("无法获取更新"); break; } }
过程是异步的,所以需要async/await。
源码https://download.csdn.net/download/q408774831/10616315
Uploader所需的辅助模块源码
https://download.csdn.net/download/q408774831/10616311
原文链接:https://blog.csdn.net/q408774831/article/details/81876761
原创文章,作者:1402239773,如若转载,请注明出处:https://blog.ytso.com/275365.html