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

Revision 864, 28.7 KB checked in by lowjoel, 6 years ago (diff)

Inform the user if there are no updates or plugins available for download.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
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
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
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
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            {
88                updates.OnProgressEvent += updateListDownloader_ProgressChanged;
89                updates.GetUpdates();
90            }
91            finally
92            {
93                updates.OnProgressEvent -= updateListDownloader_ProgressChanged;
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>
102        private void updateListDownloader_ProgressChanged(object sender, UpdateManager.ProgressEventArgs e)
103        {
104            if (InvokeRequired)
105            {
106                if (updateListDownloader.CancellationPending)
107                    throw new OperationCanceledException();
108
109                Invoke(new UpdateManager.ProgressEventFunction(updateListDownloader_ProgressChanged),
110                    sender, e);
111                return;
112            }
113
114            progressPb.Style = ProgressBarStyle.Continuous;
115            progressPb.Value = (int)(e.OverallProgressPercentage * 100);
116            progressProgressLbl.Text = e.Message;
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        {
130            //The Error property will normally be null unless there are errors during the download.
131            if (e.Error != null)
132            {
133                if (!(e.Error is OperationCanceledException))
134                    MessageBox.Show(this, e.Error.Message, S._("Eraser"),
135                        MessageBoxButtons.OK, MessageBoxIcon.Error);
136
137                Close();
138                return;
139            }
140
141            progressPanel.Visible = false;
142            updatesPanel.Visible = true;
143
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
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>();
154            updateCategories.Add("update", S._("Updates"));
155            updateCategories.Add("plugin", S._("Plugins"));
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);
196                    item.SubItems.Add(Util.File.GetHumanReadableFilesize(update.FileSize));
197
198                    item.Tag = update;
199                    item.Group = group;
200                    item.Checked = true;
201
202                    updatesLv.Items.Add(item);
203                    uiUpdates.Add(update, new UpdateData(update, item));
204                }
205            }
206
207            updatesBtn.Enabled = updatesLv.Items.Count > 0;
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            }
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<UpdateManager.Update> updatesToInstall =
250                new List<UpdateManager.Update>();
251
252            //Set the mirror
253            updates.SelectedMirror = (UpdateManager.Mirror)
254                updatesMirrorCmb.SelectedItem;
255
256            //Collect the items that need to be installed
257            foreach (ListViewItem item in updatesLv.Items)
258                if (item.Checked)
259                {
260                    item.Remove();
261                    item.SubItems.RemoveAt(1);
262                    item.SubItems.RemoveAt(1);
263                    downloadingLv.Items.Add(item);
264
265                    updatesToInstall.Add((UpdateManager.Update)item.Tag);
266                }
267                else
268                    uiUpdates.Remove((UpdateManager.Update)item.Tag);
269
270            //Then run the thread if there are updates.
271            if (updatesToInstall.Count > 0)
272                downloader.RunWorkerAsync(updatesToInstall);
273            else
274                Close();
275        }
276
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>
282        private void downloader_DoWork(object sender, DoWorkEventArgs e)
283        {
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            {
305                if (updateListDownloader.CancellationPending)
306                    throw new OperationCanceledException();
307
308                Invoke(new UpdateManager.ProgressEventFunction(downloader_ProgressChanged),
309                    sender, e);
310                return;
311            }
312
313            UpdateData update = uiUpdates[(UpdateManager.Update)e.UserState];
314            long amountLeft = (long)((1 - e.ProgressPercentage) * update.Update.FileSize);
315
316            if (e is UpdateManager.ProgressErrorEventArgs)
317            {
318                update.Error = ((UpdateManager.ProgressErrorEventArgs)e).Exception;
319                update.LVItem.ImageIndex = 3;
320                update.LVItem.SubItems[1].Text = S._("Error");
321                update.LVItem.ToolTipText = update.Error.Message;
322            }
323            else
324            {
325                if (e.ProgressPercentage >= 1.0f)
326                {
327                    update.LVItem.ImageIndex = -1;
328                    update.LVItem.SubItems[1].Text = S._("Downloaded");
329                }
330                else
331                {
332                    update.amountDownloaded = (long)(e.ProgressPercentage * update.Update.FileSize);
333                    update.LVItem.ImageIndex = 0;
334                    update.LVItem.SubItems[1].Text = Util.File.GetHumanReadableFilesize(
335                        update.Update.FileSize - update.amountDownloaded);
336                }
337            }
338
339            downloadingItemLbl.Text = e.Message;
340            downloadingItemPb.Value = (int)(e.ProgressPercentage * 100);
341            downloadingOverallPb.Value = (int)(e.OverallProgressPercentage * 100);
342
343            long amountToDownload = 0;
344            foreach (UpdateData upd in uiUpdates.Values)
345                amountToDownload += upd.Update.FileSize - upd.amountDownloaded;
346            downloadingOverallLbl.Text = S._("Overall progress: {0} left",
347                Util.File.GetHumanReadableFilesize(amountToDownload));
348        }
349
350        /// <summary>
351        /// Handles the completion of updating event
352        /// </summary>
353        /// <param name="sender">The object triggering this event/</param>
354        /// <param name="e">Event argument.</param>
355        private void downloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
356        {
357            if (e.Error != null)
358            {
359                if (!(e.Error is OperationCanceledException))
360                    MessageBox.Show(this, e.Error.Message, S._("Eraser"),
361                        MessageBoxButtons.OK, MessageBoxIcon.Error);
362
363                Close();
364                return;
365            }
366
367            downloadingPnl.Visible = false;
368            installingPnl.Visible = true;
369
370            foreach (ListViewItem item in downloadingLv.Items)
371            {
372                item.Remove();
373                installingLv.Items.Add(item);
374
375                UpdateData update = uiUpdates[(UpdateManager.Update)item.Tag];
376                if (update.Error == null)
377                    item.SubItems[1].Text = string.Empty;
378                else
379                    item.SubItems[1].Text = S._("Error: {0}", update.Error.Message);
380            }
381
382            installer.RunWorkerAsync(e.Result);
383        }
384        #endregion
385
386        #region Update installer
387        /// <summary>
388        /// Background thread to install downloaded updates
389        /// </summary>
390        /// <param name="sender">The object triggering this event/</param>
391        /// <param name="e">Event argument.</param>
392        private void installer_DoWork(object sender, DoWorkEventArgs e)
393        {
394            try
395            {
396                updates.OnProgressEvent += installer_ProgressChanged;
397                updates.InstallUpdates(e.Argument);
398            }
399            finally
400            {
401                updates.OnProgressEvent -= installer_ProgressChanged;
402            }
403        }
404
405        /// <summary>
406        /// Handles the progress events generated during update installation.
407        /// </summary>
408        /// <param name="sender">The object triggering this event/</param>
409        /// <param name="e">Event argument.</param>
410        private void installer_ProgressChanged(object sender, ProgressChangedEventArgs e)
411        {
412            if (InvokeRequired)
413            {
414                if (updateListDownloader.CancellationPending)
415                    throw new OperationCanceledException();
416
417                Invoke(new UpdateManager.ProgressEventFunction(installer_ProgressChanged),
418                    sender, e);
419                return;
420            }
421
422            UpdateData update = uiUpdates[(UpdateManager.Update)e.UserState];
423            if (e is UpdateManager.ProgressErrorEventArgs)
424            {
425                update.Error = ((UpdateManager.ProgressErrorEventArgs)e).Exception;
426                update.LVItem.ImageIndex = 3;
427                update.LVItem.SubItems[1].Text = S._("Error: {0}", update.Error.Message);
428            }
429            else
430                switch (update.LVItem.ImageIndex)
431                {
432                    case -1:
433                        update.LVItem.ImageIndex = 1;
434                        break;
435                    case 1:
436                        update.LVItem.ImageIndex = 2;
437                        break;
438                }
439        }
440
441        /// <summary>
442        /// Re-enables the close dialog button.
443        /// </summary>
444        /// <param name="sender">The object triggering this event/</param>
445        /// <param name="e">Event argument.</param>
446        private void installer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
447        {
448            if (e.Error is OperationCanceledException)
449                Close();
450
451            installingPnl.UseWaitCursor = false;
452        }
453        #endregion
454
455        /// <summary>
456        /// The Update manager instance used by this form.
457        /// </summary>
458        UpdateManager updates;
459
460        /// <summary>
461        /// Maps listview items to the UpdateManager.Update object.
462        /// </summary>
463        Dictionary<UpdateManager.Update, UpdateData> uiUpdates =
464            new Dictionary<UpdateManager.Update, UpdateData>();
465
466        /// <summary>
467        /// Manages information associated with the update.
468        /// </summary>
469        private class UpdateData
470        {
471            /// <summary>
472            /// Constructor.
473            /// </summary>
474            /// <param name="update">The UpdateManager.Update object containing the
475            /// internal representation of the update.</param>
476            /// <param name="item">The ListViewItem used for the display of the
477            /// update.</param>
478            public UpdateData(UpdateManager.Update update, ListViewItem item)
479            {
480                Update = update;
481                LVItem = item;
482                amountDownloaded = 0;
483                Error = null;
484            }
485
486            /// <summary>
487            /// The UpdateManager.Update object containing the internal representation
488            /// of the update.
489            /// </summary>
490            public UpdateManager.Update Update;
491
492            /// <summary>
493            /// The ListViewItem used for the display of the update.
494            /// </summary>
495            public ListViewItem LVItem;
496
497            /// <summary>
498            /// The amount of the download already completed.
499            /// </summary>
500            public long amountDownloaded;
501
502            /// <summary>
503            /// The error raised when downloading/installing the update, if any. Null
504            /// otherwise.
505            /// </summary>
506            public Exception Error;
507        }
508
509        /// <summary>
510        /// The number of updates selected for download.
511        /// </summary>
512        private int selectedUpdates = -1;
513
514        /// <summary>
515        /// The number of updates present in the previous count, so the Selected
516        /// Updates number can be deemed invalid.
517        /// </summary>
518        private int updatesCount = -1;
519    }
520
521    public class UpdateManager
522    {
523        /// <summary>
524        /// Represents a download mirror.
525        /// </summary>
526        public struct Mirror
527        {
528            public Mirror(string location, string link)
529            {
530                Location = location;
531                Link = link;
532            }
533
534            /// <summary>
535            /// The location where the mirror is at.
536            /// </summary>
537            public string Location;
538
539            /// <summary>
540            /// The URL prefix to utilise the mirror.
541            /// </summary>
542            public string Link;
543
544            public override string ToString()
545            {
546                return Location;
547            }
548        }
549
550        /// <summary>
551        /// Represents an update available on the server.
552        /// </summary>
553        public struct Update
554        {
555            public string Name;
556            public Version Version;
557            public string Publisher;
558            public string Architecture;
559            public long FileSize;
560            public string Link;
561        }
562
563        /// <summary>
564        /// Specialised progress event argument, containing message describing
565        /// current action, and overall progress percentage.
566        /// </summary>
567        public class ProgressEventArgs : ProgressChangedEventArgs
568        {
569            public ProgressEventArgs(float progressPercentage, float overallPercentage,
570                object userState, string message)
571                : base((int)(progressPercentage * 100), userState)
572            {
573                this.progressPercentage = progressPercentage;
574                this.overallProgressPercentage = overallPercentage;
575                this.message = message;
576            }
577
578            /// <summary>
579            /// Gets the asynchronous task progress percentage.
580            /// </summary>
581            public new float ProgressPercentage
582            {
583                get
584                {
585                    return progressPercentage;
586                }
587            }
588
589            /// <summary>
590            /// Gets the asynchronous task overall progress percentage.
591            /// </summary>
592            public float OverallProgressPercentage
593            {
594                get
595                {
596                    return overallProgressPercentage;
597                }
598            }
599
600            /// <summary>
601            /// Gets the message associated with the current task.
602            /// </summary>
603            public string Message
604            {
605                get
606                {
607                    return message;
608                }
609            }
610
611            float progressPercentage;
612            float overallProgressPercentage;
613            string message;
614        }
615
616        /// <summary>
617        /// Extends the ProgressEventArgs further by allowing for the inclusion of
618        /// an exception.
619        /// </summary>
620        public class ProgressErrorEventArgs : ProgressEventArgs
621        {
622            /// <summary>
623            /// Constructor.
624            /// </summary>
625            /// <param name="e">The base ProgressEventArgs object.</param>
626            /// <param name="ex">The exception</param>
627            public ProgressErrorEventArgs(ProgressEventArgs e, Exception ex)
628                : base(e.ProgressPercentage, e.OverallProgressPercentage, e.UserState, e.Message)
629            {
630                this.exception = ex;
631            }
632
633            /// <summary>
634            /// The exception associated with the progress event.
635            /// </summary>
636            public Exception Exception
637            {
638                get
639                {
640                    return exception;
641                }
642            }
643
644            private Exception exception;
645        }
646
647        /// <summary>
648        /// Retrieves the update list from the server.
649        /// </summary>
650        public void GetUpdates()
651        {
652            WebRequest.DefaultCachePolicy = new HttpRequestCachePolicy(
653                HttpRequestCacheLevel.Refresh);
654            HttpWebRequest req = (HttpWebRequest)
655                WebRequest.Create("http://eraser.heidi.ie/updates?action=listupdates&" +
656                    "version=" + Assembly.GetExecutingAssembly().GetName().Version.ToString());
657           
658            using (WebResponse resp = req.GetResponse())
659            using (Stream strm = resp.GetResponseStream())
660            {
661                //Download the response
662                int bytesRead = 0;
663                byte[] buffer = new byte[16384];
664                List<byte> responseBuffer = new List<byte>();
665                while ((bytesRead = strm.Read(buffer, 0, buffer.Length)) != 0)
666                {
667                    byte[] tmpDest = new byte[bytesRead];
668                    Buffer.BlockCopy(buffer, 0, tmpDest, 0, bytesRead);
669                    responseBuffer.AddRange(tmpDest);
670
671                    float progress = responseBuffer.Count / (float)resp.ContentLength;
672                    OnProgress(new ProgressEventArgs(progress, progress, null,
673                        S._("{0} of {1} downloaded",
674                            Util.File.GetHumanReadableFilesize(responseBuffer.Count),
675                            Util.File.GetHumanReadableFilesize(resp.ContentLength))));
676                }
677
678                //Parse it.
679                using (MemoryStream mStrm = new MemoryStream(responseBuffer.ToArray()))
680                    ParseUpdateList(mStrm);
681            }
682        }
683
684        /// <summary>
685        /// Parses the list of updates provided by the server
686        /// </summary>
687        /// <param name="strm">The stream containing the XML data.</param>
688        private void ParseUpdateList(Stream strm)
689        {
690            //Move the XmlReader to the root node
691            updates.Clear();
692            mirrors.Clear();
693            XmlReader rdr = XmlReader.Create(strm);
694            rdr.ReadToFollowing("updateList");
695
696            //Read the descendants of the updateList node (which are categories,
697            //except for the <mirrors> element)
698            XmlReader categories = rdr.ReadSubtree();
699            bool cont = categories.ReadToDescendant("mirrors");
700            while (cont)
701            {
702                if (categories.NodeType == XmlNodeType.Element)
703                {
704                    if (categories.Name == "mirrors")
705                    {
706                        Dictionary<string, string> mirrorsList =
707                            ParseMirror(categories.ReadSubtree());
708                        Dictionary<string, string>.Enumerator e = mirrorsList.GetEnumerator();
709                        while (e.MoveNext())
710                            this.mirrors.Add(e.Current.Key,
711                                new Mirror(e.Current.Value, e.Current.Key));
712                    }
713                    else
714                        updates.Add(categories.Name, ParseUpdateCategory(categories.ReadSubtree()));
715                }
716
717                cont = categories.Read();
718            }
719        }
720
721        /// <summary>
722        /// Parses a list of mirrors.
723        /// </summary>
724        /// <param name="rdr">The XML reader object representing the &lt;mirrors&gt; node</param>
725        /// <returns>The list of mirrors defined by the element.</returns>
726        private static Dictionary<string, string> ParseMirror(XmlReader rdr)
727        {
728            Dictionary<string, string> result = new Dictionary<string,string>();
729            if (!rdr.ReadToDescendant("mirror"))
730                return result;
731
732            //Load every element.
733            do
734            {
735                if (rdr.NodeType != XmlNodeType.Element || rdr.Name != "mirror")
736                    continue;
737
738                string location = rdr.GetAttribute("location");
739                result.Add(rdr.ReadElementContentAsString(), location);
740            }
741            while (rdr.ReadToNextSibling("mirror"));
742
743            return result;
744        }
745
746        /// <summary>
747        /// Parses a specific category and its assocaited updates.
748        /// </summary>
749        /// <param name="rdr">The XML reader object representing the element and its children.</param>
750        /// <returns>A list of updates in the category.</returns>
751        private static List<Update> ParseUpdateCategory(XmlReader rdr)
752        {
753            List<Update> result = new List<Update>();
754            if (!rdr.ReadToDescendant("item"))
755                return result;
756
757            //Load every element.
758            do
759            {
760                if (rdr.Name != "item")
761                    continue;
762
763                Update update = new Update();
764                update.Name = rdr.GetAttribute("name");
765                update.Version = new Version(rdr.GetAttribute("version"));
766                update.Publisher = rdr.GetAttribute("publisher");
767                update.Architecture = rdr.GetAttribute("architecture");
768                update.FileSize = Convert.ToInt64(rdr.GetAttribute("filesize"));
769                update.Link = rdr.ReadElementContentAsString();
770
771                result.Add(update);
772            }
773            while (rdr.ReadToNextSibling("item"));
774
775            return result;
776        }
777
778        /// <summary>
779        /// Downloads the list of updates.
780        /// </summary>
781        /// <param name="updates">The updates to retrieve and install.</param>
782        /// <returns>An opaque object for use with InstallUpdates.</returns>
783        public object DownloadUpdates(List<Update> updates)
784        {
785            //Create a folder to hold all our updates.
786            DirectoryInfo tempDir = new DirectoryInfo(Path.GetTempPath());
787            tempDir = tempDir.CreateSubdirectory("eraser" + Environment.TickCount.ToString());
788
789            int currUpdate = 0;
790            Dictionary<string, Update> tempFilesMap = new Dictionary<string, Update>();
791            foreach (Update update in updates)
792            {
793                try
794                {
795                    //Decide on the URL to connect to. The Link of the update may
796                    //be a relative path (relative to the selected mirror) or an
797                    //absolute path (which we have no choice)
798                    Uri reqUri = null;
799                    if (Uri.IsWellFormedUriString(update.Link, UriKind.Absolute))
800                        reqUri = new Uri(update.Link);
801                    else
802                        reqUri = new Uri(new Uri(SelectedMirror.Link), update.Link);
803                   
804                    //Then grab the download.
805                    HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);
806                    using (WebResponse resp = req.GetResponse())
807                    {
808                        byte[] tempBuffer = new byte[16384];
809                        string tempFilePath = Path.Combine(
810                            tempDir.FullName, string.Format("{0}-{1}", ++currUpdate,
811                            Path.GetFileName(reqUri.GetComponents(UriComponents.Path, UriFormat.Unescaped))));
812
813                        using (Stream strm = resp.GetResponseStream())
814                        using (FileStream tempStrm = new FileStream(tempFilePath, FileMode.CreateNew))
815                        using (BufferedStream bufStrm = new BufferedStream(tempStrm))
816                        {
817                            //Copy the information into the file stream
818                            int readBytes = 0;
819                            while ((readBytes = strm.Read(tempBuffer, 0, tempBuffer.Length)) != 0)
820                            {
821                                bufStrm.Write(tempBuffer, 0, readBytes);
822
823                                //Compute progress
824                                float itemProgress = tempStrm.Position / (float)resp.ContentLength;
825                                float overallProgress = (currUpdate - 1 + itemProgress) / updates.Count;
826                                OnProgress(new ProgressEventArgs(itemProgress, overallProgress,
827                                    update, S._("Downloading: {0}", update.Name)));
828                            }
829                        }
830
831                        //Store the filename-to-update mapping
832                        tempFilesMap.Add(tempFilePath, update);
833
834                        //Let the event handler know the download is complete.
835                        OnProgress(new ProgressEventArgs(1.0f, (float)currUpdate / updates.Count,
836                            update, S._("Downloaded: {0}", update.Name)));
837                    }
838                }
839                catch (Exception e)
840                {
841                    OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,
842                        (float)currUpdate / updates.Count, update,
843                            S._("Error downloading {0}: {1}", update.Name, e.Message)),
844                        e));
845                }
846            }
847
848            return tempFilesMap;
849        }
850
851        public void InstallUpdates(object downloadObject)
852        {
853            Dictionary<string, Update> tempFiles = (Dictionary<string, Update>)downloadObject;
854            Dictionary<string, Update>.KeyCollection files = tempFiles.Keys;
855            int currItem = 0;
856
857            try
858            {
859                foreach (string path in files)
860                {
861                    Update item = tempFiles[path];
862                    float progress = (float)currItem++ / files.Count;
863                    OnProgress(new ProgressEventArgs(0.0f, progress,
864                        item, S._("Installing {0}", item.Name)));
865
866                    System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
867                    info.FileName = path;
868                    info.UseShellExecute = true;
869
870                    System.Diagnostics.Process process = System.Diagnostics.Process.Start(info);
871                    process.WaitForExit(Int32.MaxValue);
872                    if (process.ExitCode == 0)
873                        OnProgress(new ProgressEventArgs(1.0f, progress,
874                            item, S._("Installed {0}", item.Name)));
875                    else
876                        OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,
877                            progress, item, S._("Error installing {0}", item.Name)),
878                            new Exception(S._("The installer exited with an error code {0}",
879                                process.ExitCode))));
880                }
881            }
882            finally
883            {
884                //Clean up after ourselves
885                foreach (string file in files)
886                {
887                    DirectoryInfo tempDir = null;
888                    {
889                        FileInfo info = new FileInfo(file);
890                        tempDir = info.Directory;
891                    }
892
893                    tempDir.Delete(true);
894                    break;
895                }
896            }
897        }
898
899        /// <summary>
900        /// Prototype of the callback functions from this object.
901        /// </summary>
902        /// <param name="sender">The source of the event.</param>
903        /// <param name="arg">The ProgressEventArgs object holding information
904        /// about the progress of the current operation.</param>
905        public delegate void ProgressEventFunction(object sender, ProgressEventArgs arg);
906
907        /// <summary>
908        /// Called when the progress of the operation changes.
909        /// </summary>
910        public ProgressEventFunction OnProgressEvent;
911
912        /// <summary>
913        /// Helper function: invokes the OnProgressEvent delegate.
914        /// </summary>
915        /// <param name="arg">The ProgressEventArgs object holding information
916        /// about the progress of the current operation.</param>
917        private void OnProgress(ProgressEventArgs arg)
918        {
919            if (OnProgressEvent != null)
920                OnProgressEvent(this, arg);
921        }
922
923        /// <summary>
924        /// Retrieves the list of mirrors which the server has indicated to exist.
925        /// </summary>
926        public Dictionary<string, Mirror> Mirrors
927        {
928            get
929            {
930                return mirrors;
931            }
932        }
933
934        /// <summary>
935        /// Gets or sets the active mirror to use to download mirrored updates.
936        /// </summary>
937        public Mirror SelectedMirror
938        {
939            get
940            {
941                if (selectedMirror.Link.Length == 0)
942                {
943                    Dictionary<string, Mirror>.Enumerator iter = mirrors.GetEnumerator();
944                    if (iter.MoveNext())
945                        return iter.Current.Value;
946                }
947                return selectedMirror;
948            }
949            set
950            {
951                foreach (Mirror mirror in Mirrors.Values)
952                    if (mirror.Equals(value))
953                    {
954                        selectedMirror = value;
955                        return;
956                    }
957
958                throw new IndexOutOfRangeException();
959            }
960        }
961
962        /// <summary>
963        /// Retrieves the categories available.
964        /// </summary>
965        public Dictionary<string, List<Update>>.KeyCollection Categories
966        {
967            get
968            {
969                return updates.Keys;
970            }
971        }
972
973        /// <summary>
974        /// Retrieves all updates available.
975        /// </summary>
976        public Dictionary<string, List<Update>> Updates
977        {
978            get
979            {
980                return updates;
981            }
982        }
983
984        /// <summary>
985        /// Retrieves the updates in the given category.
986        /// </summary>
987        /// <param name="key">The category to retrieve.</param>
988        /// <returns>All updates in the given category.</returns>
989        public List<Update> this[string key]
990        {
991            get
992            {
993                return updates[key];
994            }
995        }
996
997        /// <summary>
998        /// The list of mirrors to download updates from.
999        /// </summary>
1000        private Dictionary<string, Mirror> mirrors =
1001            new Dictionary<string, Mirror>();
1002
1003        /// <summary>
1004        /// The currently selected mirror.
1005        /// </summary>
1006        private Mirror selectedMirror;
1007
1008        /// <summary>
1009        /// The list of updates downloaded.
1010        /// </summary>
1011        private Dictionary<string, List<Update>> updates =
1012            new Dictionary<string, List<Update>>();
1013    }
1014}
Note: See TracBrowser for help on using the repository browser.