Changeset 1747
- Timestamp:
- 2/4/2010 2:31:56 AM (2 years ago)
- File:
-
- 1 edited
-
branches/eraser6/CodeReview/Eraser/UpdateForm.cs (modified) (21 diffs)
Legend:
- Unmodified
- Added
- Removed
-
branches/eraser6/CodeReview/Eraser/UpdateForm.cs
r1709 r1747 22 22 using System; 23 23 using System.Collections.Generic; 24 using System.ComponentModel;25 using System.Data;26 using System.Drawing;27 using System.Text;28 24 using System.Windows.Forms; 29 using System.Net; 25 26 using System.Diagnostics; 30 27 using System.Reflection; 31 28 using System.IO; 29 using System.Linq; 32 30 using System.Xml; 33 using Eraser.Util; 31 32 using System.Net; 34 33 using System.Net.Cache; 35 34 using System.Net.Mime; 36 35 using System.Globalization; 37 36 38 using ProgressChangedEventArgs = System.ComponentModel.ProgressChangedEventArgs; 37 using Eraser.Util; 38 39 using ProgressChangedEventArgs = Eraser.Util.ProgressChangedEventArgs; 40 using DoWorkEventArgs = System.ComponentModel.DoWorkEventArgs; 41 using RunWorkerCompletedEventArgs = System.ComponentModel.RunWorkerCompletedEventArgs; 39 42 40 43 namespace Eraser … … 87 90 private void updateListDownloader_DoWork(object sender, DoWorkEventArgs e) 88 91 { 89 try 90 { 91 updates.OnProgressEvent += updateListDownloader_ProgressChanged; 92 updates.DownloadUpdateList(); 93 } 94 finally 95 { 96 updates.OnProgressEvent -= updateListDownloader_ProgressChanged; 97 } 92 e.Result = DownloadManager.GetDownloads(updateListDownloader_ProgressChanged); 98 93 } 99 94 … … 103 98 /// <param name="sender">The object triggering this event/</param> 104 99 /// <param name="e">Event argument.</param> 105 private void updateListDownloader_ProgressChanged(object sender, Progress EventArgs e)100 private void updateListDownloader_ProgressChanged(object sender, ProgressChangedEventArgs e) 106 101 { 107 102 if (InvokeRequired) … … 110 105 throw new OperationCanceledException(); 111 106 112 Invoke((EventHandler<Progress EventArgs>)updateListDownloader_ProgressChanged,107 Invoke((EventHandler<ProgressChangedEventArgs>)updateListDownloader_ProgressChanged, 113 108 sender, e); 114 109 return; … … 116 111 117 112 progressPb.Style = ProgressBarStyle.Continuous; 118 progressPb.Value = (int)(e. OverallProgressPercentage* 100);119 progressProgressLbl.Text = e. Message;113 progressPb.Value = (int)(e.Progress.Progress * 100); 114 progressProgressLbl.Text = e.UserState as string; 120 115 121 116 if (progressPb.Value == 100) … … 147 142 148 143 //First list all available mirrors 149 Dictionary<string, Mirror>.Enumerator iter = updates.Mirrors.GetEnumerator(); 150 while (iter.MoveNext()) 151 updatesMirrorCmb.Items.Add(iter.Current.Value); 152 updatesMirrorCmb.SelectedIndex = 0; 144 IList<DownloadInfo> downloads = (IList<DownloadInfo>)e.Result; 153 145 154 146 //Get a list of translatable categories (this will change as more categories 155 147 //are added) 156 Dictionary< string, string> updateCategories = new Dictionary<string, string>();157 updateCategories.Add("update", S._("Updates"));158 updateCategories.Add("plugin", S._("Plugins"));148 Dictionary<DownloadType, string> categories = new Dictionary<DownloadType, string>(); 149 categories.Add(DownloadType.Update, S._("Updates")); 150 categories.Add(DownloadType.Plugin, S._("Plugins")); 159 151 160 152 //Only include those whose architecture is compatible with ours. 161 List<string> compatibleArchs = new List<string>();153 List<string> architectures = new List<string>(); 162 154 { 163 155 //any is always compatible. 164 compatibleArchs.Add("any");156 architectures.Add("any"); 165 157 166 158 switch (SystemInfo.ProcessorArchitecture) 167 159 { 168 160 case ProcessorArchitecture.Amd64: 169 compatibleArchs.Add("x64");161 architectures.Add("x64"); 170 162 break; 171 163 case ProcessorArchitecture.IA64: 172 compatibleArchs.Add("ia64");164 architectures.Add("ia64"); 173 165 break; 174 166 case ProcessorArchitecture.X86: 175 compatibleArchs.Add("x86");167 architectures.Add("x86"); 176 168 break; 177 169 } 178 170 } 179 171 180 foreach (string key in updates.Categories) 181 { 182 ListViewGroup group = new ListViewGroup(updateCategories.ContainsKey(key) ? 183 updateCategories[key] : key); 184 updatesLv.Groups.Add(group); 185 186 foreach (UpdateInfo update in updates.Updates[key]) 187 { 188 //Skip if this update won't work on our current architecture. 189 if (compatibleArchs.IndexOf(update.Architecture) == -1) 190 continue; 191 192 ListViewItem item = new ListViewItem(update.Name); 193 item.SubItems.Add(update.Version.ToString()); 194 item.SubItems.Add(update.Publisher); 195 item.SubItems.Add(FileSize.ToString(update.FileSize)); 196 197 item.Tag = update; 198 item.Group = group; 199 item.Checked = true; 200 201 updatesLv.Items.Add(item); 202 uiUpdates.Add(update, new UpdateData(update, item)); 203 } 172 foreach (DownloadInfo download in downloads) 173 { 174 //Skip this download if it is not for our current architecture. 175 if (architectures.IndexOf(download.Architecture) == -1) 176 continue; 177 178 //Create or retrieve the ListViewGroup object to categorise our downloads. 179 string categoryText = categories.ContainsKey(download.Type) ? 180 categories[download.Type] : download.Type.ToString(); 181 int groupIndex = updatesLv.Groups.IndexOf(new ListViewGroup(categoryText)); 182 if (groupIndex == -1) 183 groupIndex = updatesLv.Groups.Add(new ListViewGroup(categoryText)); 184 ListViewGroup group = updatesLv.Groups[groupIndex]; 185 186 //Add the item to the list of downloads available. 187 ListViewItem item = new ListViewItem(download.Name); 188 item.SubItems.Add(download.Version.ToString()); 189 item.SubItems.Add(download.Publisher); 190 item.SubItems.Add(FileSize.ToString(download.FileSize)); 191 192 item.Tag = download; 193 item.Group = group; 194 item.Checked = true; 195 196 updatesLv.Items.Add(item); 204 197 } 205 198 … … 226 219 private void updatesLv_ItemChecked(object sender, ItemCheckedEventArgs e) 227 220 { 228 if (selectedUpdates == -1 || updatesCount != updatesLv.Items.Count) 229 { 230 updatesCount = updatesLv.Items.Count; 231 selectedUpdates = 0; 232 foreach (ListViewItem item in updatesLv.Items) 233 if (item.Checked) 234 ++selectedUpdates; 235 } 236 else 237 selectedUpdates += e.Item.Checked ? 1 : -1; 238 updatesBtn.Text = selectedUpdates == 0 ? S._("Close") : S._("Install"); 221 updatesBtn.Text = updatesLv.CheckedIndices.Count == 0 ? S._("Close") : S._("Install"); 239 222 } 240 223 … … 248 231 updatesPanel.Visible = false; 249 232 downloadingPnl.Visible = true; 250 List<UpdateInfo> updatesToInstall = new List<UpdateInfo>(); 251 252 //Set the mirror 253 updates.SelectedMirror = (Mirror)updatesMirrorCmb.SelectedItem; 233 List<DownloadInfo> updatesToInstall = new List<DownloadInfo>(); 254 234 255 235 //Collect the items that need to be installed 256 foreach (ListViewItem item in updatesLv.Items) 257 if (item.Checked) 258 { 259 item.Remove(); 260 item.SubItems.RemoveAt(1); 261 item.SubItems.RemoveAt(1); 262 downloadingLv.Items.Add(item); 263 264 updatesToInstall.Add((UpdateInfo)item.Tag); 265 } 266 else 267 uiUpdates.Remove((UpdateInfo)item.Tag); 236 foreach (ListViewItem item in updatesLv.CheckedItems) 237 { 238 item.Remove(); 239 item.SubItems.RemoveAt(1); 240 item.SubItems.RemoveAt(1); 241 downloadingLv.Items.Add(item); 242 243 DownloadInfo download = (DownloadInfo)item.Tag; 244 updatesToInstall.Add(download); 245 DownloadItems.Add(download, new DownloadUIInfo(download, item)); 246 } 268 247 269 248 //Then run the thread if there are updates. … … 281 260 private void downloader_DoWork(object sender, DoWorkEventArgs e) 282 261 { 283 try 284 { 285 updates.OnProgressEvent += downloader_ProgressChanged; 286 object downloadedUpdates = updates.DownloadUpdates((List<UpdateInfo>)e.Argument); 287 e.Result = downloadedUpdates; 288 } 289 finally 290 { 291 updates.OnProgressEvent -= downloader_ProgressChanged; 292 } 262 List<DownloadInfo> downloads = (List<DownloadInfo>)e.Argument; 263 SteppedProgressManager overallProgress = new SteppedProgressManager(); 264 long totalDownloadSize = downloads.Sum(delegate(DownloadInfo download) 265 { 266 return download.FileSize; 267 }); 268 269 foreach (DownloadInfo download in downloads) 270 { 271 ProgressManagerBase downloadProgress = null; 272 ProgressChangedEventHandler localHandler = 273 delegate(object sender2, ProgressChangedEventArgs e2) 274 { 275 DownloadInfo downloadInfo = (DownloadInfo)sender2; 276 if (downloadProgress == null) 277 { 278 downloadProgress = e2.Progress; 279 overallProgress.Steps.Add(new SteppedProgressManagerStep( 280 e2.Progress, download.FileSize / (float)totalDownloadSize)); 281 } 282 283 downloader_ProgressChanged(sender2, 284 new ProgressChangedEventArgs(overallProgress, e2.UserState)); 285 }; 286 287 download.Download(localHandler); 288 } 289 290 e.Result = e.Argument; 293 291 } 294 292 … … 298 296 /// <param name="sender">The object triggering this event/</param> 299 297 /// <param name="e">Event argument.</param> 300 private void downloader_ProgressChanged(object sender, Progress EventArgs e)298 private void downloader_ProgressChanged(object sender, ProgressChangedEventArgs e) 301 299 { 302 300 if (InvokeRequired) … … 305 303 throw new OperationCanceledException(); 306 304 307 Invoke((EventHandler<Progress EventArgs>)downloader_ProgressChanged,305 Invoke((EventHandler<ProgressChangedEventArgs>)downloader_ProgressChanged, 308 306 sender, e); 309 307 return; 310 308 } 311 309 312 UpdateData update = uiUpdates[(UpdateInfo)e.UserState]; 313 314 if (e is ProgressErrorEventArgs) 315 { 316 update.Error = ((ProgressErrorEventArgs)e).Exception; 317 update.LVItem.ImageIndex = 3; 318 update.LVItem.SubItems[1].Text = S._("Error"); 319 update.LVItem.ToolTipText = update.Error.Message; 310 DownloadInfo download = (DownloadInfo)sender; 311 DownloadUIInfo downloadUIInfo = DownloadItems[download]; 312 SteppedProgressManager overallProgress = (SteppedProgressManager)e.Progress; 313 314 if (e.UserState is Exception) 315 { 316 downloadUIInfo.ListViewItem.ImageIndex = 3; 317 downloadUIInfo.ListViewItem.SubItems[1].Text = S._("Error"); 318 downloadUIInfo.ListViewItem.ToolTipText = ((Exception)e.UserState).Message; 320 319 } 321 320 else 322 321 { 323 if (e.Progress Percentage>= 1.0f)324 { 325 update.LVItem.ImageIndex = -1;326 update.LVItem.SubItems[1].Text = S._("Downloaded");322 if (e.Progress.Progress >= 1.0f) 323 { 324 downloadUIInfo.ListViewItem.ImageIndex = -1; 325 downloadUIInfo.ListViewItem.SubItems[1].Text = S._("Downloaded"); 327 326 } 328 327 else 329 328 { 330 update.amountDownloaded = (long)(e.ProgressPercentage * update.Update.FileSize);331 update.LVItem.ImageIndex = 0;332 update.LVItem.SubItems[1].Text = FileSize.ToString(333 update.Update.FileSize - update.amountDownloaded);329 downloadUIInfo.Downloaded = (long)(overallProgress.Progress * download.FileSize); 330 downloadUIInfo.ListViewItem.ImageIndex = 0; 331 downloadUIInfo.ListViewItem.SubItems[1].Text = FileSize.ToString(download.FileSize - 332 downloadUIInfo.Downloaded); 334 333 } 335 334 } 336 335 337 downloadingItemLbl.Text = e.Message; 338 downloadingItemPb.Value = (int)(e.ProgressPercentage * 100); 339 downloadingOverallPb.Value = (int)(e.OverallProgressPercentage * 100); 340 341 long amountToDownload = 0; 342 foreach (UpdateData upd in uiUpdates.Values) 343 amountToDownload += upd.Update.FileSize - upd.amountDownloaded; 336 downloadingItemLbl.Text = S._("Downloading: {0}", download.Name); 337 downloadingItemPb.Value = (int)(overallProgress.CurrentStep.Progress.Progress * 100); 338 downloadingOverallPb.Value = (int)(overallProgress.Progress * 100); 344 339 downloadingOverallLbl.Text = S._("Overall progress: {0} left", 345 FileSize.ToString(amountToDownload)); 346 } 347 348 /// <summary> 349 /// Handles the completion of updating event 340 FileSize.ToString(DownloadItems.Values.Sum(delegate(DownloadUIInfo item) 341 { 342 return item.Download.FileSize - item.Downloaded; 343 } 344 ))); 345 } 346 347 /// <summary> 348 /// Handles the completion of download event 350 349 /// </summary> 351 350 /// <param name="sender">The object triggering this event/</param> … … 368 367 installingPnl.Visible = true; 369 368 370 foreach (ListViewItem item in downloadingLv.Items) 371 { 372 item.Remove(); 373 installingLv.Items.Add(item); 374 375 UpdateData update = uiUpdates[(UpdateInfo)item.Tag]; 376 if (update.Error == null) 377 item.SubItems[1].Text = string.Empty; 369 foreach (DownloadUIInfo download in DownloadItems.Values) 370 { 371 download.ListViewItem.Remove(); 372 installingLv.Items.Add(download.ListViewItem); 373 374 if (download.Error == null) 375 download.ListViewItem.SubItems[1].Text = string.Empty; 378 376 else 379 item.SubItems[1].Text = S._("Error: {0}", update.Error.Message);377 download.ListViewItem.SubItems[1].Text = S._("Error: {0}", download.Error.Message); 380 378 } 381 379 … … 392 390 private void installer_DoWork(object sender, DoWorkEventArgs e) 393 391 { 394 try 395 { 396 updates.OnProgressEvent += installer_ProgressChanged; 397 updates.InstallUpdates(e.Argument); 398 } 399 finally 400 { 401 updates.OnProgressEvent -= installer_ProgressChanged; 402 } 392 List<DownloadInfo> downloads = (List<DownloadInfo>)e.Argument; 393 ProgressManager progress = new ProgressManager(); 394 progress.Total = downloads.Count; 395 396 foreach (DownloadInfo download in downloads) 397 { 398 ++progress.Completed; 399 int exitCode = download.Install(); 400 401 if (exitCode == 0) 402 installer_ProgressChanged(download, 403 new ProgressChangedEventArgs(progress, null)); 404 else 405 installer_ProgressChanged(download, 406 new ProgressChangedEventArgs(progress, 407 new ApplicationException(S._( 408 "The installer exited with an error code {0}", exitCode)))); 409 } 410 411 e.Result = e.Argument; 403 412 } 404 413 … … 415 424 throw new OperationCanceledException(); 416 425 417 Invoke((EventHandler<Progress EventArgs>)installer_ProgressChanged,426 Invoke((EventHandler<ProgressChangedEventArgs>)installer_ProgressChanged, 418 427 sender, e); 419 428 return; 420 429 } 421 430 422 UpdateData update = uiUpdates[(UpdateInfo)e.UserState]; 423 if (e is ProgressErrorEventArgs) 424 { 425 update.Error = ((ProgressErrorEventArgs)e).Exception; 426 update.LVItem.ImageIndex = 3; 427 update.LVItem.SubItems[1].Text = S._("Error: {0}", update.Error.Message); 431 DownloadInfo download = (DownloadInfo)sender; 432 DownloadUIInfo downloadUIInfo = DownloadItems[download]; 433 434 if (e.UserState is Exception) 435 { 436 downloadUIInfo.Error = (Exception)e.UserState; 437 downloadUIInfo.ListViewItem.ImageIndex = 3; 438 downloadUIInfo.ListViewItem.SubItems[1].Text = 439 S._("Error: {0}", downloadUIInfo.Error.Message); 428 440 } 429 441 else 430 switch (update.LVItem.ImageIndex) 442 { 443 downloadUIInfo.ListViewItem.SubItems[1].Text = S._("Installed {0}", download.Name); 444 switch (downloadUIInfo.ListViewItem.ImageIndex) 431 445 { 432 446 case -1: 433 update.LVItem.ImageIndex = 1;447 downloadUIInfo.ListViewItem.ImageIndex = 1; 434 448 break; 435 449 case 1: 436 update.LVItem.ImageIndex = 2;450 downloadUIInfo.ListViewItem.ImageIndex = 2; 437 451 break; 438 452 } 453 } 439 454 } 440 455 … … 454 469 455 470 /// <summary> 456 /// The Update manager instance used by this form.457 /// </summary>458 UpdateManager updates = new UpdateManager();459 460 /// <summary>461 /// Maps listview items to the UpdateManager.Update object.462 /// </summary>463 Dictionary<UpdateInfo, UpdateData> uiUpdates = new Dictionary<UpdateInfo, UpdateData>();464 465 /// <summary>466 471 /// Manages information associated with the update. 467 472 /// </summary> 468 private class UpdateData473 private class DownloadUIInfo 469 474 { 470 475 /// <summary> 471 476 /// Constructor. 472 477 /// </summary> 473 /// <param name="update">The UpdateManager.Updateobject containing the474 /// in ternal representation of the update.</param>478 /// <param name="update">The DownloadInfo object containing the 479 /// information about the download.</param> 475 480 /// <param name="item">The ListViewItem used for the display of the 476 481 /// update.</param> 477 public UpdateData(UpdateInfo update, ListViewItem item)478 { 479 Update = update;480 L VItem = item;482 public DownloadUIInfo(DownloadInfo download, ListViewItem item) 483 { 484 Download = download; 485 ListViewItem = item; 481 486 } 482 487 483 488 /// <summary> 484 /// The UpdateManager.Update object containing the internal representation 485 /// of the update. 489 /// The DownloadInfo object containing information about the update. 486 490 /// </summary> 487 public UpdateInfo Update;491 public DownloadInfo Download { get; private set; } 488 492 489 493 /// <summary> 490 494 /// The ListViewItem used for the display of the update. 491 495 /// </summary> 492 public ListViewItem L VItem;496 public ListViewItem ListViewItem { get; private set; } 493 497 494 498 /// <summary> 495 499 /// The amount of the download already completed. 496 500 /// </summary> 497 public long amountDownloaded;501 public long Downloaded { get; set; } 498 502 499 503 /// <summary> … … 501 505 /// otherwise. 502 506 /// </summary> 503 public Exception Error; 504 } 505 506 /// <summary> 507 /// The number of updates selected for download. 508 /// </summary> 509 private int selectedUpdates = -1; 510 511 /// <summary> 512 /// The number of updates present in the previous count, so the Selected 513 /// Updates number can be deemed invalid. 514 /// </summary> 515 private int updatesCount = -1; 507 public Exception Error { get; set; } 508 } 509 510 /// <summary> 511 /// Maps downloads to the list view items. 512 /// </summary> 513 private Dictionary<DownloadInfo, DownloadUIInfo> DownloadItems = 514 new Dictionary<DownloadInfo, DownloadUIInfo>(); 516 515 } 517 516 518 public class UpdateManager 517 /// <summary> 518 /// Manages the list of downloads that can be retrieved from the Eraser update server. 519 /// </summary> 520 public static class DownloadManager 519 521 { 520 /// <summary> 521 /// Constructor. 522 /// </summary> 523 public UpdateManager() 524 { 525 Updates = new UpdateCategoriesDictionary(); 526 } 527 528 /// <summary> 529 /// Retrieves the update list from the server. 530 /// </summary> 531 public void DownloadUpdateList() 522 public static IList<DownloadInfo> GetDownloads(Eraser.Util.ProgressChangedEventHandler handler) 532 523 { 533 524 WebRequest.DefaultCachePolicy = new HttpRequestCachePolicy( 534 HttpRequestCacheLevel.Refresh); 535 HttpWebRequest req = (HttpWebRequest) 536 WebRequest.Create(new Uri("http://eraser.heidi.ie/scripts/updates?" + 537 "action=listupdates&version=" + 525 HttpRequestCacheLevel.Revalidate); 526 HttpWebRequest request = (HttpWebRequest)WebRequest.Create( 527 new Uri("http://eraser.heidi.ie/scripts/updates?action=listupdates&version=" + 538 528 Assembly.GetExecutingAssembly().GetName().Version.ToString())); 539 540 using ( WebResponse response = req.GetResponse())529 530 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 541 531 using (Stream responseStream = response.GetResponseStream()) 542 532 using (MemoryStream memoryStream = new MemoryStream()) … … 552 542 memoryStream.Write(buffer, 0, lastRead); 553 543 progress.Completed = memoryStream.Position; 554 OnProgress(new ProgressEventArgs(progress.Progress, progress.Progress, null,555 S._("{0} of {1} downloaded",556 FileSize.ToString(progress.Completed),557 FileSize.ToString(progress.Total))));544 if (handler != null) 545 handler(null, new Eraser.Util.ProgressChangedEventArgs(progress, 546 S._("{0} of {1} downloaded", FileSize.ToString(progress.Completed), 547 FileSize.ToString(progress.Total)))); 558 548 } 559 549 560 550 //Parse it. 561 551 memoryStream.Position = 0; 562 ParseUpdateList(memoryStream);552 return ParseDownloadList(memoryStream).AsReadOnly(); 563 553 } 564 554 } … … 568 558 /// </summary> 569 559 /// <param name="strm">The stream containing the XML data.</param> 570 private void ParseUpdateList(Stream strm)560 private static List<DownloadInfo> ParseDownloadList(Stream strm) 571 561 { 572 562 //Move the XmlReader to the root node 573 Updates.Clear();574 mirrors.Clear();575 563 XmlReader rdr = XmlReader.Create(strm); 576 564 rdr.ReadToFollowing("updateList"); 577 565 578 //Read the descendants of the updateList node ( which are categories,579 // except for the <mirrors> element)566 //Read the descendants of the updateList node (ignoring the <mirrors> element) 567 //These are categories. 580 568 XmlReader categories = rdr.ReadSubtree(); 581 569 bool cont = categories.ReadToDescendant("mirrors"); 570 if (!cont) 571 throw new InvalidDataException(); 572 cont = categories.Read(); 573 574 List<DownloadInfo> result = new List<DownloadInfo>(); 582 575 while (cont) 583 576 { 584 577 if (categories.NodeType == XmlNodeType.Element) 585 578 { 586 if (categories.Name == "mirrors") 587 { 588 Dictionary<string, string> mirrorsList = 589 ParseMirror(categories.ReadSubtree()); 590 Dictionary<string, string>.Enumerator e = mirrorsList.GetEnumerator(); 591 while (e.MoveNext()) 592 this.mirrors.Add(e.Current.Key, 593 new Mirror(e.Current.Value, e.Current.Key)); 594 } 595 else 596 Updates.Add(categories.Name, ParseUpdateCategory(categories.ReadSubtree())); 579 result.AddRange(ParseDownloadCategory(categories.Name, categories.ReadSubtree())); 597 580 } 598 581 599 582 cont = categories.Read(); 600 583 } 601 } 602 603 /// <summary> 604 /// Parses a list of mirrors. 605 /// </summary> 606 /// <param name="rdr">The XML reader object representing the <mirrors> node</param> 607 /// <returns>The list of mirrors defined by the element.</returns> 608 private static Dictionary<string, string> ParseMirror(XmlReader rdr) 609 { 610 Dictionary<string, string> result = new Dictionary<string,string>(); 611 if (!rdr.ReadToDescendant("mirror")) 584 585 return result; 586 } 587 588 /// <summary> 589 /// Parses a specific category and its assocaited updates. 590 /// </summary> 591 /// <param name="category">The name of the category.</param> 592 /// <param name="rdr">The XML reader object representing the element and its children.</param> 593 /// <returns>A list of downloads in the category.</returns> 594 private static List<DownloadInfo> ParseDownloadCategory(string category, XmlReader rdr) 595 { 596 List<DownloadInfo> result = new List<DownloadInfo>(); 597 if (!rdr.ReadToDescendant("item")) 612 598 return result; 613 599 … … 615 601 do 616 602 { 617 if (rdr.NodeType != XmlNodeType.Element || rdr.Name != "mirror")618 continue;619 620 string location = rdr.GetAttribute("location");621 result.Add(rdr.ReadElementContentAsString(), location);622 }623 while (rdr.ReadToNextSibling("mirror"));624 625 return result;626 }627 628 /// <summary>629 /// Parses a specific category and its assocaited updates.630 /// </summary>631 /// <param name="rdr">The XML reader object representing the element and its children.</param>632 /// <returns>A list of updates in the category.</returns>633 private static UpdateCollection ParseUpdateCategory(XmlReader rdr)634 {635 UpdateCollection result = new UpdateCollection();636 if (!rdr.ReadToDescendant("item"))637 return result;638 639 //Load every element.640 do641 {642 603 if (rdr.Name != "item") 643 604 continue; 644 605 645 UpdateInfo update = new UpdateInfo(); 646 update.Name = rdr.GetAttribute("name"); 647 update.Version = new Version(rdr.GetAttribute("version")); 648 update.Publisher = rdr.GetAttribute("publisher"); 649 update.Architecture = rdr.GetAttribute("architecture"); 650 update.FileSize = Convert.ToInt64(rdr.GetAttribute("filesize"), 651 CultureInfo.InvariantCulture); 652 update.Link = rdr.ReadElementContentAsString(); 653 654 result.Add(update); 606 result.Add(new DownloadInfo(rdr.GetAttribute("name"), 607 (DownloadType)Convert.ChangeType(category, typeof(DownloadType)), 608 new Version(rdr.GetAttribute("version")), rdr.GetAttribute("publisher"), 609 rdr.GetAttribute("architecture"), Convert.ToInt64(rdr.GetAttribute("filesize")), 610 new Uri(rdr.ReadElementContentAsString()))); 655 611 } 656 612 while (rdr.ReadToNextSibling("item")); … … 658 614 return result; 659 615 } 660 661 /// <summary>662 /// Downloads the list of updates.663 /// </summary>664 /// <param name="updates">The updates to retrieve and install.</param>665 /// <returns>An opaque object for use with InstallUpdates.</returns>666 public object DownloadUpdates(ICollection<UpdateInfo> downloadQueue)667 {668 //Create a folder to hold all our updates.669 DirectoryInfo tempDir = new DirectoryInfo(Path.GetTempPath());670 tempDir = tempDir.CreateSubdirectory("eraser" + Environment.TickCount.ToString(671 CultureInfo.InvariantCulture));672 673 int currUpdate = 0;674 Dictionary<string, UpdateInfo> tempFilesMap = new Dictionary<string, UpdateInfo>();675 Util.SteppedProgressManager progress = new Util.SteppedProgressManager();676 foreach (UpdateInfo update in downloadQueue)677 {678 try679 {680 //Add the update to the overall progress.681 Util.ProgressManager step = new Eraser.Util.ProgressManager();682 progress.Steps.Add(new Util.SteppedProgressManagerStep(683 step, 1.0f / downloadQueue.Count));684 685 //Decide on the URL to connect to. The Link of the update may686 //be a relative path (relative to the selected mirror) or an687 //absolute path (which we have no choice)688 Uri reqUri = null;689 if (Uri.IsWellFormedUriString(update.Link, UriKind.Absolute))690 reqUri = new Uri(update.Link);691 else692 reqUri = new Uri(new Uri(SelectedMirror.Link), new Uri(update.Link));693 694 //Then grab the download.695 HttpWebRequest req = (HttpWebRequest)WebRequest.Create(reqUri);696 using (WebResponse resp = req.GetResponse())697 {698 //Check for a suggested filename.699 ContentDisposition contentDisposition = null;700 foreach (string header in resp.Headers.AllKeys)701 if (header.ToUpperInvariant() == "CONTENT-DISPOSITION")702 contentDisposition = new ContentDisposition(resp.Headers[header]);703 704 string tempFilePath = Path.Combine(705 tempDir.FullName, string.Format(CultureInfo.InvariantCulture, "{0}-{1}",706 ++currUpdate,707 contentDisposition == null ?708 Path.GetFileName(reqUri.GetComponents(UriComponents.Path,709 UriFormat.Unescaped)) : contentDisposition.FileName));710 711 using (Stream responseStream = resp.GetResponseStream())712 using (FileStream fileStream = new FileStream(tempFilePath, FileMode.CreateNew))713 {714 //Update the progress of this step715 step.Total = resp.ContentLength;716 717 //Copy the information into the file stream718 int lastRead = 0;719 byte[] buffer = new byte[16384];720 while ((lastRead = responseStream.Read(buffer, 0, buffer.Length)) != 0)721 {722 fileStream.Write(buffer, 0, lastRead);723 724 //Compute progress725 step.Completed = fileStream.Position;726 OnProgress(new ProgressEventArgs(step.Progress, progress.Progress,727 update, S._("Downloading: {0}", update.Name)));728 }729 }730 731 //Store the filename-to-update mapping732 tempFilesMap.Add(tempFilePath, update);733 734 //Let the event handler know the download is complete.735 step.MarkComplete();736 OnProgress(new ProgressEventArgs(step.Progress, progress.Progress,737 update, S._("Downloaded: {0}", update.Name)));738 }739 }740 catch (Exception e)741 {742 OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,743 (float)currUpdate / downloadQueue.Count, update,744 S._("Error downloading {0}: {1}", update.Name, e.Message)),745 e));746 }747 }748 749 return tempFilesMap;750 }751 752 /// <summary>753 /// Installs all updates downloaded.754 /// </summary>755 /// <param name="value">The value returned from a call to756 /// <see cref="DownloadUpdates"/>.</param>757 public void InstallUpdates(object value)758 {759 Util.ProgressManager progress = new Util.ProgressManager();760 Dictionary<string, UpdateInfo> tempFiles = (Dictionary<string, UpdateInfo>)value;761 Dictionary<string, UpdateInfo>.KeyCollection files = tempFiles.Keys;762 763 try764 {765 progress.Total = files.Count;766 foreach (string path in files)767 {768 UpdateInfo item = tempFiles[path];769 ++progress.Completed;770 OnProgress(new ProgressEventArgs(0.0f, progress.Progress,771 item, S._("Installing {0}", item.Name)));772 773 System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();774 info.FileName = path;775 info.UseShellExecute = true;776 777 System.Diagnostics.Process process = System.Diagnostics.Process.Start(info);778 process.WaitForExit(Int32.MaxValue);779 if (process.ExitCode == 0)780 OnProgress(new ProgressEventArgs(1.0f, progress.Progress,781 item, S._("Installed {0}", item.Name)));782 else783 OnProgress(new ProgressErrorEventArgs(new ProgressEventArgs(1.0f,784 progress.Progress, item, S._("Error installing {0}", item.Name)),785 new ApplicationException(S._("The installer exited with an error code {0}",786 process.ExitCode))));787 }788 }789 finally790 {791 //Clean up after ourselves792 foreach (string file in files)793 {794 DirectoryInfo tempDir = null;795 {796 FileInfo info = new FileInfo(file);797 tempDir = info.Directory;798 }799 800 tempDir.Delete(true);801 break;802 }803 }804 }805 806 /// <summary>807 /// Called when the progress of the operation changes.808 /// </summary>809 public EventHandler<ProgressEventArgs> OnProgressEvent { get; set; }810 811 /// <summary>812 /// Helper function: invokes the OnProgressEvent delegate.813 /// </summary>814 /// <param name="arg">The ProgressEventArgs object holding information815 /// about the progress of the current operation.</param>816 private void OnProgress(ProgressEventArgs arg)817 {818 if (OnProgressEvent != null)819 OnProgressEvent(this, arg);820 }821 822 /// <summary>823 /// Retrieves the list of mirrors which the server has indicated to exist.824 /// </summary>825 public Dictionary<string, Mirror> Mirrors826 {827 get828 {829 return mirrors;830 }831 }832 833 /// <summary>834 /// Gets or sets the active mirror to use to download mirrored updates.835 /// </summary>836 public Mirror SelectedMirror837 {838 get839 {840 if (selectedMirror.Link.Length == 0)841 {842 Dictionary<string, Mirror>.Enumerator iter = mirrors.GetEnumerator();843 if (iter.MoveNext())844 return iter.Current.Value;845 }846 return selectedMirror;847 }848 set849 {850 foreach (Mirror mirror in Mirrors.Values)851 if (mirror.Equals(value))852 {853 selectedMirror = value;854 return;855 }856 857 throw new ArgumentException(S._("Unknown mirror selected."));858 }859 }860 861 /// <summary>862 /// Retrieves the categories available.863 /// </summary>864 public ICollection<string> Categories865 {866 get867 {868 return Updates.Keys;869 }870 }871 872 /// <summary>873 /// Retrieves all updates available.874 /// </summary>875 public UpdateCategoriesDictionary Updates { get; private set; }876 877 /// <summary>878 /// The list of mirrors to download updates from.879 /// </summary>880 private Dictionary<string, Mirror> mirrors =881 new Dictionary<string, Mirror>();882 883 /// <summary>884 /// The currently selected mirror.885 /// </summary>886 private Mirror selectedMirror;887 616 } 888 617 889 618 /// <summary> 890 /// Manages a list of categories, mapping categories to a list of updates.619 /// The types of downloads we support. 891 620 /// </summary> 892 public class UpdateCategoriesDictionary : IDictionary<string, UpdateCollection>, 893 ICollection<KeyValuePair<string, UpdateCollection>>, 894 IEnumerable<KeyValuePair<string, UpdateCollection>> 621 public enum DownloadType 895 622 { 896 #region IDictionary<string,UpdateList> Members 897 public void Add(string key, UpdateCollection value) 898 { 899 dictionary.Add(key, value); 900 } 901 902 public bool ContainsKey(string key) 903 { 904 return dictionary.ContainsKey(key); 905 } 906 907 public ICollection<string> Keys 908 { 909 get { return dictionary.Keys; } 910 } 911 912 public bool Remove(string key) 913 { 914 return dictionary.Remove(key); 915 } 916 917 public bool TryGetValue(string key, out UpdateCollection value) 918 { 919 return dictionary.TryGetValue(key, out value); 920 } 921 922 public ICollection<UpdateCollection> Values 923 { 924 get { return dictionary.Values; } 925 } 926 927 public UpdateCollection this[string key] 928 { 929 get 930 { 931 return dictionary[key]; 932 } 933 set 934 { 935 dictionary[key] = value; 936 } 937 } 938 #endregion 939 940 #region ICollection<KeyValuePair<string,UpdateList>> Members 941 public void Add(KeyValuePair<string, UpdateCollection> item) 942 { 943 dictionary.Add(item.Key, item.Value); 944 } 945 946 public void Clear() 947 { 948 dictionary.Clear(); 949 } 950 951 public bool Contains(KeyValuePair<string, UpdateCollection> item) 952 { 953 return dictionary.ContainsKey(item.Key) && dictionary[item.Key] == item.Value; 954 } 955 956 public void CopyTo(KeyValuePair<string, UpdateCollection>[] array, int arrayIndex) 957 { 958 throw new NotImplementedException(); 959 } 960 961 public int Count 962 { 963 get { return dictionary.Count; } 964 } 965 966 public bool IsReadOnly 967 { 968 get { return true; } 969 } 970 971 public bool Remove(KeyValuePair<string, UpdateCollection> item) 972 { 973 return dictionary.Remove(item.Key); 974 } 975 #endregion 976 977 #region IEnumerable<KeyValuePair<string,UpdateList>> Members 978 public IEnumerator<KeyValuePair<string, UpdateCollection>> GetEnumerator() 979 { 980 return dictionary.GetEnumerator(); 981 } 982 #endregion 983 984 #region IEnumerable Members 985 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 986 { 987 return GetEnumerator(); 988 } 989 #endregion 990 991 /// <summary> 992 /// The store for the current object. 993 /// </summary> 994 private Dictionary<string, UpdateCollection> dictionary = 995 new Dictionary<string, UpdateCollection>(); 996 } 997 998 /// <summary> 999 /// Manages a category, containing a list of updates. 1000 /// </summary> 1001 public class UpdateCollection : IList<UpdateInfo>, ICollection<UpdateInfo>, 1002 IEnumerable<UpdateInfo> 1003 { 1004 #region IList<UpdateInfo> Members 1005 public int IndexOf(UpdateInfo item) 1006 { 1007 return list.IndexOf(item); 1008 } 1009 1010 public void Insert(int index, UpdateInfo item) 1011 { 1012 list.Insert(index, item); 1013 } 1014 1015 public void RemoveAt(int index) 1016 { 1017 list.RemoveAt(index); 1018 } 1019 1020 public UpdateInfo this[int index] 1021 { 1022 get 1023 { 1024 return list[index]; 1025 } 1026 set 1027 { 1028 list[index] = value; 1029 } 1030 } 1031 #endregion 1032 1033 #region ICollection<UpdateInfo> Members 1034 public void Add(UpdateInfo item) 1035 { 1036 list.Add(item); 1037 } 1038 1039 public void Clear() 1040 { 1041 list.Clear(); 1042 } 1043 1044 public bool Contains(UpdateInfo item) 1045 { 1046 return list.Contains(item); 1047 } 1048 1049 public void CopyTo(UpdateInfo[] array, int arrayIndex) 1050 { 1051 list.CopyTo(array, arrayIndex); 1052 } 1053 1054 public int Count 1055 { 1056 get { return list.Count; } 1057 } 1058 1059 public bool IsReadOnly 1060 { 1061 get { return true; } 1062 } 1063 1064 public bool Remove(UpdateInfo item) 1065 { 1066 return list.Remove(item); 1067 } 1068 #endregion 1069 1070 #region IEnumerable<UpdateInfo> Members 1071 public IEnumerator<UpdateInfo> GetEnumerator() 1072 { 1073 return list.GetEnumerator(); 1074 } 1075 #endregion 1076 1077 #region IEnumerable Members 1078 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 1079 { 1080 return list.GetEnumerator(); 1081 } 1082 #endregion 1083 1084 /// <summary> 1085 /// The store for this object. 1086 /// </summary> 1087 private List<UpdateInfo> list = new List<UpdateInfo>(); 1088 } 1089 1090 /// <summary> 1091 /// Represents a download mirror. 1092 /// </summary> 1093 public struct Mirror 1094 { 1095 public Mirror(string location, string link) 1096 : this() 1097 { 1098 Location = location; 1099 Link = link; 1100 } 1101 1102 /// <summary> 1103 /// The location where the mirror is at. 1104 /// </summary> 1105 public string Location { get; set; } 1106 1107 /// <summary> 1108 /// The URL prefix to utilise the mirror. 1109 /// </summary> 1110 public string Link { get; set; } 1111 1112 public override string ToString() 1113 { 1114 return Location; 1115 } 1116 1117 public override bool Equals(object obj) 1118 { 1119 if (!(obj is Mirror)) 1120 return false; 1121 return Equals((Mirror)obj); 1122 } 1123 1124 public bool Equals(Mirror other) 1125 { 1126 return Link == other.Link; 1127 } 1128 1129 public static bool operator ==(Mirror mirror1, Mirror mirror2) 1130 { 1131 return mirror1.Equals(mirror2); 1132 } 1133 1134 public static bool operator !=(Mirror mirror1, Mirror mirror2) 1135 { 1136 return !mirror1.Equals(mirror2); 1137 } 1138 1139 public override int GetHashCode() 1140 { 1141 return Link.GetHashCode(); 1142 } 623 /// <summary> 624 /// The type of the download is unknown. 625 /// </summary> 626 Unknown, 627 628 /// <summary> 629 /// The download is an update. 630 /// </summary> 631 Update, 632 633 /// <summary> 634 /// The download is a plugin. 635 /// </summary> 636 Plugin 1143 637 } 1144 638 … … 1146 640 /// Represents an update available on the server. 1147 641 /// </summary> 1148 public struct UpdateInfo642 public class DownloadInfo 1149 643 { 1150 public string Name { get; set; }1151 public Version Version { get; set; }1152 public string Publisher { get; set; }1153 public string Architecture { get; set; }1154 public long FileSize { get; set; }1155 public string Link { get; set; }1156 1157 public override bool Equals(object obj)1158 {1159 if (!(obj is UpdateInfo))1160 return false;1161 return Equals((UpdateInfo)obj);1162 }1163 1164 public bool Equals(UpdateInfo other)1165 {1166 return Link == other.Link;1167 }1168 1169 public static bool operator ==(UpdateInfo update1, UpdateInfo update2)1170 {1171 return update1.Equals(update2);1172 }1173 1174 public static bool operator !=(UpdateInfo update1, UpdateInfo update2)1175 {1176 return !update1.Equals(update2);1177 }1178 1179 public override int GetHashCode()1180 {1181 return Link.GetHashCode();1182 }1183 }1184 1185 /// <summary>1186 /// Specialised progress event argument, containing message describing1187 /// current action, and overall progress percentage.1188 /// </summary>1189 public class ProgressEventArgs : ProgressChangedEventArgs1190 {1191 public ProgressEventArgs(float progressPercentage, float overallPercentage,1192 object userState, string message)1193 : base((int)(progressPercentage * 100), userState)1194 {1195 ProgressPercentage = progressPercentage;1196 OverallProgressPercentage = overallPercentage;1197 Message = message;1198 }1199 1200 /// <summary>1201 /// Gets the asynchronous task progress percentage.1202 /// </summary>1203 public new float ProgressPercentage { get; private set; }1204 1205 /// <summary>1206 /// Gets the asynchronous task overall progress percentage.1207 /// </summary>1208 public float OverallProgressPercentage { get; private set; }1209 1210 /// <summary>1211 /// Gets the message associated with the current task.1212 /// </summary>1213 public string Message { get; private set; }1214 }1215 1216 /// <summary>1217 /// Extends the ProgressEventArgs further by allowing for the inclusion of1218 /// an exception.1219 /// </summary>1220 public class ProgressErrorEventArgs : ProgressEventArgs1221 {1222 644 /// <summary> 1223 645 /// Constructor. 1224 646 /// </summary> 1225 /// <param name="e">The base ProgressEventArgs object.</param> 1226 /// <param name="ex">The exception</param> 1227 public ProgressErrorEventArgs(ProgressEventArgs e, Exception ex) 1228 : base(e.ProgressPercentage, e.OverallProgressPercentage, e.UserState, e.Message) 1229 { 1230 Exception = ex; 1231 } 1232 1233 /// <summary> 1234 /// The exception associated with the progress event. 1235 /// </summary> 1236 public Exception Exception { get; private set; } 647 /// <param name="name">The name of the download.</param> 648 /// <param name="type">The type of the download.</param> 649 /// <param name="version">The version of the download.</param> 650 /// <param name="publisher">The publisher of the download.</param> 651 /// <param name="architecture">The architecture of the binaries.</param> 652 /// <param name="fileSize">The size of the download.</param> 653 /// <param name="link">The link to the download.</param> 654 internal DownloadInfo(string name, DownloadType type, Version version, 655 string publisher, string architecture, long fileSize, Uri link) 656 { 657 Name = name; 658 Type = type; 659 Version = version; 660 Publisher = publisher; 661 Architecture = architecture; 662 FileSize = fileSize; 663 Link = link; 664 } 665 666 public string Name { get; private set; } 667 public DownloadType Type { get; private set; } 668 public Version Version { get; private set; } 669 public string Publisher { get; private set; } 670 public string Architecture { get; private set; } 671 public long FileSize { get; private set; } 672 public Uri Link { get; private set; } 673 674 /// <summary> 675 /// Downloads the file to disk, storing the path into the DownloadedFile field. 676 /// </summary> 677 public void Download(Eraser.Util.ProgressChangedEventHandler handler) 678 { 679 if (DownloadedFile != null && DownloadedFile.Length > 0) 680 throw new InvalidOperationException("The Download method cannot be called " + 681 "before the Download method has been called."); 682 683 //Create a folder to hold all our updates. 684 lock (TempPathLock) 685 { 686 if (TempPath == null) 687 { 688 TempPath = new DirectoryInfo(Path.GetTempPath()); 689 TempPath = TempPath.CreateSubdirectory("eraser" + Environment.TickCount.ToString( 690 CultureInfo.InvariantCulture)); 691 } 692 } 693 694 //Create the progress manager for this download. 695 ProgressManager progress = new ProgressManager(); 696 697 try 698 { 699 //Request the download. 700 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Link); 701 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 702 { 703 //Do the progress calculations 704 progress.Total = response.ContentLength; 705 706 //Check for a suggested filename. 707 ContentDisposition contentDisposition = null; 708 foreach (string header in response.Headers.AllKeys) 709 if (header.ToUpperInvariant() == "CONTENT-DISPOSITION") 710 contentDisposition = new ContentDisposition(response.Headers[header]); 711 712 //Create the file name. 713 DownloadedFile = new FileInfo(Path.Combine( 714 TempPath.FullName, contentDisposition == null ? 715 Path.GetFileName(Link.GetComponents(UriComponents.Path, UriFormat.Unescaped)) : 716 contentDisposition.FileName)); 717 718 using (Stream responseStream = response.GetResponseStream()) 719 using (FileStream fileStream = DownloadedFile.OpenWrite()) 720 { 721 //Copy the information into the file stream 722 int lastRead = 0; 723 byte[] buffer = new byte[16384]; 724 while ((lastRead = responseStream.Read(buffer, 0, buffer.Length)) != 0) 725 { 726 fileStream.Write(buffer, 0, lastRead); 727 728 //Compute progress 729 progress.Completed = fileStream.Position; 730 731 //Call the progress handler 732 if (handler != null) 733 handler(this, new ProgressChangedEventArgs(progress, null)); 734 } 735 } 736 737 //Let the event handler know the download is complete. 738 progress.MarkComplete(); 739 if (handler != null) 740 handler(this, new ProgressChangedEventArgs(progress, null)); 741 } 742 } 743 catch (Exception e) 744 { 745 if (handler != null) 746 handler(this, new ProgressChangedEventArgs(progress, e)); 747 } 748 } 749 750 /// <summary> 751 /// Installs the file, by calling Process.Start on the file. 752 /// </summary> 753 /// <returns>The exit code of the program.</returns> 754 public int Install() 755 { 756 if (DownloadedFile == null || !DownloadedFile.Exists || DownloadedFile.Length == 0) 757 throw new InvalidOperationException("The Install method cannot be called " + 758 "before the Download method has been called."); 759 760 ProcessStartInfo info = new ProcessStartInfo(); 761 info.FileName = DownloadedFile.FullName; 762 info.UseShellExecute = true; 763 //info.Verb = "runas"; 764 765 Process process = Process.Start(info); 766 process.WaitForExit(Int32.MaxValue); 767 return process.ExitCode; 768 } 769 770 /// <summary> 771 /// The lock object for the TempPath field. 772 /// </summary> 773 private static object TempPathLock = new object(); 774 775 /// <summary> 776 /// The temporary path we are storing our downloads in. 777 /// </summary> 778 private static DirectoryInfo TempPath; 779 780 /// <summary> 781 /// Stores information about the temporary file which the download was stored into. 782 /// </summary> 783 private FileInfo DownloadedFile; 1237 784 } 1238 785 }
Note: See TracChangeset
for help on using the changeset viewer.
