aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.WebDashboard/Api/DashboardService.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.WebDashboard/Api/DashboardService.cs')
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs447
1 files changed, 447 insertions, 0 deletions
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
new file mode 100644
index 000000000..50472423e
--- /dev/null
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -0,0 +1,447 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Common.ScheduledTasks.Tasks;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Model.Tasks;
+using ServiceStack.ServiceHost;
+using ServiceStack.WebHost.Endpoints;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.WebDashboard.Api
+{
+ /// <summary>
+ /// Class GetDashboardConfigurationPages
+ /// </summary>
+ [Route("/dashboard/ConfigurationPages", "GET")]
+ public class GetDashboardConfigurationPages : IReturn<List<BaseConfigurationPage>>
+ {
+ /// <summary>
+ /// Gets or sets the type of the page.
+ /// </summary>
+ /// <value>The type of the page.</value>
+ public ConfigurationPageType? PageType { get; set; }
+ }
+
+ /// <summary>
+ /// Class GetDashboardConfigurationPage
+ /// </summary>
+ [Route("/dashboard/ConfigurationPage", "GET")]
+ public class GetDashboardConfigurationPage : IReturn<BaseConfigurationPage>
+ {
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+ }
+
+ /// <summary>
+ /// Class GetDashboardResource
+ /// </summary>
+ public class GetDashboardResource
+ {
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+ /// <summary>
+ /// Gets or sets the V.
+ /// </summary>
+ /// <value>The V.</value>
+ public string V { get; set; }
+ }
+
+ /// <summary>
+ /// Class GetDashboardInfo
+ /// </summary>
+ [Route("/dashboard/dashboardInfo", "GET")]
+ public class GetDashboardInfo : IReturn<DashboardInfo>
+ {
+ }
+
+ /// <summary>
+ /// Class DashboardService
+ /// </summary>
+ [Export(typeof(IRestfulService))]
+ public class DashboardService : BaseRestService
+ {
+ /// <summary>
+ /// Adds the routes.
+ /// </summary>
+ /// <param name="appHost">The app host.</param>
+ public override void Configure(IAppHost appHost)
+ {
+ base.Configure(appHost);
+
+ appHost.Routes.Add<GetDashboardResource>("/dashboard/{name*}", "GET");
+ }
+
+ /// <summary>
+ /// Gets the specified request.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>System.Object.</returns>
+ public object Get(GetDashboardInfo request)
+ {
+ var kernel = (Kernel)Kernel;
+
+ return GetDashboardInfo(kernel);
+ }
+
+ /// <summary>
+ /// Gets the dashboard info.
+ /// </summary>
+ /// <param name="kernel">The kernel.</param>
+ /// <returns>DashboardInfo.</returns>
+ public static DashboardInfo GetDashboardInfo(Kernel kernel)
+ {
+ var connections = kernel.UserManager.ActiveConnections.ToArray();
+
+ return new DashboardInfo
+ {
+ SystemInfo = kernel.GetSystemInfo(),
+
+ RunningTasks = kernel.ScheduledTasks.Where(i => i.State == TaskState.Running || i.State == TaskState.Cancelling)
+ .Select(ScheduledTaskHelpers.GetTaskInfo)
+ .ToArray(),
+
+ ApplicationUpdateTaskId = kernel.ScheduledTasks.OfType<SystemUpdateTask>().First().Id,
+
+ ActiveConnections = connections,
+
+ Users = kernel.Users.Where(u => connections.Any(c => c.UserId == u.Id)).Select(DtoBuilder.GetDtoUser).ToArray()
+ };
+ }
+
+ /// <summary>
+ /// Gets the specified request.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>System.Object.</returns>
+ public object Get(GetDashboardConfigurationPage request)
+ {
+ var kernel = (Kernel)Kernel;
+
+ var page = kernel.PluginConfigurationPages.First(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
+ var plugin = page.GetOwnerPlugin();
+
+ return ToStaticResult(plugin.Version.ToString().GetMD5(), plugin.AssemblyDateLastModified, null, MimeTypes.GetMimeType("page.html"), () => ModifyHtml(page.GetHtmlStream()));
+ }
+
+ /// <summary>
+ /// Gets the specified request.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>System.Object.</returns>
+ public object Get(GetDashboardConfigurationPages request)
+ {
+ var kernel = (Kernel)Kernel;
+
+ var pages = kernel.PluginConfigurationPages;
+
+ if (request.PageType.HasValue)
+ {
+ pages = pages.Where(p => p.ConfigurationPageType == request.PageType.Value);
+ }
+
+ return ToOptimizedResult(pages.ToList());
+ }
+
+ /// <summary>
+ /// Gets the specified request.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>System.Object.</returns>
+ public object Get(GetDashboardResource request)
+ {
+ var path = request.Name;
+
+ var contentType = MimeTypes.GetMimeType(path);
+
+ 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) || !string.IsNullOrEmpty(request.V))
+ {
+ cacheDuration = TimeSpan.FromDays(365);
+ }
+
+ var assembly = GetType().Assembly.GetName();
+
+ return ToStaticResult(assembly.Version.ToString().GetMD5(), null, cacheDuration, contentType, () => GetResourceStream(path));
+ }
+
+ /// <summary>
+ /// Gets the resource stream.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns>Task{Stream}.</returns>
+ private async Task<Stream> GetResourceStream(string path)
+ {
+ Stream resourceStream;
+
+ if (path.Equals("scripts/all.js", StringComparison.OrdinalIgnoreCase))
+ {
+ resourceStream = await GetAllJavascript().ConfigureAwait(false);
+ }
+ else
+ {
+ resourceStream = GetType().Assembly.GetManifestResourceStream("MediaBrowser.WebDashboard.Html." + ConvertUrlToResourcePath(path));
+ }
+
+ if (resourceStream != null)
+ {
+ var isHtml = IsHtml(path);
+
+ // Don't apply any caching for html pages
+ // jQuery ajax doesn't seem to handle if-modified-since correctly
+ if (isHtml)
+ {
+ resourceStream = await ModifyHtml(resourceStream).ConfigureAwait(false);
+ }
+ }
+
+ return resourceStream;
+ }
+
+ /// <summary>
+ /// Redirects the specified CTX.
+ /// </summary>
+ /// <param name="ctx">The CTX.</param>
+ /// <param name="url">The URL.</param>
+ private void Redirect(HttpListenerContext ctx, string url)
+ {
+ // Try to prevent the browser from caching the redirect response (the right way)
+ ctx.Response.Headers[HttpResponseHeader.CacheControl] = "no-cache, no-store, must-revalidate";
+ ctx.Response.Headers[HttpResponseHeader.Pragma] = "no-cache, no-store, must-revalidate";
+ ctx.Response.Headers[HttpResponseHeader.Expires] = "-1";
+
+ ctx.Response.Redirect(url);
+ ctx.Response.Close();
+ }
+
+ /// <summary>
+ /// Preserves the current query string when redirecting
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <param name="newUrl">The new URL.</param>
+ /// <returns>System.String.</returns>
+ private string GetRedirectUrl(HttpListenerRequest request, string newUrl)
+ {
+ var query = request.Url.Query;
+
+ return string.IsNullOrEmpty(query) ? newUrl : newUrl + query;
+ }
+
+ /// <summary>
+ /// Converts the URL to a manifest resource path.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <returns>System.String.</returns>
+ private string ConvertUrlToResourcePath(string url)
+ {
+ var parts = url.Split('/');
+ var normalizedParts = new string[parts.Length];
+
+ for (var i = 0; i < parts.Length; i++)
+ {
+ // We have to do some tricky string replacements for all parts of the path except the last
+ if (i < parts.Length - 1)
+ {
+ // Find the index of the first period as well as the first dash
+ var periodIndex = parts[i].IndexOf('.');
+ var slashIndex = parts[i].IndexOf('-');
+
+ // Replace all periods with "._" and dashes with "_"
+ normalizedParts[i] = parts[i].Replace(".", "._").Replace("-", "_");
+
+ // If the first period occurred before the first slash, change it back from "._" to just "."
+ if (periodIndex < slashIndex)
+ {
+ var regex = new Regex("\\._");
+ normalizedParts[i] = regex.Replace(normalizedParts[i], ".", 1);
+ }
+ }
+ else
+ {
+ normalizedParts[i] = parts[i];
+ }
+ }
+
+ return string.Join(".", normalizedParts);
+ }
+
+ /// <summary>
+ /// Determines whether the specified path is HTML.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns><c>true</c> if the specified path is HTML; otherwise, <c>false</c>.</returns>
+ private bool IsHtml(string path)
+ {
+ return Path.GetExtension(path).EndsWith("html", StringComparison.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Modifies the HTML by adding common meta tags, css and js.
+ /// </summary>
+ /// <param name="sourceStream">The source stream.</param>
+ /// <returns>Task{Stream}.</returns>
+ internal async Task<Stream> ModifyHtml(Stream sourceStream)
+ {
+ string html;
+
+ using (var memoryStream = new MemoryStream())
+ {
+ await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+
+ html = Encoding.UTF8.GetString(memoryStream.ToArray());
+ }
+
+ var version = GetType().Assembly.GetName().Version;
+
+ html = html.Replace("<head>", "<head>" + GetMetaTags() + GetCommonCss(version) + GetCommonJavascript(version));
+
+ var bytes = Encoding.UTF8.GetBytes(html);
+
+ sourceStream.Dispose();
+
+ return new MemoryStream(bytes);
+ }
+
+ /// <summary>
+ /// Gets the meta tags.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ private static string GetMetaTags()
+ {
+ var sb = new StringBuilder();
+
+ sb.Append("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">");
+ sb.Append("<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">");
+ sb.Append("<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">");
+
+ // http://developer.apple.com/library/ios/#DOCUMENTATION/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html
+ sb.Append("<link rel=\"apple-touch-icon\" href=\"css/images/touchicon.png\" />");
+ sb.Append("<link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"css/images/touchicon72.png\" />");
+ sb.Append("<link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"css/images/touchicon114.png\" />");
+ sb.Append("<link rel=\"apple-touch-startup-image\" href=\"css/images/iossplash.png\">");
+
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// Gets the common CSS.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ private static string GetCommonCss(Version version)
+ {
+ var versionString = "?v=" + version;
+
+ var files = new[]
+ {
+ "http://code.jquery.com/mobile/1.3.0-rc.1/jquery.mobile-1.3.0-rc.1.min.css",
+ "thirdparty/jqm-icon-pack-3.0/font-awesome/jqm-icon-pack-3.0.0-fa.css",
+ "css/site.css" + versionString
+ };
+
+ var tags = files.Select(s => string.Format("<link rel=\"stylesheet\" href=\"{0}\" />", s)).ToArray();
+
+ return string.Join(string.Empty, tags);
+ }
+
+ /// <summary>
+ /// Gets the common javascript.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ private static string GetCommonJavascript(Version version)
+ {
+ var versionString = "?v=" + version;
+
+ var files = new[]
+ {
+ "http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js",
+ "http://code.jquery.com/mobile/1.3.0-rc.1/jquery.mobile-1.3.0-rc.1.min.js",
+ "../jsapiclient.js" + versionString,
+ "scripts/all.js" + versionString
+ };
+
+ var tags = files.Select(s => string.Format("<script src=\"{0}\"></script>", s)).ToArray();
+
+ return string.Join(string.Empty, tags);
+ }
+
+ /// <summary>
+ /// Gets a stream containing all concatenated javascript
+ /// </summary>
+ /// <returns>Task{Stream}.</returns>
+ private async Task<Stream> GetAllJavascript()
+ {
+ const string resourcePrefix = "MediaBrowser.WebDashboard.Html.scripts.";
+ var assembly = GetType().Assembly;
+
+ var scriptFiles = new[]
+ {
+ "Extensions.js",
+ "Site.js",
+ "AddPluginPage.js",
+ "AdvancedConfigurationPage.js",
+ "AdvancedMetadataConfigurationPage.js",
+ "PluginCatalogPage.js",
+ "DashboardPage.js",
+ "DisplaySettingsPage.js",
+ "EditUserPage.js",
+ "IndexPage.js",
+ "ItemDetailPage.js",
+ "LoginPage.js",
+ "LogPage.js",
+ "MediaLibraryPage.js",
+ "MediaPlayer.js",
+ "MetadataConfigurationPage.js",
+ "MetadataImagesPage.js",
+ "PluginsPage.js",
+ "PluginUpdatesPage.js",
+ "ScheduledTaskPage.js",
+ "ScheduledTasksPage.js",
+ "UpdatePasswordPage.js",
+ "UserImagePage.js",
+ "UserProfilesPage.js",
+ "WizardStartPage.js",
+ "WizardUserPage.js",
+ "SupporterKeyPage.js",
+ "SupporterPage.js"
+ };
+
+ var memoryStream = new MemoryStream();
+
+ var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine);
+
+ foreach (var file in scriptFiles)
+ {
+ using (var stream = assembly.GetManifestResourceStream(resourcePrefix + file))
+ {
+ await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
+
+ await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
+ }
+ }
+
+ memoryStream.Position = 0;
+ return memoryStream;
+ }
+
+ }
+
+}