/Application/Core/UpgradeManager.cs
C# | 820 lines | 522 code | 105 blank | 193 comment | 80 complexity | 7bb574442d44af7878aced9944c5d410 MD5 | raw file
1/** 2 * UpgradeManager.cs 3 * 4 * Takes care of upgrading Stoffi to a new version by probing the 5 * upgrade servers at regular intervals, or doing a probe on user 6 * request when mode is set to manual. 7 * 8 * Downloads the file via HTTPS and examines it to determine if 9 * the file is a pure text response from the server or a tar.bz2 10 * package containing the different packages for each new version. 11 * 12 * If the downloaded file contains version package each is unpacked 13 * and the containing files are copied to the program folder. 14 * If the settings migrator library is found it is hooked into and 15 * a migration of the user.config file is performed. 16 * 17 * * * * * * * * * 18 * 19 * Copyright 2012 Simplare 20 * 21 * This code is part of the Stoffi Music Player Project. 22 * Visit our website at: stoffiplayer.com 23 * 24 * This program is free software; you can redistribute it and/or 25 * modify it under the terms of the GNU General Public License 26 * as published by the Free Software Foundation; either version 27 * 3 of the License, or (at your option) any later version. 28 * 29 * See stoffiplayer.com/license for more information. 30 **/ 31 32using System; 33using System.Collections.Generic; 34using System.Collections.Specialized; 35using System.Configuration; 36using System.ComponentModel; 37using System.IO; 38using System.Linq; 39using System.Net; 40using System.Net.Sockets; 41using System.Net.Security; 42using System.Reflection; 43using System.Security.Cryptography.X509Certificates; 44using System.Text; 45using System.Threading; 46using System.Xml; 47using ICSharpCode.SharpZipLib.Tar; 48using ICSharpCode.SharpZipLib.BZip2; 49 50namespace Stoffi 51{ 52 /// <summary> 53 /// Represents the upgrade manager which check for upgrades and installs them 54 /// </summary> 55 public static class UpgradeManager 56 { 57 #region Members 58 59 private static string upgradeServer = "upgrade.stoffiplayer.com"; 60 private static string upgradeFolder = "UpgradeFolder/"; 61 private static string baseFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); 62 private static string downloadFilename = ""; 63 private static TimeSpan interval; 64 private static int pendingUpgradeVersion = -1; 65 private static Timer prober = null; 66 private static bool haveID = false; 67 private static WebClient client = null; 68 69 #endregion 70 71 #region Properties 72 73 /// <summary> 74 /// Gets whether there's a upgrade in progress 75 /// </summary> 76 public static bool InProgress { get; private set; } 77 78 /// <summary> 79 /// Gets whether there's a upgrade pending, waiting for a restart of Stoffi 80 /// </summary> 81 public static bool Pending { get; private set; } 82 83 /// <summary> 84 /// Gets whether there's a upgrade available 85 /// </summary> 86 public static bool Found { get; private set; } 87 88 /// <summary> 89 /// Sets whether the upgrade should download the upgrades in case the policy is set to manual 90 /// </summary> 91 public static bool ForceDownload { get; set; } 92 93 /// <summary> 94 /// Gets or sets the policy regarding upgrading 95 /// </summary> 96 public static UpgradePolicy Policy 97 { 98 get { return SettingsManager.UpgradePolicy; } 99 set { SettingsManager.UpgradePolicy = value; } 100 } 101 102 #endregion 103 104 #region Methods 105 106 #region Public 107 108 /// <summary> 109 /// Initialize the upgrade manager 110 /// </summary> 111 /// <param name="interv">How often to check for new upgrades</param> 112 public static void Initialize(TimeSpan interv) 113 { 114 interval = interv; 115 Pending = false; 116 ForceDownload = false; 117 InProgress = false; 118 Clear(); 119 120 if (Directory.Exists(Path.Combine(baseFolder, upgradeFolder))) 121 Directory.Delete(Path.Combine(baseFolder, upgradeFolder), true); 122 123 prober = new Timer(Probe, null, (long)interval.TotalSeconds, 3600); 124 125 if (Policy != UpgradePolicy.Manual) 126 Start(); 127 } 128 129 /// <summary> 130 /// Start the prober 131 /// </summary> 132 public static void Start() 133 { 134#if (DEBUG) 135 U.L(LogLevel.Warning, "UPGRADE", "Running debug build, no upgrade checks"); 136#else 137 prober.IsEnabled = true; 138#endif 139 } 140 141 /// <summary> 142 /// Stop the prober 143 /// </summary> 144 public static void Stop() 145 { 146 prober.Dispose(); 147 prober = null; 148 ForceDownload = false; 149 if (client != null) 150 client.CancelAsync(); 151 } 152 153 /// <summary> 154 /// Check if there's any upgrades available. 155 /// This is usually called from the timer. 156 /// </summary> 157 /// <param name="state">The timer state</param> 158 public static void Probe(object state) 159 { 160 Found = false; 161 InProgress = false; 162 163 // ignore SSL cert error 164 ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback( 165 delegate { return true; } 166 ); 167 ServicePointManager.ServerCertificateValidationCallback += 168 delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) 169 { 170 return true; 171 }; 172#if (DEBUG) 173 U.L(LogLevel.Warning, "UPGRADE", "Running debug build, no upgrade checks"); 174 if (SettingsManager.UpgradePolicy == UpgradePolicy.Manual) 175 { 176 DispatchErrorOccured(U.T("UpgradeDebug")); 177 } 178 179#else 180 if (Pending) 181 { 182 if (SettingsManager.UpgradePolicy == UpgradePolicy.Manual) 183 { 184 DispatchErrorOccured(U.T("UpgradePending")); 185 } 186 U.L(LogLevel.Debug, "UPGRADE", "Waiting for restart"); 187 Stop(); 188 return; 189 } 190 191 U.L(LogLevel.Debug, "UPGRADE", "Probe"); 192 193 // create temporary folder if it doesn't already exist 194 if (!Directory.Exists(Path.Combine(baseFolder, upgradeFolder))) 195 Directory.CreateDirectory(Path.Combine(baseFolder, upgradeFolder)); 196 197 String URL = String.Format("https://{0}?version={1}&channel={2}&arch={3}", 198 upgradeServer, 199 SettingsManager.Version, 200 SettingsManager.Channel, 201 SettingsManager.Architecture); 202 203 U.L(LogLevel.Debug, "UPGRADE", "Probing " + URL); 204 205 // send unique ID if we have one 206 haveID = true; 207 try { URL += String.Format("&id={0}", SettingsManager.ID); } 208 catch { haveID = false; } 209 if (SettingsManager.UpgradePolicy != UpgradePolicy.Automatic && !ForceDownload) 210 URL += "&p=1"; 211 212 DispatchProgressChanged(0, new ProgressState(U.T("UpgradeDownloading"), false)); 213 214 if (client != null) 215 client.CancelAsync(); 216 else 217 { 218 client = new WebClient(); 219 client.Headers.Add("User-Agent", String.Format("Stoffi/{0}", SettingsManager.Version)); 220 client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Client_ProgressChanged); 221 client.DownloadFileCompleted += new AsyncCompletedEventHandler(Examine); 222 } 223 224 // generate a random file name 225 String Chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 226 StringBuilder Filename = new StringBuilder(); 227 Random random = new Random(); 228 for (int i=0; i < 20; i++) 229 Filename.Append(Chars[(int)(random.NextDouble() * Chars.Length)]); 230 downloadFilename = Filename.ToString(); 231 232 // download upgrade file 233 U.L(LogLevel.Debug, "UPGRADE", "Start download"); 234 try 235 { 236 InProgress = true; 237 client.DownloadFileAsync(new Uri(URL), baseFolder + "/" + upgradeFolder + downloadFilename); 238 } 239 catch (Exception exc) 240 { 241 InProgress = false; 242 U.L(LogLevel.Warning, "UPGRADE", "Could not contact Upgrade Server: " + exc.Message); 243 } 244#endif 245 } 246 247 /// <summary> 248 /// Examines downloaded file to see. 249 /// 250 /// The method will try to uncompress and unpack the file, assuming that it's a tar.bz2 package containing 251 /// all upgrade packages. 252 /// If that fails it will treat the file as a text file which contains the response from the upgrade server. 253 /// Either the text repsonse is an error message or a message telling the client that no upgrades are available. 254 /// If the attempt does not fail and the downloaded file is indeed a package then it will be unpacked to a 255 /// temporary folder and Examine() will look for a textfile called "data" which should contain basic information 256 /// about the versions included and the ID of the client. The client will change its ID to this number unconditionally. 257 /// 258 /// This method is called from the WebClient when download is complete. 259 /// </summary> 260 /// <param name="sender">The sender of the event</param> 261 /// <param name="eventArgs">The event data</param> 262 public static void Examine(object sender, AsyncCompletedEventArgs eventArgs) 263 { 264 if (eventArgs.Error != null) 265 { 266 InProgress = false; 267 if (eventArgs.Cancelled) 268 { 269 return; 270 } 271 272 U.L(LogLevel.Error, "UPGRADE", eventArgs.Error.Message); 273 DispatchErrorOccured(eventArgs.Error.Message); 274 275 ForceDownload = false; 276 return; 277 } 278 ForceDownload = false; 279 280 U.L(LogLevel.Debug, "UPGRADE", "Examine downloaded file"); 281 282 // try to extract file 283 try 284 { 285 U.L(LogLevel.Debug, "UPGRADE", "Try to extract files"); 286 Stream inStream = File.OpenRead(baseFolder + "/" + upgradeFolder + downloadFilename); 287 TarArchive archive = TarArchive.CreateInputTarArchive(inStream, TarBuffer.DefaultBlockFactor); 288 System.IO.Directory.CreateDirectory(baseFolder + "/" + upgradeFolder + downloadFilename + "_tmp/"); 289 archive.ExtractContents(baseFolder + "/" + upgradeFolder + downloadFilename + "_tmp/"); 290 archive.Close(); 291 inStream.Close(); 292 293 // set unique ID that we got from server 294 if (!haveID) 295 { 296 if (File.Exists(baseFolder + "/" + upgradeFolder + downloadFilename + "_tmp/outgoing/data")) 297 { 298 String line; 299 StreamReader file = new StreamReader(baseFolder + "/" + upgradeFolder + downloadFilename + "_tmp/outgoing/data"); 300 while ((line = file.ReadLine()) != null) 301 { 302 string[] field = line.Split(new[]{':'},2); 303 if (field[0] == "client id") 304 { 305 SettingsManager.ID = Convert.ToInt32(field[1].Trim()); 306 SettingsManager.Save(); 307 } 308 } 309 file.Close(); 310 } 311 else 312 U.L(LogLevel.Warning, "UPGRADE", "No data file found in Upgrade Package"); 313 } 314 Unpack(); 315 U.L(LogLevel.Debug, "UPGRADE", "Upgrade installed"); 316 Pending = true; 317 DispatchChecked(); 318 DispatchUpgraded(); 319 client.CancelAsync(); 320 InProgress = false; 321 return; 322 } 323 catch (Exception e) 324 { 325 // invalid checksum means not an archive, 326 // so it must be a text file 327 InProgress = false; 328 U.L(LogLevel.Debug, "UPGRADE", "Got message from server"); 329 if (e.Message == "Header checksum is invalid") 330 { 331 String line; 332 StreamReader file = new StreamReader(baseFolder + "/" + upgradeFolder + downloadFilename); 333 try 334 { 335 while ((line = file.ReadLine()) != null) // TODO: check if closed? 336 { 337 U.L(LogLevel.Debug, "UPGRADE", "Message from server: " + line); 338 if (line.Length > 23 + 25 && (line.Substring(0, 23) == "No upgrade needed, mr. " && line.Substring(line.Length - 25, 25) == ". You're already awesome!")) 339 { 340 String id = line.Substring(23, line.Length - 48); 341 SettingsManager.ID = Convert.ToInt32(id); 342 SettingsManager.Save(); 343 file.Close(); 344 Clear(); 345 U.L(LogLevel.Information, "UPGRADE", "No new versions found"); 346 DispatchChecked(); 347 return; 348 } 349 else if (line.Length > 20 && line.Substring(0, 20) == "Versions available: ") 350 { 351 String[] versions = line.Substring(20, line.Length - 20).Split(new[]{':'},2); 352 Found = true; 353 Notify(versions); 354 } 355 else 356 { 357 if (SettingsManager.UpgradePolicy == UpgradePolicy.Manual) 358 DispatchErrorOccured("The upgrade server complained:\n" + line); 359 U.L(LogLevel.Debug, "UPGRADE", "Server says: " + line); 360 } 361 } 362 } 363 catch (Exception exc) 364 { 365 if (SettingsManager.UpgradePolicy != UpgradePolicy.Automatic) 366 DispatchErrorOccured(exc.Message); 367 U.L(LogLevel.Warning, "UPGRADE", "Could not read downloaded file: " + exc.Message); 368 } 369 file.Close(); 370 Clear(); 371 DispatchChecked(); 372 return; 373 } 374 else 375 { 376 U.L(LogLevel.Warning, "UPGRADE", e.Message); 377 Clear(); 378 DispatchErrorOccured(e.Message); 379 return; 380 } 381 } 382 } 383 384 /// <summary> 385 /// Unpacks the different upgrade packages contained inside the downloaded file. 386 /// 387 /// Each upgrade of Stoffi is to be packaged inside a file called N.tar.bz2 (where N 388 /// is the version number) file containing the following: 389 /// 1) All files that should be copied to the program folder 390 /// 2) Optional: "Settings Migrator.dll", this will be used later to migrate the settings in SettingsManager 391 /// 392 /// The method will call Propare() on each version as they are decompressed and unpacked, this will copy the files to a 393 /// folder called "Queue". From here the content will be copied to the program folder at Finish(). 394 /// </summary> 395 public static void Unpack() 396 { 397 string folder = downloadFilename + "_tmp/packages/"; 398 399 U.L(LogLevel.Debug, "UPGRADE", "Unpack versions"); 400 DirectoryInfo folderInfo = new DirectoryInfo(baseFolder + "/" + upgradeFolder + folder); 401 FileInfo[] files = folderInfo.GetFiles("*.tar.bz2", SearchOption.AllDirectories); 402 List<int> versions = new List<int>(); 403 404 if (!Directory.Exists(baseFolder + "/" + upgradeFolder + "Queue")) Directory.CreateDirectory(baseFolder + "/" + upgradeFolder + "Queue"); 405 406 // check each file inside the folder and add the version number to our list 407 foreach (FileInfo file in files) 408 { 409 U.L(LogLevel.Debug, "UPGRADE", "Version file @ " + file.FullName); 410 versions.Add(Convert.ToInt32(file.Name.Substring(0, file.Name.Length - 8))); 411 } 412 413 versions.Sort(); 414 foreach (int version in versions) 415 { 416 DispatchProgressChanged(0, new ProgressState(U.T("UpgradeProcessing"), true)); 417 U.L(LogLevel.Debug, "UPGRADE", "Processing version " + version.ToString()); 418 419 String bz2File = baseFolder + "/" + upgradeFolder + folder + SettingsManager.Channel + "/" + SettingsManager.Architecture + "/" + version.ToString() + ".tar.bz2"; 420 String tarFile = baseFolder + "/" + upgradeFolder + folder + SettingsManager.Channel + "/" + SettingsManager.Architecture + "/" + version.ToString() + ".tar"; 421 String tmpFold = baseFolder + "/" + upgradeFolder + folder + SettingsManager.Channel + "/" + SettingsManager.Architecture + "/" + version.ToString() + "_tmp/"; 422 423 U.L(LogLevel.Debug, "UPGRADE", "Decompressing"); 424 BZip2.Decompress(File.OpenRead(bz2File), File.Create(tarFile), true); 425 426 Stream inStream = File.OpenRead(tarFile); 427 TarArchive archive = TarArchive.CreateInputTarArchive(inStream, TarBuffer.DefaultBlockFactor); 428 Directory.CreateDirectory(tmpFold); 429 archive.ExtractContents(tmpFold); 430 archive.Close(); 431 inStream.Close(); 432 433 U.L(LogLevel.Debug, "UPGRADE", "Prepare version " + version.ToString()); 434 Prepare(tmpFold, baseFolder + "/" + upgradeFolder + "Queue/"); 435 pendingUpgradeVersion = version; 436 } 437 438 DispatchUpgraded(); 439 U.L(LogLevel.Debug, "UPGRADE", "Upgrade completed"); 440 } 441 442 /// <summary> 443 /// Copy all files from a given folder to a given destination, keeping the filestructure of the 444 /// source folder. 445 /// </summary> 446 /// <param name="folder">The folder to copy into</param> 447 /// <param name="dest">The folder to copy from</param> 448 public static void Prepare(String folder, String dest) 449 { 450 U.L(LogLevel.Debug, "UPGRADE", "Preparing " + folder + " with destination " + dest); 451 DirectoryInfo folderInfo = new DirectoryInfo(folder); 452 453 U.L(LogLevel.Debug, "UPGRADE", "Getting directories of " + folder + " and creating equivalents"); 454 DirectoryInfo[] dirs = folderInfo.GetDirectories("*", SearchOption.AllDirectories); 455 foreach (DirectoryInfo dir in dirs) 456 if (!Directory.Exists(dest + dir.FullName.Remove(0, folder.Length))) 457 Directory.CreateDirectory(dest + dir.FullName.Remove(0, folder.Length)); 458 459 U.L(LogLevel.Debug, "UPGRADE", "Getting files of " + folder + " and moving them to destination"); 460 FileInfo[] files = folderInfo.GetFiles("*", SearchOption.AllDirectories); 461 foreach (FileInfo file in files) 462 { 463 U.L(LogLevel.Debug, "UPGRADE", String.Format("Moving {0} to {1}", file.FullName, dest + file.FullName.Remove(0, folder.Length))); 464 File.Copy(file.FullName, dest + file.FullName.Remove(0, folder.Length), true); 465 } 466 } 467 468 /// <summary> 469 /// Goes through the "Queue" folder inside the upgrade folder and copies each file into the 470 /// program folder, backing up files if they already exist. 471 /// 472 /// A special case is the file called "Settings Migrator.dll". This file will be loaded into the 473 /// assembly as an external library. It should declare an interface named "Stoffi.IMigrator" which 474 /// contains a method called Migrate() that takes two arguments: 475 /// 1) The existing settings file to read from 476 /// 2) The new file to write the migrated settings to 477 /// This method will be called using the current user.config file and the new settings file will 478 /// be copied both over the old file as well as into a new folder (we do this since we don't know 479 /// if Windows will use the old or the new folder to look for user.config, but the one not used will 480 /// be removed later anyway). 481 /// 482 /// This method is called when the application shuts down. 483 /// </summary> 484 public static void Finish() 485 { 486 prober.Dispose(); 487 prober = null; 488 if (!Directory.Exists(baseFolder + "/" + upgradeFolder + "Queue")) return; 489 if (!Pending) return; 490 U.L(LogLevel.Debug, "UPGRADE", "Finishing upgrade"); 491 492 string src = baseFolder + "/" + upgradeFolder + "Queue"; 493 string dst = baseFolder; 494 DirectoryInfo folderInfo = new DirectoryInfo(src); 495 496 U.L(LogLevel.Debug, "UPGRADE", "Creating destination folders"); 497 DirectoryInfo[] dirs = folderInfo.GetDirectories("*", SearchOption.AllDirectories); 498 foreach (DirectoryInfo dir in dirs) 499 if (!Directory.Exists(dst + dir.FullName.Remove(0, src.Length))) 500 Directory.CreateDirectory(dst + dir.FullName.Remove(0, src.Length)); 501 502 U.L(LogLevel.Debug, "UPGRADE", "Copying files"); 503 FileInfo[] files = folderInfo.GetFiles("*", SearchOption.AllDirectories); 504 505 Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal); 506 string configCompanyFolder = new DirectoryInfo(config.FilePath).Parent.Parent.Parent.Name; 507 string configBaseFolder = new DirectoryInfo(config.FilePath).Parent.Parent.Name; 508 string usersFolder = @"C:\Users\"; 509 string configVersionFolder = new DirectoryInfo(config.FilePath).Parent.Name; 510 string newVersion = pendingUpgradeVersion.ToString(); 511 newVersion = Convert.ToInt32(newVersion.Substring(0, 1)).ToString() + "." + 512 Convert.ToInt32(newVersion.Substring(1, 2)).ToString() + "." + 513 Convert.ToInt32(newVersion.Substring(3, 3)).ToString() + "." + 514 Convert.ToInt32(newVersion.Substring(6, 4)).ToString(); 515 516 foreach (FileInfo file in files) 517 { 518 if (file.Name == "Settings Migrator.dll") 519 { 520 U.L(LogLevel.Debug, "UPGRADE", "Migrating user settings"); 521 522 // load dll file 523 U.L(LogLevel.Debug, "UPGRADE", "Loading migrator library"); 524 Assembly assembly = Assembly.LoadFrom(file.FullName); 525 526 foreach (Type type in assembly.GetTypes()) 527 { 528 if (type.GetInterface("Stoffi.IMigrator") == null) 529 continue; 530 531 object migratorObject = Activator.CreateInstance(type); 532 533 foreach (string userFolder in Directory.GetDirectories(usersFolder)) 534 { 535 string configFilePath = Path.Combine(userFolder, "AppData", "Local", configCompanyFolder, configBaseFolder, configVersionFolder, "user.config"); 536 string user = new DirectoryInfo(userFolder).Name; 537 if (!File.Exists(configFilePath)) continue; 538 539 U.L(LogLevel.Debug, "UPGRADE", "Migrating settings for user " + user); 540 541 542 // find user settings file 543 if (!System.IO.File.Exists(configFilePath)) 544 { 545 U.L(LogLevel.Warning, "UPGRADE", String.Format("No settings file found at {0}", configFilePath)); 546 continue; 547 } 548 549 object[] arguments = new object[] { configFilePath, configFilePath + ".new" }; 550 551 U.L(LogLevel.Debug, "UPGRADE", String.Format("Invoking DLL procedure")); 552 type.InvokeMember("Migrate", BindingFlags.Default | BindingFlags.InvokeMethod, null, migratorObject, arguments); 553 554 // move settings file to current folder 555 U.L(LogLevel.Debug, "UPGRADE", String.Format("Move settings file to: " + configFilePath)); 556 if (File.Exists(configFilePath + ".old")) 557 File.Delete(configFilePath + ".old"); 558 if (File.Exists(configFilePath)) 559 File.Move(configFilePath, configFilePath + ".old"); 560 File.Move(configFilePath + ".new", configFilePath); 561 } 562 563 U.L(LogLevel.Debug, "UPGRADE", String.Format("Settings have been migrated")); 564 } 565 } 566 else 567 { 568 String oldFile = file.FullName; 569 String newFile = dst + file.FullName.Remove(0, src.Length); 570 571 String bakFile = Path.GetDirectoryName(newFile) + "/bak." + Path.GetFileName(newFile); 572 573 if (File.Exists(bakFile)) 574 File.Delete(bakFile); 575 576 if (File.Exists(newFile)) 577 File.Move(newFile, bakFile); 578 579 U.L(LogLevel.Debug, "UPGRADE", String.Format("Backing up {0} to {1}", newFile, bakFile)); 580 File.Copy(oldFile, newFile, true); 581 } 582 } 583 584 // we need to move the settings with us during upgrade, in case .NET decides to use a new folder 585 foreach (string userFolder in Directory.GetDirectories(usersFolder)) 586 { 587 string configFilePath = Path.Combine(userFolder, "AppData", "Local", configCompanyFolder, configBaseFolder, configVersionFolder, "user.config"); 588 string newConfigFolder = Path.Combine(userFolder, "AppData", "Local", configCompanyFolder, configBaseFolder, newVersion); 589 if (File.Exists(configFilePath)) 590 { 591 string newSettingsFile = Path.Combine(newConfigFolder, "user.config"); 592 593 U.L(LogLevel.Debug, "UPGRADE", String.Format("Creating destination for settings file: " + newConfigFolder)); 594 if (!Directory.Exists(newConfigFolder)) 595 Directory.CreateDirectory(newConfigFolder); 596 597 U.L(LogLevel.Debug, "UPGRADE", String.Format("Move settings file to: " + newSettingsFile)); 598 if (File.Exists(newSettingsFile)) 599 File.Move(newSettingsFile, newSettingsFile + ".old"); 600 File.Copy(configFilePath, newSettingsFile); 601 } 602 } 603 604 // remove temporary upgrade folder 605 if (Directory.Exists(Path.Combine(baseFolder, upgradeFolder))) 606 { 607 U.L(LogLevel.Debug, "UPGRADE", "Removing temporary upgrade folder"); 608 try 609 { 610 Directory.Delete(Path.Combine(baseFolder, upgradeFolder), true); 611 } 612 catch (Exception e) 613 { 614 U.L(LogLevel.Debug, "UPGRADE", "Could not remove upgrade folder: " + e.Message); 615 } 616 } 617 } 618 619 /// <summary> 620 /// Removes any temporary backup files and unused settings files. 621 /// </summary> 622 public static void Clear() 623 { 624 try 625 { 626 DirectoryInfo folderInfo = new DirectoryInfo(baseFolder); 627 FileInfo[] files = folderInfo.GetFiles("bak.*", SearchOption.AllDirectories); 628 foreach (FileInfo file in files) 629 { 630 U.L(LogLevel.Debug, "UPGRADE", String.Format("Removing temporary backup file: {0}", file.FullName)); 631 File.Delete(file.FullName); 632 } 633 634 Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal); 635 string[] dirs = Directory.GetDirectories(Path.GetDirectoryName(Path.GetDirectoryName(config.FilePath))); 636 foreach (string dir in dirs) 637 { 638 if (dir != Path.GetDirectoryName(config.FilePath)) 639 { 640 U.L(LogLevel.Debug, "UPGRADE", String.Format("Removing unused settings folder: {0}", dir)); 641 Directory.Delete(dir, true); 642 } 643 } 644 } 645 catch (Exception e) 646 { 647 U.L(LogLevel.Warning, "UPGRADE", "There was a problem cleaning up: " + e.Message); 648 } 649 } 650 651 /// <summary> 652 /// Completely removes the upgrade folder 653 /// </summary> 654 public static void Clean() 655 { 656 if (Directory.Exists(upgradeFolder)) 657 { 658 U.L(LogLevel.Debug, "UPGRADE", "Removing temporary upgrade folder"); 659 try 660 { 661 Directory.Delete(upgradeFolder, true); 662 } 663 catch (Exception e) 664 { 665 U.L(LogLevel.Debug, "UPGRADE", "Could not remove upgrade folder: " + e.Message); 666 } 667 } 668 } 669 670 #endregion 671 672 #region Private 673 674 /// <summary> 675 /// Called when the progress of the download is changed to update the progressbar. 676 /// </summary> 677 /// <param name="sender">The sender of the event</param> 678 /// <param name="e">The event data</param> 679 private static void Client_ProgressChanged(object sender, DownloadProgressChangedEventArgs e) 680 { 681 if (Policy != UpgradePolicy.Automatic && !ForceDownload) 682 return; 683 684 DispatchProgressChanged(e.ProgressPercentage, new ProgressState(String.Format("{0}%", e.ProgressPercentage), false)); 685 } 686 687 /// <summary> 688 /// Notifies the user that an upgrade is available. 689 /// </summary> 690 /// <param name="versions">A list of all available versions</param> 691 private static void Notify(String[] versions) 692 { 693 if (Policy != UpgradePolicy.Automatic) 694 DispatchUpgradeFound(); 695 } 696 697 #endregion 698 699 #region Dispatchers 700 701 /// <summary> 702 /// The dispatcher of the <see cref="ProgressChanged"/> event 703 /// </summary> 704 /// <param name="progressPercentage">The playlist that was modified</param> 705 /// <param name="userState">The type of modification that occured</param> 706 private static void DispatchProgressChanged(int progressPercentage, ProgressState userState) 707 { 708 if (ProgressChanged != null) 709 ProgressChanged(null, new ProgressChangedEventArgs(progressPercentage, userState)); 710 } 711 712 /// <summary> 713 /// The dispatcher of the <see cref="ErrorOccured"/> event 714 /// </summary> 715 /// <param name="message">The error message</param> 716 private static void DispatchErrorOccured(string message) 717 { 718 if (ErrorOccured != null) 719 ErrorOccured(null, message); 720 } 721 722 /// <summary> 723 /// The dispatcher of the <see cref="UpgradeFound"/> event 724 /// </summary> 725 private static void DispatchUpgradeFound() 726 { 727 if (UpgradeFound != null) 728 UpgradeFound(null, null); 729 } 730 731 /// <summary> 732 /// The dispatcher of the <see cref="Upgraded"/> event 733 /// </summary> 734 private static void DispatchUpgraded() 735 { 736 if (Upgraded != null) 737 Upgraded(null, null); 738 } 739 740 /// <summary> 741 /// The dispatcher of the <see cref="Checked"/> event 742 /// </summary> 743 private static void DispatchChecked() 744 { 745 if (Checked != null) 746 Checked(null, null); 747 } 748 749 #endregion 750 751 #endregion 752 753 #region Events 754 755 /// <summary> 756 /// Occurs when the progress of an upgrade is changed 757 /// </summary> 758 public static event ProgressChangedEventHandler ProgressChanged; 759 760 /// <summary> 761 /// Occurs when an error is encountered 762 /// </summary> 763 public static event ErrorEventHandler ErrorOccured; 764 765 /// <summary> 766 /// Occurs when an upgrade is found 767 /// </summary> 768 public static event EventHandler UpgradeFound; 769 770 /// <summary> 771 /// Occurs when the application has been upgraded 772 /// </summary> 773 public static event EventHandler Upgraded; 774 775 /// <summary> 776 /// Occurs when a check for new upgrades has completed 777 /// </summary> 778 public static event EventHandler Checked; 779 780 #endregion 781 } 782 783 #region Delegates 784 785 /// <summary> 786 /// Represents the method that will be called when an ErrorEvent occurs 787 /// </summary> 788 /// <param name="sender">The sender of the event</param> 789 /// <param name="message">The error message</param> 790 public delegate void ErrorEventHandler(object sender, string message); 791 792 #endregion 793 794 /// <summary> 795 /// Represents the state of the progress 796 /// </summary> 797 public class ProgressState 798 { 799 /// <summary> 800 /// Gets or sets the message to display 801 /// </summary> 802 public string Message { get; set; } 803 804 /// <summary> 805 /// Gets or sets whether the progressbar is indetermined 806 /// </summary> 807 public bool IsIndetermined { get; set; } 808 809 /// <summary> 810 /// Creates an instance of the ProgressState class 811 /// </summary> 812 /// <param name="message">The message to display</param> 813 /// <param name="isIndetermined">Whether the progressbar is indetermined</param> 814 public ProgressState(string message, bool isIndetermined) 815 { 816 Message = message; 817 IsIndetermined = isIndetermined; 818 } 819 } 820}