source: branches/eraser6/Eraser/UpdateForm.cs @ 903

Revision 903, 28.6 KB checked in by lowjoel, 5 years ago (diff)

Ran Static code analysis on the Eraser project and implemented a few recommendations.

-Use more specific exceptions to allow code to determine the type of exception.
-Made GUIProgram implement IDisposable since the global mutex must be freed.
-Mde AboutForm? implement IDisposable, clearing the caching bitmaps on dispose.
-Made a few CommandLineProgram? functions static since they don't reference this.
-Name parameters/local with more unique, descriptive names for clarity.
-Use EventHandler?<EventArg?> instead of declaring our own delegate and event types as the type of the evenr handler

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.ComponentModel;
25using System.Data;
26using System.Drawing;
27using System.Text;
28using System.Windows.Forms;
29using System.Net;
30using System.Reflection;
31using System.IO;
32using System.Xml;
33using Eraser.Util;
34using System.Net.Cache;
35
36namespace Eraser
37{
38    public partial class UpdateForm : Form
39    {
40        /// <summary>
41        /// Constructor.
42        /// </summary>
43        public UpdateForm()
44        {
45            InitializeComponent();
46
47            updates = new UpdateManager();
48            updateListDownloader.RunWorkerAsync();
49        }
50
51        /// <summary>
52        /// Called when the form is about to be closed.
53        /// </summary>
54        /// <param name="sender">The object triggering this event/</param>
55        /// <param name="e">Event argument.</param>
56        private void UpdateForm_FormClosing(object sender, FormClosingEventArgs e)
57        {
58            //Cancel all running background tasks
59            if (updateListDownloader.IsBusy || downloader.IsBusy || installer.IsBusy)
60            {
61                updateListDownloader.CancelAsync();
62                downloader.CancelAsync();
63                installer.CancelAsync();
64                e.Cancel = true;
65            }
66        }
67
68        /// <summary>
69        /// Called when any of the Cancel buttons are clicked.
70        /// </summary>
71        /// <param name="sender">The object triggering this event/</param>
72        /// <param name="e">Event argument.</param>
73        private void cancelBtn_Click(object sender, EventArgs e)
74        {
75            Close();
76        }
77
78        #region Update List retrieval
79        /// <summary>
80        /// Downloads and parses the list of updates available for this client.
81        /// </summary>
82        /// <param name="sender">The object triggering this event/</param>
83        /// <param name="e">Event argument.</param>
84        private void updateListDownloader_DoWork(object sender, DoWorkEventArgs e)
85        {
86            try
87            {
88                updates.OnProgressEvent += updateListDownloader_ProgressChanged;
89                updates.DownloadUpdateList();
90            }
91            finally
92            {
93                updates.OnProgressEvent -= updateListDownloader_ProgressChanged;
94            }
95        }
96
97        /// <summary>
98        /// Called when progress has been made in the update list download.
99        /// </summary>
100        /// <param name="sender">The object triggering this event/</param>
101        /// <param name="e">Event argument.</param>
102        private void updateListDownloader_ProgressChanged(object sender, UpdateManager.ProgressEventArgs e)
103        {
104            if (InvokeRequired)
105            {
106                if (updateListDownloader.CancellationPending)
107                    throw new OperationCanceledException();
108
109                Invoke(new EventHandler<UpdateManager.ProgressEventArgs>(
110                    updateListDownloader_ProgressChanged), sender, e);
111                return;
112            }
113
114            progressPb.Style = ProgressBarStyle.Continuous;
115            progressPb.Value = (int)(e.OverallProgressPercentage * 100);
116            progressProgressLbl.Text = e.Message;
117
118            if (progressPb.Value == 100)
119                progressProgressLbl.Text = S._("Processing update list...");
120        }
121
122        /// <summary>
123        /// Displays the parsed updates on the updates list view, filtering and displaying
124        /// only those relevant to the current system's architecture.
125        /// </summary>
126        /// <param name="sender">The object triggering this event/</param>
127        /// <param name="e">Event argument.</param>
128        private void updateListDownloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
129        {
130            //The Error property will normally be null unless there are errors during the download.
131            if (e.Error != null)
132            {
133                if (!(e.Error is OperationCanceledException))
134                    MessageBox.Show(this, e.Error.Message, S._("Eraser"),
135                        MessageBoxButtons.OK, MessageBoxIcon.Error);
136
137                Close();
138                return;
139            }
140
141            progressPanel.Visible = false;
142            updatesPanel.Visible = true;
143
144            //First list all available mirrors
145            Dictionary<string, UpdateManager.Mirror>.Enumerator iter =
146                updates.Mirrors.GetEnumerator();
147            while (iter.MoveNext())
148                updatesMirrorCmb.Items.Add(iter.Current.Value);
149            updatesMirrorCmb.SelectedIndex = 0;
150
151            //Get a list of translatable categories (this will change as more categories
152            //are added)
153            Dictionary<string, string> updateCategories = new Dictionary<string, string>();
154            updateCategories.Add("update", S._("Updates"));
155            updateCategories.Add("plugin", S._("Plugins"));
156
157            //Only include those whose architecture is compatible with ours.
158            List<string> compatibleArchs = new List<string>();
159            {
160                //any is always compatible.
161                compatibleArchs.Add("any");
162
163                KernelAPI.SYSTEM_INFO info = new KernelAPI.SYSTEM_INFO();
164                KernelAPI.GetSystemInfo(out info);
165                switch (info.processorArchitecture)
166                {
167                    case KernelAPI.SYSTEM_INFO.ProcessorArchitecture.PROCESSOR_ARCHITECTURE_AMD64:
168                        compatibleArchs.Add("x64");
169                        break;
170
171                    case KernelAPI.SYSTEM_INFO.ProcessorArchitecture.PROCESSOR_ARCHITECTURE_IA64:
172                        compatibleArchs.Add("ia64");
173                        break;
174
175                    case KernelAPI.SYSTEM_INFO.ProcessorArchitecture.PROCESSOR_ARCHITECTURE_INTEL:
176                        compatibleArchs.Add("x86");
177                        break;
178                }
179            }
180
181            foreach (string key in updates.Categories)
182            {
183                ListViewGroup group = new ListViewGroup(updateCategories.ContainsKey(key) ?
184                    updateCategories[key] : key);
185                updatesLv.Groups.Add(group);
186
187                foreach (UpdateManager.Update update in updates[key])
188                {
189                    //Skip if this update won't work on our current architecture.
190                    if (compatibleArchs.IndexOf(update.Architecture) == -1)
191                        continue;
192
193                    ListViewItem item = new ListViewItem(update.Name);
194                    item.SubItems.Add(update.Version.ToString());
195                    item.SubItems.Add(update.Publisher);
196                    item.SubItems.Add(Util.File.GetHumanReadableFilesize(update.FileSize));
197
198                    item.Tag = update;
199                    item.Group = group;
200                    item.Checked = true;
201
202                    updatesLv.Items.Add(item);
203                    uiUpdates.Add(update, new UpdateData(update, item));
204                }
205            }
206
207            updatesBtn.Enabled = updatesLv.Items.Count > 0;
208
209            //Check if there are any updates at all.
210            if (updatesLv.Items.Count == 0)
211            {
212                MessageBox.Show(this, S._("There are no new updates or plugins available for " +
213                    "Eraser."), S._("Eraser"), MessageBoxButtons.OK, MessageBoxIcon.Information);
214                Close();
215            }
216        }
217        #endregion
218
219        #region Update downloader
220        /// <summary>
221        /// Handles the update checked event.
222        /// </summary>
223        /// <param name="sender">The object triggering this event/</param>
224        /// <param name="e">Event argument.</param>
225        private void updatesLv_ItemChecked(object sender, ItemCheckedEventArgs e)
226        {
227            if (selectedUpdates == -1 || updatesCount != updatesLv.Items.Count)
228            {
229                updatesCount = updatesLv.Items.Count;
230                selectedUpdates = 0;
231                foreach (ListViewItem item in updatesLv.Items)
232                    if (item.Checked)
233                        ++selectedUpdates;
234            }
235            else
236                selectedUpdates += e.Item.Checked ? 1 : -1;
237            updatesBtn.Text = selectedUpdates == 0 ? S._("Close") : S._("Install");
238        }
239
240        /// <summary>
241        /// Handles the Install button click; fetches and installs the updates selected.
242        /// </summary>
243        /// <param name="sender">The object triggering this event/</param>
244        /// <param name="e">Event argument.</param>
245        private void updatesBtn_Click(object sender, EventArgs e)
246        {
247            updatesPanel.Visible = false;
248            downloadingPnl.Visible = true;
249            List<UpdateManager.Update> updatesToInstall =
250                new List<UpdateManager.Update>();
251
252            //Set the mirror
253            updates.SelectedMirror = (UpdateManager.Mirror)
254                updatesMirrorCmb.SelectedItem;
255
256            //Collect the items that need to be installed
257            foreach (ListViewItem item in updatesLv.Items)
258                if (item.Checked)
259                {
260                    item.Remove();
261                    item.SubItems.RemoveAt(1);
262                    item.SubItems.RemoveAt(1);
263                    downloadingLv.Items.Add(item);
264
265                    updatesToInstall.Add((UpdateManager.Update)item.Tag);
266                }
267                else
268                    uiUpdates.Remove((UpdateManager.Update)item.Tag);
269
270            //Then run the thread if there are updates.
271            if (updatesToInstall.Count > 0)
272                downloader.RunWorkerAsync(updatesToInstall);
273            else
274                Close();
275        }
276
277        /// <summary>
278        /// Background thread to do the downloading and installing of updates.
279        /// </summary>
280        /// <param name="sender">The object triggering this event/</param>
281        /// <param name="e">Event argument.</param>
282        private void downloader_DoWork(object sender, DoWorkEventArgs e)
283        {
284            try
285            {
286                updates.OnProgressEvent += downloader_ProgressChanged;
287                object downloadedUpdates = updates.DownloadUpdates((List<UpdateManager.Update>)e.Argument);
288                e.Result = downloadedUpdates;
289            }
290            finally
291            {
292                updates.OnProgressEvent -= downloader_ProgressChanged;
293            }
294        }
295
296        /// <summary>
297        /// Handles the download progress changed event.
298        /// </summary>
299        /// <param name="sender">The object triggering this event/</param>
300        /// <param name="e">Event argument.</param>
301        private void downloader_ProgressChanged(object sender, UpdateManager.ProgressEventArgs e)
302        {
303            if (InvokeRequired)
304            {
305                if (updateListDownloader.CancellationPending)
306                    throw new OperationCanceledException();
307
308                Invoke(new EventHandler<UpdateManager.ProgressEventArgs>(downloader_ProgressChanged),
309                    sender, e);
310                return;
311            }
312
313            UpdateData update = uiUpdates[(UpdateManager.Update)e.UserState];
314
315            if (e is UpdateManager.ProgressErrorEventArgs)
316            {
317                update.Error = ((UpdateManager.ProgressErrorEventArgs)e).Exception;
318                update.LVItem.ImageIndex = 3;
319                update.LVItem.SubItems[1].Text = S._("Error");
320                update.LVItem.ToolTipText = update.Error.Message;
321            }
322            else
323            {
324                if (e.ProgressPercentage >= 1.0f)
325                {
326                    update.LVItem.ImageIndex = -1;
327                    update.LVItem.SubItems[1].Text = S._("Downloaded");
328                }
329                else
330                {
331                    update.amountDownloaded = (long)(e.ProgressPercentage * update.Update.FileSize);
332                    update.LVItem.ImageIndex = 0;
333                    update.LVItem.SubItems[1].Text = Util.File.GetHumanReadableFilesize(
334                        update.Update.FileSize - update.amountDownloaded);
335                }
336            }
337
338            downloadingItemLbl.Text = e.Message;
339            downloadingItemPb.Value = (int)(e.ProgressPercentage * 100);
340            downloadingOverallPb.Value = (int)(e.OverallProgressPercentage * 100);
341
342            long amountToDownload = 0;
343            foreach (UpdateData upd in uiUpdates.Values)
344                amountToDownload += upd.Update.FileSize - upd.amountDownloaded;
345            downloadingOverallLbl.Text = S._("Overall progress: {0} left",
346                Util.File.GetHumanReadableFilesize(amountToDownload));
347        }
348
349        /// <summary>
350        /// Handles the completion of updating event
351        /// </summary>
352        /// <param name="sender">The object triggering this event/</param>
353        /// <param name="e">Event argument.</param>
354        private void downloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
355        {
356            if (e.Error != null)
357            {
358                if (!(e.Error is OperationCanceledException))
359                    MessageBox.Show(this, e.Error.Message, S._("Eraser"),
360                        MessageBoxButtons.OK, MessageBoxIcon.Error);
361
362                Close();
363                return;
364            }
365
366            downloadingPnl.Visible = false;
367            installingPnl.Visible = true;
368
369            foreach (ListViewItem item in downloadingLv.Items)
370            {
371                item.Remove();
372                installingLv.Items.Add(item);
373
374                UpdateData update = uiUpdates[(UpdateManager.Update)item.Tag];
375                if (update.Error == null)
376                    item.SubItems[1].Text = string.Empty;
377                else
378                    item.SubItems[1].Text = S._("Error: {0}", update.Error.Message);
379            }
380
381            installer.RunWorkerAsync(e.Result);
382        }
383        #endregion
384
385        #region Update installer
386        /// <summary>
387        /// Background thread to install downloaded updates
388        /// </summary>
389        /// <param name="sender">The object triggering this event/</param>
390        /// <param name="e">Event argument.</param>
391        private void installer_DoWork(object sender, DoWorkEventArgs e)
392        {
393            try
394            {
395                updates.OnProgressEvent += installer_ProgressChanged;
396                updates.InstallUpdates(e.Argument);
397            }
398            finally
399            {
400                updates.OnProgressEvent -= installer_ProgressChanged;
401            }
402        }
403
404        /// <summary>
405        /// Handles the progress events generated during update installation.
406        /// </summary>
407        /// <param name="sender">The object triggering this event/</param>
408        /// <param name="e">Event argument.</param>
409        private void installer_ProgressChanged(object sender, ProgressChangedEventArgs e)
410        {
411            if (InvokeRequired)
412            {
413                if (updateListDownloader.CancellationPending)
414                    throw new OperationCanceledException();
415
416                Invoke(new EventHandler<UpdateManager.ProgressEventArgs>(installer_ProgressChanged),
417                    sender, e);
418                return;
419            }
420
421            UpdateData update = uiUpdates[(UpdateManager.Update)e.UserState];
422            if (e is UpdateManager.ProgressErrorEventArgs)
423            {
424                update.Error = ((UpdateManager.ProgressErrorEventArgs)e).Exception;
425                update.LVItem.ImageIndex = 3;
426                update.LVItem.SubItems[1].Text = S._("Error: {0}", update.Error.Message);
427            }
428            else
429                switch (update.LVItem.ImageIndex)
430                {
431                    case -1:
432                        update.LVItem.ImageIndex = 1;
433                        break;
434                    case 1:
435                        update.LVItem.ImageIndex = 2;
436                        break;
437                }
438        }
439
440        /// <summary>
441        /// Re-enables the close dialog button.
442        /// </summary>
443        /// <param name="sender">The object triggering this event/</param>
444        /// <param name="e">Event argument.</param>
445        private void installer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
446        {
447            if (e.Error is OperationCanceledException)
448                Close();
449
450            installingPnl.UseWaitCursor = false;
451        }
452        #endregion
453
454        /// <summary>
455        /// The Update manager instance used by this form.
456        /// </summary>
457        UpdateManager updates;
458
459        /// <summary>
460        /// Maps listview items to the UpdateManager.Update object.
461        /// </summary>
462        Dictionary<UpdateManager.Update, UpdateData> uiUpdates =
463            new Dictionary<UpdateManager.Update, UpdateData>();
464
465        /// <summary>
466        /// Manages information associated with the update.
467        /// </summary>
468        private class UpdateData
469        {
470            /// <summary>
471            /// Constructor.
472            /// </summary>
473            /// <param name="update">The UpdateManager.Update object containing the
474            /// internal representation of the update.</param>
475            /// <param name="item">The ListViewItem used for the display of the
476            /// update.</param>
477            public UpdateData(UpdateManager.Update update, ListViewItem item)
478            {
479                Update = update;
480                LVItem = item;
481            }
482
483            /// <summary>
484            /// The UpdateManager.Update object containing the internal representation
485            /// of the update.
486            /// </summary>
487            public UpdateManager.Update Update;
488
489            /// <summary>
490            /// The ListViewItem used for the display of the update.
491            /// </summary>
492            public ListViewItem LVItem;
493
494            /// <summary>
495            /// The amount of the download already completed.
496            /// </summary>
497            public long amountDownloaded;
498
499            /// <summary>
500            /// The error raised when downloading/installing the update, if any. Null
501            /// otherwise.
502            /// </summary>
503            public Exception Error;
504        }
505
506        /// <summary>
507        /// The number of updates selected for download.
508        /// </summary>
509        private int selectedUpdates = -1;
510
511        /// <summary>
512        /// The number of updates present in the previous count, so the Selected
513        /// Updates number can be deemed invalid.
514        /// </summary>
515        private int updatesCount = -1;
516    }
517
518    public class UpdateManager
519    {
520        /// <summary>
521        /// Represents a download mirror.
522        /// </summary>
523        public struct Mirror
524        {
525            public Mirror(string location, string link)
526                : this()
527            {
528                Location = location;
529                Link = link;
530            }
531
532            /// <summary>
533            /// The location where the mirror is at.
534            /// </summary>
535            public string Location
536            {
537                get;
538                set;
539            }
540
541            /// <summary>
542            /// The URL prefix to utilise the mirror.
543            /// </summary>
544            public string Link
545            {
546                get;
547                set;
548            }
549
550            public override string ToString()
551            {
552                return Location;
553            }
554        }
555
556        /// <summary>
557        /// Represents an update available on the server.
558        /// </summary>
559        public struct Update
560        {
561            public string Name;
562            public Version Version;
563            public string Publisher;
564            public string Architecture;
565            public long FileSize;
566            public string Link;
567        }
568
569        /// <summary>
570        /// Specialised progress event argument, containing message describing
571        /// current action, and overall progress percentage.
572        /// </summary>
573        public class ProgressEventArgs : ProgressChangedEventArgs
574        {
575            public ProgressEventArgs(float progressPercentage, float overallPercentage,
576                object userState, string message)
577                : base((int)(progressPercentage * 100), userState)
578            {
579                this.progressPercentage = progressPercentage;
580                this.overallProgressPercentage = overallPercentage;
581                this.message = message;
582            }
583
584            /// <summary>
585            /// Gets the asynchronous task progress percentage.
586            /// </summary>
587            public new float ProgressPercentage
588            {
589                get
590                {
591                    return progressPercentage;
592                }
593            }
594
595            /// <summary>
596            /// Gets the asynchronous task overall progress percentage.
597            /// </summary>
598            public float OverallProgressPercentage
599            {
600                get
601                {
602                    return overallProgressPercentage;
603                }
604            }
605
606            /// <summary>
607            /// Gets the message associated with the current task.
608            /// </summary>
609            public string Message
610            {
611                get
612                {
613                    return message;
614                }
615            }
616
617            float progressPercentage;
618            float overallProgressPercentage;
619            string message;
620        }
621
622        /// <summary>
623        /// Extends the ProgressEventArgs further by allowing for the inclusion of
624        /// an exception.
625        /// </summary>
626        public class ProgressErrorEventArgs : ProgressEventArgs
627        {
628            /// <summary>
629            /// Constructor.
630            /// </summary>
631            /// <param name="e">The base ProgressEventArgs object.</param>
632            /// <param name="ex">The exception</param>
633            public ProgressErrorEventArgs(ProgressEventArgs e, Exception ex)
634                : base(e.ProgressPercentage, e.OverallProgressPercentage, e.UserState, e.Message)
635            {
636                this.exception = ex;
637            }
638
639            /// <summary>
640            /// The exception associated with the progress event.
641            /// </summary>
642            public Exception Exception
643            {
644                get
645                {
646                    return exception;
647                }
648            }
649
650            private Exception exception;
651        }
652
653        /// <summary>
654        /// Retrieves the update list from the server.
655        /// </summary>
656        public void DownloadUpdateList()
657        {
658            WebRequest.DefaultCachePolicy = new HttpRequestCachePolicy(
659                HttpRequestCacheLevel.Refresh);
660            HttpWebRequest req = (HttpWebRequest)
661                WebRequest.Create(new Uri("http://eraser.heidi.ie/updates?action=listupdates&" +
662                    "version=" + Assembly.GetExecutingAssembly().GetName().Version.ToString()));
663           
664            using (WebResponse resp = req.GetResponse())
665            using (Stream strm = resp.GetResponseStream())
666            {
667                //Download the response
668                int bytesRead = 0;
669                byte[] buffer = new byte[16384];
670                List<byte> responseBuffer = new List<byte>();
671                while ((bytesRead = strm.Read(buffer, 0, buffer.Length)) != 0)
672                {
673                    byte[] tmpDest = new byte[bytesRead];
674                    Buffer.BlockCopy(buffer, 0, tmpDest, 0, bytesRead);
675                    responseBuffer.AddRange(tmpDest);
676
677                    float progress = responseBuffer.Count / (float)resp.ContentLength;
678                    OnProgress(new ProgressEventArgs(progress, progress, null,
679                        S._("{0} of {1} downloaded",
680                            Util.File.GetHumanReadableFilesize(responseBuffer.Count),
681                            Util.File.GetHumanReadableFilesize(resp.ContentLength))));
682                }
683
684                //Parse it.
685                using (MemoryStream mStrm = new MemoryStream(responseBuffer.ToArray()))
686                    ParseUpdateList(mStrm);
687            }
688        }
689
690        /// <summary>
691        /// Parses the list of updates provided by the server
692        /// </summary>
693        /// <param name="strm">The stream containing the XML data.</param>
694        private void ParseUpdateList(Stream strm)
695        {
696            //Move the XmlReader to the root node
697            updates.Clear();
698            mirrors.Clear();
699            XmlReader rdr = XmlReader.Create(strm);
700            rdr.ReadToFollowing("updateList");
701
702            //Read the descendants of the updateList node (which are categories,
703            //except for the <mirrors> element)
704            XmlReader categories = rdr.ReadSubtree();
705            bool cont = categories.ReadToDescendant("mirrors");
706            while (cont)
707            {
708                if (categories.NodeType == XmlNodeType.Element)
709                {
710                    if (categories.Name == "mirrors")
711                    {
712                        Dictionary<string, string> mirrorsList =
713                            ParseMirror(categories.ReadSubtree());
714                        Dictionary<string, string>.Enumerator e = mirrorsList.GetEnumerator();
715                        while (e.MoveNext())
716                            this.mirrors.Add(e.Current.Key,
717                                new Mirror(e.Current.Value, e.Current.Key));
718                    }
719                    else
720                        updates.Add(categories.Name, ParseUpdateCategory(categories.ReadSubtree()));
721                }
722
723                cont = categories.Read();
724            }
725        }
726
727        /// <summary>
728        /// Parses a list of mirrors.
729        /// </summary>
730        /// <param name="rdr">The XML reader object representing the &lt;mirrors&gt; node</param>
731        /// <returns>The list of mirrors defined by the element.</returns>
732        private static Dictionary<string, string> ParseMirror(XmlReader rdr)
733        {
734            Dictionary<string, string> result = new Dictionary<string,string>();
735            if (!rdr.ReadToDescendant("mirror"))
736                return result;
737
738            //Load every element.
739            do
740            {
741                if (rdr.NodeType != XmlNodeType.Element || rdr.Name != "mirror")
742                    continue;
743
744                string location = rdr.GetAttribute("location");
745                result.Add(rdr.ReadElementContentAsString(), location);
746            }
747            while (rdr.ReadToNextSibling("mirror"));
748
749            return result;
750        }
751
752        /// <summary>
753        /// Parses a specific category and its assocaited updates.
754        /// </summary>
755        /// <param name="rdr">The XML reader object representing the element and its children.</param>
756        /// <returns>A list of updates in the category.</returns>
757        private static List<Update> ParseUpdateCategory(XmlReader rdr)
758        {
759            List<Update> result = new List<Update>();
760            if (!rdr.ReadToDescendant("item"))
761                return result;
762
763            //Load every element.
764            do
765            {
766                if (rdr.Name != "item")
767                    continue;
768
769                Update update = new Update();
770                update.Name = rdr.GetAttribute("name");
771                update.Version = new Version(rdr.GetAttribute("version"));
772                update.Publisher = rdr.GetAttribute("publisher");
773                update.Architecture = rdr.GetAttribute("architecture");
774                update.FileSize = Convert.ToInt64(rdr.GetAttribute("filesize"));
775                update.Link = rdr.ReadElementContentAsString();
776
777                result.Add(update);
778            }
779            while (rdr.ReadToNextSibling("item"));
780
781            return result;
782        }
783
784        /// <summary>
785        /// Downloads the list of updates.
786        /// </summary>
787        /// <param name="updates">The updates to retrieve and install.</param>
788        /// <returns>An opaque object for use with InstallUpdates.</returns>
789        public object DownloadUpdates(ICollection<Update> downloadQueue)
790        {
791            //Create a folder to hold all our updates.
792            DirectoryInfo tempDir = new DirectoryInfo(Path.GetTempPath());
793            tempDir = tempDir.CreateSubdirectory("eraser" + Environment.TickCount.ToString());
794
795            int currUpdate = 0;
796            Dictionary<string, Update> tempFilesMap = new Dictionary<string, Update>();
797            foreach (Update update in downloadQueue)
798            {
799                try
800                {
801                    //Decide on the URL to connect to. The Link of the update may
802                    //be a relative path (relative to the selected mirror) or an
803                    //absolute path (which we have no choice)
804                    Uri reqUri = null;
805                    if (Uri.IsWellFormedUriString(update.Link, UriKind.Absolute))
806                        reqUri = new Uri(update.Link);
807                    else
808                        reqUri = new Uri(new Uri(SelectedMirror.Link), new Uri(update.Link));
809                   
810                    //Then grab the download.
811                    HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
812                    using (WebResponse resp = req.GetResponse())
813                    {
814                        byte[] tempBuffer = new byte[16384];
815                        string tempFilePath = Path.Combine(
816                            tempDir.FullName, string.Format("{0}-{1}", ++currUpdate,
817                            Path.GetFileName(reqUri.GetComponents(UriComponents.Path, UriFormat.Unescaped))));
818
819                        using (Stream strm = resp.GetResponseStream())
820                        using (FileStream tempStrm = new FileStream(tempFilePath, FileMode.CreateNew))
821                        using (BufferedStream bufStrm = new BufferedStream(tempStrm))
822                        {
823                            //Copy the information into the file stream
824                            int readBytes = 0;
825                            while ((readBytes = strm.Read(tempBuffer, 0, tempBuffer.Length)) != 0)
826                            {
827                                bufStrm.Write(tempBuffer, 0, readBytes);
828
829                                //Compute progress
830                                float itemProgress = tempStrm.Position / (float)resp.ContentLength;
831                                float overallProgress = (currUpdate - 1 + itemProgress) / downloadQueue.Count;
832                                OnProgress(new ProgressEventArgs(itemProgress, overallProgress,
833                                    update, S._("Downloading: {0}", update.Name)));
834                            }
835                        }
836
837                        //Store the filename-to-update mapping
838                        tempFilesMap.Add(tempFilePath, update);
839
840                        //Let the event handler know the download is complete.
841                        OnProgress(new ProgressEventArgs(1.0f, (float)currUpdate / downloadQueue.Count,
842                            update, S._("Downloaded: {0}", update.Name)));
843                    }
844                }
845                catch (Exception e)
846                {
847                    OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,
848                        (float)currUpdate / downloadQueue.Count, update,
849                            S._("Error downloading {0}: {1}", update.Name, e.Message)),
850                        e));
851                }
852            }
853
854            return tempFilesMap;
855        }
856
857        public void InstallUpdates(object value)
858        {
859            Dictionary<string, Update> tempFiles = (Dictionary<string, Update>)value;
860            Dictionary<string, Update>.KeyCollection files = tempFiles.Keys;
861            int currItem = 0;
862
863            try
864            {
865                foreach (string path in files)
866                {
867                    Update item = tempFiles[path];
868                    float progress = (float)currItem++ / files.Count;
869                    OnProgress(new ProgressEventArgs(0.0f, progress,
870                        item, S._("Installing {0}", item.Name)));
871
872                    System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
873                    info.FileName = path;
874                    info.UseShellExecute = true;
875
876                    System.Diagnostics.Process process = System.Diagnostics.Process.Start(info);
877                    process.WaitForExit(Int32.MaxValue);
878                    if (process.ExitCode == 0)
879                        OnProgress(new ProgressEventArgs(1.0f, progress,
880                            item, S._("Installed {0}", item.Name)));
881                    else
882                        OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,
883                            progress, item, S._("Error installing {0}", item.Name)),
884                            new ApplicationException(S._("The installer exited with an error code {0}",
885                                process.ExitCode))));
886                }
887            }
888            finally
889            {
890                //Clean up after ourselves
891                foreach (string file in files)
892                {
893                    DirectoryInfo tempDir = null;
894                    {
895                        FileInfo info = new FileInfo(file);
896                        tempDir = info.Directory;
897                    }
898
899                    tempDir.Delete(true);
900                    break;
901                }
902            }
903        }
904
905        /// <summary>
906        /// Called when the progress of the operation changes.
907        /// </summary>
908        public EventHandler<ProgressEventArgs> OnProgressEvent
909        {
910            get { return onProgressEvent; }
911            set { onProgressEvent = value; }
912        }
913        private EventHandler<ProgressEventArgs> onProgressEvent;
914
915        /// <summary>
916        /// Helper function: invokes the OnProgressEvent delegate.
917        /// </summary>
918        /// <param name="arg">The ProgressEventArgs object holding information
919        /// about the progress of the current operation.</param>
920        private void OnProgress(ProgressEventArgs arg)
921        {
922            if (OnProgressEvent != null)
923                OnProgressEvent(this, arg);
924        }
925
926        /// <summary>
927        /// Retrieves the list of mirrors which the server has indicated to exist.
928        /// </summary>
929        public Dictionary<string, Mirror> Mirrors
930        {
931            get
932            {
933                return mirrors;
934            }
935        }
936
937        /// <summary>
938        /// Gets or sets the active mirror to use to download mirrored updates.
939        /// </summary>
940        public Mirror SelectedMirror
941        {
942            get
943            {
944                if (selectedMirror.Link.Length == 0)
945                {
946                    Dictionary<string, Mirror>.Enumerator iter = mirrors.GetEnumerator();
947                    if (iter.MoveNext())
948                        return iter.Current.Value;
949                }
950                return selectedMirror;
951            }
952            set
953            {
954                foreach (Mirror mirror in Mirrors.Values)
955                    if (mirror.Equals(value))
956                    {
957                        selectedMirror = value;
958                        return;
959                    }
960
961                throw new ArgumentException(S._("Unknown mirror selected."));
962            }
963        }
964
965        /// <summary>
966        /// Retrieves the categories available.
967        /// </summary>
968        public Dictionary<string, List<Update>>.KeyCollection Categories
969        {
970            get
971            {
972                return updates.Keys;
973            }
974        }
975
976        /// <summary>
977        /// Retrieves all updates available.
978        /// </summary>
979        public Dictionary<string, List<Update>> Updates
980        {
981            get
982            {
983                return updates;
984            }
985        }
986
987        /// <summary>
988        /// Retrieves the updates in the given category.
989        /// </summary>
990        /// <param name="key">The category to retrieve.</param>
991        /// <returns>All updates in the given category.</returns>
992        public ICollection<Update> this[string key]
993        {
994            get
995            {
996                return updates[key];
997            }
998        }
999
1000        /// <summary>
1001        /// The list of mirrors to download updates from.
1002        /// </summary>
1003        private Dictionary<string, Mirror> mirrors =
1004            new Dictionary<string, Mirror>();
1005
1006        /// <summary>
1007        /// The currently selected mirror.
1008        /// </summary>
1009        private Mirror selectedMirror;
1010
1011        /// <summary>
1012        /// The list of updates downloaded.
1013        /// </summary>
1014        private Dictionary<string, List<Update>> updates =
1015            new Dictionary<string, List<Update>>();
1016    }
1017}
Note: See TracBrowser for help on using the repository browser.