版本更新基本操作
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); } } }