winform自動升級

 最近單位開發一個項目,其中需要用到自動升級功能。因爲自動升級是一個比較常用的功能,可能會在很多程序中用到,於是,我就想寫一個自動升級的組件,在應用程序中,只需要引用這個自動升級組件,並添加少量代碼,即可實現自動升級功能。因爲我們的程序中可能包含多個exe或者dll文件,所以要支持多文件的更新。 首先,要確定程序應該去哪裏下載需要升級的文件。我選擇了到指定的網站上去下載,這樣比較簡單,也通用一些。在這個網站上,需要放置一個當前描述最新文件列表的文件,我們估且叫它服務器配置文件。這個文件保存了當前最新文件的版本號(lastver),大小(size),下載地址(url),本地文件的保存路徑(path),還有當更新了這個文件後,程序是否需要重新啓動(needRestart)。這個文件大致如下: updateservice.xml

同時,客戶端也保存了一個需要升級的本地文件的列表,形式和服務器配置文件差不多,我們叫它本地配置文件。其中,節點表示是否啓用自動升級功能,表示服務器配置文件的地址。 update.config

true http://update.iyond.com/updateservice.xml 使用自動各級組件的程序在啓動時,會去檢查這個配置文件。如果發現有配置文件中的文件版本和本地配置文件中描述的文件版本不一致,則提示用戶下載。同時,如果本地配置文件中某些文件在服務器配置文件的文件列表中不存在,則說明這個文件已經不需要了,需要刪除。最後,當升級完成後,會更新本地配置文件。 我們先來看一下如何使用這個組件。在程序的Program.cs的Main函數中: [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); AutoUpdater au = new AutoUpdater(); try { au.Update(); } catch (WebException exp) { MessageBox.Show(String.Format("無法找到指定資源/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (XmlException exp) { MessageBox.Show(String.Format("下載的升級文件有錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (NotSupportedException exp) { MessageBox.Show(String.Format("升級地址配置錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (ArgumentException exp) { MessageBox.Show(String.Format("下載的升級文件有錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (Exception exp) { MessageBox.Show(String.Format("升級過程中發生錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } Application.Run(new MainUI()); } 如上所示,只需要簡單的幾行代碼,就可以實現自動升級功能了。 軟件運行截圖: 下面,我們來詳細說一下這個自動升級組件的實現。先看一下類圖: AutoUpdater:自動升級的管理類,負責整體的自動升級功能的實現。 Config:配置類,負責管理本地配置文件。 DownloadConfirm:一個對話框,向用戶顯示需要升級的文件的列表,並允許用戶選擇是否馬上升級。 DownloadFileInfo:要下載的文件的信息 DownloadProgress:一個對話框,顯示下載進度。 DownloadProgress.ExitCallBack, DownloadProgress.SetProcessBarCallBack, DownloadProgress.ShowCurrentDownloadFileNameCallBack:由於.NET2.0不允許在一個線程中訪問另一個線程的對象,所以需要通過委託來實現。 LocalFile:表示本地配置文件中的一個文件 RemoteFile:表示服務器配置文件中的一個文件。 UpdateFileList:一個集合,從List繼承 我們先整體看一下AutoUpdater.cs: AutoUpdater.cs public class AutoUpdater { const string FILENAME = "update.config"; private Config config = null; private bool bNeedRestart = false; public AutoUpdater() { config = Config.LoadConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME)); } /**//// /// 檢查新版本 /// /// 無法找到指定資源 /// 升級地址配置錯誤 /// 下載的升級文件有錯誤 /// 下載的升級文件有錯誤 /// 未知錯誤 /// public void Update() { if (!config.Enabled) return; /**//* * 請求Web服務器,得到當前最新版本的文件列表,格式同本地的FileList.xml。 * 與本地的FileList.xml比較,找到不同版本的文件 * 生成一個更新文件列表,開始DownloadProgress * * * * path爲相對於應用程序根目錄的相對目錄位置,包括文件名 */ WebClient client = new WebClient(); string strXml = client.DownloadString(config.ServerUrl); Dictionary listRemotFile = ParseRemoteXml(strXml); List downloadList = new List(); //某些文件不再需要了,刪除 List preDeleteFile = new List(); foreach (LocalFile file in config.UpdateFileList) { if (listRemotFile.ContainsKey(file.Path)) { RemoteFile rf = listRemotFile[file.Path]; if (rf.LastVer != file.LastVer) { downloadList.Add(new DownloadFileInfo(rf.Url, file.Path, rf.LastVer, rf.Size)); file.LastVer = rf.LastVer; file.Size = rf.Size; if (rf.NeedRestart) bNeedRestart = true; } listRemotFile.Remove(file.Path); } else { preDeleteFile.Add(file); } } foreach (RemoteFile file in listRemotFile.Values) { downloadList.Add(new DownloadFileInfo(file.Url, file.Path, file.LastVer, file.Size)); config.UpdateFileList.Add(new LocalFile(file.Path, file.LastVer, file.Size)); if (file.NeedRestart) bNeedRestart = true; } if (downloadList.Count > 0) { DownloadConfirm dc = new DownloadConfirm(downloadList); if (this.OnShow != null) this.OnShow(); if (DialogResult.OK == dc.ShowDialog()) { foreach (LocalFile file in preDeleteFile) { string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.Path); if (File.Exists(filePath)) File.Delete(filePath); config.UpdateFileList.Remove(file); } StartDownload(downloadList); } } } private void StartDownload(List downloadList) { DownloadProgress dp = new DownloadProgress(downloadList); if (dp.ShowDialog() == DialogResult.OK) { //更新成功 config.SaveConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME)); if (bNeedRestart) { MessageBox.Show("程序需要重新啓動才能應用更新,請點擊確定重新啓動程序。", "自動更新", MessageBoxButtons.OK, MessageBoxIcon.Information); Process.Start(Application.ExecutablePath); Environment.Exit(0); } } } private Dictionary ParseRemoteXml(string xml) { XmlDocument document = new XmlDocument(); document.LoadXml(xml); Dictionary list = new Dictionary(); foreach (XmlNode node in document.DocumentElement.ChildNodes) { list.Add(node.Attributes["path"].Value, new RemoteFile(node)); } return list; } public event ShowHandler OnShow; } 在構造函數中,我們先要加載配置文件: public AutoUpdater() { config = Config.LoadConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME)); } 最主要的就是Update()這個函數了。當程序調用au.Update時,首先檢查當前是否開戶了自動更新: if (!config.Enabled) return; 如果啓用了自動更新,就需要去下載服務器配置文件了: WebClient client = new WebClient(); string strXml = client.DownloadString(config.ServerUrl); 然後,解析服務器配置文件到一個Dictionary中: Dictionary listRemotFile = ParseRemoteXml(strXml); 接下來比較服務器配置文件和本地配置文件,找出需要下載的文件和本地需要刪除的文件: List downloadList = new List(); //某些文件不再需要了,刪除 List preDeleteFile = new List(); foreach (LocalFile file in config.UpdateFileList) { if (listRemotFile.ContainsKey(file.Path)) { RemoteFile rf = listRemotFile[file.Path]; if (rf.LastVer != file.LastVer) { downloadList.Add(new DownloadFileInfo(rf.Url, file.Path, rf.LastVer, rf.Size)); file.LastVer = rf.LastVer; file.Size = rf.Size; if (rf.NeedRestart) bNeedRestart = true; } listRemotFile.Remove(file.Path); } else { preDeleteFile.Add(file); } } foreach (RemoteFile file in listRemotFile.Values) { downloadList.Add(new DownloadFileInfo(file.Url, file.Path, file.LastVer, file.Size)); config.UpdateFileList.Add(new LocalFile(file.Path, file.LastVer, file.Size)); if (file.NeedRestart) bNeedRestart = true; } 如果發現有需要下載的文件,則向用戶顯示這些文件,並提示其是否馬上更新。如果用戶選擇了馬上更新,則先刪除本地不再需要的文件,然後開始下載更新文件。 if (downloadList.Count > 0) { DownloadConfirm dc = new DownloadConfirm(downloadList); if (this.OnShow != null) this.OnShow(); if (DialogResult.OK == dc.ShowDialog()) { foreach (LocalFile file in preDeleteFile) { string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.Path); if (File.Exists(filePath)) File.Delete(filePath); config.UpdateFileList.Remove(file); } StartDownload(downloadList); } } 我們再來看一下StartDownload函數 private void StartDownload(List downloadList) { DownloadProgress dp = new DownloadProgress(downloadList); if (dp.ShowDialog() == DialogResult.OK) { //更新成功 config.SaveConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME)); if (bNeedRestart) { MessageBox.Show("程序需要重新啓動才能應用更新,請點擊確定重新啓動程序。", "自動更新", MessageBoxButtons.OK, MessageBoxIcon.Information); Process.Start(Application.ExecutablePath); Environment.Exit(0); } } } 在這個函數中,先調用DownloadProgress下載所有需要下載的文件,然後更新本地配置文件,最後,如果發現某些更新文件需要重新啓動應用程序的話,會提示用戶重新啓動程序。 至此,AutoUpdater這個類的使命就完成了,其實,整個的升級過程也就完成了。(廢話)。 最後,我們來看一下這個組件是如何下載更新文件的 DownloadProgress.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Threading; using System.Net; using System.IO; using System.Diagnostics; namespace Iyond.Utility { public partial class DownloadProgress : Form { private bool isFinished = false; private List downloadFileList = null; private ManualResetEvent evtDownload = null; private ManualResetEvent evtPerDonwload = null; private WebClient clientDownload = null; public DownloadProgress(List downloadFileList) { InitializeComponent(); this.downloadFileList = downloadFileList; } private void OnFormClosing(object sender, FormClosingEventArgs e) { if (!isFinished && DialogResult.No == MessageBox.Show("當前正在更新,是否取消?", "自動升級", MessageBoxButtons.YesNo, MessageBoxIcon.Question)) { e.Cancel = true; return; } else { if (clientDownload != null) clientDownload.CancelAsync(); evtDownload.Set(); evtPerDonwload.Set(); } } private void OnFormLoad(object sender, EventArgs e) { evtDownload = new ManualResetEvent(true); evtDownload.Reset(); Thread t = new Thread(new ThreadStart(ProcDownload)); t.Name = "download"; t.Start(); } long total = 0; long nDownloadedTotal = 0; private void ProcDownload() { evtPerDonwload = new ManualResetEvent(false); foreach (DownloadFileInfo file in this.downloadFileList) { total += file.Size; } while (!evtDownload.WaitOne(0, false)) { if (this.downloadFileList.Count == 0) break; DownloadFileInfo file = this.downloadFileList[0]; //Debug.WriteLine(String.Format("Start Download:{0}", file.FileName)); this.ShowCurrentDownloadFileName(file.FileName); //下載 clientDownload = new WebClient(); clientDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnDownloadProgressChanged); clientDownload.DownloadFileCompleted += new AsyncCompletedEventHandler(OnDownloadFileCompleted); evtPerDonwload.Reset(); clientDownload.DownloadFileAsync(new Uri(file.DownloadUrl), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName + ".tmp"), file); //等待下載完成 evtPerDonwload.WaitOne(); clientDownload.Dispose(); clientDownload = null; //移除已下載的文件 this.downloadFileList.Remove(file); } //Debug.WriteLine("All Downloaded"); if (this.downloadFileList.Count == 0) Exit(true); else Exit(false); evtDownload.Set(); } void OnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { DownloadFileInfo file = e.UserState as DownloadFileInfo; nDownloadedTotal += file.Size; this.SetProcessBar(0, (int)(nDownloadedTotal * 100 / total)); //Debug.WriteLine(String.Format("Finish Download:{0}", file.FileName)); //替換現有文件 string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName); if (File.Exists(filePath)) { if (File.Exists(filePath + ".old")) File.Delete(filePath + ".old"); File.Move(filePath, filePath + ".old"); } File.Move(filePath + ".tmp", filePath); //繼續下載其它文件 evtPerDonwload.Set(); } void OnDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { this.SetProcessBar(e.ProgressPercentage, (int)((nDownloadedTotal + e.BytesReceived) * 100 / total)); } delegate void ShowCurrentDownloadFileNameCallBack(string name); private void ShowCurrentDownloadFileName(string name) { if (this.labelCurrentItem.InvokeRequired) { ShowCurrentDownloadFileNameCallBack cb = new ShowCurrentDownloadFileNameCallBack(ShowCurrentDownloadFileName); this.Invoke(cb, new object[] { name }); } else { this.labelCurrentItem.Text = name; } } delegate void SetProcessBarCallBack(int current, int total); private void SetProcessBar(int current, int total) { if (this.progressBarCurrent.InvokeRequired) { SetProcessBarCallBack cb = new SetProcessBarCallBack(SetProcessBar); this.Invoke(cb, new object[] { current, total }); } else { this.progressBarCurrent.Value = current; this.progressBarTotal.Value = total; } } delegate void ExitCallBack(bool success); private void Exit(bool success) { if (this.InvokeRequired) { ExitCallBack cb = new ExitCallBack(Exit); this.Invoke(cb, new object[] { success }); } else { this.isFinished = success; this.DialogResult = success ? DialogResult.OK : DialogResult.Cancel; this.Close(); } } private void OnCancel(object sender, EventArgs e) { evtDownload.Set(); evtPerDonwload.Set(); } } } 在構造函數中,將要下載的文件列表傳進來 public DownloadProgress(List downloadFileList) { InitializeComponent(); this.downloadFileList = downloadFileList; } 在Form的Load事件中,啓動下載線程,開始下載。 private void OnFormLoad(object sender, EventArgs e) { evtDownload = new ManualResetEvent(true); evtDownload.Reset(); Thread t = new Thread(new ThreadStart(ProcDownload)); t.Name = "download"; t.Start(); } 下載線程沒什麼特殊的,使用了WebClient的異步下載文件函數DownloadFileAsync,並且註冊了兩個事件,分別負責下載進度顯示和下載完成後的處理: clientDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnDownloadProgressChanged); clientDownload.DownloadFileCompleted += new AsyncCompletedEventHandler(OnDownloadFileCompleted); 大家看一下就明白了。 private void ProcDownload() { evtPerDonwload = new ManualResetEvent(false); foreach (DownloadFileInfo file in this.downloadFileList) { total += file.Size; } while (!evtDownload.WaitOne(0, false)) { if (this.downloadFileList.Count == 0) break; DownloadFileInfo file = this.downloadFileList[0]; //Debug.WriteLine(String.Format("Start Download:{0}", file.FileName)); this.ShowCurrentDownloadFileName(file.FileName); //下載 clientDownload = new WebClient(); clientDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnDownloadProgressChanged); clientDownload.DownloadFileCompleted += new AsyncCompletedEventHandler(OnDownloadFileCompleted); evtPerDonwload.Reset(); clientDownload.DownloadFileAsync(new Uri(file.DownloadUrl), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName + ".tmp"), file); //等待下載完成 evtPerDonwload.WaitOne(); clientDownload.Dispose(); clientDownload = null; //移除已下載的文件 this.downloadFileList.Remove(file); } //Debug.WriteLine("All Downloaded"); if (this.downloadFileList.Count == 0) Exit(true); else Exit(false); evtDownload.Set(); } 最後,在OnDownloadFileCompleted函數中進行最後的處理。包括備份原文件,替換現有文件等。 void OnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { DownloadFileInfo file = e.UserState as DownloadFileInfo; nDownloadedTotal += file.Size; this.SetProcessBar(0, (int)(nDownloadedTotal * 100 / total)); //Debug.WriteLine(String.Format("Finish Download:{0}", file.FileName)); //替換現有文件 string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName); if (File.Exists(filePath)) { if (File.Exists(filePath + ".old")) File.Delete(filePath + ".old"); File.Move(filePath, filePath + ".old"); } File.Move(filePath + ".tmp", filePath); //繼續下載其它文件 evtPerDonwload.Set(); } 其它的函數只是一些顯示進度條和下載信息的,這裏就不再詳細介紹了。大家可以下載源碼看一下。 在WinForm應用程序中實現自動升級 Posted on 2007-06-14 11:48 伊颺 閱讀(8425) 評論(54) 編輯 收藏 網摘 所屬分類: 備忘錄 這是本人第一次寫比較複雜的文章,表達不清之處,請各位見諒。好,閒話少說,入正題。 最近單位開發一個項目,其中需要用到自動升級功能。因爲自動升級是一個比較常用的功能,可能會在很多程序中用到,於是,我就想寫一個自動升級的組件,在應用程序中,只需要引用這個自動升級組件,並添加少量代碼,即可實現自動升級功能。因爲我們的程序中可能包含多個exe或者dll文件,所以要支持多文件的更新。 首先,要確定程序應該去哪裏下載需要升級的文件。我選擇了到指定的網站上去下載,這樣比較簡單,也通用一些。在這個網站上,需要放置一個當前描述最新文件列表的文件,我們估且叫它服務器配置文件。這個文件保存了當前最新文件的版本號(lastver),大小(size),下載地址(url),本地文件的保存路徑(path),還有當更新了這個文件後,程序是否需要重新啓動(needRestart)。這個文件大致如下: updateservice.xml

同時,客戶端也保存了一個需要升級的本地文件的列表,形式和服務器配置文件差不多,我們叫它本地配置文件。其中,節點表示是否啓用自動升級功能,表示服務器配置文件的地址。 update.config

true http://update.iyond.com/updateservice.xml 使用自動各級組件的程序在啓動時,會去檢查這個配置文件。如果發現有配置文件中的文件版本和本地配置文件中描述的文件版本不一致,則提示用戶下載。同時,如果本地配置文件中某些文件在服務器配置文件的文件列表中不存在,則說明這個文件已經不需要了,需要刪除。最後,當升級完成後,會更新本地配置文件。 我們先來看一下如何使用這個組件。在程序的Program.cs的Main函數中: [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); AutoUpdater au = new AutoUpdater(); try { au.Update(); } catch (WebException exp) { MessageBox.Show(String.Format("無法找到指定資源/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (XmlException exp) { MessageBox.Show(String.Format("下載的升級文件有錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (NotSupportedException exp) { MessageBox.Show(String.Format("升級地址配置錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (ArgumentException exp) { MessageBox.Show(String.Format("下載的升級文件有錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (Exception exp) { MessageBox.Show(String.Format("升級過程中發生錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error); } Application.Run(new MainUI()); } 如上所示,只需要簡單的幾行代碼,就可以實現自動升級功能了。 軟件運行截圖: 下面,我們來詳細說一下這個自動升級組件的實現。先看一下類圖: AutoUpdater:自動升級的管理類,負責整體的自動升級功能的實現。 Config:配置類,負責管理本地配置文件。 DownloadConfirm:一個對話框,向用戶顯示需要升級的文件的列表,並允許用戶選擇是否馬上升級。 DownloadFileInfo:要下載的文件的信息 DownloadProgress:一個對話框,顯示下載進度。 DownloadProgress.ExitCallBack, DownloadProgress.SetProcessBarCallBack, DownloadProgress.ShowCurrentDownloadFileNameCallBack:由於.NET2.0不允許在一個線程中訪問另一個線程的對象,所以需要通過委託來實現。 LocalFile:表示本地配置文件中的一個文件 RemoteFile:表示服務器配置文件中的一個文件。 UpdateFileList:一個集合,從List繼承 我們先整體看一下AutoUpdater.cs: AutoUpdater.cs public class AutoUpdater { const string FILENAME = "update.config"; private Config config = null; private bool bNeedRestart = false; public AutoUpdater() { config = Config.LoadConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME)); } /**//// /// 檢查新版本 /// /// 無法找到指定資源 /// 升級地址配置錯誤 /// 下載的升級文件有錯誤 /// 下載的升級文件有錯誤 /// 未知錯誤 /// public void Update() { if (!config.Enabled) return; /**//* * 請求Web服務器,得到當前最新版本的文件列表,格式同本地的FileList.xml。 * 與本地的FileList.xml比較,找到不同版本的文件 * 生成一個更新文件列表,開始DownloadProgress * * * * path爲相對於應用程序根目錄的相對目錄位置,包括文件名 */ WebClient client = new WebClient(); string strXml = client.DownloadString(config.ServerUrl); Dictionary listRemotFile = ParseRemoteXml(strXml); List downloadList = new List(); //某些文件不再需要了,刪除 List preDeleteFile = new List(); foreach (LocalFile file in config.UpdateFileList) { if (listRemotFile.ContainsKey(file.Path)) { RemoteFile rf = listRemotFile[file.Path]; if (rf.LastVer != file.LastVer) { downloadList.Add(new DownloadFileInfo(rf.Url, file.Path, rf.LastVer, rf.Size)); file.LastVer = rf.LastVer; file.Size = rf.Size; if (rf.NeedRestart) bNeedRestart = true; } listRemotFile.Remove(file.Path); } else { preDeleteFile.Add(file); } } foreach (RemoteFile file in listRemotFile.Values) { downloadList.Add(new DownloadFileInfo(file.Url, file.Path, file.LastVer, file.Size)); config.UpdateFileList.Add(new LocalFile(file.Path, file.LastVer, file.Size)); if (file.NeedRestart) bNeedRestart = true; } if (downloadList.Count > 0) { DownloadConfirm dc = new DownloadConfirm(downloadList); if (this.OnShow != null) this.OnShow(); if (DialogResult.OK == dc.ShowDialog()) { foreach (LocalFile file in preDeleteFile) { string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.Path); if (File.Exists(filePath)) File.Delete(filePath); config.UpdateFileList.Remove(file); } StartDownload(downloadList); } } } private void StartDownload(List downloadList) { DownloadProgress dp = new DownloadProgress(downloadList); if (dp.ShowDialog() == DialogResult.OK) { //更新成功 config.SaveConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME)); if (bNeedRestart) { MessageBox.Show("程序需要重新啓動才能應用更新,請點擊確定重新啓動程序。", "自動更新", MessageBoxButtons.OK, MessageBoxIcon.Information); Process.Start(Application.ExecutablePath); Environment.Exit(0); } } } private Dictionary ParseRemoteXml(string xml) { XmlDocument document = new XmlDocument(); document.LoadXml(xml); Dictionary list = new Dictionary(); foreach (XmlNode node in document.DocumentElement.ChildNodes) { list.Add(node.Attributes["path"].Value, new RemoteFile(node)); } return list; } public event ShowHandler OnShow; } 在構造函數中,我們先要加載配置文件: public AutoUpdater() { config = Config.LoadConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME)); } 最主要的就是Update()這個函數了。當程序調用au.Update時,首先檢查當前是否開戶了自動更新: if (!config.Enabled) return; 如果啓用了自動更新,就需要去下載服務器配置文件了: WebClient client = new WebClient(); string strXml = client.DownloadString(config.ServerUrl); 然後,解析服務器配置文件到一個Dictionary中: Dictionary listRemotFile = ParseRemoteXml(strXml); 接下來比較服務器配置文件和本地配置文件,找出需要下載的文件和本地需要刪除的文件: List downloadList = new List(); //某些文件不再需要了,刪除 List preDeleteFile = new List(); foreach (LocalFile file in config.UpdateFileList) { if (listRemotFile.ContainsKey(file.Path)) { RemoteFile rf = listRemotFile[file.Path]; if (rf.LastVer != file.LastVer) { downloadList.Add(new DownloadFileInfo(rf.Url, file.Path, rf.LastVer, rf.Size)); file.LastVer = rf.LastVer; file.Size = rf.Size; if (rf.NeedRestart) bNeedRestart = true; } listRemotFile.Remove(file.Path); } else { preDeleteFile.Add(file); } } foreach (RemoteFile file in listRemotFile.Values) { downloadList.Add(new DownloadFileInfo(file.Url, file.Path, file.LastVer, file.Size)); config.UpdateFileList.Add(new LocalFile(file.Path, file.LastVer, file.Size)); if (file.NeedRestart) bNeedRestart = true; } 如果發現有需要下載的文件,則向用戶顯示這些文件,並提示其是否馬上更新。如果用戶選擇了馬上更新,則先刪除本地不再需要的文件,然後開始下載更新文件。 if (downloadList.Count > 0) { DownloadConfirm dc = new DownloadConfirm(downloadList); if (this.OnShow != null) this.OnShow(); if (DialogResult.OK == dc.ShowDialog()) { foreach (LocalFile file in preDeleteFile) { string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.Path); if (File.Exists(filePath)) File.Delete(filePath); config.UpdateFileList.Remove(file); } StartDownload(downloadList); } } 我們再來看一下StartDownload函數 private void StartDownload(List downloadList) { DownloadProgress dp = new DownloadProgress(downloadList); if (dp.ShowDialog() == DialogResult.OK) { //更新成功 config.SaveConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME)); if (bNeedRestart) { MessageBox.Show("程序需要重新啓動才能應用更新,請點擊確定重新啓動程序。", "自動更新", MessageBoxButtons.OK, MessageBoxIcon.Information); Process.Start(Application.ExecutablePath); Environment.Exit(0); } } } 在這個函數中,先調用DownloadProgress下載所有需要下載的文件,然後更新本地配置文件,最後,如果發現某些更新文件需要重新啓動應用程序的話,會提示用戶重新啓動程序。 至此,AutoUpdater這個類的使命就完成了,其實,整個的升級過程也就完成了。(廢話)。 最後,我們來看一下這個組件是如何下載更新文件的 DownloadProgress.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Threading; using System.Net; using System.IO; using System.Diagnostics; namespace Iyond.Utility { public partial class DownloadProgress : Form { private bool isFinished = false; private List downloadFileList = null; private ManualResetEvent evtDownload = null; private ManualResetEvent evtPerDonwload = null; private WebClient clientDownload = null; public DownloadProgress(List downloadFileList) { InitializeComponent(); this.downloadFileList = downloadFileList; } private void OnFormClosing(object sender, FormClosingEventArgs e) { if (!isFinished && DialogResult.No == MessageBox.Show("當前正在更新,是否取消?", "自動升級", MessageBoxButtons.YesNo, MessageBoxIcon.Question)) { e.Cancel = true; return; } else { if (clientDownload != null) clientDownload.CancelAsync(); evtDownload.Set(); evtPerDonwload.Set(); } } private void OnFormLoad(object sender, EventArgs e) { evtDownload = new ManualResetEvent(true); evtDownload.Reset(); Thread t = new Thread(new ThreadStart(ProcDownload)); t.Name = "download"; t.Start(); } long total = 0; long nDownloadedTotal = 0; private void ProcDownload() { evtPerDonwload = new ManualResetEvent(false); foreach (DownloadFileInfo file in this.downloadFileList) { total += file.Size; } while (!evtDownload.WaitOne(0, false)) { if (this.downloadFileList.Count == 0) break; DownloadFileInfo file = this.downloadFileList[0]; //Debug.WriteLine(String.Format("Start Download:{0}", file.FileName)); this.ShowCurrentDownloadFileName(file.FileName); //下載 clientDownload = new WebClient(); clientDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnDownloadProgressChanged); clientDownload.DownloadFileCompleted += new AsyncCompletedEventHandler(OnDownloadFileCompleted); evtPerDonwload.Reset(); clientDownload.DownloadFileAsync(new Uri(file.DownloadUrl), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName + ".tmp"), file); //等待下載完成 evtPerDonwload.WaitOne(); clientDownload.Dispose(); clientDownload = null; //移除已下載的文件 this.downloadFileList.Remove(file); } //Debug.WriteLine("All Downloaded"); if (this.downloadFileList.Count == 0) Exit(true); else Exit(false); evtDownload.Set(); } void OnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { DownloadFileInfo file = e.UserState as DownloadFileInfo; nDownloadedTotal += file.Size; this.SetProcessBar(0, (int)(nDownloadedTotal * 100 / total)); //Debug.WriteLine(String.Format("Finish Download:{0}", file.FileName)); //替換現有文件 string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName); if (File.Exists(filePath)) { if (File.Exists(filePath + ".old")) File.Delete(filePath + ".old"); File.Move(filePath, filePath + ".old"); } File.Move(filePath + ".tmp", filePath); //繼續下載其它文件 evtPerDonwload.Set(); } void OnDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { this.SetProcessBar(e.ProgressPercentage, (int)((nDownloadedTotal + e.BytesReceived) * 100 / total)); } delegate void ShowCurrentDownloadFileNameCallBack(string name); private void ShowCurrentDownloadFileName(string name) { if (this.labelCurrentItem.InvokeRequired) { ShowCurrentDownloadFileNameCallBack cb = new ShowCurrentDownloadFileNameCallBack(ShowCurrentDownloadFileName); this.Invoke(cb, new object[] { name }); } else { this.labelCurrentItem.Text = name; } } delegate void SetProcessBarCallBack(int current, int total); private void SetProcessBar(int current, int total) { if (this.progressBarCurrent.InvokeRequired) { SetProcessBarCallBack cb = new SetProcessBarCallBack(SetProcessBar); this.Invoke(cb, new object[] { current, total }); } else { this.progressBarCurrent.Value = current; this.progressBarTotal.Value = total; } } delegate void ExitCallBack(bool success); private void Exit(bool success) { if (this.InvokeRequired) { ExitCallBack cb = new ExitCallBack(Exit); this.Invoke(cb, new object[] { success }); } else { this.isFinished = success; this.DialogResult = success ? DialogResult.OK : DialogResult.Cancel; this.Close(); } } private void OnCancel(object sender, EventArgs e) { evtDownload.Set(); evtPerDonwload.Set(); } } } 在構造函數中,將要下載的文件列表傳進來 public DownloadProgress(List downloadFileList) { InitializeComponent(); this.downloadFileList = downloadFileList; } 在Form的Load事件中,啓動下載線程,開始下載。 private void OnFormLoad(object sender, EventArgs e) { evtDownload = new ManualResetEvent(true); evtDownload.Reset(); Thread t = new Thread(new ThreadStart(ProcDownload)); t.Name = "download"; t.Start(); } 下載線程沒什麼特殊的,使用了WebClient的異步下載文件函數DownloadFileAsync,並且註冊了兩個事件,分別負責下載進度顯示和下載完成後的處理: clientDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnDownloadProgressChanged); clientDownload.DownloadFileCompleted += new AsyncCompletedEventHandler(OnDownloadFileCompleted); 大家看一下就明白了。 private void ProcDownload() { evtPerDonwload = new ManualResetEvent(false); foreach (DownloadFileInfo file in this.downloadFileList) { total += file.Size; } while (!evtDownload.WaitOne(0, false)) { if (this.downloadFileList.Count == 0) break; DownloadFileInfo file = this.downloadFileList[0]; //Debug.WriteLine(String.Format("Start Download:{0}", file.FileName)); this.ShowCurrentDownloadFileName(file.FileName); //下載 clientDownload = new WebClient(); clientDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnDownloadProgressChanged); clientDownload.DownloadFileCompleted += new AsyncCompletedEventHandler(OnDownloadFileCompleted); evtPerDonwload.Reset(); clientDownload.DownloadFileAsync(new Uri(file.DownloadUrl), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName + ".tmp"), file); //等待下載完成 evtPerDonwload.WaitOne(); clientDownload.Dispose(); clientDownload = null; //移除已下載的文件 this.downloadFileList.Remove(file); } //Debug.WriteLine("All Downloaded"); if (this.downloadFileList.Count == 0) Exit(true); else Exit(false); evtDownload.Set(); } 最後,在OnDownloadFileCompleted函數中進行最後的處理。包括備份原文件,替換現有文件等。 void OnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { DownloadFileInfo file = e.UserState as DownloadFileInfo; nDownloadedTotal += file.Size; this.SetProcessBar(0, (int)(nDownloadedTotal * 100 / total)); //Debug.WriteLine(String.Format("Finish Download:{0}", file.FileName)); //替換現有文件 string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName); if (File.Exists(filePath)) { if (File.Exists(filePath + ".old")) File.Delete(filePath + ".old"); File.Move(filePath, filePath + ".old"); } File.Move(filePath + ".tmp", filePath); //繼續下載其它文件 evtPerDonwload.Set(); }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章