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

Revision 659, 28.4 KB checked in by lowjoel, 6 years ago (diff)

Darn, let my local testing server slip into code.

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