原文出自:http://blog.csdn.net/metaphysis/article/details/18866631
如何制作自動更新程序?
[版權所有 邱秋 2014 metaphysis@yeah.net, 轉載請注明出處]
最近為單位寫了一個C/S結構的軟件,這個軟件是工作在單位的局域網內的。為了減輕為程序進行升級的工作量,需要解決程序自動更新的問題。那么如何做一個自動更新程序呢?
想了一下,更新程序需要解決以下問題:
(A)它需要知道哪些是需要更新的文件,哪些是不需要的文件;
(B)它需要知道從哪里下載更新文件;
(C) 它需要將更新的文件下載下來,并將舊的文件替換掉,將不再需要的文件刪除掉;
(D)它需要能夠在更新完畢后自動重新啟動程序以便用戶繼續使用;
問題(A)可以通過版本控制的方法來解決。具體方法是為程序所使用的文件都設定一個版本號,所有文件的版本號都記錄在一個 XML 文件中,當升級時,檢查最新程序的版本控制文件和當前的版本控制文件,當版本號較大時,則表示該文件需要更新。最新的版本控制文件可以放在一個匿名 FTP 上以便程序下載下來和本地的版本控制文件進行比對。如果一個文件不再需要了,則將該文件的版本信息從最新的版本控制文件中刪除,通過對比控制文件,就知道該文件不再需要了,可以將之刪除。由于我寫的程序除主程序外,其他組件都不會發生太多改動,所以我使用了如下的方式來表示一個文件的版本信息:
- <?xml?version="1.0"?encoding="utf-8"?>??
- <AutoUpdater>??
- ??<Updater>??
- ????<UpdateUrl>ftp://192.168.1.24/update/</UpdateUrl>??
- ????<MainVersion>1.1.102.0</MainVersion>??
- ????<LastUpdateTime>2014-01-27</LastUpdateTime>??
- ????<UpdateDescription>自動更新程序</UpdateDescription>??
- ??</Updater>??
- ??<UpdateFileList>??
- ????<UpdateFile?Version="2.2.5.0"?Name="AForge.dll"?/>??
- ????<UpdateFile?Version="2.2.5.0"?Name="AForge.Video.DirectShow.dll"?/>??
- ????<UpdateFile?Version="2.2.5.0"?Name="AForge.Video.dll"?/>??
- ????<UpdateFile?Version="1.0.100.0"?Name="USBCleaner.exe"?/>??
- ????<UpdateFile?Version="1.0.100.0"?Name="USBViewer.exe"?/>??
- ??</UpdateFileList>??
- </AutoUpdater>??
UpdateUrl 告訴程序要從什么地方下載最新的版本控制文件和更新文件,這里我使用了 FTP 的方式,這樣簡單一些,我將版本控制文件和最新的程序文件都放在了 ftp://192.168.1.24/update/ 下。MainVersion 表示程序的版本,用來確定是否需要進行升級。LastUpdateTime 表示程序最后的更新時間。UpdateDescription 表示更新程序的描述。UpdateFile 則表示程序中的每一個文件條目,Version 表示其版本,Name 表示相對于程序根目錄的文件路徑名,如果文件是在根目錄下面,則直接是文件名,如果是在子目錄下,則在前面加上相應的子目錄。
有了這個版本控制文件,問題(B)也解決了,因為從指定的地址下載即可。
問題(C)可以通過比對版本控制文件,確定需要下載的文件和不再需要的文件。然后通過 WebClient 類來下載需要的文件。
問題(D)可以這樣解決,主程序先檢查是否有升級,如果有升級,則將舊的更新程序再復制一份,啟動復制的更新程序,并啟動它來下載更新文件,這樣的話,就可以解決更新更新程序本身的問題,因為將新的更新程序下載來后,可以直接覆蓋掉原始的更新程序而不會產生文件正在使用無法更新的問題,因為運行的是舊的更新程序的副本,在全部更新完畢后,主程序中可以加一段代碼檢測是否有更新副本產生,只要有就將它刪除即可。
想清楚了這些問題,就是具體代碼實現了,以下把版本文件解析的代碼和更新下載文件的代碼貼出來,整個更新模塊也提供了下載,供有興趣的朋友參考使用。下載鏈接:http://download.csdn.net/detail/metaphysis/6891593。
- XmlVersionConfigFile.vb??
- ??
- Imports?System.Xml??
- Imports?System.Xml.Linq??
- ??
- Public?Class?XmlVersionConfigFile??
- ??
- ????Public?Property?UpdateUrl?As?String?=?String.Empty??
- ????Public?Property?MainVersion?As?Version?=?Version.Parse("0.0.0.0")??
- ????Public?Property?LastUpdateTime?As?Date?=?DateTimePicker.MinimumDateTime??
- ????Public?Property?UpdateDescription?As?String?=?String.Empty??
- ????Public?Property?UpdateFileList?As?Dictionary(Of?String,?Version)?=?Nothing??
- ??
- ????Public?Sub?New(ByVal?fileContent?As?String)??
- ????????ParseXmlVersionFile(fileContent)??
- ????End?Sub??
- ??
- ????Private?Function?ParseXmlVersionFile(ByVal?fileContent?As?String)?As?Boolean??
- ????????Dim?xdoc?As?XDocument?=?Nothing??
- ????????Try??
- ????????????xdoc?=?XDocument.Parse(fileContent)??
- ????????Catch?ex?As?Exception??
- ????????????Return?False??
- ????????End?Try??
- ??
- ????????Me.UpdateUrl?=?xdoc.Element("AutoUpdater").Element("Updater").Element("UpdateUrl").Value??
- ????????Me.MainVersion?=?Version.Parse(xdoc.Element("AutoUpdater").Element("Updater").Element("MainVersion").Value)??
- ????????Date.TryParse(xdoc.Element("AutoUpdater").Element("Updater").Element("LastUpdateTime").Value,?Me.LastUpdateTime)??
- ????????Me.UpdateDescription?=?xdoc.Element("AutoUpdater").Element("Updater").Element("UpdateDescription").Value??
- ??
- ????????Me.UpdateFileList?=?New?Dictionary(Of?String,?Version)??
- ????????Dim?query?=?From?UpdateFile?In?xdoc.Descendants("UpdateFile")?Select?UpdateFile??
- ????????For?Each?fileInfo?As?XElement?In?query??
- ????????????Me.UpdateFileList.Add(fileInfo.Attribute("Name").Value.ToLower,?Version.Parse(fileInfo.Attribute("Version").Value))??
- ????????Next??
- ??
- ????????Return?True??
- ????End?Function??
- End?Class??
- UpdatingForm.vb??
- ??
- Imports?System.IO??
- ??
- Public?Class?UpdatingForm??
- ??
- ????Public?Property?LocalVersionConfig?As?XmlVersionConfigFile?=?Nothing??
- ????Public?Property?ServerVersionConfig?As?XmlVersionConfigFile?=?Nothing??
- ??
- ????Private?WithEvents?webClient?As?New?System.Net.WebClient??
- ??
- ????Private?_downloadIndex?As?Integer??
- ????Private?_localConfigFileName?As?String?=?"version.xml"??
- ????Private?_localXmlFilePath?As?String?=?Path.Combine(Application.StartupPath,?_localConfigFileName)??
- ????Private?_updateUrl?As?String?=?String.Empty??
- ????Private?_deleteFileList?As?New?List(Of?String)??
- ??
- ????Private?Sub?webClient_DownloadFileCompleted(ByVal?sender?As?Object,?ByVal?e?As?System.ComponentModel.AsyncCompletedEventArgs)?Handles?webClient.DownloadFileCompleted??
- ????????Me.lvwFile.Items(_downloadIndex).ImageIndex?=?2??
- ??
- ????????lblSinglePercent.Text?=?"0%"??
- ????????prbSingle.Value?=?0??
- ??
- ????????DownloadNextFile()??
- ????End?Sub??
- ??
- ????Private?Sub?webClient_DownloadProgressChanged(ByVal?sender?As?System.Object,?ByVal?e?As?System.Net.DownloadProgressChangedEventArgs)?Handles?webClient.DownloadProgressChanged??
- ????????Dim?currentPercent?As?String?=?e.ProgressPercentage?&?"%"??
- ????????If?currentPercent?<>?Me.lvwFile.Items(_downloadIndex).SubItems(3).Text?Then??
- ????????????Me.lvwFile.Items(_downloadIndex).SubItems(3).Text?=?currentPercent??
- ????????????prbSingle.Value?=?e.ProgressPercentage??
- ????????????lblSinglePercent.Text?=?currentPercent??
- ????????????prbAll.Value?=?Int((_downloadIndex?+?1)?/?Me.lvwFile.Items.Count?*?100)??
- ????????????lblAllPercent.Text?=?prbAll.Value?&?"%"??
- ????????End?If??
- ????End?Sub??
- ??
- ????Private?Sub?btnQuit_Click(ByVal?sender?As?System.Object,?ByVal?e?As?System.EventArgs)?Handles?btnQuit.Click??
- ????????Me.Close()??
- ????End?Sub??
- ??
- ????Private?Sub?DownloadNextFile()??
- ????????If?_downloadIndex?<?(lvwFile.Items.Count?-?1)?Then??
- ??
- ????????????_downloadIndex?+=?1??
- ??
- ????????????lvwFile.Items(_downloadIndex).ImageIndex?=?1??
- ??
- ????????????Try??
- ????????????????Dim?destPath?As?String?=?IO.Path.Combine(Application.StartupPath,?lvwFile.Items(_downloadIndex).SubItems(1).Text)??
- ????????????????File.Delete(destPath)??
- ????????????????webClient.DownloadFileAsync(New?Uri(_updateUrl?&?lvwFile.Items(_downloadIndex).SubItems(1).Text),?destPath)??
- ????????????Catch?ex?As?Exception??
- ????????????????Me.lvwFile.Items(_downloadIndex).ImageIndex?=?3??
- ????????????????MsgBox("下載文件發生錯誤,更新失敗。錯誤原因:?"?&?ex.Message,?MsgBoxStyle.Critical,?"錯誤")??
- ????????????????Me.Close()??
- ????????????End?Try??
- ????????Else??
- ????????????UpdateFileCompleted()??
- ????????End?If??
- ????End?Sub??
- ??
- ????Private?Sub?UpdateFileCompleted()??
- ????????'?更新顯示信息。??
- ????????prbSingle.Value?=?prbSingle.Maximum??
- ????????lblSinglePercent.Text?=?"100%"??
- ????????lblAllPercent.Text?=?"100%"??
- ??
- ????????'?刪除不需要的文件。??
- ????????For?Each?f?As?String?In?_deleteFileList??
- ????????????Try??
- ????????????????File.Delete(Path.Combine(Application.StartupPath,?f))??
- ????????????Catch?ex?As?Exception??
- ????????????????'??
- ????????????End?Try??
- ????????Next??
- ??
- ????????Me.btnQuit.Enabled?=?True??
- ????????Process.Start(IO.Path.Combine(Application.StartupPath,?"szpt.exe"))??
- ????????Me.Close()??
- ????End?Sub??
- ??
- ????Private?Sub?LoadUpdateFile()??
- ????????_updateUrl?=?Me.ServerVersionConfig.UpdateUrl??
- ??
- ????????'?查找客戶端需要更新的文件和需要刪除的文件。??
- ????????For?Each?p?As?KeyValuePair(Of?String,?Version)?In?Me.LocalVersionConfig.UpdateFileList??
- ????????????If?Me.ServerVersionConfig.UpdateFileList.ContainsKey(p.Key)?Then??
- ????????????????If?Me.ServerVersionConfig.UpdateFileList(p.Key)?>?Me.LocalVersionConfig.UpdateFileList(p.Key)?Then??
- ????????????????????Dim?item?As?ListViewItem?=?Me.lvwFile.Items.Add(String.Empty,?0)??
- ????????????????????item.SubItems.Add(p.Key)??
- ????????????????????item.SubItems.Add(Me.ServerVersionConfig.UpdateFileList(p.Key).ToString)??
- ????????????????????item.SubItems.Add(String.Empty)??
- ????????????????End?If??
- ????????????Else??
- ????????????????_deleteFileList.Add(p.Key)??
- ????????????End?If??
- ????????Next??
- ??
- ????????'?查找服務器端新增需要下載的文件。??
- ????????For?Each?p?As?KeyValuePair(Of?String,?Version)?In?Me.ServerVersionConfig.UpdateFileList??
- ????????????If?Me.LocalVersionConfig.UpdateFileList.ContainsKey(p.Key)?=?False?Then??
- ????????????????Dim?item?As?ListViewItem?=?Me.lvwFile.Items.Add(String.Empty,?0)??
- ????????????????item.SubItems.Add(p.Key)??
- ????????????????item.SubItems.Add(p.Value.ToString)??
- ????????????????item.SubItems.Add(String.Empty)??
- ????????????End?If??
- ????????Next??
- ??
- ????????'?版本控制文件為必須下載文件。??
- ????????Dim?itemVersion?As?ListViewItem?=?Me.lvwFile.Items.Add(String.Empty,?0)??
- ????????itemVersion.SubItems.Add("version.xml")??
- ????????itemVersion.SubItems.Add(Me.ServerVersionConfig.MainVersion.ToString)??
- ????????itemVersion.SubItems.Add(String.Empty)??
- ??
- ????????'?設置當前下載的文件序數。??
- ????????_downloadIndex?=?-1??
- ????End?Sub??
- ??
- ????Private?Sub?UpdatingForm_Load(ByVal?sender?As?System.Object,?ByVal?e?As?System.EventArgs)?Handles?MyBase.Load??
- ????????LoadUpdateFile()??
- ????????DownloadNextFile()??
- ????End?Sub??
- ??
- End?Class??