日前收到一個小任務,要做一個通用的在線升級程序。更新的內(nèi)容包括一些dll或exe或、配置文件。升級的大致流程是這樣的,從服務器獲取一個更新的配置文件,經(jīng)過核對后如有新的更新,則會從服務器下載相應的文件更新到被升級的程序目錄下。如果被升級的程序在升級之前已經(jīng)啟動,程序則會強制關閉它,待到升級完成之后重新啟動相應的程序。在升級之前程序會自動備份一次,以防升級失敗造成程序不能運行。
首先來的是數(shù)據(jù)實體
1 public class FileENT 2 { 3 public string FileFullName { get; set; } 4 5 public string Src { get; set; } 6 7 public string Version { get; set; } 8 9 public int Size { get; set; } 10 11 public UpdateOption Option { get; set; } 12 } 13 14 public enum UpdateOption 15 { 16 add, 17 del 18 }
下面這個類時程序的一些參數(shù),包括了系統(tǒng)的配置參數(shù),為了程序能通用一點,就加了配置上去。
1 public class AppParameter 2 { 3 /// <summary> 4 /// 備份路徑 5 /// </summary> 6 public static string BackupPath = ConfigurationManager.ApPSettings["backupPath"]; 7 8 /// <summary> 9 /// 更新的URL 10 /// </summary> 11 public static string ServerURL = ConfigurationManager.Appsettings["serverURL"]; 12 13 /// <summary> 14 /// 本地更新文件全名 15 /// </summary> 16 public static string LocalUPdateConfig = ConfigurationManager.AppSettings["localUPdateConfig"]; 17 18 /// <summary> 19 /// 版本號 20 /// </summary> 21 public static string Version = ConfigurationManager.AppSettings["version"]; 22 23 /// <summary> 24 /// 更新程序路徑 25 /// </summary> 26 public static string LocalPath = AppDomain.CurrentDomain.BaseDirectory; 27 28 /// <summary> 29 /// 主程序路徑 30 /// </summary> 31 public static string MainPath = ConfigurationManager.AppSettings["mainPath"]; 32 33 /// <summary> 34 /// 有否啟動主程序 35 /// </summary> 36 public static bool IsRunning = false; 37 38 /// <summary> 39 /// 主程序名 40 /// </summary> 41 public static List<string> AppNames = ConfigurationManager.AppSettings["appName"].Split(';').ToList(); 42 }
接著就介紹程序的代碼
程序是用窗體來實現(xiàn)的,下面三個是窗體新添加的三個字段
1 private bool isDelete=true; //是否要刪除升級配置 2 private bool runningLock = false;//是否正在升級 3 private Thread thread; //升級的線程
載入窗體時需要檢查更新,如果沒有更新就提示”暫時無更新”;如果有更新的則先進行備份,備份失敗的話提示錯誤退出更新。
1 if (CheckUpdate()) 2 { 3 if (!Backup()) 4 { 5 MessageBox.Show("備份失敗!"); 6 btnStart.Enabled = false; 7 isDelete = true; 8 return; 9 } 10 11 } 12 else 13 { 14 MessageBox.Show("暫時無更新"); 15 this.btnFinish.Enabled = true; 16 this.btnStart.Enabled = false; 17 isDelete = false; 18 this.Close(); 19 }
在這些操作之前還要檢測被更新程序有否啟動,有則將其關閉。
1 List<string> processNames = new List<string>(); 2 string mainPro = string.Empty; 3 processNames.AddRange(AppParameter.AppNames); 4 for (int i = 0; i < processNames.Count; i++) 5 { 6 processNames[i] = processNames[i].Substring(processNames[i].LastIndexOf('\\')).Trim('\\').Replace(".exe", ""); 7 } 8 mainPro = processNames.FirstOrDefault(); 9 AppParameter.IsRunning = ProcessHelper.IsRunningProcess(mainPro); 10 if (AppParameter.IsRunning) 11 { 12 MessageBox.Show("此操作需要關閉要更新的程序,請保存相關數(shù)據(jù)按確定繼續(xù)", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); 13 foreach (string item in processNames) 14 ProcessHelper.CloseProcess(item); 15 }
另外上面用到的CheckUpdate( )和Backup( )方法如下
1 /// <summary> 2 /// 檢查更新 有則提示用戶 確認后下載新的更新配置 3 /// </summary> 4 /// <returns>用戶確認信息</returns> 5 public static bool CheckUpdate() 6 { 7 bool result = false; 8 9 HttpHelper.DownLoadFile(AppParameter.ServerURL, AppParameter.LocalPath + "temp_config.xml"); 10 if (!File.Exists(AppParameter.LocalUPdateConfig)) 11 result = true; 12 else 13 { 14 long localSize = new FileInfo(AppParameter.LocalUPdateConfig).Length; 15 long tempSize = new FileInfo(AppParameter.LocalPath + "temp_config.xml").Length; 16 17 if (localSize >= tempSize) result = false; 18 19 else result = true; 20 } 21 22 if (result) 23 { 24 if (File.Exists(AppParameter.LocalUPdateConfig)) File.Delete(AppParameter.LocalUPdateConfig); 25 File.Copy(AppParameter.LocalPath + "temp_config.xml", AppParameter.LocalUPdateConfig); 26 } 27 else 28 result = false; 29 30 File.Delete(AppParameter.LocalPath + "temp_config.xml"); 31 return result; 32 } 33 34 /// <summary> 35 /// 備份 36 /// </summary> 37 public static bool Backup() 38 { 39 string sourcePath = Path.Combine(AppParameter.BackupPath, DateTime.Now.ToString("yyyy-MM-dd HH_mm_ss")+"_v_"+AppParameter.Version + ".rar"); 40 return ZipHelper.Zip(AppParameter.MainPath.Trim() , sourcePath); 41 }
下面則是更新的部分的代碼,使用了多線程,出于兩方面的考慮,一是進度條需要;二是如果用單線程,萬一更新文件下載時間過長或者更新內(nèi)容過多,界面會卡死。
1 /// <summary> 2 /// 更新 3 /// </summary> 4 public void UpdateApp() 5 { 6 int successCount = 0; 7 int failCount = 0; 8 int itemIndex = 0; 9 List<FileENT> list = ConfigHelper.GetUpdateList(); 10 if (list.Count == 0) 11 { 12 MessageBox.Show("版本已是最新", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); 13 this.btnFinish.Enabled = true; 14 this.btnStart.Enabled = false; 15 isDelete = false; 16 this.Close(); 17 return; 18 } 19 thread = new Thread(new ThreadStart(delegate 20 { 21 #region thread method 22 23 FileENT ent = null; 24 25 while (true) 26 { 27 lock (this) 28 { 29 if (itemIndex >= list.Count) 30 break; 31 ent = list[itemIndex]; 32 33 34 string msg = string.Empty; 35 if (ExecUpdateItem(ent)) 36 { 37 msg = ent.FileFullName + "更新成功"; 38 successCount++; 39 } 40 else 41 { 42 msg = ent.FileFullName + "更新失敗"; 43 failCount++; 44 } 45 46 if (this.InvokeRequired) 47 { 48 this.Invoke((Action)delegate() 49 { 50 listBox1.Items.Add(msg); 51 int val = (int)Math.Ceiling(1f / list.Count * 100); 52 progressBar1.Value = progressBar1.Value + val > 100 ? 100 : progressBar1.Value + val; 53 }); 54 } 55 56 57 itemIndex++; 58 if (successCount + failCount == list.Count && this.InvokeRequired) 59 { 60 string finishMessage = string.Empty; 61 if (this.InvokeRequired) 62 { 63 this.Invoke((Action)delegate() 64 { 65 btnFinish.Enabled = true; 66 }); 67 } 68 isDelete = failCount != 0; 69 if (!isDelete) 70 { 71 AppParameter.Version = list.Last().Version; 72 ConfigHelper.UpdateAppConfig("version", AppParameter.Version); 73 finishMessage = "升級完成,程序已成功升級到" + AppParameter.Version; 74 } 75 else 76 finishMessage = "升級完成,但不成功"; 77 MessageBox.Show(finishMessage, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); 78 runningLock = false; 79 } 80 } 81 } 82 #endregion 83 })); 84 runningLock = true; 85 thread.Start(); 86 } 87 88 /// <summary> 89 /// 執(zhí)行單個更新 90 /// </summary> 91 /// <param name="ent"></param> 92 /// <returns></returns> 93 public bool ExecUpdateItem(FileENT ent) 94 { 95 bool result = true; 96 97 try 98 { 99 100 if (ent.Option == UpdateOption.del) 101 File.Delete(ent.FileFullName); 102 else 103 HttpHelper.DownLoadFile(ent.Src, Path.Combine(AppParameter.MainPath, ent.FileFullName)); 104 } 105 catch { result = false; } 106 return result; 107 }
只開了一個子線程,原本是開了5個子線程的,但是考慮到多線程會導致下載文件的順序不確定,還是用回單線程會比較安全。線程是用了窗體實例里的thread字段,在開啟線程時還用到runningLock標識字段,表示當前正在更新。當正在更新程序時關閉窗口,則要提問用戶是否結(jié)束更新,若用戶選擇了是則要結(jié)束那個更新進程thread了,下面則是窗口關閉的時間FormClosing事件的方法。
1 if (runningLock ) 2 { 3 if (MessageBox.Show("升級還在進行中,中斷升級會導致程序不可用,是否中斷", 4 "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk) == DialogResult.Yes) 5 { 6 if (thread != null) thread.Abort(); 7 isDelete = true; 8 AppParameter.IsRunning = false; 9 } 10 else 11 { 12 e.Cancel = true; 13 return; 14 } 15 } 16 if (isDelete) File.Delete(AppParameter.LocalUPdateConfig);
在這里還要做另一件事,就是把之前關了的程序重新啟動。
1 try 2 { 3 if (AppParameter.IsRunning) ProcessHelper.StartProcess(AppParameter.AppNames.First()); 4 } 5 catch (Exception ex) 6 { 7 8 MessageBox.Show("程序無法啟動!" + ex.Message, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error); 9 }
在這里展示一下更新的界面。挺丑的,別笑哈。
更新程序的配置信息如下
1 <appSettings> 2 <add key="backupPath" value="C:\Users\Administrator\Desktop\temp\backup"/> 3 <add key="serverURL" value="http://localhost:8015/updateconfig.xml"/> 4 <add key="localUPdateConfig" value="E:\HopeGi\Code\MyUpdate\MyUpdate\bin\Debug\updateconfig.xml"/> 5 <add key="version" value="2"/> 6 <add key="mainPath" value="C:\Users\Administrator\Desktop\temp\main"/> 7 <add key="appName" value="D:\test.exe"/> 8 </appSettings>
完整文檔下載:http://pan.baidu.com/share/link?shareid=443378&uk=85241834