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

Revision 2720, 34.1 KB checked in by lowjoel, 2 years ago (diff)

Implement manual redirections. Automatic redirections are dropping the content-disposition header we need for the request.

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