///
/// Copyright © 2003-2008 JetBrains s.r.o.
/// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder).
///
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System35;
using JetBrains.Annotations;
using JetBrains.Build.InstallationData;
using Microsoft.Win32;
namespace JetBrains.Omea.Base.Install
{
///
/// Handles the Registry entries by writing them into the Windows Registry.
///
public class LocalInstaller
{
#region Data
///
/// Caches the macro substitution regex.
///
private static Regex myRegexMacro;
#endregion
#region Operations
///
/// Performs the local installation of the given installation data by writing the Registry keys and copying the files.
///
/// The installation data.
/// Stage, either install or uninstall.
/// The logging facility.
/// Resolves the source directory, for copying the files from.
/// Resolves the target directory, for copying the files into.
/// The maros to be substituted on install, if needed.
public static void Install(InstallationDataXml dataxml, RegistrationStage stage, IDictionary macros, Func ResolveSourceDirRoot, Func ResolveTargetDirRoot, Action LogMessage)
{
InstallRegistry(dataxml.Registry, stage, macros);
InstallFiles(dataxml, stage, LogMessage, ResolveSourceDirRoot, ResolveTargetDirRoot);
}
///
/// Substs the macros in a string, throws if there are undefined macros.
///
[NotNull]
public static string SubstituteMacros([NotNull] IDictionary macros, [NotNull] string sample)
{
if(macros == null)
throw new ArgumentNullException("macros");
if(sample == null)
throw new ArgumentNullException("sample");
string retval = sample;
foreach(var pair in macros)
retval = retval.Replace(string.Format("$({0})", pair.Key), pair.Value);
// Are there any undefined macros?
if(myRegexMacro == null)
myRegexMacro = new Regex(@"\$\((?[^)]*)\)", RegexOptions.Compiled);
MatchCollection matches = myRegexMacro.Matches(retval);
if(matches.Count != 0)
{
var sw = new StringWriter();
sw.Write("Error: undefined macros ");
foreach(Match match in matches)
sw.Write("“{0}” ", match.Groups["MacroName"].Value);
sw.Write("in “{0}”.", sample);
throw new InvalidOperationException(sw.ToString());
}
return retval;
}
#endregion
#region Implementation
///
/// Gets the proper Windows Registry root key.
///
private static RegistryKey GetWindowsRegistryRootKey(RegistryHiveXml hive)
{
switch(hive)
{
case RegistryHiveXml.Hkcr:
return Registry.ClassesRoot;
case RegistryHiveXml.Hklm:
return Registry.LocalMachine;
case RegistryHiveXml.Hkcu:
return Registry.CurrentUser;
case RegistryHiveXml.Hkmu:
return Registry.LocalMachine; // TODO: differentiate per-machine and per-user, if ever needed
default:
throw new ArgumentOutOfRangeException("hive", hive, "Unexpected Registry hive.");
}
}
///
/// Copies or deletes the files.
///
private static void InstallFiles(InstallationDataXml dataxml, RegistrationStage stage, Action LogMessage, Func ResolveSourceDirRoot, Func ResolveTargetDirRoot)
{
dataxml.AssertValid();
foreach(FolderXml folderxml in dataxml.Files)
{
var diSource = new DirectoryInfo(Path.Combine(ResolveSourceDirRoot(folderxml.SourceRoot).FullName, folderxml.SourceDir));
var diTarget = new DirectoryInfo(Path.Combine(ResolveTargetDirRoot(folderxml.TargetRoot).FullName, folderxml.TargetDir));
diTarget.Create();
foreach(FileXml filexml in folderxml.Files)
{
FileInfo[] files = diSource.GetFiles(filexml.SourceName);
if(files.Length == 0)
throw new InvalidOperationException(string.Format("There are no files matching the “{0}” mask in the source folder “{1}”.", filexml.SourceName, diSource.FullName));
if((files.Length > 1) && (filexml.TargetName.Length > 0))
throw new InvalidOperationException(string.Format("There are {2} files matching the “{0}” mask in the source folder “{1}”, in which case it's illegal to specify a target name for the file.", filexml.SourceName, diSource.FullName, files.Length));
foreach(FileInfo fiSource in files)
{
var fiTarget = new FileInfo(Path.Combine(diTarget.FullName, (filexml.TargetName.Length > 0 ? filexml.TargetName : fiSource.Name))); // Explicit target name, if present and if a single file; otherwise, use from source
// Don't copy and, especially, don't delete the inplace files
if(fiSource.FullName == fiTarget.FullName)
{
LogMessage(string.Format("Skipping “{0}” because source and target are the same file.", fiSource.FullName, fiTarget.FullName));
continue;
}
switch(stage)
{
case RegistrationStage.Register:
LogMessage(string.Format("Installing “{0}” -> “{1}”.", fiSource.FullName, fiTarget.FullName));
fiSource.CopyTo(fiTarget.FullName, true);
break;
case RegistrationStage.Unregister:
LogMessage(string.Format("Uninstalling “{0}”.", fiTarget.FullName));
fiTarget.Delete();
break;
default:
throw new InvalidOperationException(string.Format("Unexpected stage {0}.", stage));
}
}
}
}
}
///
/// Executes the registration/unregistration operations on the Registry keys.
///
/// The Registry to process.
/// Processing type.
/// The macros to substitute when processing the keys.
private static void InstallRegistry(RegistryXml registry, RegistrationStage registrationStage, IDictionary macros)
{
if(registry == null)
throw new ArgumentNullException("registry");
if(macros == null)
throw new ArgumentNullException("macros");
registry.AssertValid();
switch(registrationStage)
{
case RegistrationStage.Register:
foreach(RegistryKeyXml key in registry.Key)
RegisterKey(key, macros);
foreach(RegistryValueXml value in registry.Value)
RegisterValue(value, macros);
break;
case RegistrationStage.Unregister:
for(int a = registry.Value.Length - 1; a >= 0; a--) // Reverse order on uninstallation
UnregisterValue(registry.Value[a], macros);
for(int a = registry.Key.Length - 1; a >= 0; a--)
UnregisterKey(registry.Key[a], macros);
break;
default:
throw new ArgumentOutOfRangeException("registrationStage", registrationStage, "Unexpected registration stage.");
}
}
///
/// Writes the key to the Registry.
///
private static void RegisterKey([NotNull] RegistryKeyXml key, [NotNull] IDictionary macros)
{
if(key == null)
throw new ArgumentNullException("key");
if(macros == null)
throw new ArgumentNullException("macros");
try
{
string sPath = SubstituteMacros(macros, key.Key);
RegistryKey regkeyRoot = GetWindowsRegistryRootKey(key.Hive);
using(regkeyRoot.CreateSubKey(sPath))
{
}
}
catch(Exception ex)
{
throw new InvalidOperationException(string.Format("Failed to process the key {0}. {1}", key, ex.Message), ex);
}
}
///
/// Writes the value to the Registry.
///
private static void RegisterValue([NotNull] RegistryValueXml value, [NotNull] IDictionary macros)
{
if(value == null)
throw new ArgumentNullException("value");
if(macros == null)
throw new ArgumentNullException("macros");
try
{
string sPath = SubstituteMacros(macros, value.Key);
RegistryKey regkeyRoot = GetWindowsRegistryRootKey(value.Hive);
RegistryKey regkey = regkeyRoot.OpenSubKey(sPath, true);
if(regkey == null)
regkey = regkeyRoot.CreateSubKey(sPath); // TODO: check if this works when more than one level is missing
using(regkey)
{
string sName = SubstituteMacros(macros, value.Name);
object oValue;
switch(value.Type)
{
case RegistryValueTypeXml.Dword:
oValue = unchecked((int)(uint)long.Parse(value.Value)); // Ensure it qualifies as an Int32 for the DWORD registry value
break;
case RegistryValueTypeXml.String:
oValue = SubstituteMacros(macros, value.Value);
break;
default:
throw new InvalidOperationException(string.Format("The registry value type “{0}” is unknown.", value.Type));
}
regkey.SetValue(sName, oValue);
}
}
catch(Exception ex)
{
throw new InvalidOperationException(string.Format("Failed to process the value {0}. {1}", value, ex.Message), ex);
}
}
///
/// Deletes the key from the Registry.
///
private static void UnregisterKey([NotNull] RegistryKeyXml key, [NotNull] IDictionary macros)
{
if(key == null)
throw new ArgumentNullException("key");
if(macros == null)
throw new ArgumentNullException("macros");
try
{
string sKeyPath = SubstituteMacros(macros, key.Key);
RegistryKey regkeyRoot = GetWindowsRegistryRootKey(key.Hive);
using(RegistryKey regkey = regkeyRoot.OpenSubKey(sKeyPath, false))
{
if(regkey == null)
return; // No key, nothing to delete
}
regkeyRoot.DeleteSubKeyTree(sKeyPath); // Must do a check because this function will throw if there is no key
}
catch(Exception ex)
{
throw new InvalidOperationException(string.Format("Failed to process the key {0}. {1}", key, ex.Message), ex);
}
}
///
/// Deletes the value from the Registry.
///
private static void UnregisterValue([NotNull] RegistryValueXml value, [NotNull] IDictionary macros)
{
if(value == null)
throw new ArgumentNullException("value");
if(macros == null)
throw new ArgumentNullException("macros");
try
{
string sKeyPath = SubstituteMacros(macros, value.Key);
RegistryKey regkeyRoot = GetWindowsRegistryRootKey(value.Hive);
using(RegistryKey regkey = regkeyRoot.OpenSubKey(sKeyPath, true))
{
if(regkey == null)
return; // No key, no value to delete
string sName = SubstituteMacros(macros, value.Name);
regkey.DeleteValue(sName, false);
}
}
catch(Exception ex)
{
throw new InvalidOperationException(string.Format("Failed to process the value {0}. {1}", value, ex.Message), ex);
}
}
#endregion
}
}