/Application/Core/UpgradeManager.cs

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