source: branches/eraser6/CodeReview/Eraser/UpdateForm.cs @ 1527

Revision 1527, 33.5 KB checked in by lowjoel, 4 years ago (diff)

No need to create new EventHandler? objects, since what we are doing is disambiguating the functions. Addresses #275: Code Review

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2009 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;
35using System.Net.Mime;
36using System.Globalization;
37
38namespace Eraser
39{
40    public partial class UpdateForm : Form
41    {
42        /// <summary>
43        /// Constructor.
44        /// </summary>
45        public UpdateForm()
46        {
47            InitializeComponent();
48            UXThemeApi.UpdateControlTheme(this);
49            updateListDownloader.RunWorkerAsync();
50        }
51
52        /// <summary>
53        /// Called when the form is about to be closed.
54        /// </summary>
55        /// <param name="sender">The object triggering this event/</param>
56        /// <param name="e">Event argument.</param>
57        private void UpdateForm_FormClosing(object sender, FormClosingEventArgs e)
58        {
59            //Cancel all running background tasks
60            if (updateListDownloader.IsBusy || downloader.IsBusy || installer.IsBusy)
61            {
62                updateListDownloader.CancelAsync();
63                downloader.CancelAsync();
64                installer.CancelAsync();
65                e.Cancel = true;
66            }
67        }
68
69        /// <summary>
70        /// Called when any of the Cancel buttons are clicked.
71        /// </summary>
72        /// <param name="sender">The object triggering this event/</param>
73        /// <param name="e">Event argument.</param>
74        private void cancelBtn_Click(object sender, EventArgs e)
75        {
76            Close();
77        }
78
79        #region Update List retrieval
80        /// <summary>
81        /// Downloads and parses the list of updates available for this client.
82        /// </summary>
83        /// <param name="sender">The object triggering this event/</param>
84        /// <param name="e">Event argument.</param>
85        private void updateListDownloader_DoWork(object sender, DoWorkEventArgs e)
86        {
87            try
88            {
89                updates.OnProgressEvent += updateListDownloader_ProgressChanged;
90                updates.DownloadUpdateList();
91            }
92            finally
93            {
94                updates.OnProgressEvent -= updateListDownloader_ProgressChanged;
95            }
96        }
97
98        /// <summary>
99        /// Called when progress has been made in the update list download.
100        /// </summary>
101        /// <param name="sender">The object triggering this event/</param>
102        /// <param name="e">Event argument.</param>
103        private void updateListDownloader_ProgressChanged(object sender, ProgressEventArgs e)
104        {
105            if (InvokeRequired)
106            {
107                if (updateListDownloader.CancellationPending)
108                    throw new OperationCanceledException();
109
110                Invoke((EventHandler<ProgressEventArgs>)updateListDownloader_ProgressChanged,
111                    sender, e);
112                return;
113            }
114
115            progressPb.Style = ProgressBarStyle.Continuous;
116            progressPb.Value = (int)(e.OverallProgressPercentage * 100);
117            progressProgressLbl.Text = e.Message;
118
119            if (progressPb.Value == 100)
120                progressProgressLbl.Text = S._("Processing update list...");
121        }
122
123        /// <summary>
124        /// Displays the parsed updates on the updates list view, filtering and displaying
125        /// only those relevant to the current system's architecture.
126        /// </summary>
127        /// <param name="sender">The object triggering this event/</param>
128        /// <param name="e">Event argument.</param>
129        private void updateListDownloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
130        {
131            //The Error property will normally be null unless there are errors during the download.
132            if (e.Error != null)
133            {
134                if (!(e.Error is OperationCanceledException))
135                    MessageBox.Show(this, e.Error.Message, S._("Eraser"), MessageBoxButtons.OK,
136                        MessageBoxIcon.Error, MessageBoxDefaultButton.Button1,
137                        S.IsRightToLeft(this) ? MessageBoxOptions.RtlReading : 0);
138
139                Close();
140                return;
141            }
142
143            progressPanel.Visible = false;
144            updatesPanel.Visible = true;
145
146            //First list all available mirrors
147            Dictionary<string, Mirror>.Enumerator iter = updates.Mirrors.GetEnumerator();
148            while (iter.MoveNext())
149                updatesMirrorCmb.Items.Add(iter.Current.Value);
150            updatesMirrorCmb.SelectedIndex = 0;
151
152            //Get a list of translatable categories (this will change as more categories
153            //are added)
154            Dictionary<string, string> updateCategories = new Dictionary<string, string>();
155            updateCategories.Add("update", S._("Updates"));
156            updateCategories.Add("plugin", S._("Plugins"));
157
158            //Only include those whose architecture is compatible with ours.
159            List<string> compatibleArchs = new List<string>();
160            {
161                //any is always compatible.
162                compatibleArchs.Add("any");
163
164                switch (KernelApi.ProcessorArchitecture)
165                {
166                    case ProcessorArchitecture.Amd64:
167                        compatibleArchs.Add("x64");
168                        break;
169                    case ProcessorArchitecture.IA64:
170                        compatibleArchs.Add("ia64");
171                        break;
172                    case ProcessorArchitecture.X86:
173                        compatibleArchs.Add("x86");
174                        break;
175                }
176            }
177
178            foreach (string key in updates.Categories)
179            {
180                ListViewGroup group = new ListViewGroup(updateCategories.ContainsKey(key) ?
181                    updateCategories[key] : key);
182                updatesLv.Groups.Add(group);
183
184                foreach (UpdateInfo update in updates.Updates[key])
185                {
186                    //Skip if this update won't work on our current architecture.
187                    if (compatibleArchs.IndexOf(update.Architecture) == -1)
188                        continue;
189
190                    ListViewItem item = new ListViewItem(update.Name);
191                    item.SubItems.Add(update.Version.ToString());
192                    item.SubItems.Add(update.Publisher);
193                    item.SubItems.Add(Util.File.GetHumanReadableFilesize(update.FileSize));
194
195                    item.Tag = update;
196                    item.Group = group;
197                    item.Checked = true;
198
199                    updatesLv.Items.Add(item);
200                    uiUpdates.Add(update, new UpdateData(update, item));
201                }
202            }
203
204            updatesBtn.Enabled = updatesLv.Items.Count > 0;
205
206            //Check if there are any updates at all.
207            if (updatesLv.Items.Count == 0)
208            {
209                MessageBox.Show(this, S._("There are no new updates or plugins available for " +
210                    "Eraser."), S._("Eraser"), MessageBoxButtons.OK, MessageBoxIcon.Information,
211                    MessageBoxDefaultButton.Button1,
212                    S.IsRightToLeft(this) ? MessageBoxOptions.RtlReading : 0);
213                Close();
214            }
215        }
216        #endregion
217
218        #region Update downloader
219        /// <summary>
220        /// Handles the update checked event.
221        /// </summary>
222        /// <param name="sender">The object triggering this event/</param>
223        /// <param name="e">Event argument.</param>
224        private void updatesLv_ItemChecked(object sender, ItemCheckedEventArgs e)
225        {
226            if (selectedUpdates == -1 || updatesCount != updatesLv.Items.Count)
227            {
228                updatesCount = updatesLv.Items.Count;
229                selectedUpdates = 0;
230                foreach (ListViewItem item in updatesLv.Items)
231                    if (item.Checked)
232                        ++selectedUpdates;
233            }
234            else
235                selectedUpdates += e.Item.Checked ? 1 : -1;
236            updatesBtn.Text = selectedUpdates == 0 ? S._("Close") : S._("Install");
237        }
238
239        /// <summary>
240        /// Handles the Install button click; fetches and installs the updates selected.
241        /// </summary>
242        /// <param name="sender">The object triggering this event/</param>
243        /// <param name="e">Event argument.</param>
244        private void updatesBtn_Click(object sender, EventArgs e)
245        {
246            updatesPanel.Visible = false;
247            downloadingPnl.Visible = true;
248            List<UpdateInfo> updatesToInstall = new List<UpdateInfo>();
249
250            //Set the mirror
251            updates.SelectedMirror = (Mirror)updatesMirrorCmb.SelectedItem;
252
253            //Collect the items that need to be installed
254            foreach (ListViewItem item in updatesLv.Items)
255                if (item.Checked)
256                {
257                    item.Remove();
258                    item.SubItems.RemoveAt(1);
259                    item.SubItems.RemoveAt(1);
260                    downloadingLv.Items.Add(item);
261
262                    updatesToInstall.Add((UpdateInfo)item.Tag);
263                }
264                else
265                    uiUpdates.Remove((UpdateInfo)item.Tag);
266
267            //Then run the thread if there are updates.
268            if (updatesToInstall.Count > 0)
269                downloader.RunWorkerAsync(updatesToInstall);
270            else
271                Close();
272        }
273
274        /// <summary>
275        /// Background thread to do the downloading and installing of updates.
276        /// </summary>
277        /// <param name="sender">The object triggering this event/</param>
278        /// <param name="e">Event argument.</param>
279        private void downloader_DoWork(object sender, DoWorkEventArgs e)
280        {
281            try
282            {
283                updates.OnProgressEvent += downloader_ProgressChanged;
284                object downloadedUpdates = updates.DownloadUpdates((List<UpdateInfo>)e.Argument);
285                e.Result = downloadedUpdates;
286            }
287            finally
288            {
289                updates.OnProgressEvent -= downloader_ProgressChanged;
290            }
291        }
292
293        /// <summary>
294        /// Handles the download progress changed event.
295        /// </summary>
296        /// <param name="sender">The object triggering this event/</param>
297        /// <param name="e">Event argument.</param>
298        private void downloader_ProgressChanged(object sender, ProgressEventArgs e)
299        {
300            if (InvokeRequired)
301            {
302                if (updateListDownloader.CancellationPending)
303                    throw new OperationCanceledException();
304
305                Invoke((EventHandler<ProgressEventArgs>)downloader_ProgressChanged,
306                    sender, e);
307                return;
308            }
309
310            UpdateData update = uiUpdates[(UpdateInfo)e.UserState];
311
312            if (e is ProgressErrorEventArgs)
313            {
314                update.Error = ((ProgressErrorEventArgs)e).Exception;
315                update.LVItem.ImageIndex = 3;
316                update.LVItem.SubItems[1].Text = S._("Error");
317                update.LVItem.ToolTipText = update.Error.Message;
318            }
319            else
320            {
321                if (e.ProgressPercentage >= 1.0f)
322                {
323                    update.LVItem.ImageIndex = -1;
324                    update.LVItem.SubItems[1].Text = S._("Downloaded");
325                }
326                else
327                {
328                    update.amountDownloaded = (long)(e.ProgressPercentage * update.Update.FileSize);
329                    update.LVItem.ImageIndex = 0;
330                    update.LVItem.SubItems[1].Text = Util.File.GetHumanReadableFilesize(
331                        update.Update.FileSize - update.amountDownloaded);
332                }
333            }
334
335            downloadingItemLbl.Text = e.Message;
336            downloadingItemPb.Value = (int)(e.ProgressPercentage * 100);
337            downloadingOverallPb.Value = (int)(e.OverallProgressPercentage * 100);
338
339            long amountToDownload = 0;
340            foreach (UpdateData upd in uiUpdates.Values)
341                amountToDownload += upd.Update.FileSize - upd.amountDownloaded;
342            downloadingOverallLbl.Text = S._("Overall progress: {0} left",
343                Util.File.GetHumanReadableFilesize(amountToDownload));
344        }
345
346        /// <summary>
347        /// Handles the completion of updating event
348        /// </summary>
349        /// <param name="sender">The object triggering this event/</param>
350        /// <param name="e">Event argument.</param>
351        private void downloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
352        {
353            if (e.Error != null)
354            {
355                if (!(e.Error is OperationCanceledException))
356                    MessageBox.Show(this, e.Error.Message, S._("Eraser"),
357                        MessageBoxButtons.OK, MessageBoxIcon.Error,
358                        MessageBoxDefaultButton.Button1,
359                        S.IsRightToLeft(this) ? MessageBoxOptions.RtlReading : 0);
360
361                Close();
362                return;
363            }
364
365            downloadingPnl.Visible = false;
366            installingPnl.Visible = true;
367
368            foreach (ListViewItem item in downloadingLv.Items)
369            {
370                item.Remove();
371                installingLv.Items.Add(item);
372
373                UpdateData update = uiUpdates[(UpdateInfo)item.Tag];
374                if (update.Error == null)
375                    item.SubItems[1].Text = string.Empty;
376                else
377                    item.SubItems[1].Text = S._("Error: {0}", update.Error.Message);
378            }
379
380            installer.RunWorkerAsync(e.Result);
381        }
382        #endregion
383
384        #region Update installer
385        /// <summary>
386        /// Background thread to install downloaded updates
387        /// </summary>
388        /// <param name="sender">The object triggering this event/</param>
389        /// <param name="e">Event argument.</param>
390        private void installer_DoWork(object sender, DoWorkEventArgs e)
391        {
392            try
393            {
394                updates.OnProgressEvent += installer_ProgressChanged;
395                updates.InstallUpdates(e.Argument);
396            }
397            finally
398            {
399                updates.OnProgressEvent -= installer_ProgressChanged;
400            }
401        }
402
403        /// <summary>
404        /// Handles the progress events generated during update installation.
405        /// </summary>
406        /// <param name="sender">The object triggering this event/</param>
407        /// <param name="e">Event argument.</param>
408        private void installer_ProgressChanged(object sender, ProgressChangedEventArgs e)
409        {
410            if (InvokeRequired)
411            {
412                if (updateListDownloader.CancellationPending)
413                    throw new OperationCanceledException();
414
415                Invoke((EventHandler<ProgressEventArgs>)installer_ProgressChanged,
416                    sender, e);
417                return;
418            }
419
420            UpdateData update = uiUpdates[(UpdateInfo)e.UserState];
421            if (e is ProgressErrorEventArgs)
422            {
423                update.Error = ((ProgressErrorEventArgs)e).Exception;
424                update.LVItem.ImageIndex = 3;
425                update.LVItem.SubItems[1].Text = S._("Error: {0}", update.Error.Message);
426            }
427            else
428                switch (update.LVItem.ImageIndex)
429                {
430                    case -1:
431                        update.LVItem.ImageIndex = 1;
432                        break;
433                    case 1:
434                        update.LVItem.ImageIndex = 2;
435                        break;
436                }
437        }
438
439        /// <summary>
440        /// Re-enables the close dialog button.
441        /// </summary>
442        /// <param name="sender">The object triggering this event/</param>
443        /// <param name="e">Event argument.</param>
444        private void installer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
445        {
446            if (e.Error is OperationCanceledException)
447                Close();
448
449            installingPnl.UseWaitCursor = false;
450        }
451        #endregion
452
453        /// <summary>
454        /// The Update manager instance used by this form.
455        /// </summary>
456        UpdateManager updates = new UpdateManager();
457
458        /// <summary>
459        /// Maps listview items to the UpdateManager.Update object.
460        /// </summary>
461        Dictionary<UpdateInfo, UpdateData> uiUpdates = new Dictionary<UpdateInfo, UpdateData>();
462
463        /// <summary>
464        /// Manages information associated with the update.
465        /// </summary>
466        private class UpdateData
467        {
468            /// <summary>
469            /// Constructor.
470            /// </summary>
471            /// <param name="update">The UpdateManager.Update object containing the
472            /// internal representation of the update.</param>
473            /// <param name="item">The ListViewItem used for the display of the
474            /// update.</param>
475            public UpdateData(UpdateInfo update, ListViewItem item)
476            {
477                Update = update;
478                LVItem = item;
479            }
480
481            /// <summary>
482            /// The UpdateManager.Update object containing the internal representation
483            /// of the update.
484            /// </summary>
485            public UpdateInfo Update;
486
487            /// <summary>
488            /// The ListViewItem used for the display of the update.
489            /// </summary>
490            public ListViewItem LVItem;
491
492            /// <summary>
493            /// The amount of the download already completed.
494            /// </summary>
495            public long amountDownloaded;
496
497            /// <summary>
498            /// The error raised when downloading/installing the update, if any. Null
499            /// otherwise.
500            /// </summary>
501            public Exception Error;
502        }
503
504        /// <summary>
505        /// The number of updates selected for download.
506        /// </summary>
507        private int selectedUpdates = -1;
508
509        /// <summary>
510        /// The number of updates present in the previous count, so the Selected
511        /// Updates number can be deemed invalid.
512        /// </summary>
513        private int updatesCount = -1;
514    }
515
516    public class UpdateManager
517    {
518        /// <summary>
519        /// Constructor.
520        /// </summary>
521        public UpdateManager()
522        {
523            Updates = new UpdateCategoriesDictionary();
524        }
525
526        /// <summary>
527        /// Retrieves the update list from the server.
528        /// </summary>
529        public void DownloadUpdateList()
530        {
531            WebRequest.DefaultCachePolicy = new HttpRequestCachePolicy(
532                HttpRequestCacheLevel.Refresh);
533            HttpWebRequest req = (HttpWebRequest)
534                WebRequest.Create(new Uri("http://eraser.heidi.ie/scripts/updates?" +
535                    "action=listupdates&version=" +
536                    Assembly.GetExecutingAssembly().GetName().Version.ToString()));
537           
538            using (WebResponse response = req.GetResponse())
539            using (Stream responseStream = response.GetResponseStream())
540            using (MemoryStream memoryStream = new MemoryStream())
541            {
542                Manager.ProgressManager progress = new Manager.ProgressManager();
543                progress.Total = response.ContentLength;
544
545                //Download the response
546                int lastRead = 0;
547                byte[] buffer = new byte[16384];
548                while ((lastRead = responseStream.Read(buffer, 0, buffer.Length)) != 0)
549                {
550                    memoryStream.Write(buffer, 0, lastRead);
551                    progress.Completed = memoryStream.Position;
552                    OnProgress(new ProgressEventArgs(progress.Progress, progress.Progress, null,
553                        S._("{0} of {1} downloaded",
554                            Util.File.GetHumanReadableFilesize(progress.Completed),
555                            Util.File.GetHumanReadableFilesize(progress.Total))));
556                }
557
558                //Parse it.
559                memoryStream.Position = 0;
560                ParseUpdateList(memoryStream);
561            }
562        }
563
564        /// <summary>
565        /// Parses the list of updates provided by the server
566        /// </summary>
567        /// <param name="strm">The stream containing the XML data.</param>
568        private void ParseUpdateList(Stream strm)
569        {
570            //Move the XmlReader to the root node
571            Updates.Clear();
572            mirrors.Clear();
573            XmlReader rdr = XmlReader.Create(strm);
574            rdr.ReadToFollowing("updateList");
575
576            //Read the descendants of the updateList node (which are categories,
577            //except for the <mirrors> element)
578            XmlReader categories = rdr.ReadSubtree();
579            bool cont = categories.ReadToDescendant("mirrors");
580            while (cont)
581            {
582                if (categories.NodeType == XmlNodeType.Element)
583                {
584                    if (categories.Name == "mirrors")
585                    {
586                        Dictionary<string, string> mirrorsList =
587                            ParseMirror(categories.ReadSubtree());
588                        Dictionary<string, string>.Enumerator e = mirrorsList.GetEnumerator();
589                        while (e.MoveNext())
590                            this.mirrors.Add(e.Current.Key,
591                                new Mirror(e.Current.Value, e.Current.Key));
592                    }
593                    else
594                        Updates.Add(categories.Name, ParseUpdateCategory(categories.ReadSubtree()));
595                }
596
597                cont = categories.Read();
598            }
599        }
600
601        /// <summary>
602        /// Parses a list of mirrors.
603        /// </summary>
604        /// <param name="rdr">The XML reader object representing the &lt;mirrors&gt; node</param>
605        /// <returns>The list of mirrors defined by the element.</returns>
606        private static Dictionary<string, string> ParseMirror(XmlReader rdr)
607        {
608            Dictionary<string, string> result = new Dictionary<string,string>();
609            if (!rdr.ReadToDescendant("mirror"))
610                return result;
611
612            //Load every element.
613            do
614            {
615                if (rdr.NodeType != XmlNodeType.Element || rdr.Name != "mirror")
616                    continue;
617
618                string location = rdr.GetAttribute("location");
619                result.Add(rdr.ReadElementContentAsString(), location);
620            }
621            while (rdr.ReadToNextSibling("mirror"));
622
623            return result;
624        }
625
626        /// <summary>
627        /// Parses a specific category and its assocaited updates.
628        /// </summary>
629        /// <param name="rdr">The XML reader object representing the element and its children.</param>
630        /// <returns>A list of updates in the category.</returns>
631        private static UpdateCollection ParseUpdateCategory(XmlReader rdr)
632        {
633            UpdateCollection result = new UpdateCollection();
634            if (!rdr.ReadToDescendant("item"))
635                return result;
636
637            //Load every element.
638            do
639            {
640                if (rdr.Name != "item")
641                    continue;
642
643                UpdateInfo update = new UpdateInfo();
644                update.Name = rdr.GetAttribute("name");
645                update.Version = new Version(rdr.GetAttribute("version"));
646                update.Publisher = rdr.GetAttribute("publisher");
647                update.Architecture = rdr.GetAttribute("architecture");
648                update.FileSize = Convert.ToInt64(rdr.GetAttribute("filesize"),
649                    CultureInfo.InvariantCulture);
650                update.Link = rdr.ReadElementContentAsString();
651
652                result.Add(update);
653            }
654            while (rdr.ReadToNextSibling("item"));
655
656            return result;
657        }
658
659        /// <summary>
660        /// Downloads the list of updates.
661        /// </summary>
662        /// <param name="updates">The updates to retrieve and install.</param>
663        /// <returns>An opaque object for use with InstallUpdates.</returns>
664        public object DownloadUpdates(ICollection<UpdateInfo> downloadQueue)
665        {
666            //Create a folder to hold all our updates.
667            DirectoryInfo tempDir = new DirectoryInfo(Path.GetTempPath());
668            tempDir = tempDir.CreateSubdirectory("eraser" + Environment.TickCount.ToString(
669                CultureInfo.InvariantCulture));
670
671            int currUpdate = 0;
672            Dictionary<string, UpdateInfo> tempFilesMap = new Dictionary<string, UpdateInfo>();
673            Manager.SteppedProgressManager progress = new Manager.SteppedProgressManager();
674            foreach (UpdateInfo update in downloadQueue)
675            {
676                try
677                {
678                    //Add the update to the overall progress.
679                    Manager.ProgressManager step = new Eraser.Manager.ProgressManager();
680                    progress.Steps.Add(new Manager.SteppedProgressManager.Step(
681                        step, 1.0f / downloadQueue.Count));
682
683                    //Decide on the URL to connect to. The Link of the update may
684                    //be a relative path (relative to the selected mirror) or an
685                    //absolute path (which we have no choice)
686                    Uri reqUri = null;
687                    if (Uri.IsWellFormedUriString(update.Link, UriKind.Absolute))
688                        reqUri = new Uri(update.Link);
689                    else
690                        reqUri = new Uri(new Uri(SelectedMirror.Link), new Uri(update.Link));
691                   
692                    //Then grab the download.
693                    HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
694                    using (WebResponse resp = req.GetResponse())
695                    {
696                        //Check for a suggested filename.
697                        ContentDisposition contentDisposition = null;
698                        foreach (string header in resp.Headers.AllKeys)
699                            if (header.ToLowerInvariant() == "content-disposition")
700                                contentDisposition = new ContentDisposition(resp.Headers[header]);
701
702                        string tempFilePath = Path.Combine(
703                            tempDir.FullName, string.Format(CultureInfo.InvariantCulture, "{0}-{1}",
704                            ++currUpdate,
705                            contentDisposition == null ?
706                                Path.GetFileName(reqUri.GetComponents(UriComponents.Path,
707                                UriFormat.Unescaped)) : contentDisposition.FileName));
708
709                        using (Stream responseStream = resp.GetResponseStream())
710                        using (FileStream fileStream = new FileStream(tempFilePath, FileMode.CreateNew))
711                        {
712                            //Update the progress of this step
713                            step.Total = resp.ContentLength;
714
715                            //Copy the information into the file stream
716                            int lastRead = 0;
717                            byte[] buffer = new byte[16384];
718                            while ((lastRead = responseStream.Read(buffer, 0, buffer.Length)) != 0)
719                            {
720                                fileStream.Write(buffer, 0, lastRead);
721
722                                //Compute progress
723                                step.Completed = fileStream.Position;
724                                OnProgress(new ProgressEventArgs(step.Progress, progress.Progress,
725                                    update, S._("Downloading: {0}", update.Name)));
726                            }
727                        }
728
729                        //Store the filename-to-update mapping
730                        tempFilesMap.Add(tempFilePath, update);
731
732                        //Let the event handler know the download is complete.
733                        step.Completed = step.Total;
734                        OnProgress(new ProgressEventArgs(step.Progress, progress.Progress,
735                            update, S._("Downloaded: {0}", update.Name)));
736                    }
737                }
738                catch (Exception e)
739                {
740                    OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,
741                        (float)currUpdate / downloadQueue.Count, update,
742                            S._("Error downloading {0}: {1}", update.Name, e.Message)),
743                        e));
744                }
745            }
746
747            return tempFilesMap;
748        }
749
750        /// <summary>
751        /// Installs all updates downloaded.
752        /// </summary>
753        /// <param name="value">The value returned from a call to
754        /// <see cref="DownloadUpdates"/>.</param>
755        public void InstallUpdates(object value)
756        {
757            Manager.ProgressManager progress = new Manager.ProgressManager();
758            Dictionary<string, UpdateInfo> tempFiles = (Dictionary<string, UpdateInfo>)value;
759            Dictionary<string, UpdateInfo>.KeyCollection files = tempFiles.Keys;
760
761            try
762            {
763                progress.Total = files.Count;
764                foreach (string path in files)
765                {
766                    UpdateInfo item = tempFiles[path];
767                    ++progress.Completed;
768                    OnProgress(new ProgressEventArgs(0.0f, progress.Progress,
769                        item, S._("Installing {0}", item.Name)));
770
771                    System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
772                    info.FileName = path;
773                    info.UseShellExecute = true;
774
775                    System.Diagnostics.Process process = System.Diagnostics.Process.Start(info);
776                    process.WaitForExit(Int32.MaxValue);
777                    if (process.ExitCode == 0)
778                        OnProgress(new ProgressEventArgs(1.0f, progress.Progress,
779                            item, S._("Installed {0}", item.Name)));
780                    else
781                        OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,
782                            progress.Progress, item, S._("Error installing {0}", item.Name)),
783                            new ApplicationException(S._("The installer exited with an error code {0}",
784                                process.ExitCode))));
785                }
786            }
787            finally
788            {
789                //Clean up after ourselves
790                foreach (string file in files)
791                {
792                    DirectoryInfo tempDir = null;
793                    {
794                        FileInfo info = new FileInfo(file);
795                        tempDir = info.Directory;
796                    }
797
798                    tempDir.Delete(true);
799                    break;
800                }
801            }
802        }
803
804        /// <summary>
805        /// Called when the progress of the operation changes.
806        /// </summary>
807        public EventHandler<ProgressEventArgs> OnProgressEvent { get; set; }
808
809        /// <summary>
810        /// Helper function: invokes the OnProgressEvent delegate.
811        /// </summary>
812        /// <param name="arg">The ProgressEventArgs object holding information
813        /// about the progress of the current operation.</param>
814        private void OnProgress(ProgressEventArgs arg)
815        {
816            if (OnProgressEvent != null)
817                OnProgressEvent(this, arg);
818        }
819
820        /// <summary>
821        /// Retrieves the list of mirrors which the server has indicated to exist.
822        /// </summary>
823        public Dictionary<string, Mirror> Mirrors
824        {
825            get
826            {
827                return mirrors;
828            }
829        }
830
831        /// <summary>
832        /// Gets or sets the active mirror to use to download mirrored updates.
833        /// </summary>
834        public Mirror SelectedMirror
835        {
836            get
837            {
838                if (selectedMirror.Link.Length == 0)
839                {
840                    Dictionary<string, Mirror>.Enumerator iter = mirrors.GetEnumerator();
841                    if (iter.MoveNext())
842                        return iter.Current.Value;
843                }
844                return selectedMirror;
845            }
846            set
847            {
848                foreach (Mirror mirror in Mirrors.Values)
849                    if (mirror.Equals(value))
850                    {
851                        selectedMirror = value;
852                        return;
853                    }
854
855                throw new ArgumentException(S._("Unknown mirror selected."));
856            }
857        }
858
859        /// <summary>
860        /// Retrieves the categories available.
861        /// </summary>
862        public ICollection<string> Categories
863        {
864            get
865            {
866                return Updates.Keys;
867            }
868        }
869
870        /// <summary>
871        /// Retrieves all updates available.
872        /// </summary>
873        public UpdateCategoriesDictionary Updates { get; private set; }
874
875        /// <summary>
876        /// The list of mirrors to download updates from.
877        /// </summary>
878        private Dictionary<string, Mirror> mirrors =
879            new Dictionary<string, Mirror>();
880
881        /// <summary>
882        /// The currently selected mirror.
883        /// </summary>
884        private Mirror selectedMirror;
885    }
886
887    /// <summary>
888    /// Manages a list of categories, mapping categories to a list of updates.
889    /// </summary>
890    public class UpdateCategoriesDictionary : IDictionary<string, UpdateCollection>,
891        ICollection<KeyValuePair<string, UpdateCollection>>,
892        IEnumerable<KeyValuePair<string, UpdateCollection>>
893    {
894        #region IDictionary<string,UpdateList> Members
895        public void Add(string key, UpdateCollection value)
896        {
897            dictionary.Add(key, value);
898        }
899
900        public bool ContainsKey(string key)
901        {
902            return dictionary.ContainsKey(key);
903        }
904
905        public ICollection<string> Keys
906        {
907            get { return dictionary.Keys; }
908        }
909
910        public bool Remove(string key)
911        {
912            return dictionary.Remove(key);
913        }
914
915        public bool TryGetValue(string key, out UpdateCollection value)
916        {
917            return dictionary.TryGetValue(key, out value);
918        }
919
920        public ICollection<UpdateCollection> Values
921        {
922            get { return dictionary.Values; }
923        }
924
925        public UpdateCollection this[string key]
926        {
927            get
928            {
929                return dictionary[key];
930            }
931            set
932            {
933                dictionary[key] = value;
934            }
935        }
936        #endregion
937
938        #region ICollection<KeyValuePair<string,UpdateList>> Members
939        public void Add(KeyValuePair<string, UpdateCollection> item)
940        {
941            dictionary.Add(item.Key, item.Value);
942        }
943
944        public void Clear()
945        {
946            dictionary.Clear();
947        }
948
949        public bool Contains(KeyValuePair<string, UpdateCollection> item)
950        {
951            return dictionary.ContainsKey(item.Key) && dictionary[item.Key] == item.Value;
952        }
953
954        public void CopyTo(KeyValuePair<string, UpdateCollection>[] array, int arrayIndex)
955        {
956            throw new NotImplementedException();
957        }
958
959        public int Count
960        {
961            get { return dictionary.Count; }
962        }
963
964        public bool IsReadOnly
965        {
966            get { return true; }
967        }
968
969        public bool Remove(KeyValuePair<string, UpdateCollection> item)
970        {
971            return dictionary.Remove(item.Key);
972        }
973        #endregion
974
975        #region IEnumerable<KeyValuePair<string,UpdateList>> Members
976        public IEnumerator<KeyValuePair<string, UpdateCollection>> GetEnumerator()
977        {
978            return dictionary.GetEnumerator();
979        }
980        #endregion
981
982        #region IEnumerable Members
983        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
984        {
985            return GetEnumerator();
986        }
987        #endregion
988
989        /// <summary>
990        /// The store for the current object.
991        /// </summary>
992        private Dictionary<string, UpdateCollection> dictionary =
993            new Dictionary<string, UpdateCollection>();
994    }
995
996    /// <summary>
997    /// Manages a category, containing a list of updates.
998    /// </summary>
999    public class UpdateCollection : IList<UpdateInfo>, ICollection<UpdateInfo>,
1000        IEnumerable<UpdateInfo>
1001    {
1002        #region IList<UpdateInfo> Members
1003        public int IndexOf(UpdateInfo item)
1004        {
1005            return list.IndexOf(item);
1006        }
1007
1008        public void Insert(int index, UpdateInfo item)
1009        {
1010            list.Insert(index, item);
1011        }
1012
1013        public void RemoveAt(int index)
1014        {
1015            list.RemoveAt(index);
1016        }
1017
1018        public UpdateInfo this[int index]
1019        {
1020            get
1021            {
1022                return list[index];
1023            }
1024            set
1025            {
1026                list[index] = value;
1027            }
1028        }
1029        #endregion
1030
1031        #region ICollection<UpdateInfo> Members
1032        public void Add(UpdateInfo item)
1033        {
1034            list.Add(item);
1035        }
1036
1037        public void Clear()
1038        {
1039            list.Clear();
1040        }
1041
1042        public bool Contains(UpdateInfo item)
1043        {
1044            return list.Contains(item);
1045        }
1046
1047        public void CopyTo(UpdateInfo[] array, int arrayIndex)
1048        {
1049            list.CopyTo(array, arrayIndex);
1050        }
1051
1052        public int Count
1053        {
1054            get { return list.Count; }
1055        }
1056
1057        public bool IsReadOnly
1058        {
1059            get { return true; }
1060        }
1061
1062        public bool Remove(UpdateInfo item)
1063        {
1064            return list.Remove(item);
1065        }
1066        #endregion
1067
1068        #region IEnumerable<UpdateInfo> Members
1069        public IEnumerator<UpdateInfo> GetEnumerator()
1070        {
1071            return list.GetEnumerator();
1072        }
1073        #endregion
1074
1075        #region IEnumerable Members
1076        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
1077        {
1078            return list.GetEnumerator();
1079        }
1080        #endregion
1081
1082        /// <summary>
1083        /// The store for this object.
1084        /// </summary>
1085        private List<UpdateInfo> list = new List<UpdateInfo>();
1086    }
1087
1088    /// <summary>
1089    /// Represents a download mirror.
1090    /// </summary>
1091    public struct Mirror
1092    {
1093        public Mirror(string location, string link)
1094            : this()
1095        {
1096            Location = location;
1097            Link = link;
1098        }
1099
1100        /// <summary>
1101        /// The location where the mirror is at.
1102        /// </summary>
1103        public string Location { get; set; }
1104
1105        /// <summary>
1106        /// The URL prefix to utilise the mirror.
1107        /// </summary>
1108        public string Link { get; set; }
1109
1110        public override string ToString()
1111        {
1112            return Location;
1113        }
1114
1115        public override bool Equals(object obj)
1116        {
1117            if (!(obj is Mirror))
1118                return false;
1119            return Equals((Mirror)obj);
1120        }
1121
1122        public bool Equals(Mirror other)
1123        {
1124            return Link == other.Link;
1125        }
1126
1127        public static bool operator ==(Mirror mirror1, Mirror mirror2)
1128        {
1129            return mirror1.Equals(mirror2);
1130        }
1131
1132        public static bool operator !=(Mirror mirror1, Mirror mirror2)
1133        {
1134            return !mirror1.Equals(mirror2);
1135        }
1136
1137        public override int GetHashCode()
1138        {
1139            return Link.GetHashCode();
1140        }
1141    }
1142
1143    /// <summary>
1144    /// Represents an update available on the server.
1145    /// </summary>
1146    public struct UpdateInfo
1147    {
1148        public string Name { get; set; }
1149        public Version Version { get; set; }
1150        public string Publisher { get; set; }
1151        public string Architecture { get; set; }
1152        public long FileSize { get; set; }
1153        public string Link { get; set; }
1154
1155        public override bool Equals(object obj)
1156        {
1157            if (!(obj is UpdateInfo))
1158                return false;
1159            return Equals((UpdateInfo)obj);
1160        }
1161
1162        public bool Equals(UpdateInfo other)
1163        {
1164            return Link == other.Link;
1165        }
1166
1167        public static bool operator ==(UpdateInfo update1, UpdateInfo update2)
1168        {
1169            return update1.Equals(update2);
1170        }
1171
1172        public static bool operator !=(UpdateInfo update1, UpdateInfo update2)
1173        {
1174            return !update1.Equals(update2);
1175        }
1176
1177        public override int GetHashCode()
1178        {
1179            return Link.GetHashCode();
1180        }
1181    }
1182
1183    /// <summary>
1184    /// Specialised progress event argument, containing message describing
1185    /// current action, and overall progress percentage.
1186    /// </summary>
1187    public class ProgressEventArgs : ProgressChangedEventArgs
1188    {
1189        public ProgressEventArgs(float progressPercentage, float overallPercentage,
1190            object userState, string message)
1191            : base((int)(progressPercentage * 100), userState)
1192        {
1193            ProgressPercentage = progressPercentage;
1194            OverallProgressPercentage = overallPercentage;
1195            Message = message;
1196        }
1197
1198        /// <summary>
1199        /// Gets the asynchronous task progress percentage.
1200        /// </summary>
1201        public new float ProgressPercentage { get; private set; }
1202
1203        /// <summary>
1204        /// Gets the asynchronous task overall progress percentage.
1205        /// </summary>
1206        public float OverallProgressPercentage { get; private set; }
1207
1208        /// <summary>
1209        /// Gets the message associated with the current task.
1210        /// </summary>
1211        public string Message { get; private set; }
1212    }
1213
1214    /// <summary>
1215    /// Extends the ProgressEventArgs further by allowing for the inclusion of
1216    /// an exception.
1217    /// </summary>
1218    public class ProgressErrorEventArgs : ProgressEventArgs
1219    {
1220        /// <summary>
1221        /// Constructor.
1222        /// </summary>
1223        /// <param name="e">The base ProgressEventArgs object.</param>
1224        /// <param name="ex">The exception</param>
1225        public ProgressErrorEventArgs(ProgressEventArgs e, Exception ex)
1226            : base(e.ProgressPercentage, e.OverallProgressPercentage, e.UserState, e.Message)
1227        {
1228            Exception = ex;
1229        }
1230
1231        /// <summary>
1232        /// The exception associated with the progress event.
1233        /// </summary>
1234        public Exception Exception { get; private set; }
1235    }
1236}
Note: See TracBrowser for help on using the repository browser.