diff --git a/Source/ChocolateyGui.Common.Windows/ChocolateyGui.Common.Windows.csproj b/Source/ChocolateyGui.Common.Windows/ChocolateyGui.Common.Windows.csproj index ca795e875..5a96d1e0b 100644 --- a/Source/ChocolateyGui.Common.Windows/ChocolateyGui.Common.Windows.csproj +++ b/Source/ChocolateyGui.Common.Windows/ChocolateyGui.Common.Windows.csproj @@ -68,6 +68,8 @@ ..\packages\chocolatey.lib.0.10.15\lib\chocolatey.dll + False + False ..\packages\ControlzEx.4.4.0\lib\net462\ControlzEx.dll diff --git a/Source/ChocolateyGui.Common/ChocolateyGui.Common.csproj b/Source/ChocolateyGui.Common/ChocolateyGui.Common.csproj index 40b8bdc3e..2ea24e28f 100644 --- a/Source/ChocolateyGui.Common/ChocolateyGui.Common.csproj +++ b/Source/ChocolateyGui.Common/ChocolateyGui.Common.csproj @@ -55,6 +55,8 @@ ..\packages\chocolatey.lib.0.10.15\lib\chocolatey.dll + False + False ..\packages\LiteDB.5.0.5\lib\net45\LiteDB.dll @@ -187,6 +189,7 @@ + @@ -222,5 +225,8 @@ + + + \ No newline at end of file diff --git a/Source/ChocolateyGui.Common/Startup/AssemblyResolver.cs b/Source/ChocolateyGui.Common/Startup/AssemblyResolver.cs new file mode 100644 index 000000000..d18c64c13 --- /dev/null +++ b/Source/ChocolateyGui.Common/Startup/AssemblyResolver.cs @@ -0,0 +1,121 @@ +// +// Copyright 2017 - Present Chocolatey Software, LLC +// Copyright 2014 - 2017 Rob Reynolds, the maintainers of Chocolatey, and RealDimensions Software, LLC +// + +using System; +using System.Linq; +using System.Reflection; +using System.Threading; +using Serilog; + +namespace ChocolateyGui.Common.Startup +{ + public class AssemblyResolver + { + private const int LOCKRESOLUTIONTIMEOUTSECONDS = 5; + private static readonly object _lockObject = new object(); + + public static Assembly ResolveOrReloadChocolateyAssembly(string assemblyFileLocation) + { + return ResolveOrLoadAssemblyInternal( + "chocolatey", + string.Empty, + assemblyFileLocation, + (assembly) => + { + var assemblyName = assembly.GetName(); + + return string.Equals(assemblyName.Name, "chocolatey", StringComparison.OrdinalIgnoreCase) && + assemblyName.Version != new Version(0, 10, 15, 0); // TODO: Should maybe be created automatically + }); + } + + /// + /// Resolves or loads an assembly. If an assembly is already loaded, no need to reload it. + /// + /// Simple Name of the assembly, such as "chocolatey" + /// The public key token. + /// The assembly file location. Typically the path to the DLL on disk. + /// An assembly + /// Unable to enter synchronized code to determine assembly loading + public static Assembly ResolveOrLoadAssembly(string assemblySimpleName, string publicKeyToken, string assemblyFileLocation) + { + return ResolveOrLoadAssemblyInternal( + assemblySimpleName, + publicKeyToken, + assemblyFileLocation, + (assembly) => string.Equals(assembly.GetName().Name, assemblySimpleName, StringComparison.OrdinalIgnoreCase)); + } + + public static bool IsPublicKeyToken(AssemblyName assemblyName, string expectedKeyToken) + { + var publicKey = GetPublicKeyToken(assemblyName); + + return string.Equals(publicKey, expectedKeyToken, StringComparison.OrdinalIgnoreCase); + } + + public static string GetPublicKeyToken(AssemblyName assemblyName) + { + if (assemblyName == null) + { + return string.Empty; + } + + var publicKeyToken = assemblyName.GetPublicKeyToken(); + + if (publicKeyToken == null || publicKeyToken.Length == 0) + { + return string.Empty; + } + + return publicKeyToken.Select(x => x.ToString("x2")).Aggregate((x, y) => x + y); + } + + private static Assembly ResolveOrLoadAssemblyInternal(string assemblySimpleName, string publicKeyToken, string assemblyFileLocation, Func assemblyPredicate) + { + var lockTaken = false; + try + { + Monitor.TryEnter(_lockObject, TimeSpan.FromSeconds(LOCKRESOLUTIONTIMEOUTSECONDS), ref lockTaken); + } + catch (Exception) + { + throw new Exception("Unable to enter synchronized code to determine assembly loading"); + } + + Assembly resolvedAssembly = null; + if (lockTaken) + { + try + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(assemblyPredicate)) + { + if (string.IsNullOrWhiteSpace(publicKeyToken) || string.Equals(GetPublicKeyToken(assembly.GetName()), publicKeyToken, StringComparison.OrdinalIgnoreCase)) + { + Log.Debug("Returning loaded assembly type for '{0}'", assemblySimpleName); + + resolvedAssembly = assembly; + break; + } + } + + if (resolvedAssembly == null) + { + Log.Debug("Loading up '{0}' assembly type from '{1}'", assemblySimpleName, assemblyFileLocation); + + // Reading the raw bytes and calling 'Load' causes an exception, as such we use LoadFrom instead. + resolvedAssembly = Assembly.LoadFrom(assemblyFileLocation); + } + } + finally + { + Monitor.Pulse(_lockObject); + Monitor.Exit(_lockObject); + } + } + + return resolvedAssembly; + } + } +} \ No newline at end of file diff --git a/Source/ChocolateyGui/App.xaml.cs b/Source/ChocolateyGui/App.xaml.cs index d46dbb9cf..fc8d3806f 100644 --- a/Source/ChocolateyGui/App.xaml.cs +++ b/Source/ChocolateyGui/App.xaml.cs @@ -6,13 +6,13 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.IO; using System.Reflection; using System.Windows; using Autofac; -using chocolatey; -using chocolatey.infrastructure.registration; using ChocolateyGui.Common.Enums; using ChocolateyGui.Common.Services; +using ChocolateyGui.Common.Startup; using ChocolateyGui.Common.Windows; using ChocolateyGui.Common.Windows.Theming; @@ -32,30 +32,49 @@ public App() { var requestedAssembly = new AssemblyName(args.Name); + try + { + if (string.Equals(requestedAssembly.Name, "chocolatey", StringComparison.OrdinalIgnoreCase)) + { + var installDir = Environment.GetEnvironmentVariable("ChocolateyInstall"); + if (string.IsNullOrEmpty(installDir)) + { + var rootDrive = Path.GetPathRoot(Assembly.GetExecutingAssembly().Location); + if (string.IsNullOrEmpty(rootDrive)) + { + return null; // TODO: Maybe return the chocolatey.dll file instead? + } + + installDir = Path.Combine(rootDrive, "ProgramData", "chocolatey"); + } + + var assemblyLocation = Path.Combine(installDir, "choco.exe"); + + return AssemblyResolver.ResolveOrLoadAssembly("choco", string.Empty, assemblyLocation); + } + #if FORCE_CHOCOLATEY_OFFICIAL_KEY var chocolateyGuiPublicKey = Bootstrapper.OfficialChocolateyPublicKey; #else - var chocolateyGuiPublicKey = Bootstrapper.UnofficialChocolateyPublicKey; + var chocolateyGuiPublicKey = Bootstrapper.UnofficialChocolateyPublicKey; #endif - try - { - if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyGuiPublicKey) - && requestedAssembly.Name.is_equal_to(Bootstrapper.ChocolateyGuiCommonAssemblySimpleName)) + if (AssemblyResolver.IsPublicKeyToken(requestedAssembly, chocolateyGuiPublicKey) + && string.Equals(requestedAssembly.Name, Bootstrapper.ChocolateyGuiCommonAssemblySimpleName, StringComparison.OrdinalIgnoreCase)) { - return AssemblyResolution.resolve_or_load_assembly( + return AssemblyResolver.ResolveOrLoadAssembly( Bootstrapper.ChocolateyGuiCommonAssemblySimpleName, - requestedAssembly.get_public_key_token(), - Bootstrapper.ChocolateyGuiCommonAssemblyLocation).UnderlyingType; + AssemblyResolver.GetPublicKeyToken(requestedAssembly), + Bootstrapper.ChocolateyGuiCommonAssemblyLocation); } - if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyGuiPublicKey) - && requestedAssembly.Name.is_equal_to(Bootstrapper.ChocolateyGuiCommonWindowsAssemblySimpleName)) + if (AssemblyResolver.IsPublicKeyToken(requestedAssembly, chocolateyGuiPublicKey) + && string.Equals(requestedAssembly.Name, Bootstrapper.ChocolateyGuiCommonWindowsAssemblySimpleName, StringComparison.OrdinalIgnoreCase)) { - return AssemblyResolution.resolve_or_load_assembly( + return AssemblyResolver.ResolveOrLoadAssembly( Bootstrapper.ChocolateyGuiCommonWindowsAssemblySimpleName, - requestedAssembly.get_public_key_token(), - Bootstrapper.ChocolateyGuiCommonWindowsAssemblyLocation).UnderlyingType; + AssemblyResolver.GetPublicKeyToken(requestedAssembly), + Bootstrapper.ChocolateyGuiCommonWindowsAssemblyLocation); } } catch (Exception ex) diff --git a/Source/ChocolateyGui/ChocolateyGui.csproj b/Source/ChocolateyGui/ChocolateyGui.csproj index 634242a84..c1a3d4fe4 100644 --- a/Source/ChocolateyGui/ChocolateyGui.csproj +++ b/Source/ChocolateyGui/ChocolateyGui.csproj @@ -99,6 +99,9 @@ ..\ChocolateyGuiRules.ruleset true + + true + ..\packages\Autofac.4.6.1\lib\net45\Autofac.dll @@ -117,6 +120,8 @@ ..\packages\chocolatey.lib.0.10.15\lib\chocolatey.dll + False + False ..\packages\ControlzEx.4.4.0\lib\net462\ControlzEx.dll diff --git a/Source/ChocolateyGuiCli/Bootstrapper.cs b/Source/ChocolateyGuiCli/Bootstrapper.cs index 42b26d0bd..76f54ce55 100644 --- a/Source/ChocolateyGuiCli/Bootstrapper.cs +++ b/Source/ChocolateyGuiCli/Bootstrapper.cs @@ -6,6 +6,7 @@ // -------------------------------------------------------------------------------------------------------------------- using System.IO; +using System.Reflection; using Autofac; using chocolatey.infrastructure.filesystem; using ChocolateyGui.Common; @@ -21,7 +22,10 @@ public static class Bootstrapper private static readonly IFileSystem _fileSystem = new DotNetFileSystem(); #pragma warning disable SA1202 - public static readonly string ChocolateyGuiInstallLocation = _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); + + // Due to an unknown reason, we can not use chocolateys own get_current_assembly() function here, + // as it will be returning the path to the choco.exe executable instead. + public static readonly string ChocolateyGuiInstallLocation = _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)); public static readonly string ChocolateyInstallEnvironmentVariableName = "ChocolateyInstall"; public static readonly string ChocolateyInstallLocation = System.Environment.GetEnvironmentVariable(ChocolateyInstallEnvironmentVariableName) ?? _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); public static readonly string LicensedGuiAssemblyLocation = _fileSystem.combine_paths(ChocolateyInstallLocation, "extensions", "chocolateygui", "chocolateygui.licensed.dll"); diff --git a/Source/ChocolateyGuiCli/ChocolateyGuiCli.csproj b/Source/ChocolateyGuiCli/ChocolateyGuiCli.csproj index 7606e5ec3..73e419212 100644 --- a/Source/ChocolateyGuiCli/ChocolateyGuiCli.csproj +++ b/Source/ChocolateyGuiCli/ChocolateyGuiCli.csproj @@ -57,6 +57,8 @@ ..\packages\chocolatey.lib.0.10.15\lib\chocolatey.dll + False + False ..\packages\LiteDB.5.0.5\lib\net45\LiteDB.dll @@ -91,8 +93,9 @@ Properties\SolutionInfo.cs - + + diff --git a/Source/ChocolateyGuiCli/Program.cs b/Source/ChocolateyGuiCli/Program.cs index ffbe76ad5..1eca0d956 100644 --- a/Source/ChocolateyGuiCli/Program.cs +++ b/Source/ChocolateyGuiCli/Program.cs @@ -1,166 +1,82 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright 2017 - Present Chocolatey Software, LLC -// Copyright 2014 - 2017 Rob Reynolds, the maintainers of Chocolatey, and RealDimensions Software, LLC +// +// Copyright 2017 - Present Chocolatey Software, LLC +// Copyright 2014 - 2017 Rob Reynolds, the maintainers of Chocolatey, and RealDimensions Software, LLC // -// -------------------------------------------------------------------------------------------------------------------- using System; -using System.Collections.Generic; -using System.Linq; +using System.IO; using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using Autofac; -using chocolatey; -using chocolatey.infrastructure.commandline; -using chocolatey.infrastructure.registration; -using ChocolateyGui.Common.Attributes; -using ChocolateyGui.Common.Commands; -using ChocolateyGui.Common.Models; -using ChocolateyGui.Common.Services; -using LiteDB; -using Serilog; -using Assembly = chocolatey.infrastructure.adapters.Assembly; -using Console = System.Console; -using GenericRunner = ChocolateyGui.Common.Commands.GenericRunner; +using ChocolateyGui.Common.Startup; namespace ChocolateyGuiCli { public class Program { - private static readonly OptionSet _optionSet = new OptionSet(); private static ResolveEventHandler _handler = null; - public static OptionSet OptionSet - { - get { return _optionSet; } - } - public static void Main(string[] args) { - try - { - AddAssemblyResolver(); - - Bootstrapper.Configure(); - - var commandName = string.Empty; - IList commandArgs = new List(); - - // shift the first arg off - int count = 0; - foreach (var arg in args) - { - if (count == 0) - { - count += 1; - continue; - } - - commandArgs.Add(arg); - } + AddAssemblyResolver(); - var configuration = new ChocolateyGuiConfiguration(); - SetUpGlobalOptions(args, configuration, Bootstrapper.Container); - SetEnvironmentOptions(configuration); - - if (configuration.RegularOutput) - { - #if DEBUG - Bootstrapper.Logger.Warning(" (DEBUG BUILD)".format_with("Chocolatey GUI", configuration.Information.DisplayVersion)); - #else - Bootstrapper.Logger.Warning("{0}".format_with(configuration.Information.DisplayVersion)); - #endif + Runner.Run(args); + } - if (args.Length == 0) - { - Bootstrapper.Logger.Information(ChocolateyGui.Common.Properties.Resources.Command_CommandsText.format_with("chocolateyguicli")); - } - } + #region DupFinder Exclusion - var runner = new GenericRunner(); - runner.Run(configuration, Bootstrapper.Container, command => - { - ParseArgumentsAndUpdateConfiguration( - commandArgs, - configuration, - (optionSet) => command.ConfigureArgumentParser(optionSet, configuration), - (unparsedArgs) => - { - command.HandleAdditionalArgumentParsing(unparsedArgs, configuration); - }, - () => - { - Bootstrapper.Logger.Debug("Performing validation checks..."); - command.HandleValidation(configuration); - }, - () => command.HelpMessage(configuration)); - }); - } - catch (Exception ex) - { - Bootstrapper.Logger.Error(ex.Message); - } - finally + private static void AddAssemblyResolver() + { + _handler = (sender, args) => { - Log.CloseAndFlush(); + var requestedAssembly = new AssemblyName(args.Name); - if (Bootstrapper.Container != null) + try { - if (Bootstrapper.Container.IsRegisteredWithName(Bootstrapper.GlobalConfigurationDatabaseName)) + if (string.Equals(requestedAssembly.Name, "chocolatey", StringComparison.OrdinalIgnoreCase)) { - var globalDatabase = Bootstrapper.Container.ResolveNamed(Bootstrapper.GlobalConfigurationDatabaseName); - globalDatabase.Dispose(); - } + var installDir = Environment.GetEnvironmentVariable("ChocolateyInstall"); + if (string.IsNullOrEmpty(installDir)) + { + var rootDrive = Path.GetPathRoot(Assembly.GetExecutingAssembly().Location); + if (string.IsNullOrEmpty(rootDrive)) + { + return null; // TODO: Maybe return the chocolatey.dll file instead? + } - if (Bootstrapper.Container.IsRegisteredWithName(Bootstrapper.UserConfigurationDatabaseName)) - { - var userDatabase = Bootstrapper.Container.ResolveNamed(Bootstrapper.UserConfigurationDatabaseName); - userDatabase.Dispose(); - } + installDir = Path.Combine(rootDrive, "ProgramData", "chocolatey"); + } - Bootstrapper.Container.Dispose(); - } - } - } + var assemblyLocation = Path.Combine(installDir, "choco.exe"); - #region DupFinder Exclusion - private static void AddAssemblyResolver() - { - _handler = (sender, args) => - { - var requestedAssembly = new AssemblyName(args.Name); + return AssemblyResolver.ResolveOrLoadAssembly("choco", string.Empty, assemblyLocation); + } #if FORCE_CHOCOLATEY_OFFICIAL_KEY - var chocolateyGuiPublicKey = Bootstrapper.OfficialChocolateyPublicKey; + var chocolateyGuiPublicKey = Bootstrapper.OfficialChocolateyPublicKey; #else - var chocolateyGuiPublicKey = Bootstrapper.UnofficialChocolateyPublicKey; + var chocolateyGuiPublicKey = Bootstrapper.UnofficialChocolateyPublicKey; #endif - try - { - if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyGuiPublicKey) - && requestedAssembly.Name.is_equal_to(Bootstrapper.ChocolateyGuiCommonAssemblySimpleName)) + if (AssemblyResolver.IsPublicKeyToken(requestedAssembly, chocolateyGuiPublicKey) + && string.Equals(requestedAssembly.Name, Bootstrapper.ChocolateyGuiCommonAssemblySimpleName, StringComparison.OrdinalIgnoreCase)) { - return AssemblyResolution.resolve_or_load_assembly( + return AssemblyResolver.ResolveOrLoadAssembly( Bootstrapper.ChocolateyGuiCommonAssemblySimpleName, - requestedAssembly.get_public_key_token(), - Bootstrapper.ChocolateyGuiCommonAssemblyLocation).UnderlyingType; + AssemblyResolver.GetPublicKeyToken(requestedAssembly), + Bootstrapper.ChocolateyGuiCommonAssemblyLocation); } - if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyGuiPublicKey) - && requestedAssembly.Name.is_equal_to(Bootstrapper.ChocolateyGuiCommonWindowsAssemblySimpleName)) + if (AssemblyResolver.IsPublicKeyToken(requestedAssembly, chocolateyGuiPublicKey) + && string.Equals(requestedAssembly.Name, Bootstrapper.ChocolateyGuiCommonWindowsAssemblySimpleName, StringComparison.OrdinalIgnoreCase)) { - return AssemblyResolution.resolve_or_load_assembly( + return AssemblyResolver.ResolveOrLoadAssembly( Bootstrapper.ChocolateyGuiCommonWindowsAssemblySimpleName, - requestedAssembly.get_public_key_token(), - Bootstrapper.ChocolateyGuiCommonWindowsAssemblyLocation).UnderlyingType; + AssemblyResolver.GetPublicKeyToken(requestedAssembly), + Bootstrapper.ChocolateyGuiCommonWindowsAssemblyLocation); } } catch (Exception ex) { - Bootstrapper.Logger.Warning("Unable to load Chocolatey GUI assembly. {0}".format_with(ex.Message)); + Bootstrapper.Logger.Warning("Unable to load Chocolatey GUI assembly. {0}", ex.Message); } return null; @@ -168,145 +84,7 @@ private static void AddAssemblyResolver() AppDomain.CurrentDomain.AssemblyResolve += _handler; } - #endregion - private static void SetUpGlobalOptions(IList args, ChocolateyGuiConfiguration configuration, IContainer container) - { - ParseArgumentsAndUpdateConfiguration( - args, - configuration, - (option_set) => - { - option_set - .Add( - "r|limitoutput|limit-output", - ChocolateyGui.Common.Properties.Resources.Command_LimitOutputOption, - option => configuration.RegularOutput = option == null); - }, - (unparsedArgs) => - { - if (!string.IsNullOrWhiteSpace(configuration.CommandName)) - { - // save help for next menu - configuration.HelpRequested = false; - configuration.UnsuccessfulParsing = false; - } - }, - () => { }, - () => - { - var commandsLog = new StringBuilder(); - var commands = container.Resolve>(); - foreach (var command in commands.or_empty_list_if_null()) - { - var attributes = command.GetType().GetCustomAttributes(typeof(LocalizedCommandForAttribute), false).Cast(); - foreach (var attribute in attributes.or_empty_list_if_null()) - { - commandsLog.AppendFormat(" * {0} - {1}\n", attribute.CommandName, attribute.Description); - } - } - - Bootstrapper.Logger.Information(ChocolateyGui.Common.Properties.Resources.Command_CommandsListText.format_with("chocolateyguicli")); - Bootstrapper.Logger.Information(string.Empty); - Bootstrapper.Logger.Warning(ChocolateyGui.Common.Properties.Resources.Command_CommandsTitle); - Bootstrapper.Logger.Information(string.Empty); - Bootstrapper.Logger.Information("{0}".format_with(commandsLog.ToString())); - Bootstrapper.Logger.Information(ChocolateyGui.Common.Properties.Resources.Command_CommandsText.format_with("chocolateyguicli")); - Bootstrapper.Logger.Information(string.Empty); - Bootstrapper.Logger.Warning(ChocolateyGui.Common.Properties.Resources.Command_DefaultOptionsAndSwitches); - }); - } - - private static void SetEnvironmentOptions(ChocolateyGuiConfiguration config) - { - var versionService = Bootstrapper.Container.Resolve(); - config.Information.ChocolateyGuiVersion = versionService.Version; - config.Information.ChocolateyGuiProductVersion = versionService.InformationalVersion; - config.Information.DisplayVersion = versionService.DisplayVersion; - config.Information.FullName = Assembly.GetExecutingAssembly().FullName; - } - - private static void ParseArgumentsAndUpdateConfiguration( - ICollection args, - ChocolateyGuiConfiguration configuration, - Action setOptions, - Action> afterParse, - Action validateConfiguration, - Action helpMessage) - { - IList unparsedArguments = new List(); - - // add help only once - if (_optionSet.Count == 0) - { - _optionSet - .Add( - "?|help|h", - ChocolateyGui.Common.Properties.Resources.Command_HelpOption, - option => configuration.HelpRequested = option != null); - } - - if (setOptions != null) - { - setOptions(_optionSet); - } - - try - { - unparsedArguments = _optionSet.Parse(args); - } - catch (OptionException) - { - ShowHelp(_optionSet, helpMessage); - configuration.UnsuccessfulParsing = true; - } - - // the command argument - if (string.IsNullOrWhiteSpace(configuration.CommandName) && - unparsedArguments.Contains(args.FirstOrDefault())) - { - var commandName = args.FirstOrDefault(); - if (!Regex.IsMatch(commandName, @"^[-\/+]")) - { - configuration.CommandName = commandName; - } - else if (commandName.is_equal_to("-v") || commandName.is_equal_to("--version")) - { - // skip help menu - } - else - { - configuration.HelpRequested = true; - configuration.UnsuccessfulParsing = true; - } - } - - if (afterParse != null) - { - afterParse(unparsedArguments); - } - - if (configuration.HelpRequested) - { - ShowHelp(_optionSet, helpMessage); - } - else - { - if (validateConfiguration != null) - { - validateConfiguration(); - } - } - } - - private static void ShowHelp(OptionSet optionSet, Action helpMessage) - { - if (helpMessage != null) - { - helpMessage.Invoke(); - } - - optionSet.WriteOptionDescriptions(Console.Out); - } + #endregion DupFinder Exclusion } } \ No newline at end of file diff --git a/Source/ChocolateyGuiCli/Runner.cs b/Source/ChocolateyGuiCli/Runner.cs new file mode 100644 index 000000000..b0ce4f33f --- /dev/null +++ b/Source/ChocolateyGuiCli/Runner.cs @@ -0,0 +1,262 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright 2017 - Present Chocolatey Software, LLC +// Copyright 2014 - 2017 Rob Reynolds, the maintainers of Chocolatey, and RealDimensions Software, LLC +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using Autofac; +using chocolatey; +using chocolatey.infrastructure.commandline; +using ChocolateyGui.Common.Attributes; +using ChocolateyGui.Common.Commands; +using ChocolateyGui.Common.Models; +using ChocolateyGui.Common.Services; +using LiteDB; +using Serilog; +using Console = System.Console; +using GenericRunner = ChocolateyGui.Common.Commands.GenericRunner; + +namespace ChocolateyGuiCli +{ + public class Runner + { + private static readonly OptionSet _optionSet = new OptionSet(); + + public static OptionSet OptionSet + { + get { return _optionSet; } + } + + public static void Run(string[] args) + { + try + { + Bootstrapper.Configure(); + + var commandName = string.Empty; + IList commandArgs = new List(); + + // shift the first arg off + var count = 0; + foreach (var arg in args) + { + if (count == 0) + { + count += 1; + continue; + } + + commandArgs.Add(arg); + } + + var configuration = new ChocolateyGuiConfiguration(); + SetUpGlobalOptions(args, configuration, Bootstrapper.Container); + SetEnvironmentOptions(configuration); + + if (configuration.RegularOutput) + { +#if DEBUG + Bootstrapper.Logger.Warning(" (DEBUG BUILD)".format_with("Chocolatey GUI", configuration.Information.DisplayVersion)); +#else + Bootstrapper.Logger.Warning("{0}".format_with(configuration.Information.DisplayVersion)); +#endif + + if (args.Length == 0) + { + Bootstrapper.Logger.Information(ChocolateyGui.Common.Properties.Resources.Command_CommandsText.format_with("chocolateyguicli")); + } + } + + var runner = new GenericRunner(); + runner.Run(configuration, Bootstrapper.Container, command => + { + ParseArgumentsAndUpdateConfiguration( + commandArgs, + configuration, + (optionSet) => command.ConfigureArgumentParser(optionSet, configuration), + (unparsedArgs) => + { + command.HandleAdditionalArgumentParsing(unparsedArgs, configuration); + }, + () => + { + Bootstrapper.Logger.Debug("Performing validation checks..."); + command.HandleValidation(configuration); + }, + () => command.HelpMessage(configuration)); + }); + } + catch (Exception ex) + { + Bootstrapper.Logger.Error(ex.Message); + } + finally + { + Log.CloseAndFlush(); + + if (Bootstrapper.Container != null) + { + if (Bootstrapper.Container.IsRegisteredWithName(Bootstrapper.GlobalConfigurationDatabaseName)) + { + var globalDatabase = Bootstrapper.Container.ResolveNamed(Bootstrapper.GlobalConfigurationDatabaseName); + globalDatabase.Dispose(); + } + + if (Bootstrapper.Container.IsRegisteredWithName(Bootstrapper.UserConfigurationDatabaseName)) + { + var userDatabase = Bootstrapper.Container.ResolveNamed(Bootstrapper.UserConfigurationDatabaseName); + userDatabase.Dispose(); + } + + Bootstrapper.Container.Dispose(); + } + } + } + + private static void SetUpGlobalOptions(IList args, ChocolateyGuiConfiguration configuration, IContainer container) + { + ParseArgumentsAndUpdateConfiguration( + args, + configuration, + (option_set) => + { + option_set + .Add( + "r|limitoutput|limit-output", + ChocolateyGui.Common.Properties.Resources.Command_LimitOutputOption, + option => configuration.RegularOutput = option == null); + }, + (unparsedArgs) => + { + if (!string.IsNullOrWhiteSpace(configuration.CommandName)) + { + // save help for next menu + configuration.HelpRequested = false; + configuration.UnsuccessfulParsing = false; + } + }, + () => { }, + () => + { + var commandsLog = new StringBuilder(); + var commands = container.Resolve>(); + foreach (var command in commands.or_empty_list_if_null()) + { + var attributes = command.GetType().GetCustomAttributes(typeof(LocalizedCommandForAttribute), false).Cast(); + foreach (var attribute in attributes.or_empty_list_if_null()) + { + commandsLog.AppendFormat(" * {0} - {1}\n", attribute.CommandName, attribute.Description); + } + } + + Bootstrapper.Logger.Information(ChocolateyGui.Common.Properties.Resources.Command_CommandsListText.format_with("chocolateyguicli")); + Bootstrapper.Logger.Information(string.Empty); + Bootstrapper.Logger.Warning(ChocolateyGui.Common.Properties.Resources.Command_CommandsTitle); + Bootstrapper.Logger.Information(string.Empty); + Bootstrapper.Logger.Information("{0}".format_with(commandsLog.ToString())); + Bootstrapper.Logger.Information(ChocolateyGui.Common.Properties.Resources.Command_CommandsText.format_with("chocolateyguicli")); + Bootstrapper.Logger.Information(string.Empty); + Bootstrapper.Logger.Warning(ChocolateyGui.Common.Properties.Resources.Command_DefaultOptionsAndSwitches); + }); + } + + private static void SetEnvironmentOptions(ChocolateyGuiConfiguration config) + { + var versionService = Bootstrapper.Container.Resolve(); + config.Information.ChocolateyGuiVersion = versionService.Version; + config.Information.ChocolateyGuiProductVersion = versionService.InformationalVersion; + config.Information.DisplayVersion = versionService.DisplayVersion; + config.Information.FullName = Assembly.GetExecutingAssembly().FullName; + } + + private static void ParseArgumentsAndUpdateConfiguration( + ICollection args, + ChocolateyGuiConfiguration configuration, + Action setOptions, + Action> afterParse, + Action validateConfiguration, + Action helpMessage) + { + IList unparsedArguments = new List(); + + // add help only once + if (_optionSet.Count == 0) + { + _optionSet + .Add( + "?|help|h", + ChocolateyGui.Common.Properties.Resources.Command_HelpOption, + option => configuration.HelpRequested = option != null); + } + + if (setOptions != null) + { + setOptions(_optionSet); + } + + try + { + unparsedArguments = _optionSet.Parse(args); + } + catch (OptionException) + { + ShowHelp(_optionSet, helpMessage); + configuration.UnsuccessfulParsing = true; + } + + // the command argument + if (string.IsNullOrWhiteSpace(configuration.CommandName) && + unparsedArguments.Contains(args.FirstOrDefault())) + { + var commandName = args.FirstOrDefault(); + if (!Regex.IsMatch(commandName, @"^[-\/+]")) + { + configuration.CommandName = commandName; + } + else if (commandName.is_equal_to("-v") || commandName.is_equal_to("--version")) + { + // skip help menu + } + else + { + configuration.HelpRequested = true; + configuration.UnsuccessfulParsing = true; + } + } + + if (afterParse != null) + { + afterParse(unparsedArguments); + } + + if (configuration.HelpRequested) + { + ShowHelp(_optionSet, helpMessage); + } + else + { + if (validateConfiguration != null) + { + validateConfiguration(); + } + } + } + + private static void ShowHelp(OptionSet optionSet, Action helpMessage) + { + if (helpMessage != null) + { + helpMessage.Invoke(); + } + + optionSet.WriteOptionDescriptions(Console.Out); + } + } +} \ No newline at end of file