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

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

Bring the CodeReview? branch up to date with the current trunk at r1680

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