support run as service

pull/702/head
Luke Pulverenti 12 years ago
parent b5615cb233
commit 2e511fba83

@ -1,6 +1,5 @@
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events; using MediaBrowser.Common.Events;
using MediaBrowser.Common.Implementations.Logging;
using MediaBrowser.Common.Implementations.NetworkManagement; using MediaBrowser.Common.Implementations.NetworkManagement;
using MediaBrowser.Common.Implementations.ScheduledTasks; using MediaBrowser.Common.Implementations.ScheduledTasks;
using MediaBrowser.Common.Implementations.Security; using MediaBrowser.Common.Implementations.Security;
@ -71,7 +70,7 @@ namespace MediaBrowser.Common.Implementations
/// Gets the application paths. /// Gets the application paths.
/// </summary> /// </summary>
/// <value>The application paths.</value> /// <value>The application paths.</value>
protected TApplicationPathsType ApplicationPaths = new TApplicationPathsType(); protected TApplicationPathsType ApplicationPaths { get; private set; }
/// <summary> /// <summary>
/// The container /// The container
@ -153,11 +152,12 @@ namespace MediaBrowser.Common.Implementations
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class. /// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class.
/// </summary> /// </summary>
protected BaseApplicationHost() protected BaseApplicationHost(TApplicationPathsType applicationPaths, ILogManager logManager)
{ {
FailedAssemblies = new List<string>(); FailedAssemblies = new List<string>();
LogManager = new NlogManager(ApplicationPaths.LogDirectoryPath, LogFilePrefixName); ApplicationPaths = applicationPaths;
LogManager = logManager;
ConfigurationManager = GetConfigurationManager(); ConfigurationManager = GetConfigurationManager();
} }
@ -172,7 +172,10 @@ namespace MediaBrowser.Common.Implementations
Logger = LogManager.GetLogger("App"); Logger = LogManager.GetLogger("App");
LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info); LogManager.LogSeverity = ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging
? LogSeverity.Debug
: LogSeverity.Info;
OnLoggerLoaded(); OnLoggerLoaded();
DiscoverTypes(); DiscoverTypes();
@ -211,12 +214,6 @@ namespace MediaBrowser.Common.Implementations
/// <returns>IEnumerable{Assembly}.</returns> /// <returns>IEnumerable{Assembly}.</returns>
protected abstract IEnumerable<Assembly> GetComposablePartAssemblies(); protected abstract IEnumerable<Assembly> GetComposablePartAssemblies();
/// <summary>
/// Gets the name of the log file prefix.
/// </summary>
/// <value>The name of the log file prefix.</value>
protected abstract string LogFilePrefixName { get; }
/// <summary> /// <summary>
/// Gets the configuration manager. /// Gets the configuration manager.
/// </summary> /// </summary>

@ -26,6 +26,15 @@ namespace MediaBrowser.Common.Implementations
_useDebugPath = useDebugPath; _useDebugPath = useDebugPath;
} }
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
/// </summary>
/// <param name="programDataPath">The program data path.</param>
protected BaseApplicationPaths(string programDataPath)
{
_programDataPath = programDataPath;
}
/// <summary> /// <summary>
/// The _program data path /// The _program data path
/// </summary> /// </summary>

@ -1,10 +1,10 @@
using System.Linq; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Logging;
using NLog; using NLog;
using NLog.Config; using NLog.Config;
using NLog.Targets; using NLog.Targets;
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MediaBrowser.Common.Implementations.Logging namespace MediaBrowser.Common.Implementations.Logging
@ -45,6 +45,41 @@ namespace MediaBrowser.Common.Implementations.Logging
LogFilePrefix = logFileNamePrefix; LogFilePrefix = logFileNamePrefix;
} }
private LogSeverity _severity = LogSeverity.Debug;
public LogSeverity LogSeverity
{
get
{
return _severity;
}
set
{
var changed = _severity != value;
_severity = value;
if (changed)
{
UpdateLogLevel(value);
}
}
}
private void UpdateLogLevel(LogSeverity newLevel)
{
var level = GetLogLevel(newLevel);
var rules = LogManager.Configuration.LoggingRules;
foreach (var rule in rules)
{
if (!rule.IsLoggingEnabledForLevel(level))
{
rule.EnableLoggingForLevel(level);
}
}
}
/// <summary> /// <summary>
/// Adds the file target. /// Adds the file target.
/// </summary> /// </summary>
@ -154,6 +189,8 @@ namespace MediaBrowser.Common.Implementations.Logging
AddFileTarget(LogFilePath, level); AddFileTarget(LogFilePath, level);
LogSeverity = level;
if (LoggerLoaded != null) if (LoggerLoaded != null)
{ {
Task.Run(() => Task.Run(() =>

@ -25,11 +25,5 @@ namespace MediaBrowser.Controller
/// </summary> /// </summary>
/// <value>The HTTP server URL prefix.</value> /// <value>The HTTP server URL prefix.</value>
string HttpServerUrlPrefix { get; } string HttpServerUrlPrefix { get; }
/// <summary>
/// Gets a value indicating whether this instance is background service.
/// </summary>
/// <value><c>true</c> if this instance is background service; otherwise, <c>false</c>.</value>
bool IsBackgroundService { get; }
} }
} }

@ -7,6 +7,12 @@ namespace MediaBrowser.Model.Logging
/// </summary> /// </summary>
public interface ILogManager public interface ILogManager
{ {
/// <summary>
/// Gets or sets the log level.
/// </summary>
/// <value>The log level.</value>
LogSeverity LogSeverity { get; set; }
/// <summary> /// <summary>
/// Gets the logger. /// Gets the logger.
/// </summary> /// </summary>

@ -80,12 +80,6 @@ namespace MediaBrowser.Model.System
/// <value>The HTTP server port number.</value> /// <value>The HTTP server port number.</value>
public int HttpServerPortNumber { get; set; } public int HttpServerPortNumber { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is background service.
/// </summary>
/// <value><c>true</c> if this instance is background service; otherwise, <c>false</c>.</value>
public bool IsBackgroundService { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SystemInfo" /> class. /// Initializes a new instance of the <see cref="SystemInfo" /> class.
/// </summary> /// </summary>

@ -26,6 +26,17 @@ namespace MediaBrowser.Server.Implementations
{ {
} }
#endif #endif
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationPaths" /> class.
/// </summary>
/// <param name="programDataPath">The program data path.</param>
public ServerApplicationPaths(string programDataPath)
: base(programDataPath)
{
}
/// <summary> /// <summary>
/// Gets the path to the base root media directory /// Gets the path to the base root media directory
/// </summary> /// </summary>
@ -117,7 +128,7 @@ namespace MediaBrowser.Server.Implementations
return Path.Combine(ItemsByNamePath, "MusicGenre"); return Path.Combine(ItemsByNamePath, "MusicGenre");
} }
} }
/// <summary> /// <summary>
/// Gets the path to the Studio directory /// Gets the path to the Studio directory
/// </summary> /// </summary>

@ -1,4 +1,5 @@
using MediaBrowser.Controller; using MediaBrowser.Common.Events;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -12,32 +13,35 @@ namespace MediaBrowser.ServerApplication
/// <summary> /// <summary>
/// Interaction logic for App.xaml /// Interaction logic for App.xaml
/// </summary> /// </summary>
public partial class App : Application, IApplicationInterface public partial class App : Application
{ {
/// <summary> /// <summary>
/// Gets or sets the logger. /// Gets or sets the logger.
/// </summary> /// </summary>
/// <value>The logger.</value> /// <value>The logger.</value>
protected ILogger Logger { get; set; } private readonly ILogger _logger;
/// <summary> /// <summary>
/// Gets or sets the composition root. /// Gets or sets the composition root.
/// </summary> /// </summary>
/// <value>The composition root.</value> /// <value>The composition root.</value>
protected ApplicationHost CompositionRoot { get; set; } private readonly ApplicationHost _appHost;
public event EventHandler AppStarted;
public bool IsRunningAsService { get; private set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="App" /> class. /// Initializes a new instance of the <see cref="App" /> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
public App() public App(ApplicationHost appHost, ILogger logger, bool isRunningAsService)
{ {
InitializeComponent(); _appHost = appHost;
} _logger = logger;
IsRunningAsService = isRunningAsService;
public bool IsBackgroundService InitializeComponent();
{
get { return false; }
} }
/// <summary> /// <summary>
@ -51,7 +55,7 @@ namespace MediaBrowser.ServerApplication
public void OnUnhandledException(Exception ex) public void OnUnhandledException(Exception ex)
{ {
Logger.ErrorException("UnhandledException", ex); _logger.ErrorException("UnhandledException", ex);
MessageBox.Show("Unhandled exception: " + ex.Message); MessageBox.Show("Unhandled exception: " + ex.Message);
} }
@ -70,27 +74,32 @@ namespace MediaBrowser.ServerApplication
{ {
try try
{ {
CompositionRoot = new ApplicationHost(this); if (!IsRunningAsService)
{
Logger = CompositionRoot.LogManager.GetLogger("App"); ShowSplashWindow();
}
var splash = new SplashWindow(CompositionRoot.ApplicationVersion);
splash.Show(); await _appHost.Init();
await CompositionRoot.Init(); if (!IsRunningAsService)
{
HideSplashWindow();
}
splash.Hide(); var task = _appHost.RunStartupTasks();
var task = CompositionRoot.RunStartupTasks(); if (!IsRunningAsService)
{
ShowMainWindow();
}
new MainWindow(CompositionRoot.LogManager, CompositionRoot, CompositionRoot.ServerConfigurationManager, CompositionRoot.UserManager, CompositionRoot.LibraryManager, CompositionRoot.JsonSerializer, CompositionRoot.DisplayPreferencesRepository).Show(); EventHelper.FireEventIfNotNull(AppStarted, this, EventArgs.Empty, _logger);
await task.ConfigureAwait(false); await task.ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.ErrorException("Error launching application", ex); _logger.ErrorException("Error launching application", ex);
MessageBox.Show("There was an error launching Media Browser: " + ex.Message); MessageBox.Show("There was an error launching Media Browser: " + ex.Message);
@ -99,27 +108,53 @@ namespace MediaBrowser.ServerApplication
} }
} }
public void ShutdownApplication() private MainWindow _mainWindow;
private void ShowMainWindow()
{ {
Dispatcher.Invoke(Shutdown); var host = _appHost;
var win = new MainWindow(host.LogManager, host,
host.ServerConfigurationManager, host.UserManager,
host.LibraryManager, host.JsonSerializer,
host.DisplayPreferencesRepository);
win.Show();
_mainWindow = win;
} }
/// <summary> private void HideMainWindow()
/// Raises the <see cref="E:System.Windows.Application.Exit" /> event.
/// </summary>
/// <param name="e">An <see cref="T:System.Windows.ExitEventArgs" /> that contains the event data.</param>
protected override void OnExit(ExitEventArgs e)
{ {
MainStartup.ReleaseMutex(); if (_mainWindow != null)
{
_mainWindow.Hide();
_mainWindow = null;
}
}
base.OnExit(e); private SplashWindow _splashWindow;
private void ShowSplashWindow()
{
var win = new SplashWindow(_appHost.ApplicationVersion);
win.Show();
if (CompositionRoot != null) _splashWindow = win;
}
private void HideSplashWindow()
{
if (_splashWindow != null)
{ {
CompositionRoot.Dispose(); _splashWindow.Hide();
_splashWindow = null;
} }
} }
public void ShutdownApplication()
{
Dispatcher.Invoke(Shutdown);
}
/// <summary> /// <summary>
/// Opens the dashboard page. /// Opens the dashboard page.
/// </summary> /// </summary>
@ -172,20 +207,5 @@ namespace MediaBrowser.ServerApplication
{ {
((Process)sender).Dispose(); ((Process)sender).Dispose();
} }
/// <summary>
/// Restarts this instance.
/// </summary>
/// <exception cref="System.NotImplementedException"></exception>
public void RestartApplication()
{
Dispatcher.Invoke(MainStartup.ReleaseMutex);
CompositionRoot.Dispose();
System.Windows.Forms.Application.Restart();
Dispatcher.Invoke(Shutdown);
}
} }
} }

@ -26,6 +26,7 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
using MediaBrowser.IsoMounter; using MediaBrowser.IsoMounter;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using MediaBrowser.Model.Updates; using MediaBrowser.Model.Updates;
@ -59,6 +60,7 @@ using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
namespace MediaBrowser.ServerApplication namespace MediaBrowser.ServerApplication
{ {
@ -84,15 +86,6 @@ namespace MediaBrowser.ServerApplication
get { return (IServerConfigurationManager)ConfigurationManager; } get { return (IServerConfigurationManager)ConfigurationManager; }
} }
/// <summary>
/// Gets the name of the log file prefix.
/// </summary>
/// <value>The name of the log file prefix.</value>
protected override string LogFilePrefixName
{
get { return "server"; }
}
/// <summary> /// <summary>
/// Gets the name of the web application that can be used for url building. /// Gets the name of the web application that can be used for url building.
/// All api urls will be of the form {protocol}://{host}:{port}/{appname}/... /// All api urls will be of the form {protocol}://{host}:{port}/{appname}/...
@ -182,13 +175,6 @@ namespace MediaBrowser.ServerApplication
private IItemRepository ItemRepository { get; set; } private IItemRepository ItemRepository { get; set; }
private INotificationsRepository NotificationsRepository { get; set; } private INotificationsRepository NotificationsRepository { get; set; }
public bool IsBackgroundService
{
get { return _appInterface != null && _appInterface.IsBackgroundService; }
}
private readonly IApplicationInterface _appInterface;
/// <summary> /// <summary>
/// The full path to our startmenu shortcut /// The full path to our startmenu shortcut
/// </summary> /// </summary>
@ -199,9 +185,15 @@ namespace MediaBrowser.ServerApplication
private Task<IHttpServer> _httpServerCreationTask; private Task<IHttpServer> _httpServerCreationTask;
public ApplicationHost(IApplicationInterface appInterface) /// <summary>
/// Initializes a new instance of the <see cref="ApplicationHost"/> class.
/// </summary>
/// <param name="applicationPaths">The application paths.</param>
/// <param name="logManager">The log manager.</param>
public ApplicationHost(ServerApplicationPaths applicationPaths, ILogManager logManager)
: base(applicationPaths, logManager)
{ {
_appInterface = appInterface;
} }
/// <summary> /// <summary>
@ -542,7 +534,14 @@ namespace MediaBrowser.ServerApplication
Logger.ErrorException("Error sending server restart web socket message", ex); Logger.ErrorException("Error sending server restart web socket message", ex);
} }
_appInterface.RestartApplication(); // Second instance will start first, so release the mutex and dispose the http server ahead of time
Application.Current.Dispatcher.Invoke(() => MainStartup.ReleaseMutex(Logger));
Dispose();
System.Windows.Forms.Application.Restart();
ShutdownInternal();
} }
/// <summary> /// <summary>
@ -627,8 +626,7 @@ namespace MediaBrowser.ServerApplication
Id = _systemId, Id = _systemId,
ProgramDataPath = ApplicationPaths.ProgramDataPath, ProgramDataPath = ApplicationPaths.ProgramDataPath,
MacAddress = GetMacAddress(), MacAddress = GetMacAddress(),
HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber, HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber
IsBackgroundService = IsBackgroundService
}; };
} }
@ -663,7 +661,15 @@ namespace MediaBrowser.ServerApplication
Logger.ErrorException("Error sending server shutdown web socket message", ex); Logger.ErrorException("Error sending server shutdown web socket message", ex);
} }
_appInterface.ShutdownApplication(); ShutdownInternal();
}
public void ShutdownInternal()
{
Logger.Info("Shutting down application");
var app = Application.Current;
app.Dispatcher.Invoke(app.Shutdown);
} }
/// <summary> /// <summary>

@ -1,30 +1,40 @@
using System.ServiceProcess; using MediaBrowser.Model.Logging;
using System.ServiceProcess;
namespace MediaBrowser.ServerApplication namespace MediaBrowser.ServerApplication
{ {
/// <summary>
/// Class BackgroundService
/// </summary>
public class BackgroundService : ServiceBase public class BackgroundService : ServiceBase
{ {
public BackgroundService() public static string Name = "MediaBrowser";
public static string DisplayName = "Media Browser";
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="BackgroundService"/> class.
/// </summary>
public BackgroundService(ILogger logger)
{ {
_logger = logger;
CanPauseAndContinue = false; CanPauseAndContinue = false;
CanHandleSessionChangeEvent = true;
CanStop = false;
CanShutdown = true;
ServiceName = "Media Browser";
}
protected override void OnSessionChange(SessionChangeDescription changeDescription) CanStop = true;
{
base.OnSessionChange(changeDescription);
}
protected override void OnStart(string[] args) ServiceName = Name;
{
} }
protected override void OnShutdown() /// <summary>
/// When implemented in a derived class, executes when a Stop command is sent to the service by the Service Control Manager (SCM). Specifies actions to take when a service stops running.
/// </summary>
protected override void OnStop()
{ {
base.OnShutdown(); _logger.Info("Stop command received");
base.OnStop();
} }
} }
} }

@ -0,0 +1,60 @@
using System.Collections;
using System.ComponentModel;
using System.ServiceProcess;
namespace MediaBrowser.ServerApplication
{
[RunInstaller(true)]
public class BackgroundServiceInstaller : System.Configuration.Install.Installer
{
public BackgroundServiceInstaller()
{
var process = new ServiceProcessInstaller
{
Account = ServiceAccount.LocalSystem
};
var serviceAdmin = new ServiceInstaller
{
StartType = ServiceStartMode.Manual,
ServiceName = BackgroundService.Name,
DisplayName = BackgroundService.DisplayName,
DelayedAutoStart = true,
Description = "The windows background service for Media Browser Server."
};
// Microsoft didn't add the ability to add a
// description for the services we are going to install
// To work around this we'll have to add the
// information directly to the registry but I'll leave
// this exercise for later.
// now just add the installers that we created to our
// parents container, the documentation
// states that there is not any order that you need to
// worry about here but I'll still
// go ahead and add them in the order that makes sense.
Installers.Add(process);
Installers.Add(serviceAdmin);
}
protected override void OnBeforeInstall(IDictionary savedState)
{
Context.Parameters["assemblypath"] = "\"" +
Context.Parameters["assemblypath"] + "\" " + GetStartArgs();
base.OnBeforeInstall(savedState);
}
protected override void OnBeforeUninstall(IDictionary savedState)
{
Context.Parameters["assemblypath"] = "\"" +
Context.Parameters["assemblypath"] + "\" " + GetStartArgs();
base.OnBeforeUninstall(savedState);
}
private string GetStartArgs()
{
return "-service";
}
}
}

@ -64,10 +64,7 @@ namespace MediaBrowser.ServerApplication.EntryPoints
{ {
_logger.ErrorException("Error launching startup wizard", ex); _logger.ErrorException("Error launching startup wizard", ex);
if (!_appHost.IsBackgroundService) MessageBox.Show("There was an error launching the Media Browser startup wizard. Please ensure a web browser is installed on the machine and is configured as the default browser.", "Media Browser");
{
MessageBox.Show("There was an error launching the Media Browser startup wizard. Please ensure a web browser is installed on the machine and is configured as the default browser.", "Media Browser");
}
} }
} }

@ -1,32 +0,0 @@
using System;
namespace MediaBrowser.ServerApplication
{
/// <summary>
/// Interface IApplicationInterface
/// </summary>
public interface IApplicationInterface
{
/// <summary>
/// Gets a value indicating whether this instance is background service.
/// </summary>
/// <value><c>true</c> if this instance is background service; otherwise, <c>false</c>.</value>
bool IsBackgroundService { get; }
/// <summary>
/// Shutdowns the application.
/// </summary>
void ShutdownApplication();
/// <summary>
/// Restarts the application.
/// </summary>
void RestartApplication();
/// <summary>
/// Called when [unhandled exception].
/// </summary>
/// <param name="ex">The ex.</param>
void OnUnhandledException(Exception ex);
}
}

@ -1,12 +1,18 @@
using MediaBrowser.Common.Constants; using MediaBrowser.Common.Constants;
using MediaBrowser.Common.Implementations.Logging;
using MediaBrowser.Common.Implementations.Updates; using MediaBrowser.Common.Implementations.Updates;
using MediaBrowser.Model.Logging;
using MediaBrowser.Server.Implementations; using MediaBrowser.Server.Implementations;
using Microsoft.Win32;
using System; using System;
using System.Configuration.Install;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.ServiceProcess;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using Microsoft.Win32;
namespace MediaBrowser.ServerApplication namespace MediaBrowser.ServerApplication
{ {
@ -17,7 +23,9 @@ namespace MediaBrowser.ServerApplication
/// </summary> /// </summary>
private static Mutex _singleInstanceMutex; private static Mutex _singleInstanceMutex;
private static IApplicationInterface _applicationInterface; private static ApplicationHost _appHost;
private static App _app;
/// <summary> /// <summary>
/// Defines the entry point of the application. /// Defines the entry point of the application.
@ -25,6 +33,50 @@ namespace MediaBrowser.ServerApplication
[STAThread] [STAThread]
public static void Main() public static void Main()
{ {
var startFlag = Environment.GetCommandLineArgs().ElementAtOrDefault(1);
var runService = string.Equals(startFlag, "-service", StringComparison.OrdinalIgnoreCase);
var appPaths = CreateApplicationPaths(runService);
var logManager = new NlogManager(appPaths.LogDirectoryPath, "server");
logManager.ReloadLogger(LogSeverity.Info);
var logger = logManager.GetLogger("Main");
BeginLog(logger);
// Install directly
if (string.Equals(startFlag, "-installservice", StringComparison.OrdinalIgnoreCase))
{
logger.Info("Performing service installation");
InstallService(logger);
return;
}
// Restart with admin rights, then install
if (string.Equals(startFlag, "-launchinstallservice", StringComparison.OrdinalIgnoreCase))
{
logger.Info("Performing service installation");
RunServiceInstallation();
return;
}
// Uninstall directly
if (string.Equals(startFlag, "-uninstallservice", StringComparison.OrdinalIgnoreCase))
{
logger.Info("Performing service uninstallation");
UninstallService(logger);
return;
}
// Restart with admin rights, then uninstall
if (string.Equals(startFlag, "-launchuninstallservice", StringComparison.OrdinalIgnoreCase))
{
logger.Info("Performing service uninstallation");
RunServiceUninstallation();
return;
}
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
bool createdNew; bool createdNew;
@ -36,78 +88,228 @@ namespace MediaBrowser.ServerApplication
if (!createdNew) if (!createdNew)
{ {
_singleInstanceMutex = null; _singleInstanceMutex = null;
logger.Info("Shutting down because another instance of Media Browser Server is already running.");
return; return;
} }
// Look for the existence of an update archive if (PerformUpdateIfNeeded(appPaths, logger))
var appPaths = new ServerApplicationPaths();
var updateArchive = Path.Combine(appPaths.TempUpdatePath, Constants.MbServerPkgName + ".zip");
if (File.Exists(updateArchive))
{ {
// Update is there - execute update logger.Info("Exiting to perform application update.");
try return;
{ }
new ApplicationUpdater().UpdateApplication(MBApplication.MBServer, appPaths, updateArchive);
// And just let the app exit so it can update try
return; {
} RunApplication(appPaths, logManager, runService);
catch (Exception e)
{
MessageBox.Show(string.Format("Error attempting to update application.\n\n{0}\n\n{1}", e.GetType().Name, e.Message));
}
} }
finally
{
logger.Info("Shutting down");
StartApplication(); ReleaseMutex(logger);
_appHost.Dispose();
}
} }
private static void StartApplication() /// <summary>
/// Creates the application paths.
/// </summary>
/// <param name="runAsService">if set to <c>true</c> [run as service].</param>
/// <returns>ServerApplicationPaths.</returns>
private static ServerApplicationPaths CreateApplicationPaths(bool runAsService)
{
if (runAsService)
{
#if (RELEASE)
var systemPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
var programDataPath = Path.GetDirectoryName(systemPath);
return new ServerApplicationPaths(programDataPath);
#endif
}
return new ServerApplicationPaths();
}
/// <summary>
/// Begins the log.
/// </summary>
/// <param name="logger">The logger.</param>
private static void BeginLog(ILogger logger)
{
logger.Info("Media Browser Server started");
logger.Info("Command line: {0}", string.Join(" ", Environment.GetCommandLineArgs()));
logger.Info("Server: {0}", Environment.MachineName);
logger.Info("Operating system: {0}", Environment.OSVersion.ToString());
}
/// <summary>
/// Runs the application.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="runService">if set to <c>true</c> [run service].</param>
private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, bool runService)
{ {
SystemEvents.SessionEnding += SystemEvents_SessionEnding; SystemEvents.SessionEnding += SystemEvents_SessionEnding;
var commandLineArgs = Environment.GetCommandLineArgs(); var commandLineArgs = Environment.GetCommandLineArgs();
if (commandLineArgs.Length > 1 && commandLineArgs[1].Equals("-service")) _appHost = new ApplicationHost(appPaths, logManager);
_app = new App(_appHost, _appHost.LogManager.GetLogger("App"), runService);
if (runService)
{ {
// Start application as a service _app.AppStarted += (sender, args) => StartService(logManager);
StartBackgroundService();
} }
else
_app.Run();
}
/// <summary>
/// Starts the service.
/// </summary>
private static void StartService(ILogManager logManager)
{
var ctl = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == BackgroundService.Name);
if (ctl == null)
{ {
StartWpfApp(); RunServiceInstallation();
} }
var service = new BackgroundService(logManager.GetLogger("Service"));
service.Disposed += service_Disposed;
ServiceBase.Run(service);
} }
static void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) /// <summary>
/// Handles the Disposed event of the service control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
static async void service_Disposed(object sender, EventArgs e)
{ {
// Try to shutdown gracefully await _appHost.Shutdown();
if (_applicationInterface != null) }
/// <summary>
/// Installs the service.
/// </summary>
private static void InstallService(ILogger logger)
{
var runningPath = Process.GetCurrentProcess().MainModule.FileName;
try
{ {
_applicationInterface.ShutdownApplication(); ManagedInstallerClass.InstallHelper(new[] { runningPath });
logger.Info("Service installation succeeded");
}
catch (Exception ex)
{
logger.ErrorException("Uninstall failed", ex);
} }
} }
private static void StartWpfApp() /// <summary>
/// Uninstalls the service.
/// </summary>
private static void UninstallService(ILogger logger)
{ {
var app = new App(); var runningPath = Process.GetCurrentProcess().MainModule.FileName;
_applicationInterface = app; try
{
ManagedInstallerClass.InstallHelper(new[] { "/u", runningPath });
app.Run(); logger.Info("Service uninstallation succeeded");
}
catch (Exception ex)
{
logger.ErrorException("Uninstall failed", ex);
}
} }
private static void StartBackgroundService() /// <summary>
/// Runs the service installation.
/// </summary>
private static void RunServiceInstallation()
{ {
var runningPath = Process.GetCurrentProcess().MainModule.FileName;
var startInfo = new ProcessStartInfo
{
FileName = runningPath,
Arguments = "-installservice",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
Verb = "runas",
ErrorDialog = false
};
using (var process = Process.Start(startInfo))
{
process.WaitForExit();
}
} }
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) /// <summary>
/// Runs the service uninstallation.
/// </summary>
private static void RunServiceUninstallation()
{ {
var exception = (Exception)e.ExceptionObject; var runningPath = Process.GetCurrentProcess().MainModule.FileName;
if (_applicationInterface != null) var startInfo = new ProcessStartInfo
{ {
_applicationInterface.OnUnhandledException(exception); FileName = runningPath,
Arguments = "-uninstallservice",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
Verb = "runas",
ErrorDialog = false
};
using (var process = Process.Start(startInfo))
{
process.WaitForExit();
} }
}
/// <summary>
/// Handles the SessionEnding event of the SystemEvents control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="SessionEndingEventArgs"/> instance containing the event data.</param>
static void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
{
// Try to shutdown gracefully
var task = _appHost.Shutdown();
Task.WaitAll(task);
}
/// <summary>
/// Handles the UnhandledException event of the CurrentDomain control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="UnhandledExceptionEventArgs"/> instance containing the event data.</param>
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = (Exception)e.ExceptionObject;
_app.OnUnhandledException(exception);
if (!Debugger.IsAttached) if (!Debugger.IsAttached)
{ {
@ -118,17 +320,50 @@ namespace MediaBrowser.ServerApplication
/// <summary> /// <summary>
/// Releases the mutex. /// Releases the mutex.
/// </summary> /// </summary>
internal static void ReleaseMutex() internal static void ReleaseMutex(ILogger logger)
{ {
if (_singleInstanceMutex == null) if (_singleInstanceMutex == null)
{ {
return; return;
} }
logger.Debug("Releasing mutex");
_singleInstanceMutex.ReleaseMutex(); _singleInstanceMutex.ReleaseMutex();
_singleInstanceMutex.Close(); _singleInstanceMutex.Close();
_singleInstanceMutex.Dispose(); _singleInstanceMutex.Dispose();
_singleInstanceMutex = null; _singleInstanceMutex = null;
} }
/// <summary>
/// Performs the update if needed.
/// </summary>
/// <param name="appPaths">The app paths.</param>
/// <param name="logger">The logger.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
private static bool PerformUpdateIfNeeded(ServerApplicationPaths appPaths, ILogger logger)
{
// Look for the existence of an update archive
var updateArchive = Path.Combine(appPaths.TempUpdatePath, Constants.MbServerPkgName + ".zip");
if (File.Exists(updateArchive))
{
logger.Info("An update is available from {0}", updateArchive);
// Update is there - execute update
try
{
new ApplicationUpdater().UpdateApplication(MBApplication.MBServer, appPaths, updateArchive);
// And just let the app exit so it can update
return true;
}
catch (Exception e)
{
MessageBox.Show(string.Format("Error attempting to update application.\n\n{0}\n\n{1}", e.GetType().Name, e.Message));
}
}
return false;
}
} }
} }

@ -1,6 +1,4 @@
using MahApps.Metro.Controls; using MediaBrowser.Controller;
using MediaBrowser.Common;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;

@ -173,6 +173,7 @@
<HintPath>..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll</HintPath> <HintPath>..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Data.SQLite, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86"> <Reference Include="System.Data.SQLite, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
@ -209,8 +210,10 @@
</Compile> </Compile>
<Compile Include="EntryPoints\StartupWizard.cs" /> <Compile Include="EntryPoints\StartupWizard.cs" />
<Compile Include="EntryPoints\UdpServerEntryPoint.cs" /> <Compile Include="EntryPoints\UdpServerEntryPoint.cs" />
<Compile Include="IApplicationInterface.cs" />
<Compile Include="MainStartup.cs" /> <Compile Include="MainStartup.cs" />
<Compile Include="BackgroundServiceInstaller.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Splash\SplashWindow.xaml.cs"> <Compile Include="Splash\SplashWindow.xaml.cs">
<DependentUpon>SplashWindow.xaml</DependentUpon> <DependentUpon>SplashWindow.xaml</DependentUpon>
</Compile> </Compile>

Loading…
Cancel
Save