From 7db8257945edeb35d04a8553483573cf47195482 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 11 Nov 2016 23:03:07 -0500 Subject: [PATCH] stub out .net core startup --- src/Emby.Server/Program.cs | 582 ++++++++++++++++++++++++++++++++++++- 1 file changed, 580 insertions(+), 2 deletions(-) diff --git a/src/Emby.Server/Program.cs b/src/Emby.Server/Program.cs index 06aed1aeb..d364e7284 100644 --- a/src/Emby.Server/Program.cs +++ b/src/Emby.Server/Program.cs @@ -1,14 +1,592 @@ -using System; -using System.Collections.Generic; +using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations; +using MediaBrowser.Server.Startup.Common; +using MediaBrowser.ServerApplication.Native; +using MediaBrowser.ServerApplication.Splash; +using MediaBrowser.ServerApplication.Updates; +using Microsoft.Win32; +using System; +using System.Configuration.Install; +using System.Diagnostics; +using System.IO; using System.Linq; +using System.Management; +using System.Runtime.InteropServices; +using System.ServiceProcess; +using System.Text; +using System.Threading; using System.Threading.Tasks; +using System.Windows.Forms; +using Emby.Common.Implementations.EnvironmentInfo; +using Emby.Common.Implementations.IO; +using Emby.Common.Implementations.Logging; +using Emby.Common.Implementations.Networking; +using Emby.Common.Implementations.Security; +using Emby.Server.Core; +using Emby.Server.Core.Browser; +using Emby.Server.Implementations.IO; +using ImageMagickSharp; +using MediaBrowser.Common.Net; +using MediaBrowser.Server.Startup.Common.IO; namespace Emby.Server { public class Program { + private static ApplicationHost _appHost; + + private static ILogger _logger; + + private static bool _isRunningAsService = false; + private static bool _canRestartService = false; + private static bool _appHostDisposed; + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool SetDllDirectory(string lpPathName); + + /// + /// Defines the entry point of the application. + /// public static void Main(string[] args) { + var options = new StartupOptions(); + _isRunningAsService = options.ContainsOption("-service"); + + if (_isRunningAsService) + { + //_canRestartService = CanRestartWindowsService(); + } + + var currentProcess = Process.GetCurrentProcess(); + + var applicationPath = currentProcess.MainModule.FileName; + var architecturePath = Path.Combine(Path.GetDirectoryName(applicationPath), Environment.Is64BitProcess ? "x64" : "x86"); + + Wand.SetMagickCoderModulePath(architecturePath); + + var success = SetDllDirectory(architecturePath); + + var appPaths = CreateApplicationPaths(applicationPath, _isRunningAsService); + + var logManager = new NlogManager(appPaths.LogDirectoryPath, "server"); + logManager.ReloadLogger(LogSeverity.Debug); + logManager.AddConsoleOutput(); + + var logger = _logger = logManager.GetLogger("Main"); + + ApplicationHost.LogEnvironmentInfo(logger, appPaths, true); + + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + + if (IsAlreadyRunning(applicationPath, currentProcess)) + { + logger.Info("Shutting down because another instance of Emby Server is already running."); + return; + } + + if (PerformUpdateIfNeeded(appPaths, logger)) + { + logger.Info("Exiting to perform application update."); + return; + } + + try + { + RunApplication(appPaths, logManager, _isRunningAsService, options); + } + finally + { + OnServiceShutdown(); + } + } + + /// + /// Determines whether [is already running] [the specified current process]. + /// + /// The application path. + /// The current process. + /// true if [is already running] [the specified current process]; otherwise, false. + private static bool IsAlreadyRunning(string applicationPath, Process currentProcess) + { + var duplicate = Process.GetProcesses().FirstOrDefault(i => + { + try + { + if (currentProcess.Id == i.Id) + { + return false; + } + } + catch (Exception) + { + return false; + } + + try + { + //_logger.Info("Module: {0}", i.MainModule.FileName); + if (string.Equals(applicationPath, i.MainModule.FileName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + return false; + } + catch (Exception) + { + return false; + } + }); + + if (duplicate != null) + { + _logger.Info("Found a duplicate process. Giving it time to exit."); + + if (!duplicate.WaitForExit(30000)) + { + _logger.Info("The duplicate process did not exit."); + return true; + } + } + + if (!_isRunningAsService) + { + return false; + } + + return false; + } + + /// + /// Creates the application paths. + /// + /// The application path. + /// if set to true [run as service]. + /// ServerApplicationPaths. + private static ServerApplicationPaths CreateApplicationPaths(string applicationPath, bool runAsService) + { + var resourcesPath = Path.GetDirectoryName(applicationPath); + + if (runAsService) + { + var systemPath = Path.GetDirectoryName(applicationPath); + + var programDataPath = Path.GetDirectoryName(systemPath); + + return new ServerApplicationPaths(programDataPath, applicationPath, resourcesPath); + } + + return new ServerApplicationPaths(ApplicationPathHelper.GetProgramDataPath(applicationPath), applicationPath, resourcesPath); + } + + /// + /// Gets a value indicating whether this instance can self restart. + /// + /// true if this instance can self restart; otherwise, false. + public static bool CanSelfRestart + { + get + { + if (_isRunningAsService) + { + return _canRestartService; + } + else + { + return true; + } + } + } + + /// + /// Gets a value indicating whether this instance can self update. + /// + /// true if this instance can self update; otherwise, false. + public static bool CanSelfUpdate + { + get + { + if (_isRunningAsService) + { + return _canRestartService; + } + else + { + return true; + } + } + } + + private static readonly TaskCompletionSource ApplicationTaskCompletionSource = new TaskCompletionSource(); + + /// + /// Runs the application. + /// + /// The app paths. + /// The log manager. + /// if set to true [run service]. + /// The options. + private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, bool runService, StartupOptions options) + { + var fileSystem = new WindowsFileSystem(logManager.GetLogger("FileSystem")); + fileSystem.AddShortcutHandler(new LnkShortcutHandler()); + fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + + var nativeApp = new WindowsApp(fileSystem, _logger) + { + IsRunningAsService = runService + }; + + var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); + + _appHost = new ApplicationHost(appPaths, + logManager, + options, + fileSystem, + nativeApp, + new PowerManagement(), + "emby.windows.zip", + new EnvironmentInfo(), + imageEncoder, + new Server.Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")), + new RecyclableMemoryStreamProvider(), + new NetworkManager(logManager.GetLogger("NetworkManager")), + GenerateCertificate, + () => Environment.UserDomainName); + + var initProgress = new Progress(); + + if (!runService) + { + // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes + SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | + ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); + } + + var task = _appHost.Init(initProgress); + Task.WaitAll(task); + + task = task.ContinueWith(new Action(a => _appHost.RunStartupTasks()), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); + + if (runService) + { + StartService(logManager); + } + else + { + Task.WaitAll(task); + + task = InstallVcredist2013IfNeeded(_appHost, _logger); + Task.WaitAll(task); + + Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding; + Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionSwitch; + + task = ApplicationTaskCompletionSource.Task; + Task.WaitAll(task); + } + } + + private static void GenerateCertificate(string certPath, string certHost) + { + CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, _logger); + } + + static void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e) + { + if (e.Reason == SessionSwitchReason.SessionLogon) + { + BrowserLauncher.OpenDashboard(_appHost); + } + } + + /// + /// Starts the service. + /// + private static void StartService(ILogManager logManager) + { + var service = new BackgroundService(logManager.GetLogger("Service")); + + service.Disposed += service_Disposed; + + ServiceBase.Run(service); + } + + /// + /// Handles the Disposed event of the service control. + /// + /// The source of the event. + /// The instance containing the event data. + static void service_Disposed(object sender, EventArgs e) + { + ApplicationTaskCompletionSource.SetResult(true); + OnServiceShutdown(); + } + + private static void OnServiceShutdown() + { + _logger.Info("Shutting down"); + + DisposeAppHost(); + } + + /// + /// Handles the SessionEnding event of the SystemEvents control. + /// + /// The source of the event. + /// The instance containing the event data. + static void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) + { + if (e.Reason == SessionEndReasons.SystemShutdown || !_isRunningAsService) + { + Shutdown(); + } + } + + /// + /// Handles the UnhandledException event of the CurrentDomain control. + /// + /// The source of the event. + /// The instance containing the event data. + static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + var exception = (Exception)e.ExceptionObject; + + new UnhandledExceptionWriter(_appHost.ServerConfigurationManager.ApplicationPaths, _logger, _appHost.LogManager).Log(exception); + + if (!_isRunningAsService) + { + MessageBox.Show("Unhandled exception: " + exception.Message); + } + + if (!Debugger.IsAttached) + { + Environment.Exit(Marshal.GetHRForException(exception)); + } + } + + /// + /// Performs the update if needed. + /// + /// The app paths. + /// The logger. + /// true if XXXX, false otherwise + private static bool PerformUpdateIfNeeded(ServerApplicationPaths appPaths, ILogger logger) + { + // Look for the existence of an update archive + var updateArchive = Path.Combine(appPaths.TempUpdatePath, "MBServer" + ".zip"); + if (File.Exists(updateArchive)) + { + logger.Info("An update is available from {0}", updateArchive); + + // Update is there - execute update + try + { + var serviceName = _isRunningAsService ? BackgroundService.GetExistingServiceName() : string.Empty; + new ApplicationUpdater().UpdateApplication(appPaths, updateArchive, logger, serviceName); + + // And just let the app exit so it can update + return true; + } + catch (Exception e) + { + logger.ErrorException("Error starting updater.", e); + + MessageBox.Show(string.Format("Error attempting to update application.\n\n{0}\n\n{1}", e.GetType().Name, e.Message)); + } + } + + return false; + } + + public static void Shutdown() + { + if (_isRunningAsService) + { + ShutdownWindowsService(); + } + else + { + DisposeAppHost(); + + ShutdownWindowsApplication(); + } + } + + public static void Restart() + { + DisposeAppHost(); + + if (_isRunningAsService) + { + RestartWindowsService(); + } + else + { + //_logger.Info("Hiding server notify icon"); + //_serverNotifyIcon.Visible = false; + + _logger.Info("Starting new instance"); + //Application.Restart(); + Process.Start(_appHost.ServerConfigurationManager.ApplicationPaths.ApplicationPath); + + ShutdownWindowsApplication(); + } + } + + private static void DisposeAppHost() + { + if (!_appHostDisposed) + { + _logger.Info("Disposing app host"); + + _appHostDisposed = true; + _appHost.Dispose(); + } + } + + private static void ShutdownWindowsApplication() + { + //_logger.Info("Calling Application.Exit"); + //Application.Exit(); + + _logger.Info("Calling Environment.Exit"); + Environment.Exit(0); + + _logger.Info("Calling ApplicationTaskCompletionSource.SetResult"); + ApplicationTaskCompletionSource.SetResult(true); + } + + private static void ShutdownWindowsService() + { + } + + private static void RestartWindowsService() + { + } + + private static bool CanRestartWindowsService() + { + return false; + } + + private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger) + { + // Reference + // http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed + + try + { + var subkey = Environment.Is64BitProcess + ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x64" + : "SOFTWARE\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x86"; + + using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default) + .OpenSubKey(subkey)) + { + if (ndpKey != null && ndpKey.GetValue("Version") != null) + { + var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v'); + if (installedVersion.StartsWith("12", StringComparison.OrdinalIgnoreCase)) + { + return; + } + } + } + } + catch (Exception ex) + { + logger.ErrorException("Error getting .NET Framework version", ex); + return; + } + + try + { + await InstallVcredist2013().ConfigureAwait(false); + } + catch (Exception ex) + { + logger.ErrorException("Error installing Visual Studio C++ runtime", ex); + } + } + + private async static Task InstallVcredist2013() + { + var httpClient = _appHost.HttpClient; + + var tmp = await httpClient.GetTempFile(new HttpRequestOptions + { + Url = GetVcredist2013Url(), + Progress = new Progress() + + }).ConfigureAwait(false); + + var exePath = Path.ChangeExtension(tmp, ".exe"); + File.Copy(tmp, exePath); + + var startInfo = new ProcessStartInfo + { + FileName = exePath, + + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + Verb = "runas", + ErrorDialog = false + }; + + _logger.Info("Running {0}", startInfo.FileName); + + using (var process = Process.Start(startInfo)) + { + process.WaitForExit(); + } + } + + private static string GetVcredist2013Url() + { + if (Environment.Is64BitProcess) + { + return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_x64.exe"; + } + + // TODO: ARM url - https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_arm.exe + + return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_x86.exe"; + } + + /// + /// Sets the error mode. + /// + /// The u mode. + /// ErrorModes. + [DllImport("kernel32.dll")] + static extern ErrorModes SetErrorMode(ErrorModes uMode); + + /// + /// Enum ErrorModes + /// + [Flags] + public enum ErrorModes : uint + { + /// + /// The SYSTE m_ DEFAULT + /// + SYSTEM_DEFAULT = 0x0, + /// + /// The SE m_ FAILCRITICALERRORS + /// + SEM_FAILCRITICALERRORS = 0x0001, + /// + /// The SE m_ NOALIGNMENTFAULTEXCEPT + /// + SEM_NOALIGNMENTFAULTEXCEPT = 0x0004, + /// + /// The SE m_ NOGPFAULTERRORBOX + /// + SEM_NOGPFAULTERRORBOX = 0x0002, + /// + /// The SE m_ NOOPENFILEERRORBOX + /// + SEM_NOOPENFILEERRORBOX = 0x8000 } } }