using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using ServiceStack; using ServiceStack.Web; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.WebDashboard.Api { /// /// Class GetDashboardConfigurationPages /// [Route("/dashboard/ConfigurationPages", "GET")] [Route("/web/ConfigurationPages", "GET")] public class GetDashboardConfigurationPages : IReturn> { /// /// Gets or sets the type of the page. /// /// The type of the page. public ConfigurationPageType? PageType { get; set; } } /// /// Class GetDashboardConfigurationPage /// [Route("/dashboard/ConfigurationPage", "GET")] [Route("/web/ConfigurationPage", "GET")] public class GetDashboardConfigurationPage { /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } } [Route("/web/Package", "GET")] [Route("/dashboard/Package", "GET")] public class GetDashboardPackage { public string Mode { get; set; } } /// /// Class GetDashboardResource /// [Route("/web/{ResourceName*}", "GET")] [Route("/dashboard/{ResourceName*}", "GET")] public class GetDashboardResource { /// /// Gets or sets the name. /// /// The name. public string ResourceName { get; set; } /// /// Gets or sets the V. /// /// The V. public string V { get; set; } } /// /// Class DashboardService /// public class DashboardService : IRestfulService, IHasResultFactory { /// /// Gets or sets the logger. /// /// The logger. public ILogger Logger { get; set; } /// /// Gets or sets the HTTP result factory. /// /// The HTTP result factory. public IHttpResultFactory ResultFactory { get; set; } /// /// Gets or sets the request context. /// /// The request context. public IRequest Request { get; set; } /// /// The _app host /// private readonly IServerApplicationHost _appHost; /// /// The _server configuration manager /// private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; private readonly IJsonSerializer _jsonSerializer; /// /// Initializes a new instance of the class. /// /// The app host. /// The server configuration manager. /// The file system. public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer) { _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; _fileSystem = fileSystem; _localization = localization; _jsonSerializer = jsonSerializer; } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetDashboardConfigurationPage request) { var page = ServerEntryPoint.Instance.PluginConfigurationPages.First(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase)); return ResultFactory.GetStaticResult(Request, page.Plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator().ModifyHtml(page.GetHtmlStream(), null, null, false)); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetDashboardConfigurationPages request) { const string unavilableMessage = "The server is still loading. Please try again momentarily."; var instance = ServerEntryPoint.Instance; if (instance == null) { throw new InvalidOperationException(unavilableMessage); } var pages = instance.PluginConfigurationPages; if (pages == null) { throw new InvalidOperationException(unavilableMessage); } if (request.PageType.HasValue) { pages = pages.Where(p => p.ConfigurationPageType == request.PageType.Value); } // Don't allow a failing plugin to fail them all var configPages = pages.Select(p => { try { return new ConfigurationPageInfo(p); } catch (Exception ex) { Logger.ErrorException("Error getting plugin information from {0}", ex, p.GetType().Name); return null; } }) .Where(i => i != null) .ToList(); return ResultFactory.GetOptimizedResult(Request, configPages); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetDashboardResource request) { var path = request.ResourceName; var contentType = MimeTypes.GetMimeType(path); var isHtml = IsHtml(path); if (isHtml && !_serverConfigurationManager.Configuration.IsStartupWizardCompleted) { if (path.IndexOf("wizard", StringComparison.OrdinalIgnoreCase) == -1) { Request.Response.Redirect("wizardstart.html"); return null; } } path = path.Replace("scripts/jquery.mobile-1.4.5.min.map", "thirdparty/jquerymobile-1.4.5/jquery.mobile-1.4.5.min.map", StringComparison.OrdinalIgnoreCase); var localizationCulture = GetLocalizationCulture(); // Don't cache if not configured to do so // But always cache images to simulate production if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) && !contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) { return ResultFactory.GetResult(GetResourceStream(path, localizationCulture).Result, contentType); } TimeSpan? cacheDuration = null; // Cache images unconditionally - updates to image files will require new filename // If there's a version number in the query string we can cache this unconditionally if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase) || !string.IsNullOrEmpty(request.V)) { cacheDuration = TimeSpan.FromDays(365); } var assembly = GetType().Assembly.GetName(); var cacheKey = (assembly.Version + (localizationCulture ?? string.Empty) + path).GetMD5(); return ResultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path, localizationCulture)); } private string GetLocalizationCulture() { return _serverConfigurationManager.Configuration.UICulture; } /// /// Gets the resource stream. /// /// The path. /// The localization culture. /// Task{Stream}. private Task GetResourceStream(string path, string localizationCulture) { var minify = _serverConfigurationManager.Configuration.EnableDashboardResourceMinification; return GetPackageCreator() .GetResource(path, null, localizationCulture, _appHost.ApplicationVersion.ToString(), minify); } private PackageCreator GetPackageCreator() { return new PackageCreator(_fileSystem, _localization, Logger, _serverConfigurationManager, _jsonSerializer); } /// /// Determines whether the specified path is HTML. /// /// The path. /// true if the specified path is HTML; otherwise, false. private bool IsHtml(string path) { return Path.GetExtension(path).EndsWith("html", StringComparison.OrdinalIgnoreCase); } public async Task Get(GetDashboardPackage request) { var path = Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, "webclient-dump"); try { _fileSystem.DeleteDirectory(path, true); } catch (IOException) { } var creator = GetPackageCreator(); CopyDirectory(creator.DashboardUIPath, path); var culture = "en-US"; var appVersion = _appHost.ApplicationVersion.ToString(); var mode = request.Mode; await DumpHtml(creator.DashboardUIPath, path, mode, culture, appVersion); await DumpJs(creator.DashboardUIPath, path, mode, culture, appVersion); await DumpFile("scripts/all.js", Path.Combine(path, "scripts", "all.js"), mode, culture, appVersion).ConfigureAwait(false); await DumpFile("css/all.css", Path.Combine(path, "css", "all.css"), mode, culture, appVersion).ConfigureAwait(false); return ""; } private async Task DumpHtml(string source, string destination, string mode, string culture, string appVersion) { foreach (var file in Directory.GetFiles(source, "*.html", SearchOption.TopDirectoryOnly)) { var filename = Path.GetFileName(file); await DumpFile(filename, Path.Combine(destination, filename), mode, culture, appVersion).ConfigureAwait(false); } } private async Task DumpJs(string source, string mode, string destination, string culture, string appVersion) { foreach (var file in Directory.GetFiles(source, "*.js", SearchOption.TopDirectoryOnly)) { var filename = Path.GetFileName(file); await DumpFile("scripts/" + filename, Path.Combine(destination, "scripts", filename), mode, culture, appVersion).ConfigureAwait(false); } } private async Task DumpFile(string resourceVirtualPath, string destinationFilePath, string mode, string culture, string appVersion) { using (var stream = await GetPackageCreator().GetResource(resourceVirtualPath, mode, culture, appVersion, true).ConfigureAwait(false)) { using (var fs = _fileSystem.GetFileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) { stream.CopyTo(fs); } } } private void CopyDirectory(string source, string destination) { Directory.CreateDirectory(destination); //Now Create all of the directories foreach (string dirPath in Directory.GetDirectories(source, "*", SearchOption.AllDirectories)) Directory.CreateDirectory(dirPath.Replace(source, destination)); //Copy all the files & Replaces any files with the same name foreach (string newPath in Directory.GetFiles(source, "*.*", SearchOption.AllDirectories)) File.Copy(newPath, newPath.Replace(source, destination), true); } } }