PageRenderTime 45ms CodeModel.GetById 2ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 0ms

/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
 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}