非同期C#プログラムの更新

こんにちは、友達!



以前の記事( 1回2回 )で、プログラムの自動更新機能の実装について書いており、多くの欠点があったため、それを改善するとともに、コードをより「使いやすい」ものにすることを決めました。 行を短くしてフォーマットを最適化することで、非同期ファイルのダウンロードを改善し、更新ファイルの偽装(チェックサムのチェック)の可能性を実質的に排除し、いくつかの新しい開発を追加しました。 そこで、私は自分自身をリハビリするために別の試みをしています。



画像



私の仕事では、プログラムは実行可能ファイルと同じフォルダーにある次のファイルを使用します。



以前のバージョンのコードでは、各ファイルが個別にダウンロードされていたため、ダウンロードの待ち時間から多くの不便が生じていました。 また、チェックサムをチェックする機能もありませんでした。これは、使用の安全性にはあまり影響しませんでした。

それで、同じ話に関する3番目の記事を書くことにしたコードの何が変わったのでしょうか?



まず、サーバーにあるversion.xmlファイルが変更されました。



<?xml version="1.0" encoding="utf-8"?> <version> <myprogram checksumm="05b2b2eb79c4f11834b25095acc047f9">1.0.7.88</myprogram> <updater checksumm="aaef7c8a1f9437138acfc80fb2c4354b">1.0.0.7</updater> <restart checksumm="d3904a3fe5ff2ab3a0f246bdde293345">1.0.1.9</restart> <processesLibrary checksumm="2b999c9eb771374c87490f5dee5da9ec">1.0.1.10</processesLibrary> <languagePack checksumm="d5724f066cea211eb5f0efb6365ba0c9">1.0.0.4</languagePack> <Newtonsoft.Json checksumm="5619e5d4b93e544c7b9174c062f6c40b">6.0.1.17001</Newtonsoft.Json> <Ionic.Zip checksumm="6ded8fcbf5f1d9e422b327ca51625e24">1.9.1.8</Ionic.Zip> </version>
      
      







変更点



気付くと、 以前のバージョンと比較して、特定のファイルのMD5合計のみを含むchecksumm属性が追加されました。



不要なコードを使用する場合、backgroundWorkerクラスのコンポーネントが削除され、 Taskが優先され、次の行がクラス定義に追加されました。



 debug debug = new debug(); private string url = @"http://mysite/"; private ProgressBar downloadPercent = null;
      
      





デバッグクラスは、ログを改善するためにエラーをファイルに書き込みます。 しかし、この記事では彼については語りません。

url文字列パラメーターは、すべてのファイルを含むサイト上のフォルダーへのパスを設定します。 これに先立ち、各ファイルに指定されたパスが登録されましたが、無駄でした。

ProgressBarクラスのdownloadPercentコンポーネントは、メインプログラムファイルのダウンロード更新の割合を表示するために使用されます。



次に、 Check()更新プロセスを開始する機能が次のように削減されました。



 public void Check(bool launcher = false, ProgressBar report = null) { try { XmlDocument doc = new XmlDocument(); doc.Load(url + "version.xml"); if (!File.Exists("settings.xml")) { using (var client = new WebClient()) Task.Factory.StartNew(() => client.DownloadFile(new Uri(url + "settings.xml"), "settings.xml")).Wait(); } //     ,    if (File.Exists("settings.xml") && new FileInfo("settings.xml").Length == 0) { File.Delete("settings.xml"); } if (File.Exists("Ionic.Zip.dll") && new FileInfo("Ionic.Zip.dll").Length == 0) { File.Delete("Ionic.Zip.dll"); } if (File.Exists("restart.exe") && new FileInfo("restart.exe").Length == 0) { File.Delete("restart.exe"); } if (File.Exists("updater.exe") && new FileInfo("updater.exe").Length == 0) { File.Delete("updater.exe"); } if (File.Exists("Newtonsoft.Json.dll") && new FileInfo("Newtonsoft.Json.dll").Length == 0) { File.Delete("Newtonsoft.Json.dll"); } if (File.Exists("ProcessesLibrary.dll") && new FileInfo("ProcessesLibrary.dll").Length == 0) { File.Delete("ProcessesLibrary.dll"); } if (File.Exists("LanguagePack.dll") && new FileInfo("LanguagePack.dll").Length == 0) { File.Delete("LanguagePack.dll"); } if (File.Exists("launcher.update") && new FileInfo("launcher.update").Length == 0) { File.Delete("launcher.update"); } if (!launcher) { var task1 = Task.Factory.StartNew(() => DownloadFile("Ionic.Zip.dll", doc.GetElementsByTagName("Ionic.Zip")[0].InnerText, doc.GetElementsByTagName("Ionic.Zip")[0].Attributes["checksumm"].InnerText)); var task2 = Task.Factory.StartNew(() => DownloadFile("restart.exe", doc.GetElementsByTagName("restart")[0].InnerText, doc.GetElementsByTagName("restart")[0].Attributes["checksumm"].InnerText)); var task6 = Task.Factory.StartNew(() => DownloadFile("LanguagePack.dll", doc.GetElementsByTagName("languagePack")[0].InnerText, doc.GetElementsByTagName("languagePack")[0].Attributes["checksumm"].InnerText)); Task.WaitAll(task1, task2, task6); }
      
      





さて、すべての詳細について。

最初に、プログラム設定ファイル( settings.xml )が存在するかどうかを確認し、存在しない場合はダウンロードします

さらに(場合によっては)、ファイルの長さがゼロの場合は、それらも削除します。 壊れたファイルが必要な理由。 そうだろ?

その後、関数が初期化されたときにランチャーパラメーターが設定されたかどうかを確認します。 メインウィンドウのフォームを初期化するときに上記のリストから3つのファイルのみが必要なので、コードの実行シーケンスを決定し、ソリューションを最適化する必要があります。 ランチャーパラメーターがfalseの場合、メインファイル(Ionic.Zip.dll、LanguagePack.dll、restart.exe)をダウンロードし、メインプログラムコードを初期化します。



メインプログラムと補助ファイルの更新を確認するには、 InitializeComponent()関数を呼び出した後、 パブリックform1()ハンドラーのメインフォームのコードで 更新クラスに呼び出しを追加します。 はい、クラス。そのコードはすべて(便宜上)個別に配置されるためです。



 update_launcher update = new update_launcher(); update.Check(true, pbDownload);
      
      





呼び出しでupdate.Check(true、progressBar1); 最初のパラメーターとして、補助ファイルの更新とアプリケーションのメインファイルの更新がチェックされることを示します。 2番目として、メインファイルの負荷率を表示するprogressBarを指定します。



パラメーターlauncher = trueを指定したため、プログラムはCheck()関数から次のコードを実行します上記のコードの続き)。



  else { try { var task3 = Task.Factory.StartNew(() => DownloadFile("updater.exe", doc.GetElementsByTagName("updater")[0].InnerText, doc.GetElementsByTagName("updater")[0].Attributes["checksumm"].InnerText)); var task4 = Task.Factory.StartNew(() => DownloadFile("Newtonsoft.Json.dll", doc.GetElementsByTagName("Newtonsoft.Json")[0].InnerText, doc.GetElementsByTagName("Newtonsoft.Json")[0].Attributes["checksumm"].InnerText)); var task5 = Task.Factory.StartNew(() => DownloadFile("ProcessesLibrary.dll", doc.GetElementsByTagName("processesLibrary")[0].InnerText, doc.GetElementsByTagName("processesLibrary")[0].Attributes["checksumm"].InnerText)); Task.WaitAll(task3, task4, task5); if (File.Exists("launcher.update") && new Version(FileVersionInfo.GetVersionInfo("launcher.update").FileVersion) > new Version(Application.ProductVersion)) { Process.Start("updater.exe", "launcher.update \"" + Application.ProductName + ".exe\""); Process.GetCurrentProcess().CloseMainWindow(); } else if (new Version(Application.ProductVersion) < new Version(doc.GetElementsByTagName("version")[0].InnerText)) { if (report != null) { downloadPercent = report; downloadPercent.Value = 0; } Task.Factory.StartNew(() => DownloadFile("launcher.exe", doc.GetElementsByTagName("version")[0].InnerText, doc.GetElementsByTagName("version")[0].Attributes["checksumm"].InnerText, "launcher.update", true)).Wait(); } else if (File.Exists("launcher.update")) { File.Delete("launcher.update"); } } catch (Exception ex1) { debug.Save("public void Check(bool launcher = false)", "launcher.update", ex1.Message); } } } catch (Exception ex) { debug.Save("public void Check(bool launcher = false)", "", ex.Message); } }
      
      





ここには何がありますか。 System.Threading.Tasks使用して追加すること忘れないでください 、変数名( task3task4task5 )を割り当ててTaskオブジェクトを初期化します。

知らない人のために、Taskクラスはスレッドのラッパーであり、非同期操作を実行するため、開発者はスレッドの作成、開始、および破棄の方法を忘れる機会を与えられます。

一般に、この場合、 DownloadFile()関数をパラメーターとして設定します つまり、必要なパラメータを渡します:



 private void DownloadFile(string filename, string xmlVersion, string xmlChecksumm, string localFile = null, bool showStatus = false)
      
      





ここで:





関数Task.Factory.StartNew(); 任意のプロセスを非同期で実行できます。 すべてのファイルがいつダウンロードされたかを判断するために、 Task.WaitAll関数(task3、task4、task5)が使用されました。 、指定されたすべての要素でのコード実行の完了を待っています。

したがって、追加のファイルをダウンロードした後、メインファイルの更新を確認することができます。更新は既にダウンロードできるので、最初にローカル更新ファイルが存在する場合はその存在とバージョンを確認します。



 if (File.Exists("launcher.update") && new Version(FileVersionInfo.GetVersionInfo("launcher.update").FileVersion) > new Version(Application.ProductVersion))
      
      





なぜチェックサムをチェックする機能がないのか、少し後で説明しますが、今のところはこの機能に戻りましょう。

更新ファイル(「launcher.update」)が存在し、そのバージョンがより新しい場合、パラメーターにファイル名を渡すことで追加のupdater.exeユーティリティを実行します(上記のコードを参照)。



さらに、更新ファイルが欠落しているか、そのバージョンが最新でない場合、次の対応チェックを使用して、ソフトウェアバージョンをサイト上のバージョンと確認します。



 if (new Version(Application.ProductVersion) < new Version(doc.GetElementsByTagName("version")[0].InnerText))
      
      





より新しいバージョンが見つかった場合は、ダウンロードに進みます。 そして、これについては後で。

3番目の条件は、最初の2つが満たされていない場合に有効です。つまり、ファイルが存在し、古いバージョンがある場合、単純に削除します。



関数呼び出しdebug.Save(); エラーハンドラに関する情報をファイルに保存して、後で読むことができるようにします。 ソフトウェアの更新の場合、このコードは実際には重要ではありませんが、人々が「なぜcatch(Exception){}を持っているのか、それはcomme il fautではない」と尋ねないように配置されています。 ここにある。

どうぞ



ダウンロードする



プライベート関数DownloadFile()は、ファイルのダウンロードを担当します 上記の一連のパラメーターと、以下に示す猫のコードがあります。



 private void DownloadFile(string filename, string xmlVersion, string xmlChecksumm, string localFile = null, bool showStatus = false) { localFile = localFile != null ? localFile : filename; if (File.Exists(localFile) && new FileInfo(localFile).Length == 0) { File.Delete(localFile); } try { if ((File.Exists(localFile) && new Version(FileVersionInfo.GetVersionInfo(localFile).FileVersion) < new Version(xmlVersion)) || !File.Exists(localFile)) { using (var client = new WebClient()) { try { if (showStatus && downloadPercent != null) { client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged); } client.DownloadFileAsync(new Uri(url + filename), localFile); if (!Checksumm(localFile, xmlChecksumm) && File.Exists(localFile)) { File.Delete(localFile); } } catch (Exception ex) { debug.Save("private void DownloadFile(string filename, string xmlVersion, string xmlChecksumm)", "Filename: " + filename + Environment.NewLine + "Localname: " + (localFile != null ? localFile : "null") + Environment.NewLine + "URL: " + url, ex.Message); } } } } catch (Exception ex1) { debug.Save("private void DownloadFile(string filename, string xmlVersion, string xmlChecksumm)", "Filename: " + filename + Environment.NewLine + "Localname: " + (localFile != null ? localFile : "null") + Environment.NewLine + "URL: " + url, ex1.Message); } }
      
      





最初に、渡された値がlocalFileパラメーターでチェックされ、パラメーターがnullの場合、 ファイル名パラメーターの値がそれに割り当てられます。 その後、ファイルのサイズがチェックされ、ゼロの場合は削除されます。

次に、関数の重要な部分が始まります-ファイルの存在とそのバージョンの関連性を確認し、ファイルが見つからない場合や新しいバージョンが見つかった場合はダウンロードに進み、そうでない場合はそれに応じてスキップします。

ダウンロードする直前に、 showStatusパラメーターを確認します。このパラメーターは、ダウンロードステータスの表示を有効/無効にする必要があります。 ステータスが必要な場合の例を検討します。 そのため、 showStatusパラメーター nullではなく、 downloadPercentパラメーターが設定されている場合、 ProgressChanged()関数をWebClient()クラスのクライアントオブジェクトに接続します ダウンロード状況を追跡します。

次は、 DownloadFileAsync()ファイルを非同期でダウンロードするプロセスです。 ファイルをダウンロードしましたが、次は何ですか?

そして、ダウンロードされたファイルのチェックサムを、ローカルファイルの名前とサイトからmd5キャッシュを含む文字列が送信されるパラメーターのChecksumm()関数を介して、 version.xmlファイルのサイトの値で確認します。



 private bool Checksumm(string filename, string summ) { try { if (File.Exists(filename) && summ != null && new FileInfo(filename).Length > 0) using (FileStream fs = File.OpenRead(filename)) { System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); byte[] fileData = new byte[fs.Length]; fs.Read(fileData, 0, (int)fs.Length); byte[] checkSumm = md5.ComputeHash(fileData); return BitConverter.ToString(checkSumm) == summ.ToUpper() ? true : false; } else return false; } catch (Exception ex) { debug.Save("private bool Checksumm(string filename, string summ)", "Filename: " + filename, ex.Message); return false; } }
      
      





ローカルファイルのチェックサムがサイトの値と一致する場合、関数はtrueを返します 。それ以外の場合、ロジックのアクションによって返されます

しかし、 DownloadFile()はどうですか? チェックサムが正しい場合は関数を終了し、そうでない場合はファイルを削除します。



まだ示されていないものは何ですか? ええと...そうそう! ダウンロードステータス処理機能:



 Private void ProgressChanged(object sender, DownloadProgressChangedEventArgs e) { downloadPercent.Value = e.ProgressPercentage; }
      
      





そして、私はほとんど忘れていました、指定されたコードでは、メインプログラムの更新がlauncher.updateファイルにダウンロードされます。 更新プログラムの適用は、次回プログラムが起動されたときに自動的に実行されます。つまり、ユーザーは通知を受け取らないため、プログラムの「親しみやすさ」が向上します。



おわりに



ここでは、実際には、更新コード全体が個別のクラスに配置されているため、あらゆる種類のプロジェクトで簡単に使用でき、更新のソースとファイルの数と名前を示します。

PS: デバッグクラスでは、保存時に3つのパラメーターが設定されます-それらによってコード内の適切な場所を検索する方が簡単です。



誰かがそれを必要とする場合、2017年11月以来、リポジトリはここに投稿されています。



よろしく、アンドリュー・ヘルダー!



All Articles