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

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

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

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

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