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;
using System.IO; using System.IO;
using System.Web.Hosting; using System.Web.Hosting;
using Orchard;
using Orchard.Host; using Orchard.Host;
namespace OrchardCLI { namespace Orchard.HostContext {
public class CommandHostContext { public class CommandHostContext {
public int StartSessionResult { get; set; } public bool Interactive { get; set; }
public int RetryResult { get; set; } public int RetryResult { get; set; }
public OrchardParameters Arguments { get; set; } public OrchardParameters Arguments { get; set; }
public DirectoryInfo OrchardDirectory { get; set; } public DirectoryInfo OrchardDirectory { get; set; }
public int StartSessionResult { get; set; }
public bool ShowHelp { get; set; }
public ApplicationManager AppManager { get; set; } public ApplicationManager AppManager { get; set; }
public ApplicationObject AppObject { get; set; } public ApplicationObject AppObject { get; set; }
public CommandHost CommandHost { get; set; } public CommandHost CommandHost { get; set; }

View File

@@ -1,14 +1,14 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Web; using System.Web;
using System.Web.Hosting; using System.Web.Hosting;
using Orchard;
using Orchard.Host; using Orchard.Host;
using Orchard.Parameters; using Orchard.Parameters;
namespace OrchardCLI { namespace Orchard.HostContext {
public class CommandHostContextProvider : ICommandHostContextProvider { public class CommandHostContextProvider : ICommandHostContextProvider {
private readonly string[] _args; private readonly string[] _args;
private TextWriter _output; private TextWriter _output;
@@ -20,8 +20,9 @@ namespace OrchardCLI {
_args = args; _args = args;
} }
public CommandHostContext CreateContext() { public CommandHostContext CreateContext(bool interactive) {
var context = new CommandHostContext(); var context = new CommandHostContext();
context.Interactive = interactive;
context.RetryResult = 240;/*special return code for "Retry"*/ context.RetryResult = 240;/*special return code for "Retry"*/
Initialize(context); Initialize(context);
return context; return context;
@@ -51,18 +52,24 @@ namespace OrchardCLI {
// Perform some argument validation and display usage if something is incorrect // Perform some argument validation and display usage if something is incorrect
bool showHelp = context.Arguments.Switches.ContainsKey("?"); bool showHelp = context.Arguments.Switches.ContainsKey("?");
if (!showHelp) { 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) { if (!showHelp) {
//showHelp = (_arguments.Arguments.Any() && _arguments.ResponseFiles.Any()); // If not interactive CLI, we need
//if (showHelp) { if (!context.Interactive) {
// _output.WriteLine("Incorrect syntax: Response files cannot be used in conjunction with commands"); 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) { if (showHelp) {
GeneralHelp(); context.ShowHelp = true;
return; return;
} }
@@ -87,58 +94,6 @@ namespace OrchardCLI {
context.StartSessionResult = context.CommandHost.StartSession(_input, _output); 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) { private void LogInfo(CommandHostContext context, string format, params object[] args) {
if (context.Logger != null) if (context.Logger != null)
context.Logger.LogInfo(format, args); context.Logger.LogInfo(format, args);

View File

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

View File

@@ -1,30 +1,18 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Globalization;
using System.IO; using System.IO;
using System.Reflection; using Orchard.HostContext;
using System.Web;
using System.Web.Hosting;
using Orchard.Host;
using Orchard.Parameters;
using System.Threading;
namespace Orchard { namespace Orchard {
class OrchardHost { class OrchardHost {
private readonly TextReader _input; private readonly TextReader _input;
private readonly TextWriter _output; private readonly TextWriter _output;
private readonly string[] _args; private readonly ICommandHostContextProvider _commandHostContextProvider;
private OrchardParameters _arguments;
private Logger _logger;
public OrchardHost(TextReader input, TextWriter output, string[] args) { public OrchardHost(TextReader input, TextWriter output, string[] args) {
_input = input; _input = input;
_output = output; _output = output;
_args = args; _commandHostContextProvider = new CommandHostContextProvider(args);
}
private bool Verbose {
get { return _arguments != null && _arguments.Verbose; }
} }
public int Run() { public int Run() {
@@ -32,86 +20,46 @@ namespace Orchard {
return DoRun(); return DoRun();
} }
catch (Exception e) { catch (Exception e) {
_output.WriteLine("Error:"); _output.WriteLine("Error running command:");
for (; e != null; e = e.InnerException) { for (; e != null; e = e.InnerException) {
_output.WriteLine(" {0}", e.Message); _output.WriteLine(" {0}", e.Message);
if (_logger != null) {
_output.WriteLine(" Stacktrace:");
_output.WriteLine("{0}", e.StackTrace);
_output.WriteLine();
}
} }
return -1; return -1;
} }
} }
private int DoRun() { private int DoRun() {
_arguments = new OrchardParametersParser().Parse(new CommandParametersParser().Parse(_args)); var context = CommandHostContext();
_logger = new Logger(_arguments.Verbose, _output); if (context.ShowHelp) {
DisplayHelp();
// Perform some argument validation and display usage if something is incorrect return 0;
bool showHelp = _arguments.Switches.ContainsKey("?");
if (!showHelp) {
showHelp = (!_arguments.Arguments.Any() && !_arguments.ResponseFiles.Any());
} }
if (!showHelp) { var result = Execute(context);
showHelp = (_arguments.Arguments.Any() && _arguments.ResponseFiles.Any()); _commandHostContextProvider.Shutdown(context);
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);
return result; return result;
} }
private int CreateHostAndExecute(DirectoryInfo orchardDirectory) { private CommandHostContext CommandHostContext() {
var appManager = ApplicationManager.GetApplicationManager(); _output.WriteLine("Initializing Orchard host. (This might take a few seconds...)");
var result = _commandHostContextProvider.CreateContext(false/*interactive*/);
LogInfo("Creating ASP.NET AppDomain for command agent..."); if (result.StartSessionResult == result.RetryResult) {
var appObject = CreateWorkerAppDomainWithHost(appManager, _arguments.VirtualPath, orchardDirectory.FullName, typeof(CommandHost)); result = _commandHostContextProvider.CreateContext(false/*interactive*/);
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);
return result; return result;
} }
private int Execute(CommandHost host) { private int Execute(CommandHostContext context) {
if (_arguments.ResponseFiles.Any()) { if (context.Arguments.ResponseFiles.Any()) {
var responseLines = new ResponseFiles.ResponseFiles().ReadFiles(_arguments.ResponseFiles); var responseLines = new ResponseFiles.ResponseFiles().ReadFiles(context.Arguments.ResponseFiles);
return host.RunCommands(_input, _output, _logger, responseLines.ToArray()); return context.CommandHost.RunCommands(_input, _output, context.Logger, responseLines.ToArray());
} }
else { 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("Executes Orchard commands from a Orchard installation directory.");
_output.WriteLine(""); _output.WriteLine("");
_output.WriteLine("Usage:"); _output.WriteLine("Usage:");
@@ -160,64 +108,7 @@ namespace Orchard {
_output.WriteLine(" /t:tenant-name"); _output.WriteLine(" /t:tenant-name");
_output.WriteLine(" Specifies which tenant to run the command into. \"Default\" tenant by default."); _output.WriteLine(" Specifies which tenant to run the command into. \"Default\" tenant by default.");
_output.WriteLine(""); _output.WriteLine("");
return 0; return;
}
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) };
} }
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using Orchard; using Orchard;
using Orchard.HostContext;
using Orchard.Parameters; using Orchard.Parameters;
using Orchard.ResponseFiles; using Orchard.ResponseFiles;
@@ -17,7 +18,26 @@ namespace OrchardCLI {
} }
public int Run() { 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(); var context = CommandHostContext();
if (context.ShowHelp) {
DisplayHelp();
return 0;
}
_output.WriteLine("Type \"help commands\" for help, \"exit\" to exit, \"cls\" to clear screen"); _output.WriteLine("Type \"help commands\" for help, \"exit\" to exit, \"cls\" to clear screen");
while (true) { while (true) {
var command = ReadCommand(context); var command = ReadCommand(context);
@@ -46,9 +66,9 @@ namespace OrchardCLI {
private CommandHostContext CommandHostContext() { private CommandHostContext CommandHostContext() {
_output.WriteLine("Initializing Orchard session. (This might take a few seconds...)"); _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) { if (result.StartSessionResult == result.RetryResult) {
result = _commandHostContextProvider.CreateContext(); result = _commandHostContextProvider.CreateContext(true/*interactive*/);
} }
return result; return result;
} }
@@ -78,5 +98,34 @@ namespace OrchardCLI {
return context.RetryResult; 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("");
}
} }
} }