📜 ⬆️ ⬇️

Asynchronous C # program update

Good day, friends!

In my previous articles ( once and twice ) I wrote about the implementation of the automatic program update function and having many shortcomings, it was decided to improve it, and also to make the code more “friendly” or something. By shortening the lines and optimizing the format, I was able to achieve better asynchronous file downloads, virtually eliminating the likelihood of replacing the update file (checksum verification), and several new developments were added. There, I am making the next attempt to rehabilitate myself.

image
')
In my work, my program uses the following files in the same folder as the executable file:

In previous versions of the code, each file was downloaded separately, which caused a lot of inconvenience, starting from the waiting time for downloading. There was also no checksum check function, which did not have a good effect on the safety of their use.
What has changed in the code that I decided to write a third article about the same story?

First, the version.xml file located on the server has changed:

<?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> 


Changes


As you have noticed, compared to its previous version, the checksumm attribute was added, containing just the MD5 sum of a specific file.

When using the code, as unnecessary, the components of the backgroundWorker class were removed in favor of the Task , and the following lines were added to the class definition:

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

The debug class writes errors to a file for better logging. But in this article we will not talk about it.
The string parameter url sets the path to the folder on the site containing all of our files. Before this path for each file was registered - but in vain.
The downloadPercent component from the ProgressBar class is used to display the percent download of the update of the main program file.

Further, the function of starting the update process Check () was reduced to the form:

 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); } 

Now about all the details.
At the very beginning, we check if the program settings file ( settings.xml ) exists and if it is missing - download it
Further (sometimes it happened), if the files are of zero length, then we also delete them. Why do we need non-working files. Right, right?
After that, there is a check whether the launcher parameter was set during the function initialization. It is needed to determine the sequence of code execution, as well as to optimize the solution, since only 3 files from the above list are required when initializing the form of the main window. If the launcher parameter is false , then we download the main files (Ionic.Zip.dll, LanguagePack.dll, restart.exe), and then initialize the code of the main program.

To check for updates of the main program and auxiliary files, in the code of the main form in the handler public Form1 () after calling the function InitializeComponent (); add a call to our update class. Yes class, since all its code is placed separately (for convenience).

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

In the call update.Check (true, progressBar1); we, as the first parameter, we indicate that checks for updates of auxiliary files and updates for the main file of the application will now be made. As the second, we indicate the progressBar, which is responsible for displaying the percent load of the main file.

Since we specified the launcher = true parameter, the program will execute the following code from the Check () function (continuation of the code specified above):

  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); } } 

What we have here. Do not forget to add using System.Threading.Tasks; , we initialize the Task object, assigning variables to them ( task3 , task4 , task5 ).
For those who are not in the subject, the Task class is a wrapper over threads for performing asynchronous operations, giving the developer the opportunity to forget about how to create a stream, start it and destroy it when finished.
In general, in our case, we set the function DownloadFile () as a parameter ; By passing it the necessary parameters, namely:

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

Where:


Task.Factory.StartNew () function ; Allows you to asynchronously run any processes for execution. In order to determine when all the files were downloaded, the Task.WaitAll function was used (task3, task4, task5); , waiting for completion of code execution in all specified elements.
So, after downloading additional files, you can go to check for updates of the main one, and since updates can already be downloaded, we first check the existence and version of the local update file, if it exists.

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

Why there is no function to check the checksum, I will tell a little later, but for now let us return to this one.
If the update file (“launcher.update”) exists and its version is more recent, then run the additional utility updater.exe , passing the file names to the parameter (see the code above).

Further, if the update file is missing or its version is not fresh, then we use the following compliance check, where we check the software version with the version on the site:

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

If a more recent version is found, then go on to downloading it. And more about that later.
The third condition is valid when the first two are not met, namely, if the file is present and has an older version, then we simply delete it.

Call the function debug.Save (); saves information about the error handler to a file so that it can be read later. For software updates, this code does not matter much, and it is placed so that people do not ask "why you have catch (Exception) {} , this is not comme il faut." This is how it is.
Go ahead.

Download


For downloading files the private function DownloadFile (); , which has a set of parameters described above, and its cat code you can see below:

 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); } } 

At the very beginning, the transferred value is checked in the localFile parameter , and if the parameter equals null , then we assign the value of the filename parameter to it. After that, the file is checked for length and if it is zero, we delete it.
Then the key part of the function begins - we check the existence of the file and the relevance of its version, and if the file is missing or a new version is found, then go on to download, otherwise, skip.
Just before downloading, we check the showStatus parameter, which we need to enable / disable the display of the download status. I will consider an example when status is needed. So, if the showStatus parameter is not null and the downloadPercent parameter is set, then the client object of the WebClient () class is connected to the ProgressChanged () function ; to track download status.
Next is the process of asynchronous downloading of the DownloadFileAsync () file. File downloaded, what next?
And then we check the checksum of the downloaded file with the value on the site in the version.xml file through the function Checksumm () in the parameters of which the name of the local file and the string containing the md5-cache from the site are transferred.

 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; } } 

If the checksum of the local file matches the value on the site, the function returns true , otherwise, by the action of logic.
And what is there in DownloadFile () ? If the checksum is correct, then terminate the function; if not, delete the file.

What else has not indicated? Um ... oh yeah! Load status processing function:

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

And, I almost forgot, in the specified code, the update of the main program is downloaded to the file launcher.update . The application of the update will be carried out automatically upon the subsequent launch of the program, that is, no notification will be issued to the user, which, in my opinion, increases the “friendliness” of the program.

Conclusion


That is, in fact, the entire update code, and given that it is located in a separate class, it can easily be used in any kind of project, specifying your sources of updates and the number and names of files.
PS: in the debug class, when saving, 3 parameters are set - it is easier for them to look for the right place by code.

If anyone needs it, since November 2017 the repository is posted HERE .

Sincerely, Andrei Helldar!

Source: https://habr.com/ru/post/217627/


All Articles