orchard.exe now uses CommandHostContextProvider

--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-07-24 17:34:41 -07:00
parent 639961ca93
commit 4d8a459c0e
5 changed files with 97 additions and 201 deletions

View File

@@ -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; }

View File

@@ -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 <command-name>");
_output.WriteLine(" Display help for a given command.");
_output.WriteLine("");
_output.WriteLine(" Built-in switches");
_output.WriteLine(" =================");
_output.WriteLine("");
_output.WriteLine(" /WorkingDirectory:<physical-path>");
_output.WriteLine(" /wd:<physical-path>");
_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:<virtual-path>");
_output.WriteLine(" /vp:<virtual-path>");
_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);

View File

@@ -1,6 +1,6 @@
namespace OrchardCLI {
namespace Orchard.HostContext {
public interface ICommandHostContextProvider {
CommandHostContext CreateContext();
CommandHostContext CreateContext(bool interactive);
void Shutdown(CommandHostContext context);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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:<physical-path>");
_output.WriteLine(" /wd:<physical-path>");
_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:<virtual-path>");
_output.WriteLine(" /vp:<virtual-path>");
_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("");
}
}
}