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

Revision 824, 28.5 KB checked in by lowjoel, 6 years ago (diff)

Enable the Install button only if updates are present for installation. Fixes #107.

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