434 lines
13 KiB
C#
434 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Threading;
|
|
using System.Windows.Forms;
|
|
using JD.Kernel.Classes;
|
|
using JD.Kernel.StaticHelpers;
|
|
using JD.LogService;
|
|
using JD.TeaBoxAddinSystem.Addins;
|
|
using JD.TeaBoxAddinSystem.Addins.MEF;
|
|
using JD.TeaBoxAddinSystem.Addins.MEF.Integration;
|
|
using JD.Threading;
|
|
using Message = JD.LogService.Message;
|
|
|
|
namespace JD.TeaBoxAddinSystem.Bootstrap
|
|
{
|
|
/// <summary>
|
|
/// Base class for objects used to bootstrap applications.
|
|
/// </summary>
|
|
public class ApplicationStartup
|
|
{
|
|
#region Variables
|
|
|
|
internal const string CoreBootstrapPath = "Bootstrap/All";
|
|
|
|
private readonly Arguments m_arguments;
|
|
private readonly string m_applicationName;
|
|
private readonly RuntimeMode m_runtimeMode = RuntimeMode.Normal;
|
|
private readonly ApplicationConfiguration m_configuration;
|
|
private readonly LoggingContext m_bootstrapLoggingContext = new LoggingContext();
|
|
|
|
private static ApplicationStartup m_instance;
|
|
|
|
private IDisposable m_splashScreenHandle;
|
|
|
|
// Metrics
|
|
private StartupStatistic m_startupStatistic;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="arguments">Application Arguments.</param>
|
|
/// <param name="applicationName">Application name.</param>
|
|
/// <param name="runtimeMode">Runtime mode.</param>
|
|
public ApplicationStartup(string[] arguments, string applicationName, RuntimeMode runtimeMode)
|
|
{
|
|
if (string.IsNullOrEmpty(applicationName))
|
|
{
|
|
applicationName = (Runtime.EntryAssembly != null ? Runtime.EntryAssembly.FullName : Runtime.ApplicationName);
|
|
}
|
|
|
|
m_arguments = new Arguments(arguments);
|
|
m_applicationName = applicationName;
|
|
m_configuration = new ApplicationConfiguration(ApplicationName);
|
|
m_runtimeMode = runtimeMode;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Unique instance of the started application startup. Not for external use. Back off.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Consider using <see cref="Runtime"/> instead.
|
|
/// </remarks>
|
|
public static ApplicationStartup Instance
|
|
{
|
|
get
|
|
{
|
|
return m_instance;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Arguments.
|
|
/// </summary>
|
|
public Arguments Arguments
|
|
{
|
|
get
|
|
{
|
|
return m_arguments;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Application Name.
|
|
/// </summary>
|
|
public string ApplicationName
|
|
{
|
|
get
|
|
{
|
|
return m_applicationName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the application was started in embedded mode.
|
|
/// </summary>
|
|
public RuntimeMode RuntimeMode
|
|
{
|
|
get
|
|
{
|
|
return m_runtimeMode;
|
|
}
|
|
}
|
|
|
|
internal ApplicationConfiguration Configuration
|
|
{
|
|
get
|
|
{
|
|
return m_configuration;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if this application startup forces the application in command-line mode.
|
|
/// </summary>
|
|
protected virtual bool ForceCommandLineMode
|
|
{
|
|
get
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Addin Logging Context.
|
|
/// </summary>
|
|
public LoggingContext BootstrapLoggingContext
|
|
{
|
|
get { return m_bootstrapLoggingContext; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
#region Main Methods
|
|
|
|
/// <summary>
|
|
/// Starts the application (must be in embedded mode).
|
|
/// </summary>
|
|
/// <returns>Dispose the result to quit the application.</returns>
|
|
public IDisposable StartEmbedded()
|
|
{
|
|
Throw.Assert(RuntimeMode == RuntimeMode.EmbeddedInProcess || RuntimeMode == RuntimeMode.SystemTests);
|
|
|
|
Start();
|
|
|
|
return new DisposableHelper(Quit);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the application.
|
|
/// </summary>
|
|
/// <returns>Exit code for the application.</returns>
|
|
public int Start()
|
|
{
|
|
if (Runtime.Initialized)
|
|
{
|
|
throw new InvalidOperationException("Cannot start an ApplicationStartup twice in a same AppDomain");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(Thread.CurrentThread.Name))
|
|
{
|
|
Thread.CurrentThread.Name = "Main Thread";
|
|
}
|
|
|
|
Debug.Assert(m_instance == null);
|
|
m_instance = this;
|
|
|
|
m_startupStatistic = new StartupStatistic();
|
|
if (ForceCommandLineMode == false)
|
|
{
|
|
Idle.Event += Application_Idle;
|
|
}
|
|
|
|
#if DEBUG
|
|
if (Debugger.IsAttached)
|
|
{
|
|
return StartImpl();
|
|
}
|
|
#endif
|
|
return StartImpl();
|
|
}
|
|
|
|
private int StartImpl()
|
|
{
|
|
#if DEBUG
|
|
Control.CheckForIllegalCrossThreadCalls = true;
|
|
#endif
|
|
|
|
// To have invariant type conversion
|
|
ThreadHelper.SetDefaultThreadCulture(Thread.CurrentThread);
|
|
|
|
LogService.LogService.LogMessage("Starting {0}...", ApplicationName);
|
|
ConfigureInternal();
|
|
|
|
// Initialize the runtime.
|
|
LogService.LogService.LogMessage("Initializing runtime...");
|
|
Runtime.Initialize(this, m_bootstrapLoggingContext);
|
|
|
|
LogService.LogService.LogMessage("Starting core services...");
|
|
StartCoreServices();
|
|
|
|
LogService.LogService.LogMessage("Running...");
|
|
return Run();
|
|
}
|
|
|
|
|
|
|
|
private void ConfigureInternal()
|
|
{
|
|
Configure(m_configuration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the main services
|
|
/// </summary>
|
|
protected virtual void StartCoreServices()
|
|
{
|
|
StringParserService.StringParserService.Properties["ApplicationName"] = ApplicationName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configures the application, using the given <paramref name="configuration"/>.
|
|
/// </summary>
|
|
/// <param name="configuration"></param>
|
|
protected virtual void Configure(ApplicationConfiguration configuration)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes and processes the command-line arguments
|
|
/// </summary>
|
|
/// <exception cref="ArgumentException">Thrown when some arguments are invalid.</exception>
|
|
private void InitializeCommandLineArguments()
|
|
{
|
|
Runtime.CommandLineMode = ShouldStartInCommandLineMode(Runtime.CommandLineHandlerPaths);
|
|
Runtime.CommandLineMode |= ForceCommandLineMode;
|
|
}
|
|
|
|
private bool ShouldStartInCommandLineMode(IEnumerable<string> handlersPath)
|
|
{
|
|
bool commandLineMode = ForceCommandLineMode;
|
|
|
|
foreach (string path in handlersPath)
|
|
{
|
|
foreach (object obj in AddinManager.GetExtensionObjects<object>(path))
|
|
{
|
|
ICommandLineHandler handler = obj as ICommandLineHandler;
|
|
if (handler != null && handler.Initialize())
|
|
{
|
|
commandLineMode = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return commandLineMode;
|
|
}
|
|
|
|
private void BootstrapAddinEngine()
|
|
{
|
|
m_bootstrapLoggingContext.MessageAdded += delegate(object sender, ItemEventArgs<Message> e)
|
|
{
|
|
LogService.LogService.LogMessage(e.Item);
|
|
};
|
|
|
|
m_bootstrapLoggingContext.WarningAdded += delegate(object sender, ItemEventArgs<Error> e)
|
|
{
|
|
LogService.LogService.LogWarning(e.Item);
|
|
};
|
|
|
|
m_bootstrapLoggingContext.ErrorAdded += delegate(object sender, ItemEventArgs<Error> e)
|
|
{
|
|
LogService.LogService.LogError(e.Item);
|
|
};
|
|
|
|
AddinManager.Loggers.Add(m_bootstrapLoggingContext);
|
|
Runtime.AddinRepository.Update(m_bootstrapLoggingContext);
|
|
Runtime.AddinRepository.Load();
|
|
}
|
|
|
|
private void BootstrapMEFAddinEngine()
|
|
{
|
|
AddinRepository repository = new AddinRepository(Runtime.ApplicationRootPath, m_bootstrapLoggingContext);
|
|
repository.Load();
|
|
|
|
var service = new MEFAddinService(repository, m_bootstrapLoggingContext);
|
|
AddinService.Use(service);
|
|
}
|
|
|
|
private int Run()
|
|
{
|
|
LogService.LogService.LogMessage("Initializing Addin Tree subsystem");
|
|
var stopwatch = Stopwatch.StartNew();
|
|
BootstrapAddinEngine();
|
|
LogService.LogService.LogMessage($"Addin Tree subsystem initialized in {stopwatch.Elapsed.TotalSeconds}s");
|
|
|
|
LogService.LogService.LogMessage("Initializing MEF subsystem");
|
|
stopwatch.Restart();
|
|
BootstrapMEFAddinEngine();
|
|
LogService.LogService.LogMessage($"MEF subsystem initialized in {stopwatch.Elapsed.TotalSeconds}s");
|
|
|
|
try
|
|
{
|
|
LogService.LogService.LogMessage("Parsing command-line arguments...");
|
|
InitializeCommandLineArguments();
|
|
|
|
ShowSplashScreen();
|
|
|
|
int exitCode;
|
|
OnStarting(out exitCode);
|
|
if (exitCode != 0)
|
|
{
|
|
return exitCode;
|
|
}
|
|
|
|
// run bootstrap commands
|
|
LogService.LogService.LogMessage("Running Bootstrap commands...");
|
|
var coreBootstrap = new BootstrapCommandRunner(AddinManager.GetExtensionObjects<IBootstrapCommand>(CoreBootstrapPath));
|
|
coreBootstrap.Run();
|
|
var addinBootstrap = new BootstrapCommandRunner(AddinManager.GetExtensionObjects<IBootstrapCommand>(m_configuration.BootstrapAddinPath));
|
|
addinBootstrap.Run();
|
|
|
|
if (Runtime.RuntimeMode != RuntimeMode.Normal)
|
|
{
|
|
OnAfterApplicationStarted();
|
|
}
|
|
else
|
|
{
|
|
Quit();
|
|
}
|
|
|
|
return Runtime.ExitCode;
|
|
}
|
|
catch (CommandLineArgumentException e)
|
|
{
|
|
LogService.LogService.LogError("Error in arguments: {0}", e.Message);
|
|
Console.Error.WriteLine(e.Message);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should not be called directly. To quit properly the application, use Application.Quit().
|
|
/// </summary>
|
|
/// <returns>Exit code</returns>
|
|
private void Quit()
|
|
{
|
|
LogService.LogService.LogMessage("Leaving application...");
|
|
OnQuitting();
|
|
OnQuitted(EventArgs.Empty);
|
|
}
|
|
|
|
private void ShowSplashScreen()
|
|
{
|
|
#if !DEBUG
|
|
bool noLogo = Arguments["nologo"].Count > 0;
|
|
|
|
if (!noLogo && !Runtime.CommandLineMode && Runtime.ShouldStartOwnInstance)
|
|
{
|
|
Idle.Event -= Application_Idle;
|
|
m_splashScreenHandle = OnShowSplashScreen();
|
|
Application.DoEvents();
|
|
Idle.Event += Application_Idle;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
protected virtual IDisposable OnShowSplashScreen()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called just before the application starts.
|
|
/// </summary>
|
|
protected virtual void OnStarting(out int exitCode)
|
|
{
|
|
PropertyService.PropertyService.Load();
|
|
MessageService.MessageService.Initialize();
|
|
exitCode = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called just before the application quits.
|
|
/// </summary>
|
|
protected virtual void OnQuitting()
|
|
{
|
|
PropertyService.PropertyService.Close();
|
|
MessageService.MessageService.Close();
|
|
DisposableHelper.SafeDispose(ref m_splashScreenHandle);
|
|
}
|
|
|
|
private void OnAfterApplicationStarted()
|
|
{
|
|
m_startupStatistic.MemoryUsedAfterStartUp = Process.GetCurrentProcess().PrivateMemorySize64;
|
|
m_startupStatistic.Dispose();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
void Application_Idle(object sender, EventArgs e)
|
|
{
|
|
Idle.Event -= Application_Idle;
|
|
DisposableHelper.SafeDispose(ref m_splashScreenHandle);
|
|
OnAfterApplicationStarted();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public event EventHandler Quitted;
|
|
|
|
private void OnQuitted(EventArgs e)
|
|
{
|
|
Quitted?.Invoke(this, e);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|