context: #region License
  
  // Copyright (c) 2013, ClearCanvas Inc.
  // All rights reserved.
  // http://www.clearcanvas.ca
  //
  // This file is part of the ClearCanvas RIS/PACS open source project.
  //
  // The ClearCanvas RIS/PACS open source project is free software: you can
  // redistribute it and/or modify it under the terms of the GNU General Public
  // License as published by the Free Software Foundation, either version 3 of the
  // License, or (at your option) any later version.
  //
  // The ClearCanvas RIS/PACS open source project is distributed in the hope that it
  // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
  // Public License for more details.
  //
  // You should have received a copy of the GNU General Public License along with
  // the ClearCanvas RIS/PACS open source project.  If not, see
  // <http://www.gnu.org/licenses/>.
  
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Configuration;
  using System.IO;
  using System.Linq;
  using System.Reflection;
  using System.Runtime.Serialization.Formatters.Binary;
  using System.Security.Cryptography;
  using System.Security.Policy;
  using ClearCanvas.Common.Utilities;
  
  namespace ClearCanvas.Common.Configuration
  {
  	public interface IUserUpgradeStrategy
  	{
  		void Run();
  		bool IsRunning { get; }
  
  		int TotalSteps { get; }
  		int CurrentStep { get; }
  
  		int SuccessfulCount { get; }
  		int FailedCount { get; }
  		int RemainingCount { get; }
  
  		event EventHandler ProgressChanged;
  	}
  
  	public class UserUpgradeStrategy : IUserUpgradeStrategy
  	{
  		private event EventHandler _progressChanged;
  
  		private UserUpgradeStrategy(ICollection<UserUpgradeStep> steps)
  		{
  			Steps = steps;
  		}
  
  		private ICollection<UserUpgradeStep> Steps { get; set; }
  
  		public int TotalSteps
  		{
  			get { return Steps.Count; }
  		}
  
  		public int CurrentStep { get; private set; }
  
  		public int SuccessfulCount { get; private set; }
  		public int FailedCount { get; private set; }
  		public int RemainingCount { get; private set; }
  
  		public event EventHandler ProgressChanged
  		{
  			add { _progressChanged += value; }
  			remove { _progressChanged -= value; }
  		}
  
  		public static IUserUpgradeStrategy Create()
  		{
  			if (!UpgradeSettings.IsUserUpgradeEnabled())
  				return null;
  
  			ICollection<UserUpgradeStep> steps = UserUpgradeStep.CreateAll();
  			if (steps.Count == 0)
  				return null;
  
  			return new UserUpgradeStrategy(steps);
  		}
  
  		public bool IsRunning { get; private set; }
  
  		public void Run()
  		{
  			if (IsRunning)
  				return;
  
  			IsRunning = true;
  
  			// if we know of an alternate app settings folder where a user.config might be found in previous versions, try to migrate it if necessary
  			var formerAppConfigFolder = GetAlternateAppSettingsFolder();
  			if (!string.IsNullOrWhiteSpace(formerAppConfigFolder))
  			{
  				MigrateFormerAppConfigFile(true, formerAppConfigFolder);
  				MigrateFormerAppConfigFile(false, formerAppConfigFolder);
  			}
  
  			foreach (UserUpgradeStep step in Steps)
  			{
  				try
  				{
  					++CurrentStep;
  					step.Run();
  					++SuccessfulCount;
  				}
  				catch (Exception e)
  				{
  					Platform.Log(LogLevel.Warn, e, "User upgrade step failed: {0}", step.Identifier);
  					++FailedCount;
  				}
  				finally
  				{
  					--RemainingCount;
  					EventsHelper.Fire(_progressChanged, this, EventArgs.Empty);
  				}
  			}
  
  			IsRunning = false;
  			EventsHelper.Fire(_progressChanged, this, EventArgs.Empty);
  		}
  
  		private static void MigrateFormerAppConfigFile(bool roamingProfile, params string[] formerAppConfigFolders)
  		{
  			try
  			{
  				// get the current version of the entry assembly (i.e. executable)
  				var currentVersionString = Assembly.GetEntryAssembly().GetName().Version.ToString();
  
  				// if the entry assembly isn't even versioned, upgrading settings doesn't make any sense, so just stop here
  				if (String.IsNullOrWhiteSpace(currentVersionString)) return;
  				var currentVersion = new Version(currentVersionString.Trim());
  
  				// get the base user configuration path for the entry assembly
  				var configuration = ConfigurationManager.OpenExeConfiguration(roamingProfile ? ConfigurationUserLevel.PerUserRoaming : ConfigurationUserLevel.PerUserRoamingAndLocal);
  				var appConfigPath = Path.GetDirectoryName(Path.GetDirectoryName(configuration.FilePath));
  				var baseConfigPath = Path.GetDirectoryName(appConfigPath);
  
  				// the .NET implementation for getting previous version's config file just checks for existence of highest version directory before current version
  				// it doesn't even care whether or not a user.config file actually exists in the directory, just that the directory with correct name format is there
  				// thus, we won't check for existence of user.config either for determining whether or not we need to migrate from former directories
  
  				// check all subdirectories of the current app config path for one which represents a version earlier than the current version
  				// if such a folder exists, then it will be used as the previous version config file, so we are done here
  				if (String.IsNullOrWhiteSpace(appConfigPath) || String.IsNullOrWhiteSpace(baseConfigPath)
  				    || (Directory.Exists(appConfigPath) && Directory.GetDirectories(appConfigPath).Any(x =>
  				                                                                                       	{
  				                                                                                       		Version version;
  				                                                                                       		return Version.TryParse(Path.GetFileName(x), out version) && version < currentVersion;
  				                                                                                       	}))) return;
  
  				// if an earlier version doesn't exist, check the former app settings folder names (former names usually happen due to change of CLR version)
  				foreach (var formerAppConfigFolder in formerAppConfigFolders)
  				{
  					var formerAppConfigPath = Path.Combine(baseConfigPath, formerAppConfigFolder);
  					if (!Directory.Exists(formerAppConfigPath)) continue;
  
  					// enumerate the subdirectories of the former app config path for the highest version prior to current version that has a user.config file
  					const string userConfig = "user.config";
  					foreach (var previousVersion in Directory.GetDirectories(formerAppConfigPath).Select(x =>
  					                                                                                     	{
  					                                                                                     		Version version;
  					                                                                                     		return Version.TryParse(Path.GetFileName(x), out version) && version < currentVersion ? version : null;
  					                                                                                     	}).Where(x => x != null).OrderByDescending(x => x))
  					{
  						var formerConfigFile = Path.Combine(formerAppConfigPath, previousVersion.ToString().Trim(), userConfig);
  						if (File.Exists(formerConfigFile))
  						{
  							// copy the user.config for the highest previous version in one of the former directories (respecting order of preference) to the current app config path
  							// we keep the previous version, so that when .NET tries to get previous config file, it will see this copy as the previous version (which it was, but in another CLR)
  							var migratedConfigPath = Path.Combine(appConfigPath, previousVersion.ToString().Trim());
  							var migratedConfigFile = Path.Combine(migratedConfigPath, userConfig);
  							Directory.CreateDirectory(migratedConfigPath);
  							File.Copy(formerConfigFile, migratedConfigFile);
  							Platform.Log(LogLevel.Debug, "Migrated previous version user settings from a different app directory\r\nSource: {0}\r\nDestination: {1}", formerConfigFile, migratedConfigFile);
  							return;
  						}
  					}
  				}
  			}
  			catch (Exception ex)
  			{
  				// if any exception is thrown, just log and continue
  				Platform.Log(LogLevel.Debug, ex, "Failure while attempting to migrate previous version user settings in a different app directory");
  			}
  		}
  
  		/// <summary>
  		/// Determines an alternate application settings folder for the current app domain.
  		/// </summary>
  		private static string GetAlternateAppSettingsFolder()
  		{
  			try
  			{
  				var appDomainEvidence = AppDomain.CurrentDomain.Evidence;
  				var configurationFilePath = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;
  				var previousInstallDir = UpgradeSettings.Default.PreviousInstallDir;
  				return GetAlternateAppSettingsFolder(previousInstallDir, appDomainEvidence, configurationFilePath);
  			}
  			catch (Exception ex)
  			{
  				// if any exception is thrown, just log and continue
  				Platform.Log(LogLevel.Debug, ex, "Failure while attempting to determine an alternate application settings directory");
  				return null;
  			}
  		}
  
  		/// <summary>
  		/// Determines an alternate application settings folder for the provided details. (This overload really only exists for unit test purposes)
  		/// </summary>
  		internal static string GetAlternateAppSettingsFolder(string previousInstallDir, Evidence appDomainEvidence, string configurationFilePath)
  		{
  			try
  			{
  				// if the strong name evidence is available for the appdomain, it would've been used and there wouldn't be an 'alternate'
  				if (appDomainEvidence.GetHostEvidence<StrongName>() != null) return null;
  
  				// get the current app settings folder name
  				var currentAppConfigFolder = Path.GetFileName(Path.GetDirectoryName(Path.GetDirectoryName(configurationFilePath)));
  				if (string.IsNullOrWhiteSpace(currentAppConfigFolder) || currentAppConfigFolder.Length <= 32) return null;
  
  				// check that it uses the 'Url' type and strip off the hash
  				var baseAppConfigFolder = currentAppConfigFolder.Substring(0, currentAppConfigFolder.Length - 32);
  				if (!baseAppConfigFolder.EndsWith("_Url_", StringComparison.InvariantCultureIgnoreCase)) return null;
  
  				// get the url evidence for the appdomain
  
gt: 				var urlEvidence = appDomainEvidence.GetHostEvidence<Url>();