|
一、基本情况
最近的项目在做一个桌面程序,做完之后到客户机器上部署想到后续可能会新需求需要不定期程序,为了给客户最(lan)好(de)的(pao)体(xian)验(chang),想到做一个自动更新程序。简单的一顿百度,找到了这样一个项目 WPF客户端自动升级 比较简单,且对环境要求比较低,可以支持到XP。
原项目有些瑕疵,这里做了些改进,现记录如下。
1.开发环境:
c#+WPF
2.实现方式:
- 双击版本更新程序;
- 版本更新程序检测服务端版本配置信息并于本地版本配置信息比对;
- 发现服务器端版本与本地版本不一致则自动下载新版本压缩包,版本一致则启动程序;
- 新版本程序压缩包下载后解压缩覆盖本地文件,然后启动程序。
3.需要的依赖
1.原项目用到了压缩包处理工具库CL.IO.Zip,这里继续使用;
2.新增NLog依赖,记录操作日志,NuGet上可以获取。
4.准备工作
新建一个项目名称为:AutoUpdateTool
Talk is cheep, show you the code 二、上代码
1.版本配置信息xml对应的实体类
namespace AutoUpdateTool.Model
{
public class UpdateInfo
{
/// <summary>
/// 软件版本号
/// </summary>
public string Version { get; set; }
/// <summary>
/// 更新工具版本号
/// </summary>
public string ToolVersion { get; set; }
/// <summary>
/// 更新工具url
/// </summary>
public string AutoUpdateToolUrl { get; set; }
/// <summary>
/// 版本配置xml
/// </summary>
public string VersionXml { get; set; }
/// <summary>
/// 更新信息
/// </summary>
public string UpdateContent { get; set; }
/// <summary>
/// 更新包的下载地址
/// </summary>
public string DownLoadUrl { get; set; }
/// <summary>
/// 启动脚本名称
/// </summary>
public string StartName { get; set; }
/// <summary>
/// 关闭脚本名称
/// </summary>
public string StopName { get; set; }
/// <summary>
/// 下载文件进度显示的计量单位.GB/MB/KB/Byte
/// </summary>
public string DownloadFileUnit { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public string Time { get; set; }
/// <summary>
/// 更新包大小
/// </summary>
public string Size { get; set; }
}
}
2.xml操作接口及实现
using System.Collections.Generic;
using System.Xml.Linq;
namespace AutoUpdateTool.Class
{
interface IXmlHelper
{
void SaveToXml(string path,Dictionary<string,string> dic);
XDocument ReadFromXml(string path);
XElement ReadFromXml(XDocument document);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.IO;
using AutoUpdateTool.Model;
namespace AutoUpdateTool.Class
{
/// <summary>
/// xml帮助
/// </summary>
public class XmlHelper : IXmlHelper
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
private static object _insLocker = new object();//实例化对象锁
private static XmlHelper ins;
public static XmlHelper Instance
{
get
{
if (ins == null)
{
lock (_insLocker)
{
if (ins == null)
{
ins = new XmlHelper();
}
}
}
return ins;
}
}
public void SaveToXml(string path, Dictionary<string, string> dic)
{
if (string.IsNullOrEmpty(path) || dic == null)
{
Logger.Error(&#34;保存配置文件失败,路径或者内容为空&#34;);
return;
}
//获取根节点对象
XDocument document = new XDocument();
XElement root = new XElement(&#34;AutoConfig&#34;);
foreach (var item in dic)
{
root.SetElementValue(item.Key, item.Value);
}
root.Save(path);
}
/// <summary>
///
/// </summary>
/// <param name=&#34;path&#34;>文件的路径</param>
/// <param name=&#34;info&#34;></param>
public void SaveToXml(string path, List<UpdateInfo> info)
{
if (string.IsNullOrEmpty(path) || info == null)
{
Logger.Error(&#34;保存配置文件失败,路径或者内容为空&#34;);
return;
}
//获取根节点对象
XDocument document = new XDocument();
XElement root = new XElement(&#34;AutoConfig&#34;);
foreach (var item in info)
{
root.SetElementValue(&#34;Version&#34;, item.Version);
root.SetElementValue(&#34;UpdateContent&#34;, item.UpdateContent);
root.SetElementValue(&#34;DownLoadUrl&#34;, item.DownLoadUrl);
root.SetElementValue(&#34;Time&#34;, item.Time);
root.SetElementValue(&#34;Size&#34;, item.Size);
root.SetElementValue(&#34;DownloadFileUnit&#34;, item.DownloadFileUnit);
root.SetElementValue(&#34;StartName&#34;, item.StartName);
root.SetElementValue(&#34;StopName&#34;, item.StopName);
root.SetElementValue(&#34;AutoUpdateToolUrl&#34;, item.AutoUpdateToolUrl);
root.SetElementValue(&#34;ToolVersion&#34;, item.ToolVersion);
root.SetElementValue(&#34;VersionXml&#34;, item.VersionXml);
}
root.Save(path);
}
/// <summary>
/// 读取xml,返回XDocument对象
/// </summary>
/// <param name=&#34;path&#34;></param>
/// <returns></returns>
public XDocument ReadFromXml(string path)
{
try
{
if (!File.Exists(path))
{
Logger.Error(&#34;读取配置文件失败,路径为空&#34;);
return null;
}
//将XML文件加载进来
XDocument document = XDocument.Load(path);
return document;
}
catch (Exception ex)
{
Logger.Error(&#34;读取配置文件失败,&#34;+ex.Message);
}
return null;
}
/// <summary>
/// 从XDocument读取并返回根元素
/// </summary>
/// <param name=&#34;document&#34;></param>
/// <returns></returns>
public XElement ReadFromXml(XDocument document)
{
if (document == null)
{
Logger.Error(&#34;读取配置文件失败,XDocument为空&#34;);
return null;
}
//获取到XML的根元素进行操作
XElement root = document.Root;
return root;
}
/// <summary>
/// 从xml读取用户新建稿件配置
/// </summary>
/// <param name=&#34;path&#34;></param>
/// <returns></returns>
public Dictionary<string, string> ReadConfigOfManuscript(string path)
{
Dictionary<string, string> dic = new Dictionary<string, string>();
XDocument doc = ReadFromXml(path);
XElement root = ReadFromXml(doc);
if (root == null)
{
Logger.Error(&#34;读取配置文件失败,xml文件内容为空&#34;);
return null;
}
//获取根元素下的所有子元素
IEnumerable<XElement> enumerable = root.Elements();
foreach (XElement item in enumerable)
{
dic.Add(item.Name.ToString(), item.Value);
}
return dic;
}
/// <summary>
/// 从流中读取版本信息
/// </summary>
/// <param name=&#34;s&#34;></param>
/// <returns></returns>
public UpdateInfo ReadVersionConfig(Stream s)
{
XDocument document = XDocument.Load(s);
XElement root = ReadFromXml(document);
if (root == null)
{
Logger.Error(&#34;读取版本信息失败,配置文件内容为空&#34;);
return null;
}
//获取根元素下的所有子元素
IEnumerable<XElement> enumerable = root.Elements();
UpdateInfo info = new UpdateInfo()
{
DownLoadUrl = enumerable.Where(p => p.Name == &#34;DownLoadUrl&#34;).Select(x => x.Value).First(),
Version = enumerable.Where(p => p.Name == &#34;Version&#34;).Select(x => x.Value).First(),
Size = enumerable.Where(p => p.Name == &#34;Size&#34;).Select(x => x.Value).First(),
Time = enumerable.Where(p => p.Name == &#34;Time&#34;).Select(x => x.Value).First(),
UpdateContent = enumerable.Where(p => p.Name == &#34;UpdateContent&#34;).Select(x => x.Value).First(),
DownloadFileUnit = enumerable.Where(p => p.Name == &#34;DownloadFileUnit&#34;).Select(x => x.Value).First(),
StartName = enumerable.Where(p => p.Name == &#34;StartName&#34;).Select(x => x.Value).First(),
StopName = enumerable.Where(p => p.Name == &#34;StopName&#34;).Select(x => x.Value).First(),
AutoUpdateToolUrl = enumerable.Where(p => p.Name == &#34;AutoUpdateToolUrl&#34;).Select(x => x.Value).First(),
ToolVersion = enumerable.Where(p => p.Name == &#34;ToolVersion&#34;).Select(x => x.Value).First(),
VersionXml = enumerable.Where(p => p.Name == &#34;VersionXml&#34;).Select(x => x.Value).First()
};
return info;
}
}
}
3.文件下载、解压缩、拷贝相关操作
using System;
using System.Net;
using System.IO;
using AutoUpdateTool.Model;
using CL.IO.Zip;
using System.ComponentModel;
namespace AutoUpdateTool.Class
{
public class DownloadHelper
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public static AutoUpdate dispatcher;
/// <summary>
/// 获取版本信息
/// </summary>
/// <param name=&#34;url&#34;>版本信息文件的url</param>
/// <returns></returns>
public static Tuple<bool, UpdateInfo> GetLocalConfigInfo(string path)
{
try
{
if (string.IsNullOrEmpty(path))
{
return new Tuple<bool, UpdateInfo>(false, null);
}
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
Stream s = new MemoryStream(buffer);
UpdateInfo info = XmlHelper.Instance.ReadVersionConfig(s);
s.Close();
return new Tuple<bool, UpdateInfo>(true, info);
}
catch (Exception ex)
{
Logger.Error(&#34;获取本地版本信息失败:&#34; + ex.Message);
return new Tuple<bool, UpdateInfo>(false, null);
}
}
/// <summary>
/// 获取版本信息
/// </summary>
/// <param name=&#34;url&#34;>版本信息文件的url</param>
/// <returns></returns>
public static Tuple<bool, UpdateInfo> GetConfigInfo(string url)
{
try
{
if (string.IsNullOrEmpty(url))
{
return new Tuple<bool, UpdateInfo>(false, null);
}
WebClient client = new WebClient();
Stream s = client.OpenRead(new Uri(url));
UpdateInfo info = XmlHelper.Instance.ReadVersionConfig(s);
s.Close();
return new Tuple<bool, UpdateInfo>(true, info);
}
catch (Exception ex)
{
Logger.Error(&#34;获取远端版本信息失败:&#34; + ex.Message);
return new Tuple<bool, UpdateInfo>(false, null);
}
}
/// <summary>
/// 解压缩,拷贝,删除
/// </summary>
/// <param name=&#34;sourcePath&#34;>zip的路径</param>
/// <param name=&#34;targetPath&#34;>目的路径</param>
/// <param name=&#34;pBar&#34;>ProgressBar显示进度</param>
/// <returns></returns>
public static bool UnZip(string sourcePath, string targetPath)
{
try
{
ZipHandler handler = ZipHandler.GetInstance();
string zipFile = Path.Combine(sourcePath, &#34;temp.zip&#34;);
string extractPath = Path.Combine(targetPath, &#34;temp&#34;);
handler.UnpackAll(zipFile, extractPath, (num) =>
{
dispatcher.UpdateUI(null, num);
});
//将临时文件夹下的文件复制到原程序路径中
CopyDirectory(extractPath, sourcePath);//注意,此时临时文件夹为源地址,sourcePath为目标地址
File.Delete(zipFile);//删除zip文件
Directory.Delete(Path.Combine(targetPath, &#34;temp&#34;), true);
return true;
}
catch (Exception ex)
{
Logger.Error(&#34;解压缩、拷贝、删除操作失败:&#34;+ex.Message);
return false;
}
}
/// <summary>
/// 下载zip文件
/// </summary>
/// <param name=&#34;zipUrl&#34;>zip的url</param>
/// <param name=&#34;targetDirPath&#34;>目标文件夹路径</param>
/// <returns></returns>
public static bool DownloadZip(string zipUrl, string targetDirPath)
{
string zipFile = Path.Combine(targetDirPath, &#34;temp.zip&#34;);
if (!Directory.Exists(targetDirPath))
{
return false;
}
try
{
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(Client_DownloadFileCompleted);
client.DownloadFileAsync(new Uri(zipUrl), zipFile);
return true;
}
catch (Exception ex)
{
Logger.Error(&#34;文件下载失败:&#34; + ex.Message);
if (dispatcher != null)
dispatcher.HandleDownloadFail();
return false;
}
}
private static void Client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (dispatcher != null)
dispatcher.HandleDownloadSucess();
}
private static void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
if (dispatcher != null)
{
dispatcher.UpdateUI(null, e.ProgressPercentage);
}
}
/// <summary>
/// 下载xml配置
/// </summary>
/// <param name=&#34;url&#34;></param>
/// <param name=&#34;targetPath&#34;></param>
/// <returns></returns>
public static bool DownLoadXMLConfig(string url, string targetPath)
{
try
{
var xmlPath = Path.Combine(targetPath, &#34;VersionConfig.xml&#34;);
WebClient client = new WebClient();
client.DownloadFile(new Uri(url), xmlPath);
return true;
}
catch (Exception ex)
{
Logger.Error(&#34;配置文件下载失败:&#34; + ex.Message);
return false;
}
}
/// <summary>
/// 获取Zip的总大小
/// </summary>
/// <param name=&#34;zipUrl&#34;></param>
/// <returns></returns>
public static double GetZipTotalSize(string zipUrl)
{
try
{
WebClient client = new WebClient();
if (dispatcher != null)
dispatcher.UpdateUI(&#34;正在获取文件大小……&#34;, 0);
byte[] sr = client.DownloadData(new Uri(zipUrl));
return sr.Length;
}
catch (Exception ex)
{
Logger.Error(&#34;获取文件大小失败:&#34; + ex.Message);
return 0;
}
}
/// <summary>
/// 递归copy文件
/// </summary>
/// <param name=&#34;sourcePath&#34;></param>
/// <param name=&#34;targetPath&#34;></param>
private static void CopyDirectory(string sourcePath, string targetPath)
{
try
{
if (!Directory.Exists(targetPath))
{
Directory.CreateDirectory(targetPath);
}
string[] files = Directory.GetFiles(sourcePath);//Copy文件
foreach (string file in files)
{
try
{
string pFilePath = targetPath + &#34;\\&#34; + Path.GetFileName(file);
File.Copy(file, pFilePath, true);
}
catch (Exception ex)
{
Logger.Error(&#34;文件拷贝失败:[&#34; + Path.GetFileName(file) + &#34;]&#34; + ex.Message);
continue;
}
}
string[] dirs = Directory.GetDirectories(sourcePath);//Copy目录
foreach (string dir in dirs)
{
CopyDirectory(dir, targetPath + &#34;\\&#34; + Path.GetFileName(dir));
}
}
catch (Exception ex)
{
Logger.Error(&#34;文件拷贝失败:&#34; + ex.Message);
}
}
}
}
4.主界面
<Window x:Class=&#34;AutoUpdateTool.AutoUpdate&#34;
xmlns=&#34;http://schemas.microsoft.com/winfx/2006/xaml/presentation&#34;
xmlns:x=&#34;http://schemas.microsoft.com/winfx/2006/xaml&#34;
Title=&#34;软件更新(2.01.09)&#34; Height=&#34;350&#34; Width=&#34;525&#34; Background=&#34;AntiqueWhite&#34; Loaded=&#34;Window_Loaded&#34;>
<Grid>
<Grid.Resources>
<Style TargetType=&#34;{x:Type ProgressBar}&#34;>
<Setter Property=&#34;FocusVisualStyle&#34; Value=&#34;{x:Null}&#34;/>
<Setter Property=&#34;SnapsToDevicePixels&#34; Value=&#34;True&#34;/>
<Setter Property=&#34;Height&#34; Value=&#34;15&#34;/>
<Setter Property=&#34;Background&#34; Value=&#34;#6fae5f&#34;/>
<Setter Property=&#34;FontSize&#34; Value=&#34;10&#34;/>
<Setter Property=&#34;Padding&#34; Value=&#34;5,0&#34;/>
<Setter Property=&#34;Template&#34;>
<Setter.Value>
<ControlTemplate TargetType=&#34;{x:Type ProgressBar}&#34;>
<Grid Background=&#34;#00000000&#34;>
<Grid.RowDefinitions>
<RowDefinition Height=&#34;Auto&#34;/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name=&#34;CommonStates&#34;>
<VisualState x:Name=&#34;Determinate&#34;/>
<VisualState x:Name=&#34;Indeterminate&#34;>
<Storyboard RepeatBehavior=&#34;Forever&#34;>
<PointAnimationUsingKeyFrames Storyboard.TargetName=&#34;Animation&#34; Storyboard.TargetProperty=&#34;(UIElement.RenderTransformOrigin)&#34;>
<EasingPointKeyFrame KeyTime=&#34;0:0:0&#34; Value=&#34;0.5,0.5&#34;/>
<EasingPointKeyFrame KeyTime=&#34;0:0:1.5&#34; Value=&#34;1.95,0.5&#34;/>
<EasingPointKeyFrame KeyTime=&#34;0:0:3&#34; Value=&#34;0.5,0.5&#34;/>
</PointAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Height=&#34;{TemplateBinding Height}&#34;>
<Border Background=&#34;#000000&#34; CornerRadius=&#34;7.5&#34; Opacity=&#34;0.05&#34;/>
<Border BorderBrush=&#34;#000000&#34; BorderThickness=&#34;1&#34; CornerRadius=&#34;7.5&#34; Opacity=&#34;0.1&#34;/>
<Grid Margin=&#34;{TemplateBinding BorderThickness}&#34;>
<Border x:Name=&#34;PART_Track&#34;/>
<Grid x:Name=&#34;PART_Indicator&#34; ClipToBounds=&#34;True&#34; HorizontalAlignment=&#34;Left&#34; >
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name=&#34;width1&#34;/>
<ColumnDefinition x:Name=&#34;width2&#34; Width=&#34;0&#34;/>
</Grid.ColumnDefinitions>
<Grid x:Name=&#34;Animation&#34; RenderTransformOrigin=&#34;0.5,0.5&#34;>
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY=&#34;-1&#34; ScaleX=&#34;1&#34;/>
<SkewTransform AngleY=&#34;0&#34; AngleX=&#34;0&#34;/>
<RotateTransform Angle=&#34;180&#34;/>
<TranslateTransform/>
</TransformGroup>
</Grid.RenderTransform>
<Border Background=&#34;{TemplateBinding Background}&#34; CornerRadius=&#34;7.5&#34;>
<Viewbox HorizontalAlignment=&#34;Left&#34; StretchDirection=&#34;DownOnly&#34; Margin=&#34;{TemplateBinding Padding}&#34; SnapsToDevicePixels=&#34;True&#34;>
<TextBlock Foreground=&#34;#ffffff&#34; SnapsToDevicePixels=&#34;True&#34; FontSize=&#34;{TemplateBinding FontSize}&#34; VerticalAlignment=&#34;Center&#34; Text=&#34;{Binding RelativeSource={RelativeSource TemplatedParent},Path=Value,StringFormat={}{0}%}&#34; RenderTransformOrigin=&#34;0.5,0.5&#34;>
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY=&#34;1&#34; ScaleX=&#34;-1&#34;/>
<SkewTransform AngleY=&#34;0&#34; AngleX=&#34;0&#34;/>
<RotateTransform Angle=&#34;0&#34;/>
<TranslateTransform/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Viewbox>
</Border>
<Border BorderBrush=&#34;#000000&#34; BorderThickness=&#34;1&#34; CornerRadius=&#34;7.5&#34; Opacity=&#34;0.1&#34;/>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property=&#34;IsEnabled&#34; Value=&#34;False&#34;>
<Setter Property=&#34;Background&#34; Value=&#34;#c5c5c5&#34;/>
</Trigger>
<Trigger Property=&#34;IsIndeterminate&#34; Value=&#34;true&#34;>
<Setter TargetName=&#34;width1&#34; Property=&#34;Width&#34; Value=&#34;0.25*&#34;/>
<Setter TargetName=&#34;width2&#34; Property=&#34;Width&#34; Value=&#34;0.725*&#34;/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<StackPanel>
<Label>当前进度:</Label>
<ProgressBar Height=&#34;20&#34; Margin=&#34;0,0&#34; Name=&#34;proBar&#34;></ProgressBar>
<Label Name=&#34;lb_Status&#34; HorizontalAlignment=&#34;Center&#34;></Label>
<Border BorderBrush=&#34;Aquamarine&#34; BorderThickness=&#34;1&#34; CornerRadius=&#34;10&#34; Margin=&#34;0,30&#34;>
<TextBox BorderThickness=&#34;0&#34; Name=&#34;tb_IntroduceContent&#34; Background=&#34;Transparent&#34; VerticalAlignment=&#34;Center&#34; HorizontalContentAlignment=&#34;Left&#34; Text=&#34;版本新功能:&#34; TextWrapping=&#34;Wrap&#34;></TextBox>
</Border>
</StackPanel>
</Grid>
</Window>
using AutoUpdateTool.Class;
using AutoUpdateTool.Model;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace AutoUpdateTool
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class AutoUpdate : Window
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
Tuple<bool, UpdateInfo> configLocal;
public AutoUpdate()
{
InitializeComponent();
}
#region Filed
string sourceUrl = string.Empty;//资源url路径
string zipUnit = &#34;KB&#34;;//下载文件进度显示的计量单位
string versonCode = &#34;&#34;;//版本号
double zipTotalSize = 0;
#endregion
#region private method
/// <summary>
/// 更新初始化
/// </summary>
private void Init()
{
try
{
Dispatcher.Invoke(new Action(() =>
{
lb_Status.Content = &#34;正在获取新版本信息,请耐心等待&#34;;
}));
configLocal = GetVersionInfo(AppDomain.CurrentDomain.BaseDirectory);//获取本地上的配置文件
if (!configLocal.Item1)
{
this.Dispatcher.Invoke(new Action(() =>
{
lb_Status.Content = &#34;本地配置文件读取失败,请联系管理员!&#34;;
}));
return;
}
else
{
sourceUrl = configLocal.Item2.DownLoadUrl;
zipUnit = configLocal.Item2.DownloadFileUnit;
versonCode = configLocal.Item2.Version;
}
this.Dispatcher.Invoke(new Action(() =>
{
Title = &#34;软件更新(&#34; + versonCode + &#34;)&#34;;
}));
var remoteXmlPath = configLocal.Item2.VersionXml;
var resultRemote = GetVersionInfo(remoteXmlPath);
if (!resultRemote.Item1)
{
this.Dispatcher.Invoke(new Action(() =>
{
lb_Status.Content = &#34;服务器端配置文件读取失败,请联系管理员!&#34;;
}));
return;
}
if (versonCode.Equals(resultRemote.Item2.Version))
{
RefreshConfigAndRestart();
return;
}
if (resultRemote.Item1)
{
this.Dispatcher.Invoke(new Action(() =>
{
tb_IntroduceContent.Text = resultRemote.Item2.UpdateContent;
}));
}
this.Dispatcher.Invoke(new Action(() =>
{
Title = &#34;软件更新(&#34; + versonCode + &#34;->&#34; + resultRemote.Item2.Version + &#34;)&#34;;
lb_Status.Content = &#34;获取新版本信息成功,开始下载文件!&#34;;
}));
DownloadHelper.dispatcher = this;
GetZipFileTotalSize();//获取zip文件总大小
string dir = Directory.GetCurrentDirectory();
//下载zip
var flag = DownloadHelper.DownloadZip(sourceUrl, dir);
}
catch (Exception ex)
{
Logger.Error(&#34;更新初始化相关操作失败:&#34; + ex.Message);
MessageBox.Show(ex.StackTrace);
}
}
/// <summary>
/// 更新配置文件并重启应用
/// </summary>
private void RefreshConfigAndRestart()
{
var dirPath = AppDomain.CurrentDomain.BaseDirectory;
var xmlConfig = DownloadHelper.GetLocalConfigInfo(dirPath + @&#34;VersionConfig.xml&#34;);
if (xmlConfig.Item1 && !string.IsNullOrEmpty(xmlConfig.Item2.StartName))
{
CallStop(Path.Combine(dirPath, xmlConfig.Item2.StopName));
var appDomainName = xmlConfig.Item2.StartName;
var localToolPath = Path.Combine(dirPath, appDomainName);
if (StartMainProcess(localToolPath))
{
this.Dispatcher.Invoke(new Action(() =>
{
Application.Current.Shutdown();
}));
};
}
}
private void CallStop(string scriptPath)
{
try
{
Process.Start(scriptPath);
Thread.Sleep(1000);
}
catch (Exception ex)
{
Logger.Error(&#34;调用关闭脚本(&#34; + scriptPath + &#34;)失败!&#34; + ex.Message);
}
}
/// <summary>
/// 启动主程序
/// </summary>
/// <param name=&#34;path&#34;></param>
private bool StartMainProcess(string path)
{
try
{
Logger.Info(path);
Process.Start(path);
return true;
}
catch (Exception ex)
{
Logger.Error(&#34;调用启动脚本(&#34; + path + &#34;)失败!&#34; + ex.Message);
return false;
}
}
/// <summary>
/// 获取版本信息
/// </summary>
/// <param name=&#34;path&#34;></param>
/// <returns></returns>
private Tuple<bool, UpdateInfo> GetVersionInfo(string path)
{
var versionUrl = string.Empty;
if (path.Contains(&#34;VersionConfig.xml&#34;))
{
versionUrl = path;
}
else
{
versionUrl = path + &#34;VersionConfig.xml&#34;;
}
if (path.StartsWith(&#34;http&#34;))
return DownloadHelper.GetConfigInfo(versionUrl);
else
return DownloadHelper.GetLocalConfigInfo(versionUrl);
}
/// <summary>
/// 获取文件大小
/// </summary>
private void GetZipFileTotalSize()
{
zipTotalSize = DownloadHelper.GetZipTotalSize(sourceUrl);
switch (zipUnit)
{
case &#34;KB&#34;:
zipTotalSize = zipTotalSize / 1024;//KB计算
break;
case &#34;MB&#34;:
zipTotalSize = zipTotalSize / 1024 / 1024;//KB计算
break;
case &#34;GB&#34;:
zipTotalSize = zipTotalSize / 1024 / 1024 / 1024;//KB计算
break;
}
zipTotalSize = Math.Round(zipTotalSize, 2);
this.Dispatcher.Invoke(new Action(() =>
{
lb_Status.Content = &#34;0/&#34; + zipTotalSize + zipUnit;
}));
}
#endregion
#region public method
public void UpdateUI(string status, double process)
{
this.Dispatcher.Invoke(new Action(() =>
{
if (string.IsNullOrEmpty(status))
lb_Status.Content = status;
if (process >= 0)
proBar.Value = process;
}));
}
public void HandleDownloadFail()
{
this.Dispatcher.Invoke(new Action(() =>
{
lb_Status.Content = &#34;下载新版本文件失败,请重试&#34;;
}));
}
public void HandleDownloadSucess()
{
var dirPath = AppDomain.CurrentDomain.BaseDirectory;
var xmlConfig = DownloadHelper.GetLocalConfigInfo(dirPath + @&#34;VersionConfig.xml&#34;);
if (xmlConfig.Item1 && !string.IsNullOrEmpty(xmlConfig.Item2.StopName))
{
CallStop(Path.Combine(dirPath, xmlConfig.Item2.StopName));
}
//解压zip
bool unZipFlag = false;
this.Dispatcher.Invoke(new Action(() =>
{
proBar.Value = 0;
lb_Status.Content = &#34;正在解压文件...&#34;;
}));
string dir = Directory.GetCurrentDirectory();
Task unzipTask = new Task(() =>
{
try
{
var flag = DownloadHelper.UnZip(dir, dir);
unZipFlag = flag;
}
catch (Exception ex)
{
Logger.Error(&#34;解压缩失败!&#34; + ex.Message);
unZipFlag = false;
}
});
unzipTask.Start();
Task.WaitAll(unzipTask);
if (unZipFlag)
{
this.Dispatcher.Invoke(new Action(() =>
{
lb_Status.Content = &#34;解压文件完成!&#34;;
}));
bool xmlOk = DownloadHelper.DownLoadXMLConfig(configLocal.Item2.VersionXml, AppDomain.CurrentDomain.BaseDirectory);
if (!xmlOk)
{
MessageBox.Show(&#34;配置文件下载失败&#34;);
return;
}
this.Dispatcher.Invoke(new Action(() =>
{
lb_Status.Content = &#34;更新完成&#34;;
}));
var result = MessageBox.Show(&#34;是否重新启动应用?&#34;, &#34;提示&#34;, MessageBoxButton.YesNo, MessageBoxImage.Information);
if (result == MessageBoxResult.Yes)
{
this.Dispatcher.Invoke(new Action(() =>
{
lb_Status.Content = &#34;正在重启应用,请稍后!&#34;;
}));
RefreshConfigAndRestart();
}
}
else
{
this.Dispatcher.Invoke(new Action(() =>
{
lb_Status.Content = &#34;更新失败,请重试!&#34;;
}));
}
}
#endregion
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Task task = new Task(() =>
{
Init();
});
task.Start();
}
}
}
5.NLog配置
<?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34; ?>
<nlog xmlns=&#34;http://www.nlog-project.org/schemas/NLog.xsd&#34;
xmlns:xsi=&#34;http://www.w3.org/2001/XMLSchema-instance&#34;>
<targets>
<!--此部分中的所有目标将自动异步-->
<target name=&#34;asyncFile&#34; xsi:type=&#34;AsyncWrapper&#34;>
<!--项目日志保存文件路径说明fileName=&#34;${basedir}/保存目录,以年月日的格式创建/${shortdate}/${记录器名称}-${单级记录}-${shortdate}.txt&#34;-->
<target name=&#34;log_file&#34; xsi:type=&#34;File&#34;
fileName=&#34;${basedir}/UpdateLogs/ProjectLogs/${shortdate}/${logger}-${level}-${shortdate}.txt&#34;
layout=&#34;${longdate} | ${message} ${onexception:${exception:format=message} ${newline} ${stacktrace} ${newline}&#34;
archiveFileName=&#34;${basedir}/UpdateLogs/archives/${logger}-${level}-${shortdate}-{#####}.txt&#34;
archiveAboveSize=&#34;102400&#34;
archiveNumbering=&#34;Sequence&#34;
concurrentWrites=&#34;true&#34;
keepFileOpen=&#34;false&#34; />
</target>
<!--使用可自定义的着色将日志消息写入控制台-->
<target name=&#34;colorConsole&#34; xsi:type=&#34;ColoredConsole&#34; layout=&#34;[${date:format=HH\:mm\:ss}]:${message} ${exception:format=message}&#34; />
</targets>
<!--规则配置,final - 最终规则匹配后不处理任何规则-->
<rules>
<logger name=&#34;Microsoft.*&#34; minlevel=&#34;Info&#34; writeTo=&#34;asyncFile&#34; final=&#34;true&#34; />
<logger name=&#34;*&#34; minlevel=&#34;Info&#34; writeTo=&#34;asyncFile&#34; />
<logger name=&#34;*&#34; minlevel=&#34;Warn&#34; writeTo=&#34;colorConsole&#34; />
</rules>
</nlog>6.版本配置信息
<?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34; ?>
<root>
<Version>0.9</Version>
<ToolVersion>0.9</ToolVersion>
<AutoUpdateToolUrl>http://xxxx/xxx.zip</AutoUpdateToolUrl>
<VersionXml>http://xxxx/VersionConfig.xml</VersionXml>
<UpdateContent>第一个版本哦</UpdateContent>
<DownLoadUrl>http://xxx/xxx.zip</DownLoadUrl>
<StartName>xxx.exe</StartName>
<StopName>stop.bat</StopName>
<DownloadFileUnit>KB</DownloadFileUnit>
<Time>2021-06-30 11:30:00</Time>
<Size>1000</Size>
</root>其中:
1.StartName是启动主程序的脚本,可设为主程序的exe或者自行编写bat脚本;
2.StopName为关闭主程序的脚本,需自行编写脚本,以下是参考:
@echo off
taskkill /f /t /im Trans.exe
exit
代码完毕,祝实验顺利。
一个遗留的问题:更新软件自身怎么更新?
更多内容请访问 |
|