c# 智慧升級程式程式碼
最近單位開發一個專案,其中需要用到自動升級功能。因為自動升級是一個比較常用的功能,可能會在很多程式中用到,於是,我就想寫一個自動升級的元件,在應用程式中,只需要引用這個自動升級元件,並新增少量程式碼,即可實現自動升級功能。因為我們的程式中可能包含多個exe或者dll檔案,所以要支援多檔案的更新。
首先,要確定程式應該去哪裡下載需要升級的檔案。我選擇了到指定的網站上去下載,這樣比較簡單,也通用一些。在這個網站上,需要放置一個當前描述最新檔案列表的檔案,我們估且叫它伺服器配置檔案。這個檔案儲存了當前最新檔案的版本號(lastver),大小(size),下載地址(url),本地檔案的儲存路徑(path),還有當更新了這個檔案後,程式是否需要重新啟動(needRestart)。這個檔案大致如下:
updateservice.xml
同時,客戶端也儲存了一個需要升級的本地檔案的列表,形式和伺服器配置檔案差不多,我們叫它本地配置檔案。其中,
update.config
使用自動各級元件的程式在啟動時,會去檢查這個配置檔案。如果發現有配置檔案中的檔案版本和本地配置檔案中描述的檔案版本不一致,則提示使用者下載。同時,如果本地配置檔案中某些檔案在伺服器配置檔案的檔案列表中不存在,則說明這個檔案已經不需要了,需要刪除。最後,當升級完成後,會更新本地配置檔案。
我們先來看一下如何使用這個元件。
在程式的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:
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
List
//某些檔案不再需要了,刪除
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
{
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
{
XmlDocument document = new XmlDocument();
document.LoadXml(xml);
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
接下來比較伺服器配置檔案和本地配置檔案,找出需要下載的檔案和本地需要刪除的檔案:
List
//某些檔案不再需要了,刪除
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
{
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這個類的使命就完成了,其實,整個的升級過程也就完成了。(廢話)。
最後,我們來看一下這個元件是如何下載更新檔案的
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
private ManualResetEvent evtDownload = null;
private ManualResetEvent evtPerDonwload = null;
private WebClient clientDownload = null;
public DownloadProgress(List
{
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
{
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();
}
其它的函式只是一些顯示進度條和下載資訊的,這裡就不再詳細介紹了。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-608971/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 網站升級中 HTML 程式碼網站HTML
- 全站HTTPS升級系列(四)專案程式碼升級改造HTTP
- 整塊程式碼自動生成、智慧括號匹配……CodeGeeX程式設計提效,功能再升級!程式設計
- TF1 程式碼升級 TF2 思路TF2
- 百度分享程式碼已升級到2.0
- 我的PB程式資料庫升級程式資料庫
- 企業如何正確使用低程式碼轉型升級
- C#入門程式碼C#
- 升級Win11專業工作站版金鑰,無需程式碼一秒升級
- 微信小遊戲程式碼包侵權解決方案升級版遊戲
- 程式的機器級程式碼表示
- 升級redhat5 sas驅動程式Redhat
- 程式碼修改分級
- Vue 專案升級到 webpack4.x 小紀【附程式碼】VueWeb
- 老專案升級總結之程式碼相容性檢測
- C# 呼叫Python程式碼C#Python
- 低程式碼推進服裝產業數字化升級,智慧製造賦能企業柔性生產產業
- C#網路程式設計經典程式碼C#程式設計
- Discuz!X升級/轉換程式GETSHELL漏洞分析
- 自定義開發資料庫升級程式資料庫
- 我的風變程式設計“升級”之旅程式設計
- centos中從源程式升級python方法CentOSPython
- MSIL入門(一)C#程式碼與IL程式碼對比C#
- Deco 智慧程式碼技術揭祕:設計稿智慧生成程式碼
- 中小企業的福音來咯,低程式碼助力數字化升級!
- C# 利用.NET 升級助手將.NET Framework專案升級為.NET 6C#Framework
- windows10升級程式解除安裝怎麼操作 windows10升級程式怎樣解除安裝刪除Windows
- ssh升級指令碼指令碼
- “智雲”升級為“智慧教育” 有道多元化再加碼智慧教育賽道
- C#垃圾程式碼生成器C#
- 編寫更好的C#程式碼C#
- .NET(C#)程式碼效能優化C#優化
- 為了減少程式碼複雜度,我將if-else升級為面向狀態程式設計複雜度程式設計
- 程式設計師打怪升級六件事程式設計師
- 網站伺服器如何防止被攻擊?指令碼程式升級很重要!網站伺服器指令碼
- openrewrite/rewrite: 轉換升級Java或Spring程式碼的自動化工具JavaSpring
- 前端進階篇之如何編寫可維護可升級的程式碼前端
- 本地升級idea後,不能向github上提交程式碼問題處理IdeaGithub