source: branches/eraser6/6.0/Eraser/UpdateForm.cs @ 2305

Revision 2305, 33.3 KB checked in by lowjoel, 3 years ago (diff)

Supplant r2304 for future versions of Eraser were we may not want to buffer our output to include the content-length header.

  • 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
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(new EventHandler<ProgressEventArgs>(
111                    updateListDownloader_ProgressChanged), 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(new 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(new 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/updates?action=listupdates&" +
535                    "version=" + Assembly.GetExecutingAssembly().GetName().Version.ToString()));
536           
537            using (WebResponse resp = req.GetResponse())
538            using (Stream strm = resp.GetResponseStream())
539            {
540                //Download the response
541                int bytesRead = 0;
542                byte[] buffer = new byte[16384];
543                List<byte> responseBuffer = new List<byte>();
544                while ((bytesRead = strm.Read(buffer, 0, buffer.Length)) != 0)
545                {
546                    byte[] tmpDest = new byte[bytesRead];
547                    Buffer.BlockCopy(buffer, 0, tmpDest, 0, bytesRead);
548                    responseBuffer.AddRange(tmpDest);
549
550                    //Skip the progress report if the server did not specify how long the
551                    //response is, such as when using chunked transfers
552                    if (resp.ContentLength == -1)
553                        continue;
554
555                    float progress = responseBuffer.Count / (float)resp.ContentLength;
556                    OnProgress(new ProgressEventArgs(progress, progress, null,
557                        S._("{0} of {1} downloaded",
558                            Util.File.GetHumanReadableFilesize(responseBuffer.Count),
559                            Util.File.GetHumanReadableFilesize(resp.ContentLength))));
560                }
561
562                //Parse it.
563                using (MemoryStream mStrm = new MemoryStream(responseBuffer.ToArray()))
564                    ParseUpdateList(mStrm);
565            }
566        }
567
568        /// <summary>
569        /// Parses the list of updates provided by the server
570        /// </summary>
571        /// <param name="strm">The stream containing the XML data.</param>
572        private void ParseUpdateList(Stream strm)
573        {
574            //Move the XmlReader to the root node
575            Updates.Clear();
576            mirrors.Clear();
577            XmlReader rdr = XmlReader.Create(strm);
578            rdr.ReadToFollowing("updateList");
579
580            //Read the descendants of the updateList node (which are categories,
581            //except for the <mirrors> element)
582            XmlReader categories = rdr.ReadSubtree();
583            bool cont = categories.ReadToDescendant("mirrors");
584            while (cont)
585            {
586                if (categories.NodeType == XmlNodeType.Element)
587                {
588                    if (categories.Name == "mirrors")
589                    {
590                        Dictionary<string, string> mirrorsList =
591                            ParseMirror(categories.ReadSubtree());
592                        Dictionary<string, string>.Enumerator e = mirrorsList.GetEnumerator();
593                        while (e.MoveNext())
594                            this.mirrors.Add(e.Current.Key,
595                                new Mirror(e.Current.Value, e.Current.Key));
596                    }
597                    else
598                        Updates.Add(categories.Name, ParseUpdateCategory(categories.ReadSubtree()));
599                }
600
601                cont = categories.Read();
602            }
603        }
604
605        /// <summary>
606        /// Parses a list of mirrors.
607        /// </summary>
608        /// <param name="rdr">The XML reader object representing the &lt;mirrors&gt; node</param>
609        /// <returns>The list of mirrors defined by the element.</returns>
610        private static Dictionary<string, string> ParseMirror(XmlReader rdr)
611        {
612            Dictionary<string, string> result = new Dictionary<string,string>();
613            if (!rdr.ReadToDescendant("mirror"))
614                return result;
615
616            //Load every element.
617            do
618            {
619                if (rdr.NodeType != XmlNodeType.Element || rdr.Name != "mirror")
620                    continue;
621
622                string location = rdr.GetAttribute("location");
623                result.Add(rdr.ReadElementContentAsString(), location);
624            }
625            while (rdr.ReadToNextSibling("mirror"));
626
627            return result;
628        }
629
630        /// <summary>
631        /// Parses a specific category and its assocaited updates.
632        /// </summary>
633        /// <param name="rdr">The XML reader object representing the element and its children.</param>
634        /// <returns>A list of updates in the category.</returns>
635        private static UpdateCollection ParseUpdateCategory(XmlReader rdr)
636        {
637            UpdateCollection result = new UpdateCollection();
638            if (!rdr.ReadToDescendant("item"))
639                return result;
640
641            //Load every element.
642            do
643            {
644                if (rdr.Name != "item")
645                    continue;
646
647                UpdateInfo update = new UpdateInfo();
648                update.Name = rdr.GetAttribute("name");
649                update.Version = new Version(rdr.GetAttribute("version"));
650                update.Publisher = rdr.GetAttribute("publisher");
651                update.Architecture = rdr.GetAttribute("architecture");
652                update.FileSize = Convert.ToInt64(rdr.GetAttribute("filesize"),
653                    CultureInfo.InvariantCulture);
654                update.Link = rdr.ReadElementContentAsString();
655
656                result.Add(update);
657            }
658            while (rdr.ReadToNextSibling("item"));
659
660            return result;
661        }
662
663        /// <summary>
664        /// Downloads the list of updates.
665        /// </summary>
666        /// <param name="updates">The updates to retrieve and install.</param>
667        /// <returns>An opaque object for use with InstallUpdates.</returns>
668        public object DownloadUpdates(ICollection<UpdateInfo> downloadQueue)
669        {
670            //Create a folder to hold all our updates.
671            DirectoryInfo tempDir = new DirectoryInfo(Path.GetTempPath());
672            tempDir = tempDir.CreateSubdirectory("eraser" + Environment.TickCount.ToString(
673                CultureInfo.InvariantCulture));
674
675            int currUpdate = 0;
676            Dictionary<string, UpdateInfo> tempFilesMap = new Dictionary<string, UpdateInfo>();
677            foreach (UpdateInfo update in downloadQueue)
678            {
679                try
680                {
681                    //Decide on the URL to connect to. The Link of the update may
682                    //be a relative path (relative to the selected mirror) or an
683                    //absolute path (which we have no choice)
684                    Uri reqUri = null;
685                    if (Uri.IsWellFormedUriString(update.Link, UriKind.Absolute))
686                        reqUri = new Uri(update.Link);
687                    else
688                        reqUri = new Uri(new Uri(SelectedMirror.Link), new Uri(update.Link));
689                   
690                    //Then grab the download.
691                    HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
692                    using (WebResponse resp = req.GetResponse())
693                    {
694                        //Check for a suggested filename.
695                        ContentDisposition contentDisposition = null;
696                        foreach (string header in resp.Headers.AllKeys)
697                            if (header.ToLowerInvariant() == "content-disposition")
698                                contentDisposition = new ContentDisposition(resp.Headers[header]);
699
700                        string tempFilePath = Path.Combine(
701                            tempDir.FullName, string.Format(CultureInfo.InvariantCulture, "{0}-{1}",
702                            ++currUpdate,
703                            contentDisposition == null ?
704                                Path.GetFileName(reqUri.GetComponents(UriComponents.Path,
705                                UriFormat.Unescaped)) : contentDisposition.FileName));
706
707                        byte[] tempBuffer = new byte[16384];
708                        using (Stream strm = resp.GetResponseStream())
709                        using (FileStream tempStrm = new FileStream(tempFilePath, FileMode.CreateNew))
710                        using (BufferedStream bufStrm = new BufferedStream(tempStrm))
711                        {
712                            //Copy the information into the file stream
713                            int readBytes = 0;
714                            while ((readBytes = strm.Read(tempBuffer, 0, tempBuffer.Length)) != 0)
715                            {
716                                bufStrm.Write(tempBuffer, 0, readBytes);
717
718                                //Compute progress
719                                float itemProgress = tempStrm.Position / (float)resp.ContentLength;
720                                float overallProgress = (currUpdate - 1 + itemProgress) / downloadQueue.Count;
721                                OnProgress(new ProgressEventArgs(itemProgress, overallProgress,
722                                    update, S._("Downloading: {0}", update.Name)));
723                            }
724                        }
725
726                        //Store the filename-to-update mapping
727                        tempFilesMap.Add(tempFilePath, update);
728
729                        //Let the event handler know the download is complete.
730                        OnProgress(new ProgressEventArgs(1.0f, (float)currUpdate / downloadQueue.Count,
731                            update, S._("Downloaded: {0}", update.Name)));
732                    }
733                }
734                catch (Exception e)
735                {
736                    OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,
737                        (float)currUpdate / downloadQueue.Count, update,
738                            S._("Error downloading {0}: {1}", update.Name, e.Message)),
739                        e));
740                }
741            }
742
743            return tempFilesMap;
744        }
745
746        /// <summary>
747        /// Installs all updates downloaded.
748        /// </summary>
749        /// <param name="value">The value returned from a call to
750        /// <see cref="DownloadUpdates"/>.</param>
751        public void InstallUpdates(object value)
752        {
753            Dictionary<string, UpdateInfo> tempFiles = (Dictionary<string, UpdateInfo>)value;
754            Dictionary<string, UpdateInfo>.KeyCollection files = tempFiles.Keys;
755            int currItem = 0;
756
757            try
758            {
759                foreach (string path in files)
760                {
761                    UpdateInfo item = tempFiles[path];
762                    float progress = (float)currItem++ / files.Count;
763                    OnProgress(new ProgressEventArgs(0.0f, progress,
764                        item, S._("Installing {0}", item.Name)));
765
766                    System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
767                    info.FileName = path;
768                    info.UseShellExecute = true;
769
770                    System.Diagnostics.Process process = System.Diagnostics.Process.Start(info);
771                    process.WaitForExit(Int32.MaxValue);
772                    if (process.ExitCode == 0)
773                        OnProgress(new ProgressEventArgs(1.0f, progress,
774                            item, S._("Installed {0}", item.Name)));
775                    else
776                        OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,
777                            progress, item, S._("Error installing {0}", item.Name)),
778                            new ApplicationException(S._("The installer exited with an error code {0}",
779                                process.ExitCode))));
780                }
781            }
782            finally
783            {
784                //Clean up after ourselves
785                foreach (string file in files)
786                {
787                    DirectoryInfo tempDir = null;
788                    {
789                        FileInfo info = new FileInfo(file);
790                        tempDir = info.Directory;
791                    }
792
793                    tempDir.Delete(true);
794                    break;
795                }
796            }
797        }
798
799        /// <summary>
800        /// Called when the progress of the operation changes.
801        /// </summary>
802        public EventHandler<ProgressEventArgs> OnProgressEvent { get; set; }
803
804        /// <summary>
805        /// Helper function: invokes the OnProgressEvent delegate.
806        /// </summary>
807        /// <param name="arg">The ProgressEventArgs object holding information
808        /// about the progress of the current operation.</param>
809        private void OnProgress(ProgressEventArgs arg)
810        {
811            if (OnProgressEvent != null)
812                OnProgressEvent(this, arg);
813        }
814
815        /// <summary>
816        /// Retrieves the list of mirrors which the server has indicated to exist.
817        /// </summary>
818        public Dictionary<string, Mirror> Mirrors
819        {
820            get
821            {
822                return mirrors;
823            }
824        }
825
826        /// <summary>
827        /// Gets or sets the active mirror to use to download mirrored updates.
828        /// </summary>
829        public Mirror SelectedMirror
830        {
831            get
832            {
833                if (selectedMirror.Link.Length == 0)
834                {
835                    Dictionary<string, Mirror>.Enumerator iter = mirrors.GetEnumerator();
836                    if (iter.MoveNext())
837                        return iter.Current.Value;
838                }
839                return selectedMirror;
840            }
841            set
842            {
843                foreach (Mirror mirror in Mirrors.Values)
844                    if (mirror.Equals(value))
845                    {
846                        selectedMirror = value;
847                        return;
848                    }
849
850                throw new ArgumentException(S._("Unknown mirror selected."));
851            }
852        }
853
854        /// <summary>
855        /// Retrieves the categories available.
856        /// </summary>
857        public ICollection<string> Categories
858        {
859            get
860            {
861                return Updates.Keys;
862            }
863        }
864
865        /// <summary>
866        /// Retrieves all updates available.
867        /// </summary>
868        public UpdateCategoriesDictionary Updates { get; private set; }
869
870        /// <summary>
871        /// The list of mirrors to download updates from.
872        /// </summary>
873        private Dictionary<string, Mirror> mirrors =
874            new Dictionary<string, Mirror>();
875
876        /// <summary>
877        /// The currently selected mirror.
878        /// </summary>
879        private Mirror selectedMirror;
880    }
881
882    /// <summary>
883    /// Manages a list of categories, mapping categories to a list of updates.
884    /// </summary>
885    public class UpdateCategoriesDictionary : IDictionary<string, UpdateCollection>,
886        ICollection<KeyValuePair<string, UpdateCollection>>,
887        IEnumerable<KeyValuePair<string, UpdateCollection>>
888    {
889        #region IDictionary<string,UpdateList> Members
890        public void Add(string key, UpdateCollection value)
891        {
892            dictionary.Add(key, value);
893        }
894
895        public bool ContainsKey(string key)
896        {
897            return dictionary.ContainsKey(key);
898        }
899
900        public ICollection<string> Keys
901        {
902            get { return dictionary.Keys; }
903        }
904
905        public bool Remove(string key)
906        {
907            return dictionary.Remove(key);
908        }
909
910        public bool TryGetValue(string key, out UpdateCollection value)
911        {
912            return dictionary.TryGetValue(key, out value);
913        }
914
915        public ICollection<UpdateCollection> Values
916        {
917            get { return dictionary.Values; }
918        }
919
920        public UpdateCollection this[string key]
921        {
922            get
923            {
924                return dictionary[key];
925            }
926            set
927            {
928                dictionary[key] = value;
929            }
930        }
931        #endregion
932
933        #region ICollection<KeyValuePair<string,UpdateList>> Members
934        public void Add(KeyValuePair<string, UpdateCollection> item)
935        {
936            dictionary.Add(item.Key, item.Value);
937        }
938
939        public void Clear()
940        {
941            dictionary.Clear();
942        }
943
944        public bool Contains(KeyValuePair<string, UpdateCollection> item)
945        {
946            return dictionary.ContainsKey(item.Key) && dictionary[item.Key] == item.Value;
947        }
948
949        public void CopyTo(KeyValuePair<string, UpdateCollection>[] array, int arrayIndex)
950        {
951            throw new NotImplementedException();
952        }
953
954        public int Count
955        {
956            get { return dictionary.Count; }
957        }
958
959        public bool IsReadOnly
960        {
961            get { return true; }
962        }
963
964        public bool Remove(KeyValuePair<string, UpdateCollection> item)
965        {
966            return dictionary.Remove(item.Key);
967        }
968        #endregion
969
970        #region IEnumerable<KeyValuePair<string,UpdateList>> Members
971        public IEnumerator<KeyValuePair<string, UpdateCollection>> GetEnumerator()
972        {
973            return dictionary.GetEnumerator();
974        }
975        #endregion
976
977        #region IEnumerable Members
978        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
979        {
980            return GetEnumerator();
981        }
982        #endregion
983
984        /// <summary>
985        /// The store for the current object.
986        /// </summary>
987        private Dictionary<string, UpdateCollection> dictionary =
988            new Dictionary<string, UpdateCollection>();
989    }
990
991    /// <summary>
992    /// Manages a category, containing a list of updates.
993    /// </summary>
994    public class UpdateCollection : IList<UpdateInfo>, ICollection<UpdateInfo>,
995        IEnumerable<UpdateInfo>
996    {
997        #region IList<UpdateInfo> Members
998        public int IndexOf(UpdateInfo item)
999        {
1000            return list.IndexOf(item);
1001        }
1002
1003        public void Insert(int index, UpdateInfo item)
1004        {
1005            list.Insert(index, item);
1006        }
1007
1008        public void RemoveAt(int index)
1009        {
1010            list.RemoveAt(index);
1011        }
1012
1013        public UpdateInfo this[int index]
1014        {
1015            get
1016            {
1017                return list[index];
1018            }
1019            set
1020            {
1021                list[index] = value;
1022            }
1023        }
1024        #endregion
1025
1026        #region ICollection<UpdateInfo> Members
1027        public void Add(UpdateInfo item)
1028        {
1029            list.Add(item);
1030        }
1031
1032        public void Clear()
1033        {
1034            list.Clear();
1035        }
1036
1037        public bool Contains(UpdateInfo item)
1038        {
1039            return list.Contains(item);
1040        }
1041
1042        public void CopyTo(UpdateInfo[] array, int arrayIndex)
1043        {
1044            list.CopyTo(array, arrayIndex);
1045        }
1046
1047        public int Count
1048        {
1049            get { return list.Count; }
1050        }
1051
1052        public bool IsReadOnly
1053        {
1054            get { return true; }
1055        }
1056
1057        public bool Remove(UpdateInfo item)
1058        {
1059            return list.Remove(item);
1060        }
1061        #endregion
1062
1063        #region IEnumerable<UpdateInfo> Members
1064        public IEnumerator<UpdateInfo> GetEnumerator()
1065        {
1066            return list.GetEnumerator();
1067        }
1068        #endregion
1069
1070        #region IEnumerable Members
1071        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
1072        {
1073            return list.GetEnumerator();
1074        }
1075        #endregion
1076
1077        /// <summary>
1078        /// The store for this object.
1079        /// </summary>
1080        private List<UpdateInfo> list = new List<UpdateInfo>();
1081    }
1082
1083    /// <summary>
1084    /// Represents a download mirror.
1085    /// </summary>
1086    public struct Mirror
1087    {
1088        public Mirror(string location, string link)
1089            : this()
1090        {
1091            Location = location;
1092            Link = link;
1093        }
1094
1095        /// <summary>
1096        /// The location where the mirror is at.
1097        /// </summary>
1098        public string Location { get; set; }
1099
1100        /// <summary>
1101        /// The URL prefix to utilise the mirror.
1102        /// </summary>
1103        public string Link { get; set; }
1104
1105        public override string ToString()
1106        {
1107            return Location;
1108        }
1109
1110        public override bool Equals(object obj)
1111        {
1112            if (!(obj is Mirror))
1113                return false;
1114            return Equals((Mirror)obj);
1115        }
1116
1117        public bool Equals(Mirror other)
1118        {
1119            return Link == other.Link;
1120        }
1121
1122        public static bool operator ==(Mirror mirror1, Mirror mirror2)
1123        {
1124            return mirror1.Equals(mirror2);
1125        }
1126
1127        public static bool operator !=(Mirror mirror1, Mirror mirror2)
1128        {
1129            return !mirror1.Equals(mirror2);
1130        }
1131
1132        public override int GetHashCode()
1133        {
1134            return Link.GetHashCode();
1135        }
1136    }
1137
1138    /// <summary>
1139    /// Represents an update available on the server.
1140    /// </summary>
1141    public struct UpdateInfo
1142    {
1143        public string Name { get; set; }
1144        public Version Version { get; set; }
1145        public string Publisher { get; set; }
1146        public string Architecture { get; set; }
1147        public long FileSize { get; set; }
1148        public string Link { get; set; }
1149
1150        public override bool Equals(object obj)
1151        {
1152            if (!(obj is UpdateInfo))
1153                return false;
1154            return Equals((UpdateInfo)obj);
1155        }
1156
1157        public bool Equals(UpdateInfo other)
1158        {
1159            return Link == other.Link;
1160        }
1161
1162        public static bool operator ==(UpdateInfo update1, UpdateInfo update2)
1163        {
1164            return update1.Equals(update2);
1165        }
1166
1167        public static bool operator !=(UpdateInfo update1, UpdateInfo update2)
1168        {
1169            return !update1.Equals(update2);
1170        }
1171
1172        public override int GetHashCode()
1173        {
1174            return Link.GetHashCode();
1175        }
1176    }
1177
1178    /// <summary>
1179    /// Specialised progress event argument, containing message describing
1180    /// current action, and overall progress percentage.
1181    /// </summary>
1182    public class ProgressEventArgs : ProgressChangedEventArgs
1183    {
1184        public ProgressEventArgs(float progressPercentage, float overallPercentage,
1185            object userState, string message)
1186            : base((int)(progressPercentage * 100), userState)
1187        {
1188            ProgressPercentage = progressPercentage;
1189            OverallProgressPercentage = overallPercentage;
1190            Message = message;
1191        }
1192
1193        /// <summary>
1194        /// Gets the asynchronous task progress percentage.
1195        /// </summary>
1196        public new float ProgressPercentage { get; private set; }
1197
1198        /// <summary>
1199        /// Gets the asynchronous task overall progress percentage.
1200        /// </summary>
1201        public float OverallProgressPercentage { get; private set; }
1202
1203        /// <summary>
1204        /// Gets the message associated with the current task.
1205        /// </summary>
1206        public string Message { get; private set; }
1207    }
1208
1209    /// <summary>
1210    /// Extends the ProgressEventArgs further by allowing for the inclusion of
1211    /// an exception.
1212    /// </summary>
1213    public class ProgressErrorEventArgs : ProgressEventArgs
1214    {
1215        /// <summary>
1216        /// Constructor.
1217        /// </summary>
1218        /// <param name="e">The base ProgressEventArgs object.</param>
1219        /// <param name="ex">The exception</param>
1220        public ProgressErrorEventArgs(ProgressEventArgs e, Exception ex)
1221            : base(e.ProgressPercentage, e.OverallProgressPercentage, e.UserState, e.Message)
1222        {
1223            Exception = ex;
1224        }
1225
1226        /// <summary>
1227        /// The exception associated with the progress event.
1228        /// </summary>
1229        public Exception Exception { get; private set; }
1230    }
1231}
Note: See TracBrowser for help on using the repository browser.