diff --git a/src/Tools/Orchard/HostContext/CommandHostContext.cs b/src/Tools/Orchard/HostContext/CommandHostContext.cs index 933942159..4a10f88b8 100644 --- a/src/Tools/Orchard/HostContext/CommandHostContext.cs +++ b/src/Tools/Orchard/HostContext/CommandHostContext.cs @@ -1,15 +1,16 @@ using System; using System.IO; using System.Web.Hosting; -using Orchard; using Orchard.Host; -namespace OrchardCLI { +namespace Orchard.HostContext { public class CommandHostContext { - public int StartSessionResult { get; set; } + public bool Interactive { get; set; } public int RetryResult { get; set; } public OrchardParameters Arguments { get; set; } public DirectoryInfo OrchardDirectory { get; set; } + public int StartSessionResult { get; set; } + public bool ShowHelp { get; set; } public ApplicationManager AppManager { get; set; } public ApplicationObject AppObject { get; set; } public CommandHost CommandHost { get; set; } diff --git a/src/Tools/Orchard/HostContext/CommandHostContextProvider.cs b/src/Tools/Orchard/HostContext/CommandHostContextProvider.cs index 984538f57..cc138fa7c 100644 --- a/src/Tools/Orchard/HostContext/CommandHostContextProvider.cs +++ b/src/Tools/Orchard/HostContext/CommandHostContextProvider.cs @@ -1,14 +1,14 @@ -using System; +using System; using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; using System.Web; using System.Web.Hosting; -using Orchard; using Orchard.Host; using Orchard.Parameters; -namespace OrchardCLI { +namespace Orchard.HostContext { public class CommandHostContextProvider : ICommandHostContextProvider { private readonly string[] _args; private TextWriter _output; @@ -20,8 +20,9 @@ namespace OrchardCLI { _args = args; } - public CommandHostContext CreateContext() { + public CommandHostContext CreateContext(bool interactive) { var context = new CommandHostContext(); + context.Interactive = interactive; context.RetryResult = 240;/*special return code for "Retry"*/ Initialize(context); return context; @@ -51,18 +52,24 @@ namespace OrchardCLI { // Perform some argument validation and display usage if something is incorrect bool showHelp = context.Arguments.Switches.ContainsKey("?"); if (!showHelp) { - //showHelp = (!_arguments.Arguments.Any() && !_arguments.ResponseFiles.Any()); + // If not interactive CLI, we need at least some arguments... + if (!context.Interactive) { + showHelp = (!context.Arguments.Arguments.Any() && !context.Arguments.ResponseFiles.Any()); + } } if (!showHelp) { - //showHelp = (_arguments.Arguments.Any() && _arguments.ResponseFiles.Any()); - //if (showHelp) { - // _output.WriteLine("Incorrect syntax: Response files cannot be used in conjunction with commands"); - //} + // If not interactive CLI, we need + if (!context.Interactive) { + showHelp = (context.Arguments.Arguments.Any() && context.Arguments.ResponseFiles.Any()); + if (showHelp) { + _output.WriteLine("Incorrect syntax: Response files cannot be used in conjunction with commands"); + } + } } if (showHelp) { - GeneralHelp(); + context.ShowHelp = true; return; } @@ -87,58 +94,6 @@ namespace OrchardCLI { context.StartSessionResult = context.CommandHost.StartSession(_input, _output); } - private int GeneralHelp() { - _output.WriteLine("Executes Orchard commands from a Orchard installation directory."); - _output.WriteLine(""); - _output.WriteLine("Usage:"); - _output.WriteLine(" orchard.exe command [arg1] ... [argn] [/switch1[:value1]] ... [/switchn[:valuen]]"); - _output.WriteLine(" orchard.exe @response-file1 ... [@response-filen] [/switch1[:value1]] ... [/switchn[:valuen]]"); - _output.WriteLine(""); - _output.WriteLine(" command"); - _output.WriteLine(" Specify the command to execute"); - _output.WriteLine(""); - _output.WriteLine(" [arg1] ... [argn]"); - _output.WriteLine(" Specify additional arguments for the command"); - _output.WriteLine(""); - _output.WriteLine(" [/switch1[:value1]] ... [/switchn[:valuen]]"); - _output.WriteLine(" Specify switches to apply to the command. Available switches generally "); - _output.WriteLine(" depend on the command executed, with the exception of a few built-in ones."); - _output.WriteLine(""); - _output.WriteLine(" [@response-file1] ... [@response-filen]"); - _output.WriteLine(" Specify one or more response files to be used for reading commands and switches."); - _output.WriteLine(" A response file is a text file that contains one line per command to execute."); - _output.WriteLine(""); - _output.WriteLine(" Built-in commands"); - _output.WriteLine(" ================="); - _output.WriteLine(""); - _output.WriteLine(" help commands"); - _output.WriteLine(" Display the list of available commands."); - _output.WriteLine(""); - _output.WriteLine(" help "); - _output.WriteLine(" Display help for a given command."); - _output.WriteLine(""); - _output.WriteLine(" Built-in switches"); - _output.WriteLine(" ================="); - _output.WriteLine(""); - _output.WriteLine(" /WorkingDirectory:"); - _output.WriteLine(" /wd:"); - _output.WriteLine(" Specifies the orchard installation directory. The current directory is the default."); - _output.WriteLine(""); - _output.WriteLine(" /Verbose"); - _output.WriteLine(" /v"); - _output.WriteLine(" Turn on verbose output"); - _output.WriteLine(""); - _output.WriteLine(" /VirtualPath:"); - _output.WriteLine(" /vp:"); - _output.WriteLine(" Virtual path to pass to the WebHost. Empty (i.e. root path) by default."); - _output.WriteLine(""); - _output.WriteLine(" /Tenant:tenant-name"); - _output.WriteLine(" /t:tenant-name"); - _output.WriteLine(" Specifies which tenant to run the command into. \"Default\" tenant by default."); - _output.WriteLine(""); - return 1; - } - private void LogInfo(CommandHostContext context, string format, params object[] args) { if (context.Logger != null) context.Logger.LogInfo(format, args); diff --git a/src/Tools/Orchard/HostContext/ICommandHostContextProvider.cs b/src/Tools/Orchard/HostContext/ICommandHostContextProvider.cs index 8a75c4309..5e80982f7 100644 --- a/src/Tools/Orchard/HostContext/ICommandHostContextProvider.cs +++ b/src/Tools/Orchard/HostContext/ICommandHostContextProvider.cs @@ -1,6 +1,6 @@ -namespace OrchardCLI { +namespace Orchard.HostContext { public interface ICommandHostContextProvider { - CommandHostContext CreateContext(); + CommandHostContext CreateContext(bool interactive); void Shutdown(CommandHostContext context); } } \ No newline at end of file diff --git a/src/Tools/Orchard/OrchardHost.cs b/src/Tools/Orchard/OrchardHost.cs index 4233ddcad..8a8c2bbf4 100644 --- a/src/Tools/Orchard/OrchardHost.cs +++ b/src/Tools/Orchard/OrchardHost.cs @@ -1,30 +1,18 @@ using System; using System.Linq; -using System.Globalization; using System.IO; -using System.Reflection; -using System.Web; -using System.Web.Hosting; -using Orchard.Host; -using Orchard.Parameters; -using System.Threading; +using Orchard.HostContext; namespace Orchard { class OrchardHost { private readonly TextReader _input; private readonly TextWriter _output; - private readonly string[] _args; - private OrchardParameters _arguments; - private Logger _logger; + private readonly ICommandHostContextProvider _commandHostContextProvider; public OrchardHost(TextReader input, TextWriter output, string[] args) { _input = input; _output = output; - _args = args; - } - - private bool Verbose { - get { return _arguments != null && _arguments.Verbose; } + _commandHostContextProvider = new CommandHostContextProvider(args); } public int Run() { @@ -32,86 +20,46 @@ namespace Orchard { return DoRun(); } catch (Exception e) { - _output.WriteLine("Error:"); + _output.WriteLine("Error running command:"); for (; e != null; e = e.InnerException) { _output.WriteLine(" {0}", e.Message); - if (_logger != null) { - _output.WriteLine(" Stacktrace:"); - _output.WriteLine("{0}", e.StackTrace); - _output.WriteLine(); - } } return -1; } } private int DoRun() { - _arguments = new OrchardParametersParser().Parse(new CommandParametersParser().Parse(_args)); - _logger = new Logger(_arguments.Verbose, _output); - - // Perform some argument validation and display usage if something is incorrect - bool showHelp = _arguments.Switches.ContainsKey("?"); - if (!showHelp) { - showHelp = (!_arguments.Arguments.Any() && !_arguments.ResponseFiles.Any()); + var context = CommandHostContext(); + if (context.ShowHelp) { + DisplayHelp(); + return 0; } - if (!showHelp) { - showHelp = (_arguments.Arguments.Any() && _arguments.ResponseFiles.Any()); - if (showHelp) { - _output.WriteLine("Incorrect syntax: Response files cannot be used in conjunction with commands"); - } - } - - if (showHelp) { - return GeneralHelp(); - } - - if (string.IsNullOrEmpty(_arguments.VirtualPath)) - _arguments.VirtualPath = "/"; - LogInfo("Virtual path: \"{0}\"", _arguments.VirtualPath); - - if (string.IsNullOrEmpty(_arguments.WorkingDirectory)) - _arguments.WorkingDirectory = Environment.CurrentDirectory; - LogInfo("Working directory: \"{0}\"", _arguments.WorkingDirectory); - - LogInfo("Detecting orchard installation root directory..."); - var orchardDirectory = GetOrchardDirectory(_arguments.WorkingDirectory); - LogInfo("Orchard root directory: \"{0}\"", orchardDirectory.FullName); - - int result = CreateHostAndExecute(orchardDirectory); - if (result == 240/*special return code for "Retry"*/) - result = CreateHostAndExecute(orchardDirectory); + var result = Execute(context); + _commandHostContextProvider.Shutdown(context); return result; } - private int CreateHostAndExecute(DirectoryInfo orchardDirectory) { - var appManager = ApplicationManager.GetApplicationManager(); - - LogInfo("Creating ASP.NET AppDomain for command agent..."); - var appObject = CreateWorkerAppDomainWithHost(appManager, _arguments.VirtualPath, orchardDirectory.FullName, typeof(CommandHost)); - var host = (CommandHost)appObject.ObjectInstance; - - LogInfo("Executing command in ASP.NET AppDomain..."); - var result = Execute(host); - LogInfo("Return code for command: {0}", result); - - LogInfo("Shutting down ASP.NET AppDomain..."); - appManager.ShutdownApplication(appObject.ApplicationId); - + private CommandHostContext CommandHostContext() { + _output.WriteLine("Initializing Orchard host. (This might take a few seconds...)"); + var result = _commandHostContextProvider.CreateContext(false/*interactive*/); + if (result.StartSessionResult == result.RetryResult) { + result = _commandHostContextProvider.CreateContext(false/*interactive*/); + } return result; } - private int Execute(CommandHost host) { - if (_arguments.ResponseFiles.Any()) { - var responseLines = new ResponseFiles.ResponseFiles().ReadFiles(_arguments.ResponseFiles); - return host.RunCommands(_input, _output, _logger, responseLines.ToArray()); + private int Execute(CommandHostContext context) { + if (context.Arguments.ResponseFiles.Any()) { + var responseLines = new ResponseFiles.ResponseFiles().ReadFiles(context.Arguments.ResponseFiles); + return context.CommandHost.RunCommands(_input, _output, context.Logger, responseLines.ToArray()); } else { - return host.RunCommand(_input, _output, _logger, _arguments); + return context.CommandHost.RunCommand(_input, _output, context.Logger, context.Arguments); } } - private int GeneralHelp() { + private void DisplayHelp() { _output.WriteLine("Executes Orchard commands from a Orchard installation directory."); _output.WriteLine(""); _output.WriteLine("Usage:"); @@ -160,64 +108,7 @@ namespace Orchard { _output.WriteLine(" /t:tenant-name"); _output.WriteLine(" Specifies which tenant to run the command into. \"Default\" tenant by default."); _output.WriteLine(""); - return 0; - } - - private void LogInfo(string format, params object[] args) { - if (_logger != null) - _logger.LogInfo(format, args); - } - - private DirectoryInfo GetOrchardDirectory(string directory) { - for (var directoryInfo = new DirectoryInfo(directory); directoryInfo != null; directoryInfo = directoryInfo.Parent) { - if (!directoryInfo.Exists) { - throw new ApplicationException(string.Format("Directory \"{0}\" does not exist", directoryInfo.FullName)); - } - - // We look for - // 1) .\web.config - // 2) .\bin\Orchard.Framework.dll - var webConfigFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, "web.config")); - if (!webConfigFileInfo.Exists) - continue; - - var binDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName, "bin")); - if (!binDirectoryInfo.Exists) - continue; - - var orchardFrameworkFileInfo = new FileInfo(Path.Combine(binDirectoryInfo.FullName, "Orchard.Framework.dll")); - if (!orchardFrameworkFileInfo.Exists) - continue; - - return directoryInfo; - } - - throw new ApplicationException( - string.Format("Directory \"{0}\" doesn't seem to contain an Orchard installation", new DirectoryInfo(directory).FullName)); - } - - private static ApplicationObject CreateWorkerAppDomainWithHost(ApplicationManager appManager, string virtualPath, string physicalPath, Type hostType) { - // this creates worker app domain in a way that host doesn't need to be in GAC or bin - // using BuildManagerHost via private reflection - string uniqueAppString = string.Concat(virtualPath, physicalPath).ToLowerInvariant(); - string appId = (uniqueAppString.GetHashCode()).ToString("x", CultureInfo.InvariantCulture); - - // create BuildManagerHost in the worker app domain - var buildManagerHostType = typeof(HttpRuntime).Assembly.GetType("System.Web.Compilation.BuildManagerHost"); - var buildManagerHost = appManager.CreateObject(appId, buildManagerHostType, virtualPath, physicalPath, false); - - // call BuildManagerHost.RegisterAssembly to make Host type loadable in the worker app domain - buildManagerHostType.InvokeMember( - "RegisterAssembly", - BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, - null, - buildManagerHost, - new object[] { hostType.Assembly.FullName, hostType.Assembly.Location }); - - // create Host in the worker app domain - return new ApplicationObject { - ApplicationId = appId, - ObjectInstance = appManager.CreateObject(appId, hostType, virtualPath, physicalPath, false) }; + return; } } } diff --git a/src/Tools/OrchardCli/CLIHost.cs b/src/Tools/OrchardCli/CLIHost.cs index 0a85394da..490d1a41f 100644 --- a/src/Tools/OrchardCli/CLIHost.cs +++ b/src/Tools/OrchardCli/CLIHost.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Orchard; +using Orchard.HostContext; using Orchard.Parameters; using Orchard.ResponseFiles; @@ -17,7 +18,26 @@ namespace OrchardCLI { } public int Run() { + try { + return DoRun(); + } + catch (Exception e) { + _output.WriteLine("Error:"); + for (; e != null; e = e.InnerException) { + _output.WriteLine(" {0}", e.Message); + } + return -1; + } + } + + + public int DoRun() { var context = CommandHostContext(); + if (context.ShowHelp) { + DisplayHelp(); + return 0; + } + _output.WriteLine("Type \"help commands\" for help, \"exit\" to exit, \"cls\" to clear screen"); while (true) { var command = ReadCommand(context); @@ -46,9 +66,9 @@ namespace OrchardCLI { private CommandHostContext CommandHostContext() { _output.WriteLine("Initializing Orchard session. (This might take a few seconds...)"); - var result = _commandHostContextProvider.CreateContext(); + var result = _commandHostContextProvider.CreateContext(true/*interactive*/); if (result.StartSessionResult == result.RetryResult) { - result = _commandHostContextProvider.CreateContext(); + result = _commandHostContextProvider.CreateContext(true/*interactive*/); } return result; } @@ -78,5 +98,34 @@ namespace OrchardCLI { return context.RetryResult; } } + + private void DisplayHelp() { + _output.WriteLine("Executes the Orchard interactive from a Orchard installation directory."); + _output.WriteLine(""); + _output.WriteLine("Usage:"); + _output.WriteLine(" orchardcli.exe [/switch1[:value1]] ... [/switchn[:valuen]]"); + _output.WriteLine(""); + _output.WriteLine(" Built-in switches"); + _output.WriteLine(" ================="); + _output.WriteLine(""); + _output.WriteLine(" /WorkingDirectory:"); + _output.WriteLine(" /wd:"); + _output.WriteLine(" Specifies the orchard installation directory. The current directory is the default."); + _output.WriteLine(""); + _output.WriteLine(" /Verbose"); + _output.WriteLine(" /v"); + _output.WriteLine(" Turn on verbose output"); + _output.WriteLine(""); + _output.WriteLine(" /VirtualPath:"); + _output.WriteLine(" /vp:"); + _output.WriteLine(" Virtual path to pass to the WebHost. Empty (i.e. root path) by default."); + _output.WriteLine(""); + _output.WriteLine(" /Tenant:tenant-name"); + _output.WriteLine(" /t:tenant-name"); + _output.WriteLine(" Specifies which tenant to run the command into. \"Default\" tenant by default."); + _output.WriteLine(""); + + + } } }