1.简介
本文是制作了对软件进行自动更新的程序,有三个模式:
1、模式1
程序运行时:检查到更新,先偷偷下载,下载完成后,提示更新,手动点进行更新。开机启动时,若有完整的更新包,就进行直接更新。
2、模式2
程序运行时:检查到更新,提示更新,点击更新后,进行下载,下载完成后进行更新。开机启动时,若有完整的更新包,就进行直接更新。
3、模式3
程序运行时:不检查更新开机启动时,检查更新,下载更新,进行更新。
2.功能分析
1)首先,设计了一个简单的客户端Web程序,使用Wcf进行数据传输,使客户端能够通过Wcf获取项目的版本信息和更新包下载地址。
2)设计一个客户端,在这个客户端里面,我只是随意设计了一个窗口,然后在配置文件里面写明这个客户端的详细信息。
3)最重要的来了,更新程序,先介绍一下必要的文件夹
上面四个标注的文件是写死的了,必须存在的,这四个文件也就是客户端的全部内容了,ReayProject只是我存放开发文件的文件夹,部署用不到的。
思路:更新时下载文件更新包在临时版本库,解压,把本地版本备份到备份版本,再把临时版本库的内容同步到本地版本,完成版本更新。
在开始写各个更新功能之前,我们先写一下需要用到什么通用的功能,所以就写了一个基础操作类,供各个更新模式调用
BasicOperation
using Ionic.Zip;
using Microsoft.Win32;
using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
namespace UpdateProgram
{
/// <summary>
/// 基础操作类
/// </summary>
class BasicOperation
{
/// <summary>
/// 获取更新信息
/// </summary>
/// <param name="deploy"></param>
/// <returns></returns>
public virtual bool GetUpdateInfo(ref DeployModel deploy)
{
try
{
WcfPosService.PosWcfClient client = new WcfPosService.PosWcfClient();
string retu = client.GetProjectVersion(deploy.ProjectName);
if (retu != null)
{
string[] retuArray = retu.Split(',');
deploy.NewVersion = Convert.ToDouble(retuArray[0].TrimStart('V'));
deploy.UpdateUrl = retuArray[1];
string[] vs = deploy.UpdateUrl.Split('/');
deploy.UpdateFileName = vs[vs.Length - 1];
return true;
}
else
{
return false;
}
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 关闭指定名字的服务
/// </summary>
/// <param name="ProcessName"></param>
public void KillProcess(string ProcessName)
{
try
{
Process[] processes = Process.GetProcessesByName(ProcessName);
foreach (Process p in processes)
{
p.Kill();
}
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 开启指定地址的服务
/// </summary>
/// <param name="ProcessPath"></param>
public void StartProcess(string ProcessPath)
{
try
{
Process p = new Process();
p.StartInfo.FileName = ProcessPath;
p.Start();
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 根据Url返回网络文件数据流
/// </summary>
/// <param name="Url"></param>
/// <returns></returns>
public Stream GetUrlStream(string Url)
{
try
{
HttpWebRequest request = WebRequest.Create(Url) as HttpWebRequest;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
return responseStream;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 带Range根据Url返回网络文件数据流
/// </summary>
/// <param name="Url"></param>
/// <param name="Range">断点续传</param>
/// <returns></returns>
public Stream GetUrlStreamAddRange(string Url, long Range)
{
try
{
HttpWebRequest request = WebRequest.Create(Url) as HttpWebRequest;
request.AddRange((int)Range);
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
return responseStream;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 获取网络文件大小
/// </summary>
/// <param name="src"></param>
/// <returns></returns>
public long GetUrlFileLength(string Url)
{
try
{
return WebRequest.Create(Url).GetResponse().ContentLength;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 获取本地文件大小
/// </summary>
/// <param name="localFilePath"></param>
/// <returns></returns>
public long GetlStartPos(string localFilePath)//判断断点
{
try
{
return new FileInfo(localFilePath).Length;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 获取本地安装的WinRar的路径,若未安装,返回null
/// </summary>
/// <returns></returns>
public string GetWinRarInstallPath()
{
string result = string.Empty;
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe");
if (registryKey != null)
{
result = registryKey.GetValue("").ToString();
}
registryKey.Close();
return result;
}
/// <summary>
/// 解压Rar文件,需要先安装WinRar
/// </summary>
/// <param name="CompressFileUrl">需要解压的压缩文件</param>
/// <param name="DecompressToDir">解压到哪个文件夹</param>
/// <param name="WinRarPath">WinRarPath的安装目录</param>
/// <returns>解压结果,成功为true,失败为false</returns>
public bool DecompressFile(string CompressFileUrl, string DecompressToDir, string WinRarPath)
{
try
{
String commandOptions = string.Format("x {0} {1} -y", CompressFileUrl, DecompressToDir);
string winrarDir = Path.GetDirectoryName(WinRarPath);
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = Path.Combine(winrarDir, "rar.exe");
processStartInfo.Arguments = commandOptions;
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process process = new Process();
process.StartInfo = processStartInfo;
process.Start();
process.WaitForExit();
process.Close();
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 解压Zip文件,不会报错
/// </summary>
/// <param name="CompressFileUrl">Zip文件地址</param>
/// <param name="DecompressToDir">解压到此文件夹,若无,自动生成</param>
public void DecompressZipFile(string CompressFileUrl, string DecompressToDir)
{
using (ZipFile zip = new ZipFile(CompressFileUrl, Encoding.UTF8))
{
zip.ExtractAll(DecompressToDir, ExtractExistingFileAction.OverwriteSilently);
}
}
/// <summary>
/// 删除指定文件夹下面的Rar和Zip压缩包
/// </summary>
/// <param name="Dir"></param>
public void DeleteCompressBag(string Dir)
{
DirectoryInfo dir = new DirectoryInfo(Dir);
foreach (FileInfo fi in dir.GetFiles("*.rar"))
{
fi.Delete();
}
foreach (FileInfo fi in dir.GetFiles("*.zip"))
{
fi.Delete();
}
}
/// <summary>
/// 同步文件夹里面的内容
/// </summary>
/// <param name="src">源地址</param>
/// <param name="obj">目标地址</param>
/// <param name="Complete">完全同步,true表示obj与src完全一致,false表示obj只根据src的内容进行添加和修改</param>
public void RecursiveFile(string src, string obj, bool Complete = false)
{
DirectoryInfo dirSrc;
if (Complete)
{
//没实现
//dirSrc = new DirectoryInfo(obj);
//FileSystemInfo[] FileSystemInfos = dirSrc.GetFileSystemInfos();
//foreach (DirectoryInfo dir in FileSystemInfos)
//{
// File.Delete(dir.Name);//清空所有
//}
}
dirSrc = new DirectoryInfo(src);
FileInfo[] fileInfos = dirSrc.GetFiles();
foreach (FileInfo fi in fileInfos)//同步文件
{
string strsrc = src + "\\" + fi.Name;
string strobj = obj + "\\" + fi.Name;
if (File.Exists(strobj) == false)
{
File.Copy(strsrc, strobj, true);
continue;
}
//比较两个文件的MD5码
FileStream srcStream = new FileStream(strsrc, FileMode.Open);
FileStream objStream = new FileStream(strobj, FileMode.Open);
MD5 md5 = new MD5CryptoServiceProvider();
byte[] srcVal = md5.ComputeHash(srcStream);
byte[] objVal = md5.ComputeHash(objStream);
srcStream.Close();
objStream.Close();
if (byteEquals(srcVal, objVal))
{
continue;
}
File.Copy(strsrc, strobj, true);
}
DirectoryInfo[] dirSrcs = dirSrc.GetDirectories();
foreach (DirectoryInfo dir in dirSrcs)//查找子目录
{
string strsrc = src + "\\" + dir.Name;
string strobj = obj + "\\" + dir.Name;
if (Directory.Exists(strobj) == false)
{
Directory.CreateDirectory(strobj);
}
RecursiveFile(strsrc, strobj, false);
}
}
/// <summary>
/// 比较两个字节数组是否相同
/// </summary>
/// <param name="b1"></param>
/// <param name="b2"></param>
/// <returns></returns>
public bool byteEquals(byte[] b1, byte[] b2)
{
for (int i = 0; i < b1.Length; i++)
if (b1[i] != b2[i])
return false;
return true;
}
/// <summary>
/// 获取指定config文件中appSettings节点下key的value
/// </summary>
/// <param name="key"></param>
/// <param name="configPath"></param>
/// <returns></returns>
public string GetAppSettings(string key, string configPath)
{
ExeConfigurationFileMap map = new ExeConfigurationFileMap { ExeConfigFilename = configPath };
var configuration = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
var strObj = configuration.AppSettings.Settings[key];
if (strObj == null)
{
return null;
}
else
{
return strObj.Value;
}
}
/// <summary>
/// 修改指定的XML文件钟add的值
/// </summary>
/// <param name="XmlFilePath">文件路径</param>
/// <param name="Iskey">标签内key的值</param>
/// <param name="Isvalue">即将被修改的标签内Isvalue的值</param>
public void ModifyXmlFile(string XmlFilePath, string Iskey, string Isvalue)
{
XmlDocument doc = new XmlDocument();
doc.Load(XmlFilePath);
XmlNodeList nodes = doc.GetElementsByTagName("add");
XmlAttribute att;
for (int i = 0; i < nodes.Count; i++)
{
//获得将当前元素的key属性
att = nodes[i].Attributes["key"];
//根据元素的第一个属性来判断当前的元素是不是目标元素
if (att.Value == Iskey)
{
//对目标元素中的第二个属性赋值
att = nodes[i].Attributes["value"];
att.Value = Isvalue;
break;
}
}
//保存上面的修改
doc.Save(XmlFilePath);
}
}
}