From 2d06095447b972c8c7239277428e2c67c8b7ca86 Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Mon, 25 Feb 2013 22:43:04 -0500 Subject: plugin security fixes and other abstractions --- MediaBrowser.Api/EnvironmentService.cs | 1 + MediaBrowser.Api/Images/ImageService.cs | 7 +- .../Javascript/JavascriptApiClientService.cs | 1 + MediaBrowser.Api/Library/LibraryService.cs | 3 +- .../Library/LibraryStructureService.cs | 4 +- MediaBrowser.Api/LocalizationService.cs | 4 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 4 + MediaBrowser.Api/PackageService.cs | 6 +- MediaBrowser.Api/PluginService.cs | 4 +- .../ScheduledTasks/ScheduledTaskService.cs | 4 +- MediaBrowser.Api/SystemService.cs | 2 +- .../UserLibrary/BaseItemsByNameService.cs | 5 +- MediaBrowser.Api/UserLibrary/ItemsService.cs | 4 +- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 6 +- MediaBrowser.Api/UserService.cs | 2 +- MediaBrowser.Api/WeatherService.cs | 4 +- .../DataSerializer.cs | 76 --- .../MediaBrowser.ApiInteraction.Portable.csproj | 13 +- .../packages.config | 1 - MediaBrowser.ApiInteraction/ApiClient.cs | 19 +- MediaBrowser.ApiInteraction/AsyncHttpClient.cs | 2 +- MediaBrowser.ApiInteraction/BaseApiClient.cs | 65 ++- MediaBrowser.ApiInteraction/DataSerializer.cs | 66 --- MediaBrowser.ApiInteraction/IAsyncHttpClient.cs | 2 +- .../MediaBrowser.ApiInteraction.csproj | 13 +- .../NewtonsoftJsonSerializer.cs | 141 ++++++ MediaBrowser.ApiInteraction/ServerDiscovery.cs | 63 --- MediaBrowser.ApiInteraction/packages.config | 3 +- .../BaseApplicationHost.cs | 295 +++++++++++ .../MediaBrowser.Common.Implementations.csproj | 5 + .../ScheduledTasks/ScheduledTaskWorker.cs | 538 +++++++++++++++++++++ .../ScheduledTasks/TaskManager.cs | 202 ++------ .../ScheduledTasks/Tasks/DeleteCacheFileTask.cs | 29 +- .../ScheduledTasks/Tasks/DeleteLogFileTask.cs | 29 +- .../ScheduledTasks/Tasks/ReloadLoggerTask.cs | 36 +- .../ScheduledTasks/Tasks/SystemUpdateTask.cs | 36 +- .../Serialization/JsonSerializer.cs | 16 +- .../packages.config | 1 + MediaBrowser.Common/Kernel/BaseKernel.cs | 60 +-- MediaBrowser.Common/Kernel/IApplicationHost.cs | 42 +- MediaBrowser.Common/Kernel/IKernel.cs | 18 - MediaBrowser.Common/Kernel/TcpManager.cs | 6 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 7 +- MediaBrowser.Common/Net/BaseRestService.cs | 458 ------------------ .../Net/Handlers/BaseActionHandler.cs | 31 -- .../Net/Handlers/BaseSerializationHandler.cs | 133 ----- MediaBrowser.Common/Net/IHttpClient.cs | 8 +- MediaBrowser.Common/Net/IHttpServer.cs | 6 + MediaBrowser.Common/Net/IRestfulService.cs | 2 - MediaBrowser.Common/Net/WebSocketConnection.cs | 28 +- MediaBrowser.Common/Plugins/BasePlugin.cs | 5 +- .../ScheduledTasks/BaseScheduledTask.cs | 424 ---------------- .../ScheduledTasks/IScheduledTask.cs | 56 +-- .../ScheduledTasks/IScheduledTaskWorker.cs | 86 ++++ MediaBrowser.Common/ScheduledTasks/ITaskManager.cs | 35 +- .../ScheduledTasks/ScheduledTaskHelpers.cs | 2 +- .../MediaBrowser.Controller.csproj | 4 - MediaBrowser.Controller/Persistence/TypeMapper.cs | 47 -- .../Plugins/PluginSecurityManager.cs | 22 +- .../ScheduledTasks/ChapterImagesTask.cs | 111 ----- .../ScheduledTasks/ImageCleanupTask.cs | 210 -------- .../ScheduledTasks/PeopleValidationTask.cs | 22 +- .../ScheduledTasks/PluginUpdateTask.cs | 121 ----- .../ScheduledTasks/RefreshMediaLibraryTask.cs | 25 +- .../Updates/InstallationManager.cs | 13 +- .../Serialization/IJsonSerializer.cs | 16 +- .../HttpServer/BaseRestService.cs | 453 +++++++++++++++++ MediaBrowser.Networking/HttpServer/HttpServer.cs | 43 +- .../HttpServer/ServerFactory.cs | 5 +- .../MediaBrowser.Networking.csproj | 1 + .../MediaBrowser.Server.Implementations.csproj | 4 + .../Reflection/TypeMapper.cs | 47 ++ .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 122 +++++ .../ScheduledTasks/Tasks/ImageCleanupTask.cs | 220 +++++++++ .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 136 ++++++ .../Sqlite/SQLiteItemRepository.cs | 1 + MediaBrowser.ServerApplication/App.xaml.cs | 15 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 318 ++---------- .../LibraryExplorer.xaml.cs | 7 +- MediaBrowser.ServerApplication/MainWindow.xaml.cs | 17 +- .../MediaBrowser.ServerApplication.csproj | 12 + MediaBrowser.ServerApplication/packages.config | 2 + MediaBrowser.WebDashboard/Api/DashboardService.cs | 20 +- .../MediaBrowser.WebDashboard.csproj | 4 + Nuget/MediaBrowser.ApiClient.nuspec | 4 +- 85 files changed, 2512 insertions(+), 2629 deletions(-) delete mode 100644 MediaBrowser.ApiInteraction.Portable/DataSerializer.cs delete mode 100644 MediaBrowser.ApiInteraction/DataSerializer.cs create mode 100644 MediaBrowser.ApiInteraction/NewtonsoftJsonSerializer.cs delete mode 100644 MediaBrowser.ApiInteraction/ServerDiscovery.cs create mode 100644 MediaBrowser.Common.Implementations/BaseApplicationHost.cs create mode 100644 MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs delete mode 100644 MediaBrowser.Common/Net/BaseRestService.cs delete mode 100644 MediaBrowser.Common/Net/Handlers/BaseActionHandler.cs delete mode 100644 MediaBrowser.Common/Net/Handlers/BaseSerializationHandler.cs delete mode 100644 MediaBrowser.Common/ScheduledTasks/BaseScheduledTask.cs create mode 100644 MediaBrowser.Common/ScheduledTasks/IScheduledTaskWorker.cs delete mode 100644 MediaBrowser.Controller/Persistence/TypeMapper.cs delete mode 100644 MediaBrowser.Controller/ScheduledTasks/ChapterImagesTask.cs delete mode 100644 MediaBrowser.Controller/ScheduledTasks/ImageCleanupTask.cs delete mode 100644 MediaBrowser.Controller/ScheduledTasks/PluginUpdateTask.cs create mode 100644 MediaBrowser.Networking/HttpServer/BaseRestService.cs create mode 100644 MediaBrowser.Server.Implementations/Reflection/TypeMapper.cs create mode 100644 MediaBrowser.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs create mode 100644 MediaBrowser.Server.Implementations/ScheduledTasks/Tasks/ImageCleanupTask.cs create mode 100644 MediaBrowser.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 618eeae5df..d96b69cc4d 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index d3d76f4f27..1513d0d05d 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -1,17 +1,18 @@ -using System.Threading; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; +using ServiceStack.Text.Controller; using System; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; -using ServiceStack.Text.Controller; namespace MediaBrowser.Api.Images { diff --git a/MediaBrowser.Api/Javascript/JavascriptApiClientService.cs b/MediaBrowser.Api/Javascript/JavascriptApiClientService.cs index 8c7499a7cc..3081d6cd05 100644 --- a/MediaBrowser.Api/Javascript/JavascriptApiClientService.cs +++ b/MediaBrowser.Api/Javascript/JavascriptApiClientService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using System; using System.IO; diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 0729987d9e..2abb4e9148 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -1,10 +1,9 @@ using MediaBrowser.Common.Kernel; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 35c658f912..47ea2aa2ab 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -1,6 +1,6 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; +using MediaBrowser.Controller; using MediaBrowser.Model.Entities; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs index 098c9f72a9..8c4005d479 100644 --- a/MediaBrowser.Api/LocalizationService.cs +++ b/MediaBrowser.Api/LocalizationService.cs @@ -1,7 +1,7 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Localization; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; +using MediaBrowser.Networking.HttpServer; using MoreLinq; using ServiceStack.ServiceHost; using System.Collections.Generic; diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 896e1d438e..1ebfb357e1 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -123,6 +123,10 @@ {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} MediaBrowser.Model + + {7c11010e-179a-49b7-bfb2-f1656f5e71ad} + MediaBrowser.Networking + diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 028242e725..64338e5f09 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -1,8 +1,8 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Kernel; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Model.Updates; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; @@ -142,7 +142,7 @@ namespace MediaBrowser.Api { var kernel = (Kernel)Kernel; - var packages = kernel.InstallationManager.GetAvailablePackages(CancellationToken.None, applicationVersion: kernel.ApplicationVersion).Result; + var packages = kernel.InstallationManager.GetAvailablePackages(CancellationToken.None, applicationVersion: ApplicationHost.ApplicationVersion).Result; var result = packages.FirstOrDefault(p => p.name.Equals(request.Name, StringComparison.OrdinalIgnoreCase)); @@ -158,7 +158,7 @@ namespace MediaBrowser.Api { var kernel = (Kernel)Kernel; - var packages = kernel.InstallationManager.GetAvailablePackages(CancellationToken.None, request.PackageType, kernel.ApplicationVersion).Result; + var packages = kernel.InstallationManager.GetAvailablePackages(CancellationToken.None, request.PackageType, ApplicationHost.ApplicationVersion).Result; return ToOptimizedResult(packages.ToList()); } diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs index 1f906f814c..b2e82d4d6e 100644 --- a/MediaBrowser.Api/PluginService.cs +++ b/MediaBrowser.Api/PluginService.cs @@ -1,9 +1,9 @@ using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using ServiceStack.Text.Controller; using System; @@ -148,7 +148,7 @@ namespace MediaBrowser.Api public object Get(GetPlugins request) { var result = Kernel.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToList(); - + return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs index e79ab90dae..d1c7f451f2 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -1,14 +1,14 @@ using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; +using ServiceStack.Text.Controller; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using ServiceStack.Text.Controller; namespace MediaBrowser.Api.ScheduledTasks { diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs index 7921d024a5..cf0be35a2b 100644 --- a/MediaBrowser.Api/SystemService.cs +++ b/MediaBrowser.Api/SystemService.cs @@ -1,9 +1,9 @@ using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using System; using System.IO; diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 0c99b921a6..991c87b5fd 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -1,10 +1,9 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; +using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 0586b2b5ea..2c4fef3522 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -1,10 +1,10 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; +using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 51197affb8..bad55d51c4 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; +using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; @@ -7,13 +6,14 @@ using MediaBrowser.Model.Connectivity; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Serialization; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; +using ServiceStack.Text.Controller; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using ServiceStack.Text.Controller; namespace MediaBrowser.Api.UserLibrary { diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index d4490d856a..47dcbbf4db 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -1,9 +1,9 @@ using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Serialization; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using ServiceStack.Text.Controller; using System; diff --git a/MediaBrowser.Api/WeatherService.cs b/MediaBrowser.Api/WeatherService.cs index c79c2da284..9c66f2bcf8 100644 --- a/MediaBrowser.Api/WeatherService.cs +++ b/MediaBrowser.Api/WeatherService.cs @@ -1,6 +1,6 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; +using MediaBrowser.Controller; using MediaBrowser.Model.Weather; +using MediaBrowser.Networking.HttpServer; using ServiceStack.ServiceHost; using System.Linq; using System.Threading; diff --git a/MediaBrowser.ApiInteraction.Portable/DataSerializer.cs b/MediaBrowser.ApiInteraction.Portable/DataSerializer.cs deleted file mode 100644 index ad8c38dad5..0000000000 --- a/MediaBrowser.ApiInteraction.Portable/DataSerializer.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Newtonsoft.Json; -using ProtoBuf; -using ProtoBuf.Meta; -using System; -using System.IO; - -namespace MediaBrowser.ApiInteraction -{ - /// - /// Class DataSerializer - /// - public static class DataSerializer - { - /// - /// Gets or sets the dynamically created serializer. - /// - /// The dynamic serializer. - public static TypeModel DynamicSerializer { get; set; } - - /// - /// Deserializes an object - /// - /// The stream. - /// The format. - /// The type. - /// System.Object. - /// - public static object DeserializeFromStream(Stream stream, SerializationFormats format, Type type) - { - if (format == SerializationFormats.Protobuf) - { - if (DynamicSerializer != null) - { - return DynamicSerializer.Deserialize(stream, null, type); - } - return Serializer.NonGeneric.Deserialize(type, stream); - } - if (format == SerializationFormats.Json) - { - using (var streamReader = new StreamReader(stream)) - { - using (var jsonReader = new JsonTextReader(streamReader)) - { - return JsonSerializer.Create(new JsonSerializerSettings()).Deserialize(jsonReader, type); - } - } - } - - throw new NotImplementedException(); - } - - /// - /// Serializes to json. - /// - /// The obj. - /// System.String. - public static string SerializeToJsonString(object obj) - { - using (var streamWriter = new StringWriter()) - { - using (var jsonWriter = new JsonTextWriter((streamWriter))) - { - JsonSerializer.Create(new JsonSerializerSettings()).Serialize(jsonWriter, obj); - } - return streamWriter.ToString(); - } - } - - /// - /// Configures this instance. - /// - public static void Configure() - { - } - } -} diff --git a/MediaBrowser.ApiInteraction.Portable/MediaBrowser.ApiInteraction.Portable.csproj b/MediaBrowser.ApiInteraction.Portable/MediaBrowser.ApiInteraction.Portable.csproj index 9730663079..aaf5a3bd8e 100644 --- a/MediaBrowser.ApiInteraction.Portable/MediaBrowser.ApiInteraction.Portable.csproj +++ b/MediaBrowser.ApiInteraction.Portable/MediaBrowser.ApiInteraction.Portable.csproj @@ -38,10 +38,15 @@ AsyncHttpClient.cs + + NewtonsoftJsonSerializer.cs + + + SerializationFormats.cs + Properties\SharedVersion.cs - BaseApiClient.cs @@ -52,9 +57,6 @@ IAsyncHttpClient.cs - - SerializationFormats.cs - @@ -66,9 +68,6 @@ ..\packages\Newtonsoft.Json.4.5.11\lib\portable-net40+sl4+wp7+win8\Newtonsoft.Json.dll - - ..\packages\protobuf-net.2.0.0.621\lib\portable-sl4+net40+wp7+windows8\protobuf-net.dll - ..\packages\Microsoft.Net.Http.2.1.3-beta\lib\portable-net40+sl4+win8+wp71\System.Net.Http.dll diff --git a/MediaBrowser.ApiInteraction.Portable/packages.config b/MediaBrowser.ApiInteraction.Portable/packages.config index c3efe6da4e..795bf139cd 100644 --- a/MediaBrowser.ApiInteraction.Portable/packages.config +++ b/MediaBrowser.ApiInteraction.Portable/packages.config @@ -5,5 +5,4 @@ - \ No newline at end of file diff --git a/MediaBrowser.ApiInteraction/ApiClient.cs b/MediaBrowser.ApiInteraction/ApiClient.cs index e7d4e6eff1..5268c7e1f7 100644 --- a/MediaBrowser.ApiInteraction/ApiClient.cs +++ b/MediaBrowser.ApiInteraction/ApiClient.cs @@ -45,6 +45,15 @@ namespace MediaBrowser.ApiInteraction HttpClient = httpClient; } + /// + /// Initializes a new instance of the class. + /// + /// The logger. + public ApiClient(ILogger logger) + : this(logger, new AsyncHttpClient()) + { + } + /// /// Sets the authorization header. /// @@ -67,7 +76,7 @@ namespace MediaBrowser.ApiInteraction throw new ArgumentNullException("url"); } - return HttpClient.GetStreamAsync(url, Logger, CancellationToken.None); + return HttpClient.GetAsync(url, Logger, CancellationToken.None); } /// @@ -364,7 +373,7 @@ namespace MediaBrowser.ApiInteraction var url = GetApiUrl("Plugins/" + plugin.Id + "/Assembly"); - return HttpClient.GetStreamAsync(url, Logger, CancellationToken.None); + return HttpClient.GetAsync(url, Logger, CancellationToken.None); } /// @@ -431,7 +440,7 @@ namespace MediaBrowser.ApiInteraction var url = GetApiUrl("Plugins/" + pluginId + "/ConfigurationFile"); - return await HttpClient.GetStreamAsync(url, Logger, CancellationToken.None).ConfigureAwait(false); + return await HttpClient.GetAsync(url, Logger, CancellationToken.None).ConfigureAwait(false); } /// @@ -963,7 +972,7 @@ namespace MediaBrowser.ApiInteraction const string contentType = "application/x-www-form-urlencoded"; - var postContent = DataSerializer.SerializeToJsonString(obj); + var postContent = SerializeToJson(obj); using (var stream = await HttpClient.PostAsync(url, contentType, postContent, Logger, CancellationToken.None).ConfigureAwait(false)) { @@ -991,7 +1000,7 @@ namespace MediaBrowser.ApiInteraction { url = AddDataFormat(url, serializationFormat); - return HttpClient.GetStreamAsync(url, Logger, CancellationToken.None); + return HttpClient.GetAsync(url, Logger, CancellationToken.None); } diff --git a/MediaBrowser.ApiInteraction/AsyncHttpClient.cs b/MediaBrowser.ApiInteraction/AsyncHttpClient.cs index ec8176d8a1..c6701cac08 100644 --- a/MediaBrowser.ApiInteraction/AsyncHttpClient.cs +++ b/MediaBrowser.ApiInteraction/AsyncHttpClient.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.ApiInteraction /// The cancellation token. /// Task{Stream}. /// - public async Task GetStreamAsync(string url, ILogger logger, CancellationToken cancellationToken) + public async Task GetAsync(string url, ILogger logger, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/MediaBrowser.ApiInteraction/BaseApiClient.cs b/MediaBrowser.ApiInteraction/BaseApiClient.cs index 242fc23591..4bd4ace508 100644 --- a/MediaBrowser.ApiInteraction/BaseApiClient.cs +++ b/MediaBrowser.ApiInteraction/BaseApiClient.cs @@ -2,6 +2,7 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Web; using System; using System.Collections.Generic; @@ -21,10 +22,23 @@ namespace MediaBrowser.ApiInteraction /// The logger. protected ILogger Logger { get; private set; } + /// + /// Gets the protobuf serializer. + /// + /// The protobuf serializer. + public IProtobufSerializer ProtobufSerializer { get; private set; } + + /// + /// Gets the json serializer. + /// + /// The json serializer. + public IJsonSerializer JsonSerializer { get; private set; } + /// /// Initializes a new instance of the class. /// /// The logger. + /// The json serializer. /// logger protected BaseApiClient(ILogger logger) { @@ -33,9 +47,9 @@ namespace MediaBrowser.ApiInteraction throw new ArgumentNullException("logger"); } + JsonSerializer = new NewtonsoftJsonSerializer(); Logger = logger; - - DataSerializer.Configure(); + SerializationFormat = SerializationFormats.Json; } /// @@ -90,22 +104,11 @@ namespace MediaBrowser.ApiInteraction } } - private SerializationFormats _serializationFormat = SerializationFormats.Protobuf; /// /// Gets the default data format to request from the server /// /// The serialization format. - public SerializationFormats SerializationFormat - { - get - { - return _serializationFormat; - } - set - { - _serializationFormat = value; - } - } + public SerializationFormats SerializationFormat { get; set; } /// /// Resets the authorization header. @@ -790,7 +793,39 @@ namespace MediaBrowser.ApiInteraction protected T DeserializeFromStream(Stream stream) where T : class { - return (T)DataSerializer.DeserializeFromStream(stream, SerializationFormat, typeof(T)); + return (T)DeserializeFromStream(stream, typeof(T), SerializationFormat); + } + + /// + /// Deserializes from stream. + /// + /// The stream. + /// The type. + /// The format. + /// System.Object. + /// + protected object DeserializeFromStream(Stream stream, Type type, SerializationFormats format) + { + if (format == SerializationFormats.Protobuf) + { + return ProtobufSerializer.DeserializeFromStream(stream, type); + } + if (format == SerializationFormats.Json) + { + return JsonSerializer.DeserializeFromStream(stream, type); + } + + throw new NotImplementedException(); + } + + /// + /// Serializers to json. + /// + /// The obj. + /// System.String. + protected string SerializeToJson(object obj) + { + return JsonSerializer.SerializeToString(obj); } /// diff --git a/MediaBrowser.ApiInteraction/DataSerializer.cs b/MediaBrowser.ApiInteraction/DataSerializer.cs deleted file mode 100644 index cc13d55c88..0000000000 --- a/MediaBrowser.ApiInteraction/DataSerializer.cs +++ /dev/null @@ -1,66 +0,0 @@ -using ProtoBuf; -using ProtoBuf.Meta; -using ServiceStack.Text; -using System; -using System.IO; - -namespace MediaBrowser.ApiInteraction -{ - /// - /// Class DataSerializer - /// - public static class DataSerializer - { - /// - /// Gets or sets the dynamically created serializer. - /// - /// The dynamic serializer. - public static TypeModel DynamicSerializer { get; set; } - - /// - /// Deserializes an object - /// - /// The stream. - /// The format. - /// The type. - /// System.Object. - /// - public static object DeserializeFromStream(Stream stream, SerializationFormats format, Type type) - { - if (format == SerializationFormats.Protobuf) - { - if (DynamicSerializer != null) - { - return DynamicSerializer.Deserialize(stream, null, type); - } - return Serializer.NonGeneric.Deserialize(type, stream); - } - if (format == SerializationFormats.Json) - { - return JsonSerializer.DeserializeFromStream(type, stream); - } - - throw new NotImplementedException(); - } - - /// - /// Serializes to json. - /// - /// The obj. - /// System.String. - public static string SerializeToJsonString(object obj) - { - return JsonSerializer.SerializeToString(obj, obj.GetType()); - } - - /// - /// Configures this instance. - /// - public static void Configure() - { - JsConfig.DateHandler = JsonDateHandler.ISO8601; - JsConfig.ExcludeTypeInfo = true; - JsConfig.IncludeNullValues = false; - } - } -} diff --git a/MediaBrowser.ApiInteraction/IAsyncHttpClient.cs b/MediaBrowser.ApiInteraction/IAsyncHttpClient.cs index edd11829a6..0837f150fa 100644 --- a/MediaBrowser.ApiInteraction/IAsyncHttpClient.cs +++ b/MediaBrowser.ApiInteraction/IAsyncHttpClient.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.ApiInteraction /// The logger. /// The cancellation token. /// Task{Stream}. - Task GetStreamAsync(string url, ILogger logger, CancellationToken cancellationToken); + Task GetAsync(string url, ILogger logger, CancellationToken cancellationToken); /// /// Deletes the async. diff --git a/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj b/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj index 0d9938891f..a4a9091928 100644 --- a/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj +++ b/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj @@ -32,19 +32,13 @@ 4 - - False - ..\packages\protobuf-net.2.0.0.621\lib\net40\protobuf-net.dll - - - False - ..\packages\ServiceStack.Text.3.9.37\lib\net35\ServiceStack.Text.dll + + ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - @@ -58,11 +52,10 @@ - + - diff --git a/MediaBrowser.ApiInteraction/NewtonsoftJsonSerializer.cs b/MediaBrowser.ApiInteraction/NewtonsoftJsonSerializer.cs new file mode 100644 index 0000000000..f5b43669ea --- /dev/null +++ b/MediaBrowser.ApiInteraction/NewtonsoftJsonSerializer.cs @@ -0,0 +1,141 @@ +using MediaBrowser.Model.Serialization; +using Newtonsoft.Json; +using System; +using System.IO; + +namespace MediaBrowser.ApiInteraction +{ + /// + /// Class NewtonsoftJsonSerializer + /// + public class NewtonsoftJsonSerializer : IJsonSerializer + { + /// + /// Serializes to stream. + /// + /// The obj. + /// The stream. + /// + /// obj + public void SerializeToStream(object obj, Stream stream) + { + throw new NotImplementedException(); + } + + /// + /// Deserializes from stream. + /// + /// The stream. + /// The type. + /// System.Object. + public object DeserializeFromStream(Stream stream, Type type) + { + using (var streamReader = new StreamReader(stream)) + { + using (var jsonReader = new JsonTextReader(streamReader)) + { + return JsonSerializer.Create(new JsonSerializerSettings()).Deserialize(jsonReader, type); + } + } + } + + /// + /// Deserializes from stream. + /// + /// + /// The stream. + /// ``0. + public T DeserializeFromStream(Stream stream) + { + return (T)DeserializeFromStream(stream, typeof(T)); + } + + /// + /// Deserializes from string. + /// + /// + /// The text. + /// ``0. + /// + public T DeserializeFromString(string text) + { + throw new NotImplementedException(); + } + + /// + /// Deserializes from string. + /// + /// The json. + /// The type. + /// System.Object. + /// + public object DeserializeFromString(string json, Type type) + { + throw new NotImplementedException(); + } + + /// + /// Serializes to string. + /// + /// The obj. + /// System.String. + public string SerializeToString(object obj) + { + using (var streamWriter = new StringWriter()) + { + using (var jsonWriter = new JsonTextWriter((streamWriter))) + { + JsonSerializer.Create(new JsonSerializerSettings()).Serialize(jsonWriter, obj); + } + return streamWriter.ToString(); + } + } + + /// + /// Serializes to bytes. + /// + /// The obj. + /// System.Byte[][]. + /// + public byte[] SerializeToBytes(object obj) + { + throw new NotImplementedException(); + } + + /// + /// Serializes to file. + /// + /// The obj. + /// The file. + /// + /// obj + public void SerializeToFile(object obj, string file) + { + throw new NotImplementedException(); + } + + /// + /// Deserializes from file. + /// + /// The type. + /// The file. + /// System.Object. + /// + public object DeserializeFromFile(Type type, string file) + { + throw new NotImplementedException(); + } + + /// + /// Deserializes from file. + /// + /// + /// The file. + /// ``0. + /// + public T DeserializeFromFile(string file) where T : class + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.ApiInteraction/ServerDiscovery.cs b/MediaBrowser.ApiInteraction/ServerDiscovery.cs deleted file mode 100644 index 99a65db5d7..0000000000 --- a/MediaBrowser.ApiInteraction/ServerDiscovery.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading.Tasks; - -namespace MediaBrowser.ApiInteraction -{ - public static class ServerDiscovery - { - /// - /// Attemps to discover the server within a local network - /// - public static async Task DiscoverServer() - { - // Create a udp client - var client = new UdpClient(new IPEndPoint(IPAddress.Any, GetRandomUnusedPort())); - - // Construct the message the server is expecting - var bytes = Encoding.UTF8.GetBytes("who is MediaBrowserServer?"); - - // Send it - must be IPAddress.Broadcast, 7359 - var targetEndPoint = new IPEndPoint(IPAddress.Broadcast, 7359); - - // Send it - await client.SendAsync(bytes, bytes.Length, targetEndPoint).ConfigureAwait(false); - - // Get a result back - var result = await client.ReceiveAsync().ConfigureAwait(false); - - if (result.RemoteEndPoint.Port == targetEndPoint.Port) - { - // Convert bytes to text - var text = Encoding.UTF8.GetString(result.Buffer); - - // Expected response : MediaBrowserServer|192.168.1.1:1234 - // If the response is what we're expecting, proceed - if (text.StartsWith("mediabrowserserver", StringComparison.OrdinalIgnoreCase)) - { - text = text.Split('|')[1]; - - var vals = text.Split(':'); - - return new IPEndPoint(IPAddress.Parse(vals[0]), int.Parse(vals[1])); - } - } - - return null; - } - - /// - /// Gets a random port number that is currently available - /// - private static int GetRandomUnusedPort() - { - var listener = new TcpListener(IPAddress.Any, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - return port; - } - } -} diff --git a/MediaBrowser.ApiInteraction/packages.config b/MediaBrowser.ApiInteraction/packages.config index 14eb42cace..b82a8b0dde 100644 --- a/MediaBrowser.ApiInteraction/packages.config +++ b/MediaBrowser.ApiInteraction/packages.config @@ -1,5 +1,4 @@  - - + \ No newline at end of file diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs new file mode 100644 index 0000000000..c5af5059f3 --- /dev/null +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -0,0 +1,295 @@ +using System.IO; +using System.Linq; +using System.Reflection; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using SimpleInjector; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace MediaBrowser.Common.Implementations +{ + public abstract class BaseApplicationHost : IDisposable + { + /// + /// Gets or sets the logger. + /// + /// The logger. + public ILogger Logger { get; protected set; } + + /// + /// The container + /// + protected readonly Container Container = new Container(); + + /// + /// Gets assemblies that failed to load + /// + public List FailedAssemblies { get; protected set; } + + /// + /// Gets all types within all running assemblies + /// + /// All types. + public Type[] AllTypes { get; protected set; } + + /// + /// Gets all concrete types. + /// + /// All concrete types. + public Type[] AllConcreteTypes { get; protected set; } + + /// + /// The disposable parts + /// + protected readonly List DisposableParts = new List(); + + /// + /// The _protobuf serializer initialized + /// + private bool _protobufSerializerInitialized; + /// + /// The _protobuf serializer sync lock + /// + private object _protobufSerializerSyncLock = new object(); + /// + /// Gets a dynamically compiled generated serializer that can serialize protocontracts without reflection + /// + private IProtobufSerializer _protobufSerializer; + /// + /// Gets the protobuf serializer. + /// + /// The protobuf serializer. + protected IProtobufSerializer ProtobufSerializer + { + get + { + // Lazy load + LazyInitializer.EnsureInitialized(ref _protobufSerializer, ref _protobufSerializerInitialized, ref _protobufSerializerSyncLock, () => Serialization.ProtobufSerializer.Create(AllTypes)); + return _protobufSerializer; + } + private set + { + _protobufSerializer = value; + _protobufSerializerInitialized = value != null; + } + } + + /// + /// Initializes a new instance of the class. + /// + protected BaseApplicationHost() + { + FailedAssemblies = new List(); + } + + /// + /// Gets the composable part assemblies. + /// + /// IEnumerable{Assembly}. + protected abstract IEnumerable GetComposablePartAssemblies(); + + /// + /// Discovers the types. + /// + protected void DiscoverTypes() + { + FailedAssemblies.Clear(); + + AllTypes = GetComposablePartAssemblies().SelectMany(GetTypes).ToArray(); + + AllConcreteTypes = AllTypes.Where(t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType).ToArray(); + } + + /// + /// Gets a list of types within an assembly + /// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference + /// + /// The assembly. + /// IEnumerable{Type}. + /// assembly + protected IEnumerable GetTypes(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException("assembly"); + } + + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + // If it fails we can still get a list of the Types it was able to resolve + return ex.Types.Where(t => t != null); + } + } + + /// + /// Creates an instance of type and resolves all constructor dependancies + /// + /// The type. + /// System.Object. + public object CreateInstance(Type type) + { + try + { + return Container.GetInstance(type); + } + catch + { + Logger.Error("Error creating {0}", type.Name); + + throw; + } + } + + /// + /// Registers the specified obj. + /// + /// + /// The obj. + protected void RegisterSingleInstance(T obj) + where T : class + { + Container.RegisterSingle(obj); + } + + /// + /// Registers the specified func. + /// + /// + /// The func. + protected void Register(Func func) + where T : class + { + Container.Register(func); + } + + /// + /// Registers the single instance. + /// + /// + /// The func. + protected void RegisterSingleInstance(Func func) + where T : class + { + Container.RegisterSingle(func); + } + + /// + /// Resolves this instance. + /// + /// + /// ``0. + public T Resolve() + { + return (T)Container.GetRegistration(typeof(T), true).GetInstance(); + } + + /// + /// Resolves this instance. + /// + /// + /// ``0. + public T TryResolve() + { + var result = Container.GetRegistration(typeof(T), false); + + if (result == null) + { + return default(T); + } + return (T)result.GetInstance(); + } + + /// + /// Registers the specified service type. + /// + /// Type of the service. + /// Type of the concrete. + protected void Register(Type serviceType, Type implementation) + { + Container.Register(serviceType, implementation); + } + + /// + /// Loads the assembly. + /// + /// The file. + /// Assembly. + protected Assembly LoadAssembly(string file) + { + try + { + return Assembly.Load(File.ReadAllBytes((file))); + } + catch (Exception ex) + { + FailedAssemblies.Add(file); + Logger.ErrorException("Error loading assembly {0}", ex, file); + return null; + } + } + + /// + /// Gets the exports. + /// + /// + /// if set to true [manage liftime]. + /// IEnumerable{``0}. + public IEnumerable GetExports(bool manageLiftime = true) + { + var currentType = typeof(T); + + Logger.Info("Composing instances of " + currentType.Name); + + var parts = AllConcreteTypes.Where(currentType.IsAssignableFrom).Select(CreateInstance).Cast().ToArray(); + + if (manageLiftime) + { + DisposableParts.AddRange(parts.OfType()); + } + + return parts; + } + + /// + /// Gets the current application version + /// + /// The application version. + public Version ApplicationVersion + { + get + { + return GetType().Assembly.GetName().Version; + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + foreach (var part in DisposableParts) + { + part.Dispose(); + } + + var b = Container.GetCurrentRegistrations(); + + DisposableParts.Clear(); + } + } +} diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index 736a15d423..acd798d733 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -41,6 +41,9 @@ ..\packages\ServiceStack.Text.3.9.37\lib\net35\ServiceStack.Text.dll + + ..\packages\SimpleInjector.2.0.0-beta5\lib\net40-client\SimpleInjector.dll + @@ -54,8 +57,10 @@ Properties\SharedVersion.cs + + diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs new file mode 100644 index 0000000000..7fa30f4ae0 --- /dev/null +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -0,0 +1,538 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Kernel; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Tasks; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Common.Implementations.ScheduledTasks +{ + /// + /// Class ScheduledTaskWorker + /// + public class ScheduledTaskWorker : IScheduledTaskWorker + { + /// + /// Gets or sets the scheduled task. + /// + /// The scheduled task. + public IScheduledTask ScheduledTask { get; private set; } + + /// + /// Gets or sets the json serializer. + /// + /// The json serializer. + private IJsonSerializer JsonSerializer { get; set; } + + /// + /// Gets or sets the application paths. + /// + /// The application paths. + private IApplicationPaths ApplicationPaths { get; set; } + + /// + /// Gets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + + /// + /// Gets the task manager. + /// + /// The task manager. + private ITaskManager TaskManager { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The scheduled task. + /// The application paths. + /// The task manager. + /// The json serializer. + /// The logger. + public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger) + { + ScheduledTask = scheduledTask; + ApplicationPaths = applicationPaths; + TaskManager = taskManager; + JsonSerializer = jsonSerializer; + Logger = logger; + } + + /// + /// The _last execution result + /// + private TaskResult _lastExecutionResult; + /// + /// The _last execution resultinitialized + /// + private bool _lastExecutionResultinitialized; + /// + /// The _last execution result sync lock + /// + private object _lastExecutionResultSyncLock = new object(); + /// + /// Gets the last execution result. + /// + /// The last execution result. + public TaskResult LastExecutionResult + { + get + { + LazyInitializer.EnsureInitialized(ref _lastExecutionResult, ref _lastExecutionResultinitialized, ref _lastExecutionResultSyncLock, () => + { + try + { + return JsonSerializer.DeserializeFromFile(GetHistoryFilePath()); + } + catch (IOException) + { + // File doesn't exist. No biggie + return null; + } + }); + + return _lastExecutionResult; + } + private set + { + _lastExecutionResult = value; + + _lastExecutionResultinitialized = value != null; + } + } + + /// + /// Gets the name. + /// + /// The name. + public string Name + { + get { return ScheduledTask.Name; } + } + + /// + /// Gets the description. + /// + /// The description. + public string Description + { + get { return ScheduledTask.Description; } + } + + /// + /// Gets the category. + /// + /// The category. + public string Category + { + get { return ScheduledTask.Category; } + } + + /// + /// Gets the current cancellation token + /// + /// The current cancellation token source. + private CancellationTokenSource CurrentCancellationTokenSource { get; set; } + + /// + /// Gets or sets the current execution start time. + /// + /// The current execution start time. + private DateTime CurrentExecutionStartTime { get; set; } + + /// + /// Gets the state. + /// + /// The state. + public TaskState State + { + get + { + if (CurrentCancellationTokenSource != null) + { + return CurrentCancellationTokenSource.IsCancellationRequested + ? TaskState.Cancelling + : TaskState.Running; + } + + return TaskState.Idle; + } + } + + /// + /// Gets the current progress. + /// + /// The current progress. + public double? CurrentProgress { get; private set; } + + /// + /// The _triggers + /// + private IEnumerable _triggers; + /// + /// The _triggers initialized + /// + private bool _triggersInitialized; + /// + /// The _triggers sync lock + /// + private object _triggersSyncLock = new object(); + /// + /// Gets the triggers that define when the task will run + /// + /// The triggers. + /// value + public IEnumerable Triggers + { + get + { + LazyInitializer.EnsureInitialized(ref _triggers, ref _triggersInitialized, ref _triggersSyncLock, () => LoadTriggers()); + + return _triggers; + } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + // Cleanup current triggers + if (_triggers != null) + { + DisposeTriggers(); + } + + _triggers = value.ToList(); + + _triggersInitialized = true; + + ReloadTriggerEvents(false); + + SaveTriggers(_triggers); + } + } + + /// + /// The _id + /// + private Guid? _id; + + /// + /// Gets the unique id. + /// + /// The unique id. + public Guid Id + { + get + { + if (!_id.HasValue) + { + _id = ScheduledTask.GetType().FullName.GetMD5(); + } + + return _id.Value; + } + } + + /// + /// Reloads the trigger events. + /// + /// if set to true [is application startup]. + private void ReloadTriggerEvents(bool isApplicationStartup) + { + foreach (var trigger in Triggers) + { + trigger.Stop(); + + trigger.Triggered -= trigger_Triggered; + trigger.Triggered += trigger_Triggered; + trigger.Start(isApplicationStartup); + } + } + + /// + /// Handles the Triggered event of the trigger control. + /// + /// The source of the event. + /// The instance containing the event data. + async void trigger_Triggered(object sender, EventArgs e) + { + var trigger = (ITaskTrigger)sender; + + Logger.Info("{0} fired for task: {1}", trigger.GetType().Name, Name); + + trigger.Stop(); + + TaskManager.QueueScheduledTask(ScheduledTask); + + await Task.Delay(1000).ConfigureAwait(false); + + trigger.Start(false); + } + + /// + /// Executes the task + /// + /// Task. + /// Cannot execute a Task that is already running + public async Task Execute() + { + // Cancel the current execution, if any + if (CurrentCancellationTokenSource != null) + { + throw new InvalidOperationException("Cannot execute a Task that is already running"); + } + + CurrentCancellationTokenSource = new CancellationTokenSource(); + + Logger.Info("Executing {0}", Name); + + var progress = new Progress(); + + progress.ProgressChanged += progress_ProgressChanged; + + TaskCompletionStatus status; + CurrentExecutionStartTime = DateTime.UtcNow; + + //Kernel.TcpManager.SendWebSocketMessage("ScheduledTaskBeginExecute", Name); + + try + { + await System.Threading.Tasks.Task.Run(async () => await ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress).ConfigureAwait(false)).ConfigureAwait(false); + + status = TaskCompletionStatus.Completed; + } + catch (OperationCanceledException) + { + status = TaskCompletionStatus.Cancelled; + } + catch (Exception ex) + { + Logger.ErrorException("Error", ex); + + status = TaskCompletionStatus.Failed; + } + + var startTime = CurrentExecutionStartTime; + var endTime = DateTime.UtcNow; + + //Kernel.TcpManager.SendWebSocketMessage("ScheduledTaskEndExecute", LastExecutionResult); + + progress.ProgressChanged -= progress_ProgressChanged; + CurrentCancellationTokenSource.Dispose(); + CurrentCancellationTokenSource = null; + CurrentProgress = null; + + OnTaskCompleted(startTime, endTime, status); + } + + /// + /// Progress_s the progress changed. + /// + /// The sender. + /// The e. + void progress_ProgressChanged(object sender, double e) + { + CurrentProgress = e; + } + + /// + /// Stops the task if it is currently executing + /// + /// Cannot cancel a Task unless it is in the Running state. + public void Cancel() + { + if (State != TaskState.Running) + { + throw new InvalidOperationException("Cannot cancel a Task unless it is in the Running state."); + } + + CancelIfRunning(); + } + + /// + /// Cancels if running. + /// + public void CancelIfRunning() + { + if (State == TaskState.Running) + { + Logger.Info("Attempting to cancel Scheduled Task {0}", Name); + CurrentCancellationTokenSource.Cancel(); + } + } + + /// + /// The _scheduled tasks configuration directory + /// + private string _scheduledTasksConfigurationDirectory; + /// + /// Gets the scheduled tasks configuration directory. + /// + /// The scheduled tasks configuration directory. + private string ScheduledTasksConfigurationDirectory + { + get + { + if (_scheduledTasksConfigurationDirectory == null) + { + _scheduledTasksConfigurationDirectory = Path.Combine(ApplicationPaths.ConfigurationDirectoryPath, "ScheduledTasks"); + + if (!Directory.Exists(_scheduledTasksConfigurationDirectory)) + { + Directory.CreateDirectory(_scheduledTasksConfigurationDirectory); + } + } + return _scheduledTasksConfigurationDirectory; + } + } + + /// + /// The _scheduled tasks data directory + /// + private string _scheduledTasksDataDirectory; + /// + /// Gets the scheduled tasks data directory. + /// + /// The scheduled tasks data directory. + private string ScheduledTasksDataDirectory + { + get + { + if (_scheduledTasksDataDirectory == null) + { + _scheduledTasksDataDirectory = Path.Combine(ApplicationPaths.DataPath, "ScheduledTasks"); + + if (!Directory.Exists(_scheduledTasksDataDirectory)) + { + Directory.CreateDirectory(_scheduledTasksDataDirectory); + } + } + return _scheduledTasksDataDirectory; + } + } + + /// + /// Gets the history file path. + /// + /// The history file path. + private string GetHistoryFilePath() + { + return Path.Combine(ScheduledTasksDataDirectory, Id + ".js"); + } + + /// + /// Gets the configuration file path. + /// + /// System.String. + private string GetConfigurationFilePath() + { + return Path.Combine(ScheduledTasksConfigurationDirectory, Id + ".js"); + } + + /// + /// Loads the triggers. + /// + /// IEnumerable{BaseTaskTrigger}. + private IEnumerable LoadTriggers() + { + try + { + return JsonSerializer.DeserializeFromFile>(GetConfigurationFilePath()) + .Select(ScheduledTaskHelpers.GetTrigger) + .ToList(); + } + catch (IOException) + { + // File doesn't exist. No biggie. Return defaults. + return ScheduledTask.GetDefaultTriggers(); + } + } + + /// + /// Saves the triggers. + /// + /// The triggers. + private void SaveTriggers(IEnumerable triggers) + { + JsonSerializer.SerializeToFile(triggers.Select(ScheduledTaskHelpers.GetTriggerInfo), GetConfigurationFilePath()); + } + + /// + /// Called when [task completed]. + /// + /// The start time. + /// The end time. + /// The status. + private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status) + { + var elapsedTime = endTime - startTime; + + Logger.Info("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds); + + var result = new TaskResult + { + StartTimeUtc = startTime, + EndTimeUtc = endTime, + Status = status, + Name = Name, + Id = Id + }; + + JsonSerializer.SerializeToFile(result, GetHistoryFilePath()); + + LastExecutionResult = result; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + DisposeTriggers(); + + if (State == TaskState.Running) + { + OnTaskCompleted(CurrentExecutionStartTime, DateTime.UtcNow, TaskCompletionStatus.Aborted); + } + + if (CurrentCancellationTokenSource != null) + { + CurrentCancellationTokenSource.Dispose(); + } + } + } + + /// + /// Disposes each trigger + /// + private void DisposeTriggers() + { + foreach (var trigger in Triggers) + { + trigger.Triggered -= trigger_Triggered; + trigger.Stop(); + } + } + } +} diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs index c6eca29d1c..4b61492d66 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs @@ -5,7 +5,6 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using System; using System.Collections.Generic; -using System.IO; using System.Linq; namespace MediaBrowser.Common.Implementations.ScheduledTasks @@ -19,7 +18,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks /// Gets the list of Scheduled Tasks /// /// The scheduled tasks. - public IScheduledTask[] ScheduledTasks { get; private set; } + public IScheduledTaskWorker[] ScheduledTasks { get; private set; } /// /// The _task queue @@ -27,19 +26,22 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks private readonly List _taskQueue = new List(); /// - /// The _logger + /// Gets or sets the json serializer. /// - private readonly ILogger _logger; + /// The json serializer. + private IJsonSerializer JsonSerializer { get; set; } /// - /// The _application paths + /// Gets or sets the application paths. /// - private readonly IApplicationPaths _applicationPaths; + /// The application paths. + private IApplicationPaths ApplicationPaths { get; set; } /// - /// The _json serializer + /// Gets the logger. /// - private readonly IJsonSerializer _jsonSerializer; + /// The logger. + private ILogger Logger { get; set; } /// /// Initializes a new instance of the class. @@ -50,24 +52,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks /// kernel public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger) { - if (applicationPaths == null) - { - throw new ArgumentException("applicationPaths"); - } - if (jsonSerializer == null) - { - throw new ArgumentException("jsonSerializer"); - } - if (logger == null) - { - throw new ArgumentException("logger"); - } + ApplicationPaths = applicationPaths; + JsonSerializer = jsonSerializer; + Logger = logger; - _applicationPaths = applicationPaths; - _jsonSerializer = jsonSerializer; - _logger = logger; - - ScheduledTasks = new IScheduledTask[] {}; + ScheduledTasks = new IScheduledTaskWorker[] { }; } /// @@ -77,7 +66,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks public void CancelIfRunningAndQueue() where T : IScheduledTask { - ScheduledTasks.OfType().First().CancelIfRunning(); + ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)).CancelIfRunning(); QueueScheduledTask(); } @@ -88,7 +77,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks public void QueueScheduledTask() where T : IScheduledTask { - var scheduledTask = ScheduledTasks.OfType().First(); + var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); QueueScheduledTask(scheduledTask); } @@ -99,27 +88,36 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks /// The task. public void QueueScheduledTask(IScheduledTask task) { - var type = task.GetType(); + var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == task.GetType()); - var scheduledTask = ScheduledTasks.First(t => t.GetType() == type); + QueueScheduledTask(scheduledTask); + } + + /// + /// Queues the scheduled task. + /// + /// The task. + private void QueueScheduledTask(IScheduledTaskWorker task) + { + var type = task.GetType(); lock (_taskQueue) { // If it's idle just execute immediately - if (scheduledTask.State == TaskState.Idle) + if (task.State == TaskState.Idle) { - scheduledTask.Execute(); + task.Execute(); return; } if (!_taskQueue.Contains(type)) { - _logger.Info("Queueing task {0}", type.Name); + Logger.Info("Queueing task {0}", type.Name); _taskQueue.Add(type); } else { - _logger.Info("Task already queued: {0}", type.Name); + Logger.Info("Task already queued: {0}", type.Name); } } } @@ -157,147 +155,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks { var myTasks = ScheduledTasks.ToList(); - myTasks.AddRange(tasks); + myTasks.AddRange(tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger))); ScheduledTasks = myTasks.ToArray(); } - /// - /// The _scheduled tasks configuration directory - /// - private string _scheduledTasksConfigurationDirectory; - /// - /// Gets the scheduled tasks configuration directory. - /// - /// The scheduled tasks configuration directory. - private string ScheduledTasksConfigurationDirectory - { - get - { - if (_scheduledTasksConfigurationDirectory == null) - { - _scheduledTasksConfigurationDirectory = Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks"); - - if (!Directory.Exists(_scheduledTasksConfigurationDirectory)) - { - Directory.CreateDirectory(_scheduledTasksConfigurationDirectory); - } - } - return _scheduledTasksConfigurationDirectory; - } - } - - /// - /// The _scheduled tasks data directory - /// - private string _scheduledTasksDataDirectory; - /// - /// Gets the scheduled tasks data directory. - /// - /// The scheduled tasks data directory. - private string ScheduledTasksDataDirectory - { - get - { - if (_scheduledTasksDataDirectory == null) - { - _scheduledTasksDataDirectory = Path.Combine(_applicationPaths.DataPath, "ScheduledTasks"); - - if (!Directory.Exists(_scheduledTasksDataDirectory)) - { - Directory.CreateDirectory(_scheduledTasksDataDirectory); - } - } - return _scheduledTasksDataDirectory; - } - } - - /// - /// Gets the history file path. - /// - /// The history file path. - private string GetHistoryFilePath(IScheduledTask task) - { - return Path.Combine(ScheduledTasksDataDirectory, task.Id + ".js"); - } - - /// - /// Gets the configuration file path. - /// - /// The task. - /// System.String. - private string GetConfigurationFilePath(IScheduledTask task) - { - return Path.Combine(ScheduledTasksConfigurationDirectory, task.Id + ".js"); - } - - /// - /// Called when [task completed]. - /// - /// The task. - /// The start time. - /// The end time. - /// The status. - public void OnTaskCompleted(IScheduledTask task, DateTime startTime, DateTime endTime, TaskCompletionStatus status) - { - var elapsedTime = endTime - startTime; - - _logger.Info("{0} {1} after {2} minute(s) and {3} seconds", task.Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds); - - var result = new TaskResult - { - StartTimeUtc = startTime, - EndTimeUtc = endTime, - Status = status, - Name = task.Name, - Id = task.Id - }; - - _jsonSerializer.SerializeToFile(result, GetHistoryFilePath(task)); - - //task.LastExecutionResult = result; - } - - /// - /// Gets the last execution result. - /// - /// The task. - /// TaskResult. - public TaskResult GetLastExecutionResult(IScheduledTask task) - { - return _jsonSerializer.DeserializeFromFile(GetHistoryFilePath(task)); - } - - /// - /// Loads the triggers. - /// - /// The task. - /// IEnumerable{BaseTaskTrigger}. - public IEnumerable LoadTriggers(IScheduledTask task) - { - try - { - return _jsonSerializer.DeserializeFromFile>(GetConfigurationFilePath(task)) - .Select(ScheduledTaskHelpers.GetTrigger) - .ToList(); - } - catch (IOException) - { - // File doesn't exist. No biggie. Return defaults. - return task.GetDefaultTriggers(); - } - } - - /// - /// Saves the triggers. - /// - /// The task. - /// The triggers. - public void SaveTriggers(IScheduledTask task, IEnumerable triggers) - { - _jsonSerializer.SerializeToFile(triggers.Select(ScheduledTaskHelpers.GetTriggerInfo), GetConfigurationFilePath(task)); - } - /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 2ef0566587..a9c82c3578 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -13,24 +13,35 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// /// Deletes old cache files /// - public class DeleteCacheFileTask : BaseScheduledTask + public class DeleteCacheFileTask : IScheduledTask { + /// + /// Gets or sets the kernel. + /// + /// The kernel. + private IKernel Kernel { get; set; } + /// + /// Gets or sets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + /// /// Initializes a new instance of the class. /// /// The kernel. - /// The task manager. /// The logger. - public DeleteCacheFileTask(IKernel kernel, ITaskManager taskManager, ILogger logger) - : base(kernel, taskManager, logger) + public DeleteCacheFileTask(IKernel kernel, ILogger logger) { + Kernel = kernel; + Logger = logger; } /// /// Creates the triggers that define when the task will run /// /// IEnumerable{BaseTaskTrigger}. - public override IEnumerable GetDefaultTriggers() + public IEnumerable GetDefaultTriggers() { var trigger = new DailyTrigger { TimeOfDay = TimeSpan.FromHours(2) }; //2am @@ -43,7 +54,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// The cancellation token. /// The progress. /// Task. - protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + public Task Execute(CancellationToken cancellationToken, IProgress progress) { return Task.Run(() => { @@ -90,7 +101,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the name of the task /// /// The name. - public override string Name + public string Name { get { return "Cache file cleanup"; } } @@ -99,7 +110,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the description. /// /// The description. - public override string Description + public string Description { get { return "Deletes cache files no longer needed by the system"; } } @@ -108,7 +119,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the category. /// /// The category. - public override string Category + public string Category { get { diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index dd00a71489..a7d8a68a04 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -13,24 +13,35 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// /// Deletes old log files /// - public class DeleteLogFileTask : BaseScheduledTask + public class DeleteLogFileTask : IScheduledTask { + /// + /// Gets or sets the kernel. + /// + /// The kernel. + private IKernel Kernel { get; set; } + /// + /// Gets or sets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + /// /// Initializes a new instance of the class. /// /// The kernel. - /// The task manager. /// The logger. - public DeleteLogFileTask(IKernel kernel, ITaskManager taskManager, ILogger logger) - : base(kernel, taskManager, logger) + public DeleteLogFileTask(IKernel kernel, ILogger logger) { + Kernel = kernel; + Logger = logger; } /// /// Creates the triggers that define when the task will run /// /// IEnumerable{BaseTaskTrigger}. - public override IEnumerable GetDefaultTriggers() + public IEnumerable GetDefaultTriggers() { var trigger = new DailyTrigger { TimeOfDay = TimeSpan.FromHours(2) }; //2am @@ -43,7 +54,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// The cancellation token. /// The progress. /// Task. - protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + public Task Execute(CancellationToken cancellationToken, IProgress progress) { return Task.Run(() => { @@ -78,7 +89,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the name of the task /// /// The name. - public override string Name + public string Name { get { return "Log file cleanup"; } } @@ -87,7 +98,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the description. /// /// The description. - public override string Description + public string Description { get { return string.Format("Deletes log files that are more than {0} days old.", Kernel.Configuration.LogFileRetentionDays); } } @@ -96,7 +107,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the category. /// /// The category. - public override string Category + public string Category { get { diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs index 79c633c765..ac06f111a2 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs @@ -11,24 +11,35 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// /// Class ReloadLoggerFileTask /// - public class ReloadLoggerFileTask : BaseScheduledTask + public class ReloadLoggerFileTask : IScheduledTask { + /// + /// Gets or sets the kernel. + /// + /// The kernel. + private IKernel Kernel { get; set; } + /// + /// Gets or sets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + /// /// Initializes a new instance of the class. /// /// The kernel. - /// The task manager. /// The logger. - public ReloadLoggerFileTask(IKernel kernel, ITaskManager taskManager, ILogger logger) - : base(kernel, taskManager, logger) + public ReloadLoggerFileTask(IKernel kernel, ILogger logger) { + Kernel = kernel; + Logger = logger; } /// /// Gets the default triggers. /// /// IEnumerable{BaseTaskTrigger}. - public override IEnumerable GetDefaultTriggers() + public IEnumerable GetDefaultTriggers() { var trigger = new DailyTrigger { TimeOfDay = TimeSpan.FromHours(0) }; //12am @@ -41,7 +52,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// The cancellation token. /// The progress. /// Task. - protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + public Task Execute(CancellationToken cancellationToken, IProgress progress) { cancellationToken.ThrowIfCancellationRequested(); @@ -54,7 +65,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the name. /// /// The name. - public override string Name + public string Name { get { return "Start new log file"; } } @@ -63,9 +74,18 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the description. /// /// The description. - public override string Description + public string Description { get { return "Moves logging to a new file to help reduce log file sizes."; } } + + /// + /// Gets the category. + /// + /// The category. + public string Category + { + get { return "Application"; } + } } } diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/SystemUpdateTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/SystemUpdateTask.cs index a101ad3dd4..18fcdbbda9 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/SystemUpdateTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/SystemUpdateTask.cs @@ -11,31 +11,42 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// /// Plugin Update Task /// - public class SystemUpdateTask : BaseScheduledTask + public class SystemUpdateTask : IScheduledTask { /// /// The _app host /// private readonly IApplicationHost _appHost; + /// + /// Gets or sets the kernel. + /// + /// The kernel. + private IKernel Kernel { get; set; } + /// + /// Gets or sets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + /// /// Initializes a new instance of the class. /// /// The app host. - /// The task manager. /// The kernel. /// The logger. - public SystemUpdateTask(IApplicationHost appHost, ITaskManager taskManager, IKernel kernel, ILogger logger) - : base(kernel, taskManager, logger) + public SystemUpdateTask(IApplicationHost appHost, IKernel kernel, ILogger logger) { _appHost = appHost; + Kernel = kernel; + Logger = logger; } /// /// Creates the triggers that define when the task will run /// /// IEnumerable{BaseTaskTrigger}. - public override IEnumerable GetDefaultTriggers() + public IEnumerable GetDefaultTriggers() { return new ITaskTrigger[] { @@ -52,7 +63,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// The cancellation token. /// The progress. /// Task. - protected override async Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + public async Task Execute(CancellationToken cancellationToken, IProgress progress) { if (!_appHost.CanSelfUpdate) return; @@ -105,7 +116,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the name of the task /// /// The name. - public override string Name + public string Name { get { return "Check for application updates"; } } @@ -114,9 +125,18 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// Gets the description. /// /// The description. - public override string Description + public string Description { get { return "Downloads and installs application updates."; } } + + /// + /// Gets the category. + /// + /// The category. + public string Category + { + get { return "Application"; } + } } } diff --git a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs b/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs index bc8935a86c..4a6b9255c1 100644 --- a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs +++ b/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs @@ -17,12 +17,10 @@ namespace MediaBrowser.Common.Implementations.Serialization /// /// Serializes to stream. /// - /// /// The obj. /// The stream. /// obj - public void SerializeToStream(T obj, Stream stream) - where T : class + public void SerializeToStream(object obj, Stream stream) { if (obj == null) { @@ -40,12 +38,10 @@ namespace MediaBrowser.Common.Implementations.Serialization /// /// Serializes to file. /// - /// /// The obj. /// The file. /// obj - public void SerializeToFile(T obj, string file) - where T : class + public void SerializeToFile(object obj, string file) { if (obj == null) { @@ -200,12 +196,10 @@ namespace MediaBrowser.Common.Implementations.Serialization /// /// Serializes to string. /// - /// /// The obj. /// System.String. /// obj - public string SerializeToString(T obj) - where T : class + public string SerializeToString(object obj) { if (obj == null) { @@ -218,12 +212,10 @@ namespace MediaBrowser.Common.Implementations.Serialization /// /// Serializes to bytes. /// - /// /// The obj. /// System.Byte[][]. /// obj - public byte[] SerializeToBytes(T obj) - where T : class + public byte[] SerializeToBytes(object obj) { if (obj == null) { diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config index 14eb42cace..63fd0052fc 100644 --- a/MediaBrowser.Common.Implementations/packages.config +++ b/MediaBrowser.Common.Implementations/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/MediaBrowser.Common/Kernel/BaseKernel.cs b/MediaBrowser.Common/Kernel/BaseKernel.cs index eb5381e206..5b8da5d094 100644 --- a/MediaBrowser.Common/Kernel/BaseKernel.cs +++ b/MediaBrowser.Common/Kernel/BaseKernel.cs @@ -40,9 +40,6 @@ namespace MediaBrowser.Common.Kernel internal void OnConfigurationUpdated() { EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger); - - // Notify connected clients - TcpManager.SendWebSocketMessage("ConfigurationUpdated", Configuration); } #endregion @@ -140,12 +137,6 @@ namespace MediaBrowser.Common.Kernel } } - /// - /// Gets a value indicating whether this instance is first run. - /// - /// true if this instance is first run; otherwise, false. - public bool IsFirstRun { get; private set; } - /// /// Gets or sets a value indicating whether this instance has changes that require the entire application to restart. /// @@ -176,12 +167,6 @@ namespace MediaBrowser.Common.Kernel /// The TCP manager. public TcpManager TcpManager { get; private set; } - /// - /// Gets the rest services. - /// - /// The rest services. - public IEnumerable RestServices { get; private set; } - /// /// Gets the UDP server port number. /// This can't be configurable because then the user would have to configure their client to discover the server. @@ -280,19 +265,7 @@ namespace MediaBrowser.Common.Kernel /// Initializes the Kernel /// /// Task. - public Task Init() - { - IsFirstRun = !File.Exists(ApplicationPaths.SystemConfigurationFilePath); - - // Performs initializations that can be reloaded at anytime - return Reload(); - } - - /// - /// Performs initializations that can be reloaded at anytime - /// - /// Task. - public async Task Reload() + public async Task Init() { OnReloadBeginning(); @@ -312,8 +285,6 @@ namespace MediaBrowser.Common.Kernel // Set these to null so that they can be lazy loaded again Configuration = null; - Logger.Info("Version {0} initializing", ApplicationVersion); - await OnConfigurationLoaded().ConfigureAwait(false); FindParts(); @@ -348,7 +319,6 @@ namespace MediaBrowser.Common.Kernel /// protected virtual void FindParts() { - RestServices = ApplicationHost.GetExports(); WebSocketListeners = ApplicationHost.GetExports(); Plugins = ApplicationHost.GetExports(); } @@ -425,18 +395,6 @@ namespace MediaBrowser.Common.Kernel } } - /// - /// Gets the current application version - /// - /// The application version. - public Version ApplicationVersion - { - get - { - return GetType().Assembly.GetName().Version; - } - } - /// /// Performs the pending restart. /// @@ -445,7 +403,9 @@ namespace MediaBrowser.Common.Kernel { if (HasPendingRestart) { - RestartApplication(); + Logger.Info("Restarting the application"); + + ApplicationHost.Restart(); } else { @@ -453,16 +413,6 @@ namespace MediaBrowser.Common.Kernel } } - /// - /// Restarts the application. - /// - protected void RestartApplication() - { - Logger.Info("Restarting the application"); - - ApplicationHost.Restart(); - } - /// /// Gets the system status. /// @@ -472,7 +422,7 @@ namespace MediaBrowser.Common.Kernel return new SystemInfo { HasPendingRestart = HasPendingRestart, - Version = ApplicationVersion.ToString(), + Version = ApplicationHost.ApplicationVersion.ToString(), IsNetworkDeployed = ApplicationHost.CanSelfUpdate, WebSocketPortNumber = TcpManager.WebSocketPortNumber, SupportsNativeWebSocket = TcpManager.SupportsNativeWebSocket, diff --git a/MediaBrowser.Common/Kernel/IApplicationHost.cs b/MediaBrowser.Common/Kernel/IApplicationHost.cs index 4b564581b4..af9b039bc0 100644 --- a/MediaBrowser.Common/Kernel/IApplicationHost.cs +++ b/MediaBrowser.Common/Kernel/IApplicationHost.cs @@ -21,6 +21,12 @@ namespace MediaBrowser.Common.Kernel /// void ReloadLogger(); + /// + /// Gets the application version. + /// + /// The application version. + Version ApplicationVersion { get; } + /// /// Gets the log file path. /// @@ -33,11 +39,17 @@ namespace MediaBrowser.Common.Kernel /// true if this instance can self update; otherwise, false. bool CanSelfUpdate { get; } + /// + /// Gets a value indicating whether this instance is first run. + /// + /// true if this instance is first run; otherwise, false. + bool IsFirstRun { get; } + /// /// Gets the failed assemblies. /// /// The failed assemblies. - IEnumerable FailedAssemblies { get; } + List FailedAssemblies { get; } /// /// Gets all concrete types. @@ -72,34 +84,6 @@ namespace MediaBrowser.Common.Kernel /// System.Object. object CreateInstance(Type type); - /// - /// Registers a service that other classes can use as a dependancy. - /// - /// - /// The obj. - void RegisterSingleInstance(T obj) where T : class; - - /// - /// Registers the single instance. - /// - /// - /// The func. - void RegisterSingleInstance(Func func) where T : class; - - /// - /// Registers the specified func. - /// - /// - /// The func. - void Register(Func func) where T : class; - - /// - /// Registers the specified service type. - /// - /// Type of the service. - /// Type of the implementation. - void Register(Type serviceType, Type implementation); - /// /// Resolves this instance. /// diff --git a/MediaBrowser.Common/Kernel/IKernel.cs b/MediaBrowser.Common/Kernel/IKernel.cs index fb629a24df..06c2e7b64c 100644 --- a/MediaBrowser.Common/Kernel/IKernel.cs +++ b/MediaBrowser.Common/Kernel/IKernel.cs @@ -37,12 +37,6 @@ namespace MediaBrowser.Common.Kernel /// Task. Task Init(); - /// - /// Reloads this instance. - /// - /// Task. - Task Reload(); - /// /// Gets or sets a value indicating whether this instance has pending kernel reload. /// @@ -106,12 +100,6 @@ namespace MediaBrowser.Common.Kernel /// The HTTP server URL prefix. string HttpServerUrlPrefix { get; } - /// - /// Gets a value indicating whether this instance is first run. - /// - /// true if this instance is first run; otherwise, false. - bool IsFirstRun { get; } - /// /// Gets the TCP manager. /// @@ -139,12 +127,6 @@ namespace MediaBrowser.Common.Kernel /// event EventHandler ConfigurationUpdated; - /// - /// Gets the rest services. - /// - /// The rest services. - IEnumerable RestServices { get; } - /// /// Notifies the pending restart. /// diff --git a/MediaBrowser.Common/Kernel/TcpManager.cs b/MediaBrowser.Common/Kernel/TcpManager.cs index 1c76b42f96..2dfed501af 100644 --- a/MediaBrowser.Common/Kernel/TcpManager.cs +++ b/MediaBrowser.Common/Kernel/TcpManager.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Common.Kernel /// /// The json serializer. private readonly IJsonSerializer _jsonSerializer; - + /// /// This subscribes to HttpListener requests and finds the appropriate BaseHandler to process it /// @@ -133,7 +133,7 @@ namespace MediaBrowser.Common.Kernel _applicationHost = applicationHost; _networkManager = networkManager; - if (kernel.IsFirstRun) + if (applicationHost.IsFirstRun) { RegisterServerWithAdministratorAccess(); } @@ -215,7 +215,7 @@ namespace MediaBrowser.Common.Kernel /// The instance containing the event data. void HttpServer_WebSocketConnected(object sender, WebSocketConnectEventArgs e) { - var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, ProcessWebSocketMessageReceived, _jsonSerializer, _logger); + var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger) { OnReceive = ProcessWebSocketMessageReceived }; _webSocketConnections.Add(connection); } diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index bc3678a5e2..333c20beb7 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -95,8 +95,6 @@ - - @@ -122,6 +120,8 @@ True Resources.resx + + @@ -130,13 +130,10 @@ - - - diff --git a/MediaBrowser.Common/Net/BaseRestService.cs b/MediaBrowser.Common/Net/BaseRestService.cs deleted file mode 100644 index a7e95fca2f..0000000000 --- a/MediaBrowser.Common/Net/BaseRestService.cs +++ /dev/null @@ -1,458 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Kernel; -using MediaBrowser.Model.Logging; -using ServiceStack.Common; -using ServiceStack.Common.Web; -using ServiceStack.ServiceHost; -using ServiceStack.ServiceInterface; -using ServiceStack.WebHost.Endpoints; -using System; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net -{ - /// - /// Class BaseRestService - /// - public class BaseRestService : Service, IRestfulService - { - /// - /// Gets or sets the kernel. - /// - /// The kernel. - public IKernel Kernel { get; set; } - - /// - /// Gets or sets the logger. - /// - /// The logger. - public ILogger Logger { get; set; } - - /// - /// Gets a value indicating whether this instance is range request. - /// - /// true if this instance is range request; otherwise, false. - protected bool IsRangeRequest - { - get - { - return Request.Headers.AllKeys.Contains("Range"); - } - } - - /// - /// Adds the routes. - /// - /// The app host. - public virtual void Configure(IAppHost appHost) - { - } - - /// - /// To the optimized result. - /// - /// - /// The result. - /// System.Object. - /// result - protected object ToOptimizedResult(T result) - where T : class - { - if (result == null) - { - throw new ArgumentNullException("result"); - } - - Response.AddHeader("Vary", "Accept-Encoding"); - - return RequestContext.ToOptimizedResult(result); - } - - /// - /// To the optimized result using cache. - /// - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// The factory fn. - /// System.Object. - /// cacheKey - protected object ToOptimizedResultUsingCache(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func factoryFn) - where T : class - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, string.Empty); - - if (result != null) - { - return result; - } - - return ToOptimizedResult(factoryFn()); - } - - /// - /// To the cached result. - /// - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// The factory fn. - /// Type of the content. - /// System.Object. - /// cacheKey - protected object ToCachedResult(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func factoryFn, string contentType) - where T : class - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, contentType); - - if (result != null) - { - return result; - } - - return factoryFn(); - } - - /// - /// To the static file result. - /// - /// The path. - /// System.Object. - /// path - protected object ToStaticFileResult(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - var dateModified = File.GetLastWriteTimeUtc(path); - - var cacheKey = path + dateModified.Ticks; - - return ToStaticResult(cacheKey.GetMD5(), dateModified, null, MimeTypes.GetMimeType(path), () => Task.FromResult(GetFileStream(path))); - } - - /// - /// Gets the file stream. - /// - /// The path. - /// Stream. - private Stream GetFileStream(string path) - { - return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous); - } - - /// - /// To the static result. - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// Type of the content. - /// The factory fn. - /// System.Object. - /// cacheKey - protected object ToStaticResult(Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func> factoryFn) - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, contentType); - - if (result != null) - { - return result; - } - - var compress = ShouldCompressResponse(contentType); - - if (compress) - { - Response.AddHeader("Vary", "Accept-Encoding"); - } - - return ToStaticResult(contentType, factoryFn, compress).Result; - } - - /// - /// Shoulds the compress response. - /// - /// Type of the content. - /// true if XXXX, false otherwise - private bool ShouldCompressResponse(string contentType) - { - // It will take some work to support compression with byte range requests - if (IsRangeRequest) - { - return false; - } - - // Don't compress media - if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Don't compress images - if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - return true; - } - - /// - /// To the static result. - /// - /// Type of the content. - /// The factory fn. - /// if set to true [compress]. - /// System.Object. - private async Task ToStaticResult(string contentType, Func> factoryFn, bool compress) - { - if (!compress || string.IsNullOrEmpty(RequestContext.CompressionType)) - { - Response.ContentType = contentType; - - var stream = await factoryFn().ConfigureAwait(false); - - return new StreamWriter(stream); - } - - string content; - - using (var stream = await factoryFn().ConfigureAwait(false)) - { - using (var reader = new StreamReader(stream)) - { - content = await reader.ReadToEndAsync().ConfigureAwait(false); - } - } - - var contents = content.Compress(RequestContext.CompressionType); - - return new CompressedResult(contents, RequestContext.CompressionType, contentType); - } - - /// - /// Pres the process optimized result. - /// - /// The cache key. - /// The cache key string. - /// The last date modified. - /// Duration of the cache. - /// Type of the content. - /// System.Object. - private object PreProcessCachedResult(Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType) - { - Response.AddHeader("ETag", cacheKeyString); - - if (IsNotModified(cacheKey, lastDateModified, cacheDuration)) - { - AddAgeHeader(lastDateModified); - AddExpiresHeader(cacheKeyString, cacheDuration); - //ctx.Response.SendChunked = false; - - if (!string.IsNullOrEmpty(contentType)) - { - Response.ContentType = contentType; - } - - return new HttpResult(new byte[] { }, HttpStatusCode.NotModified); - } - - SetCachingHeaders(cacheKeyString, lastDateModified, cacheDuration); - - return null; - } - - /// - /// Determines whether [is not modified] [the specified cache key]. - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// true if [is not modified] [the specified cache key]; otherwise, false. - private bool IsNotModified(Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) - { - var isNotModified = true; - - if (Request.Headers.AllKeys.Contains("If-Modified-Since")) - { - DateTime ifModifiedSince; - - if (DateTime.TryParse(Request.Headers["If-Modified-Since"], out ifModifiedSince)) - { - isNotModified = IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified); - } - } - - // Validate If-None-Match - if (isNotModified && (cacheKey.HasValue || !string.IsNullOrEmpty(Request.Headers["If-None-Match"]))) - { - Guid ifNoneMatch; - - if (Guid.TryParse(Request.Headers["If-None-Match"] ?? string.Empty, out ifNoneMatch)) - { - if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch) - { - return true; - } - } - } - - return false; - } - - /// - /// Determines whether [is not modified] [the specified if modified since]. - /// - /// If modified since. - /// Duration of the cache. - /// The date modified. - /// true if [is not modified] [the specified if modified since]; otherwise, false. - private bool IsNotModified(DateTime ifModifiedSince, TimeSpan? cacheDuration, DateTime? dateModified) - { - if (dateModified.HasValue) - { - var lastModified = NormalizeDateForComparison(dateModified.Value); - ifModifiedSince = NormalizeDateForComparison(ifModifiedSince); - - return lastModified <= ifModifiedSince; - } - - if (cacheDuration.HasValue) - { - var cacheExpirationDate = ifModifiedSince.Add(cacheDuration.Value); - - if (DateTime.UtcNow < cacheExpirationDate) - { - return true; - } - } - - return false; - } - - - /// - /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that - /// - /// The date. - /// DateTime. - private DateTime NormalizeDateForComparison(DateTime date) - { - return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind); - } - - /// - /// Sets the caching headers. - /// - /// The cache key. - /// The last date modified. - /// Duration of the cache. - private void SetCachingHeaders(string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) - { - // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant - // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching - if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue)) - { - AddAgeHeader(lastDateModified); - Response.AddHeader("LastModified", lastDateModified.Value.ToString("r")); - } - - if (cacheDuration.HasValue) - { - Response.AddHeader("Cache-Control", "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds)); - } - else if (!string.IsNullOrEmpty(cacheKey)) - { - Response.AddHeader("Cache-Control", "public"); - } - else - { - Response.AddHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - Response.AddHeader("pragma", "no-cache, no-store, must-revalidate"); - } - - AddExpiresHeader(cacheKey, cacheDuration); - } - - /// - /// Adds the expires header. - /// - /// The cache key. - /// Duration of the cache. - private void AddExpiresHeader(string cacheKey, TimeSpan? cacheDuration) - { - if (cacheDuration.HasValue) - { - Response.AddHeader("Expires", DateTime.UtcNow.Add(cacheDuration.Value).ToString("r")); - } - else if (string.IsNullOrEmpty(cacheKey)) - { - Response.AddHeader("Expires", "-1"); - } - } - - /// - /// Adds the age header. - /// - /// The last date modified. - private void AddAgeHeader(DateTime? lastDateModified) - { - if (lastDateModified.HasValue) - { - Response.AddHeader("Age", Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture)); - } - } - } -} diff --git a/MediaBrowser.Common/Net/Handlers/BaseActionHandler.cs b/MediaBrowser.Common/Net/Handlers/BaseActionHandler.cs deleted file mode 100644 index 72df88519b..0000000000 --- a/MediaBrowser.Common/Net/Handlers/BaseActionHandler.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Common.Kernel; -using MediaBrowser.Model.Entities; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net.Handlers -{ - /// - /// Class BaseActionHandler - /// - /// The type of the T kernel type. - public abstract class BaseActionHandler : BaseSerializationHandler - where TKernelType : IKernel - { - /// - /// Gets the object to serialize. - /// - /// Task{EmptyRequestResult}. - protected override async Task GetObjectToSerialize() - { - await ExecuteAction(); - - return new EmptyRequestResult(); - } - - /// - /// Performs the action. - /// - /// Task. - protected abstract Task ExecuteAction(); - } -} diff --git a/MediaBrowser.Common/Net/Handlers/BaseSerializationHandler.cs b/MediaBrowser.Common/Net/Handlers/BaseSerializationHandler.cs deleted file mode 100644 index a00152d784..0000000000 --- a/MediaBrowser.Common/Net/Handlers/BaseSerializationHandler.cs +++ /dev/null @@ -1,133 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Kernel; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net.Handlers -{ - /// - /// Class BaseSerializationHandler - /// - /// The type of the T kernel type. - /// - public abstract class BaseSerializationHandler : BaseHandler - where TKernelType : IKernel - where T : class - { - /// - /// Gets the serialization format. - /// - /// The serialization format. - public SerializationFormat SerializationFormat - { - get - { - var format = QueryString["dataformat"]; - - if (string.IsNullOrEmpty(format)) - { - return SerializationFormat.Json; - } - - return (SerializationFormat)Enum.Parse(typeof(SerializationFormat), format, true); - } - } - - /// - /// Gets the type of the content. - /// - /// The type of the content. - protected string ContentType - { - get - { - switch (SerializationFormat) - { - case SerializationFormat.Protobuf: - return "application/x-protobuf"; - default: - return MimeTypes.JsonMimeType; - } - } - } - - /// - /// Gets the response info. - /// - /// Task{ResponseInfo}. - protected override Task GetResponseInfo() - { - return Task.FromResult(new ResponseInfo - { - ContentType = ContentType - }); - } - - /// - /// Called when [processing request]. - /// - /// The response info. - /// Task. - protected override async Task OnProcessingRequest(ResponseInfo responseInfo) - { - _objectToSerialize = await GetObjectToSerialize().ConfigureAwait(false); - - if (_objectToSerialize == null) - { - throw new ResourceNotFoundException(); - } - - await base.OnProcessingRequest(responseInfo).ConfigureAwait(false); - } - - /// - /// The _object to serialize - /// - private T _objectToSerialize; - - /// - /// Gets the object to serialize. - /// - /// Task{`0}. - protected abstract Task GetObjectToSerialize(); - - /// - /// Writes the response to output stream. - /// - /// The stream. - /// The response info. - /// Length of the content. - /// Task. - protected override Task WriteResponseToOutputStream(Stream stream, ResponseInfo responseInfo, long? contentLength) - { - return Task.Run(() => - { - //switch (SerializationFormat) - //{ - // case SerializationFormat.Protobuf: - // Kernel.ProtobufSerializer.SerializeToStream(_objectToSerialize, stream); - // break; - // default: - // JsonSerializer.SerializeToStream(_objectToSerialize, stream); - // break; - //} - }); - } - } - - /// - /// Enum SerializationFormat - /// - public enum SerializationFormat - { - /// - /// The json - /// - Json, - /// - /// The protobuf - /// - Protobuf - } -} diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index ef0dd69b70..d02f08572d 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -6,6 +6,9 @@ using System.Threading.Tasks; namespace MediaBrowser.Common.Net { + /// + /// Interface IHttpClient + /// public interface IHttpClient : IDisposable { /// @@ -52,10 +55,5 @@ namespace MediaBrowser.Common.Net /// Task{MemoryStream}. /// Task GetMemoryStream(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken); - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - void Dispose(); } } \ No newline at end of file diff --git a/MediaBrowser.Common/Net/IHttpServer.cs b/MediaBrowser.Common/Net/IHttpServer.cs index a640fb2626..45da18e1b8 100644 --- a/MediaBrowser.Common/Net/IHttpServer.cs +++ b/MediaBrowser.Common/Net/IHttpServer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace MediaBrowser.Common.Net { @@ -40,5 +41,10 @@ namespace MediaBrowser.Common.Net /// Occurs when [web socket connected]. /// event EventHandler WebSocketConnected; + + /// + /// Inits this instance. + /// + void Init(IEnumerable services); } } \ No newline at end of file diff --git a/MediaBrowser.Common/Net/IRestfulService.cs b/MediaBrowser.Common/Net/IRestfulService.cs index 7fc6bb61ec..36055cd8a3 100644 --- a/MediaBrowser.Common/Net/IRestfulService.cs +++ b/MediaBrowser.Common/Net/IRestfulService.cs @@ -1,5 +1,4 @@ using ServiceStack.ServiceHost; -using ServiceStack.WebHost.Endpoints; using System; namespace MediaBrowser.Common.Net @@ -9,6 +8,5 @@ namespace MediaBrowser.Common.Net /// public interface IRestfulService : IService, IRequiresRequestContext, IDisposable { - void Configure(IAppHost appHost); } } diff --git a/MediaBrowser.Common/Net/WebSocketConnection.cs b/MediaBrowser.Common/Net/WebSocketConnection.cs index 36d649e3b1..1ad0e8f06f 100644 --- a/MediaBrowser.Common/Net/WebSocketConnection.cs +++ b/MediaBrowser.Common/Net/WebSocketConnection.cs @@ -41,17 +41,22 @@ namespace MediaBrowser.Common.Net /// The _json serializer /// private readonly IJsonSerializer _jsonSerializer; - + + /// + /// Gets or sets the receive action. + /// + /// The receive action. + public Action OnReceive { get; set; } + /// /// Initializes a new instance of the class. /// /// The socket. /// The remote end point. - /// The receive action. /// The json serializer. /// The logger. /// socket - public WebSocketConnection(IWebSocket socket, string remoteEndPoint, Action receiveAction, IJsonSerializer jsonSerializer, ILogger logger) + public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger) { if (socket == null) { @@ -61,10 +66,6 @@ namespace MediaBrowser.Common.Net { throw new ArgumentNullException("remoteEndPoint"); } - if (receiveAction == null) - { - throw new ArgumentNullException("receiveAction"); - } if (jsonSerializer == null) { throw new ArgumentNullException("jsonSerializer"); @@ -76,7 +77,7 @@ namespace MediaBrowser.Common.Net _jsonSerializer = jsonSerializer; _socket = socket; - _socket.OnReceiveDelegate = info => OnReceive(info, receiveAction); + _socket.OnReceiveDelegate = OnReceiveInternal; RemoteEndPoint = remoteEndPoint; _logger = logger; } @@ -85,21 +86,24 @@ namespace MediaBrowser.Common.Net /// Called when [receive]. /// /// The bytes. - /// The callback. - private void OnReceive(byte[] bytes, Action callback) + private void OnReceiveInternal(byte[] bytes) { + if (OnReceive == null) + { + return; + } try { WebSocketMessageInfo info; using (var memoryStream = new MemoryStream(bytes)) { - info = _jsonSerializer.DeserializeFromStream(memoryStream); + info = (WebSocketMessageInfo)_jsonSerializer.DeserializeFromStream(memoryStream, typeof(WebSocketMessageInfo)); } info.Connection = this; - callback(info); + OnReceive(info); } catch (Exception ex) { diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index dad3867fcb..72c83a5cee 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -1,12 +1,12 @@ using MediaBrowser.Common.Kernel; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; using System; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Common.Plugins { @@ -393,9 +393,6 @@ namespace MediaBrowser.Common.Plugins { XmlSerializer.SerializeToFile(Configuration, ConfigurationFilePath); } - - // Notify connected UI's - Kernel.TcpManager.SendWebSocketMessage("PluginConfigurationUpdated-" + Name, Configuration); } /// diff --git a/MediaBrowser.Common/ScheduledTasks/BaseScheduledTask.cs b/MediaBrowser.Common/ScheduledTasks/BaseScheduledTask.cs deleted file mode 100644 index 09ceaa9aee..0000000000 --- a/MediaBrowser.Common/ScheduledTasks/BaseScheduledTask.cs +++ /dev/null @@ -1,424 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Kernel; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Tasks; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.ScheduledTasks -{ - /// - /// Represents a task that can be executed at a scheduled time - /// - /// The type of the T kernel type. - public abstract class BaseScheduledTask : IScheduledTask - where TKernelType : class, IKernel - { - /// - /// Gets the kernel. - /// - /// The kernel. - protected TKernelType Kernel { get; private set; } - - /// - /// Gets the logger. - /// - /// The logger. - protected ILogger Logger { get; private set; } - - /// - /// Gets the task manager. - /// - /// The task manager. - protected ITaskManager TaskManager { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The kernel. - /// The task manager. - /// The logger. - /// kernel - protected BaseScheduledTask(TKernelType kernel, ITaskManager taskManager, ILogger logger) - { - if (kernel == null) - { - throw new ArgumentNullException("kernel"); - } - if (taskManager == null) - { - throw new ArgumentNullException("taskManager"); - } - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - - Kernel = kernel; - TaskManager = taskManager; - Logger = logger; - - ReloadTriggerEvents(true); - } - - /// - /// The _last execution result - /// - private TaskResult _lastExecutionResult; - /// - /// The _last execution resultinitialized - /// - private bool _lastExecutionResultinitialized; - /// - /// The _last execution result sync lock - /// - private object _lastExecutionResultSyncLock = new object(); - /// - /// Gets the last execution result. - /// - /// The last execution result. - public TaskResult LastExecutionResult - { - get - { - LazyInitializer.EnsureInitialized(ref _lastExecutionResult, ref _lastExecutionResultinitialized, ref _lastExecutionResultSyncLock, () => - { - try - { - return TaskManager.GetLastExecutionResult(this); - } - catch (IOException) - { - // File doesn't exist. No biggie - return null; - } - }); - - return _lastExecutionResult; - } - private set - { - _lastExecutionResult = value; - - _lastExecutionResultinitialized = value != null; - } - } - - /// - /// Gets the current cancellation token - /// - /// The current cancellation token source. - private CancellationTokenSource CurrentCancellationTokenSource { get; set; } - - /// - /// Gets or sets the current execution start time. - /// - /// The current execution start time. - private DateTime CurrentExecutionStartTime { get; set; } - - /// - /// Gets the state. - /// - /// The state. - public TaskState State - { - get - { - if (CurrentCancellationTokenSource != null) - { - return CurrentCancellationTokenSource.IsCancellationRequested - ? TaskState.Cancelling - : TaskState.Running; - } - - return TaskState.Idle; - } - } - - /// - /// Gets the current progress. - /// - /// The current progress. - public double? CurrentProgress { get; private set; } - - /// - /// The _triggers - /// - private IEnumerable _triggers; - /// - /// The _triggers initialized - /// - private bool _triggersInitialized; - /// - /// The _triggers sync lock - /// - private object _triggersSyncLock = new object(); - /// - /// Gets the triggers that define when the task will run - /// - /// The triggers. - /// value - public IEnumerable Triggers - { - get - { - LazyInitializer.EnsureInitialized(ref _triggers, ref _triggersInitialized, ref _triggersSyncLock, () => TaskManager.LoadTriggers(this)); - - return _triggers; - } - set - { - if (value == null) - { - throw new ArgumentNullException("value"); - } - - // Cleanup current triggers - if (_triggers != null) - { - DisposeTriggers(); - } - - _triggers = value.ToList(); - - _triggersInitialized = true; - - ReloadTriggerEvents(false); - - TaskManager.SaveTriggers(this, _triggers); - } - } - - /// - /// Creates the triggers that define when the task will run - /// - /// IEnumerable{BaseTaskTrigger}. - public abstract IEnumerable GetDefaultTriggers(); - - /// - /// Returns the task to be executed - /// - /// The cancellation token. - /// The progress. - /// Task. - protected abstract Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress); - - /// - /// Gets the name of the task - /// - /// The name. - public abstract string Name { get; } - - /// - /// Gets the description. - /// - /// The description. - public abstract string Description { get; } - - /// - /// Gets the category. - /// - /// The category. - public virtual string Category - { - get { return "Application"; } - } - - /// - /// The _id - /// - private Guid? _id; - - /// - /// Gets the unique id. - /// - /// The unique id. - public Guid Id - { - get - { - if (!_id.HasValue) - { - _id = GetType().FullName.GetMD5(); - } - - return _id.Value; - } - } - - /// - /// Reloads the trigger events. - /// - /// if set to true [is application startup]. - private void ReloadTriggerEvents(bool isApplicationStartup) - { - foreach (var trigger in Triggers) - { - trigger.Stop(); - - trigger.Triggered -= trigger_Triggered; - trigger.Triggered += trigger_Triggered; - trigger.Start(isApplicationStartup); - } - } - - /// - /// Handles the Triggered event of the trigger control. - /// - /// The source of the event. - /// The instance containing the event data. - async void trigger_Triggered(object sender, EventArgs e) - { - var trigger = (ITaskTrigger)sender; - - Logger.Info("{0} fired for task: {1}", trigger.GetType().Name, Name); - - trigger.Stop(); - - TaskManager.QueueScheduledTask(this); - - await Task.Delay(1000).ConfigureAwait(false); - - trigger.Start(false); - } - - /// - /// Executes the task - /// - /// Task. - /// Cannot execute a Task that is already running - public async Task Execute() - { - // Cancel the current execution, if any - if (CurrentCancellationTokenSource != null) - { - throw new InvalidOperationException("Cannot execute a Task that is already running"); - } - - CurrentCancellationTokenSource = new CancellationTokenSource(); - - Logger.Info("Executing {0}", Name); - - var progress = new Progress(); - - progress.ProgressChanged += progress_ProgressChanged; - - TaskCompletionStatus status; - CurrentExecutionStartTime = DateTime.UtcNow; - - Kernel.TcpManager.SendWebSocketMessage("ScheduledTaskBeginExecute", Name); - - try - { - await Task.Run(async () => await ExecuteInternal(CurrentCancellationTokenSource.Token, progress).ConfigureAwait(false)).ConfigureAwait(false); - - status = TaskCompletionStatus.Completed; - } - catch (OperationCanceledException) - { - status = TaskCompletionStatus.Cancelled; - } - catch (Exception ex) - { - Logger.ErrorException("Error", ex); - - status = TaskCompletionStatus.Failed; - } - - var startTime = CurrentExecutionStartTime; - var endTime = DateTime.UtcNow; - - Kernel.TcpManager.SendWebSocketMessage("ScheduledTaskEndExecute", LastExecutionResult); - - progress.ProgressChanged -= progress_ProgressChanged; - CurrentCancellationTokenSource.Dispose(); - CurrentCancellationTokenSource = null; - CurrentProgress = null; - - TaskManager.OnTaskCompleted(this, startTime, endTime, status); - } - - /// - /// Progress_s the progress changed. - /// - /// The sender. - /// The e. - void progress_ProgressChanged(object sender, double e) - { - CurrentProgress = e; - } - - /// - /// Stops the task if it is currently executing - /// - /// Cannot cancel a Task unless it is in the Running state. - public void Cancel() - { - if (State != TaskState.Running) - { - throw new InvalidOperationException("Cannot cancel a Task unless it is in the Running state."); - } - - CancelIfRunning(); - } - - /// - /// Cancels if running. - /// - public void CancelIfRunning() - { - if (State == TaskState.Running) - { - Logger.Info("Attempting to cancel Scheduled Task {0}", Name); - CurrentCancellationTokenSource.Cancel(); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - DisposeTriggers(); - - if (State == TaskState.Running) - { - TaskManager.OnTaskCompleted(this, CurrentExecutionStartTime, DateTime.UtcNow, TaskCompletionStatus.Aborted); - } - - if (CurrentCancellationTokenSource != null) - { - CurrentCancellationTokenSource.Dispose(); - } - } - } - - /// - /// Disposes each trigger - /// - private void DisposeTriggers() - { - foreach (var trigger in Triggers) - { - trigger.Triggered -= trigger_Triggered; - trigger.Stop(); - } - } - } -} diff --git a/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs b/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs index 6f3a3857fe..351e96c7d5 100644 --- a/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs @@ -1,39 +1,15 @@ -using MediaBrowser.Model.Tasks; -using System; +using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Common.ScheduledTasks { /// - /// Interface IScheduledTask + /// Interface IScheduledTaskWorker /// - public interface IScheduledTask : IDisposable + public interface IScheduledTask { - /// - /// Gets the triggers. - /// - /// The triggers. - IEnumerable Triggers { get; set; } - - /// - /// Gets the last execution result. - /// - /// The last execution result. - TaskResult LastExecutionResult { get; } - - /// - /// Gets the state. - /// - /// The state. - TaskState State { get; } - - /// - /// Gets the current progress. - /// - /// The current progress. - double? CurrentProgress { get; } - /// /// Gets the name of the task /// @@ -52,29 +28,13 @@ namespace MediaBrowser.Common.ScheduledTasks /// The category. string Category { get; } - /// - /// Gets the unique id. - /// - /// The unique id. - Guid Id { get; } - /// /// Executes the task /// + /// The cancellation token. + /// The progress. /// Task. - /// Cannot execute a Task that is already running - Task Execute(); - - /// - /// Stops the task if it is currently executing - /// - /// Cannot cancel a Task unless it is in the Running state. - void Cancel(); - - /// - /// Cancels if running. - /// - void CancelIfRunning(); + Task Execute(CancellationToken cancellationToken, IProgress progress); /// /// Gets the default triggers. @@ -82,4 +42,4 @@ namespace MediaBrowser.Common.ScheduledTasks /// IEnumerable{BaseTaskTrigger}. IEnumerable GetDefaultTriggers(); } -} \ No newline at end of file +} diff --git a/MediaBrowser.Common/ScheduledTasks/IScheduledTaskWorker.cs b/MediaBrowser.Common/ScheduledTasks/IScheduledTaskWorker.cs new file mode 100644 index 0000000000..31cb4bcb89 --- /dev/null +++ b/MediaBrowser.Common/ScheduledTasks/IScheduledTaskWorker.cs @@ -0,0 +1,86 @@ +using MediaBrowser.Model.Tasks; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Common.ScheduledTasks +{ + /// + /// Interface IScheduledTaskWorker + /// + public interface IScheduledTaskWorker : IDisposable + { + /// + /// Gets or sets the scheduled task. + /// + /// The scheduled task. + IScheduledTask ScheduledTask { get; } + + /// + /// Gets the last execution result. + /// + /// The last execution result. + TaskResult LastExecutionResult { get; } + + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + + /// + /// Gets the description. + /// + /// The description. + string Description { get; } + + /// + /// Gets the category. + /// + /// The category. + string Category { get; } + + /// + /// Gets the state. + /// + /// The state. + TaskState State { get; } + + /// + /// Gets the current progress. + /// + /// The current progress. + double? CurrentProgress { get; } + + /// + /// Gets the triggers that define when the task will run + /// + /// The triggers. + /// value + IEnumerable Triggers { get; set; } + + /// + /// Gets the unique id. + /// + /// The unique id. + Guid Id { get; } + + /// + /// Executes the task + /// + /// Task. + /// Cannot execute a Task that is already running + Task Execute(); + + /// + /// Stops the task if it is currently executing + /// + /// Cannot cancel a Task unless it is in the Running state. + void Cancel(); + + /// + /// Cancels if running. + /// + void CancelIfRunning(); + } +} \ No newline at end of file diff --git a/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs b/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs index 42d7020e60..d06f1f1946 100644 --- a/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs +++ b/MediaBrowser.Common/ScheduledTasks/ITaskManager.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Model.Tasks; -using System; +using System; using System.Collections.Generic; namespace MediaBrowser.Common.ScheduledTasks @@ -10,7 +9,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// Gets the list of Scheduled Tasks /// /// The scheduled tasks. - IScheduledTask[] ScheduledTasks { get; } + IScheduledTaskWorker[] ScheduledTasks { get; } /// /// Cancels if running and queue. @@ -37,35 +36,5 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// The tasks. void AddTasks(IEnumerable tasks); - - /// - /// Called when [task completed]. - /// - /// The task. - /// The start time. - /// The end time. - /// The status. - void OnTaskCompleted(IScheduledTask task, DateTime startTime, DateTime endTime, TaskCompletionStatus status); - - /// - /// Gets the last execution result. - /// - /// The task. - /// TaskResult. - TaskResult GetLastExecutionResult(IScheduledTask task); - - /// - /// Loads the triggers. - /// - /// The task. - /// IEnumerable{BaseTaskTrigger}. - IEnumerable LoadTriggers(IScheduledTask task); - - /// - /// Saves the triggers. - /// - /// The task. - /// The triggers. - void SaveTriggers(IScheduledTask task, IEnumerable triggers); } } \ No newline at end of file diff --git a/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs b/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs index 2c3d21a4b0..ebfc94591e 100644 --- a/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs +++ b/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// The task. /// TaskInfo. - public static TaskInfo GetTaskInfo(IScheduledTask task) + public static TaskInfo GetTaskInfo(IScheduledTaskWorker task) { return new TaskInfo { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 1e901055ef..ff2bcb213f 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -132,7 +132,6 @@ - @@ -179,10 +178,7 @@ - - - diff --git a/MediaBrowser.Controller/Persistence/TypeMapper.cs b/MediaBrowser.Controller/Persistence/TypeMapper.cs deleted file mode 100644 index 2b9ec9e5ec..0000000000 --- a/MediaBrowser.Controller/Persistence/TypeMapper.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; - -namespace MediaBrowser.Controller.Persistence -{ - /// - /// Class TypeMapper - /// - public class TypeMapper - { - /// - /// This holds all the types in the running assemblies so that we can de-serialize properly when we don't have strong types - /// - private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary(); - - /// - /// Gets the type. - /// - /// Name of the type. - /// Type. - /// - public Type GetType(string typeName) - { - if (string.IsNullOrEmpty(typeName)) - { - throw new ArgumentNullException(); - } - - return _typeMap.GetOrAdd(typeName, LookupType); - } - - /// - /// Lookups the type. - /// - /// Name of the type. - /// Type. - private Type LookupType(string typeName) - { - return AppDomain - .CurrentDomain - .GetAssemblies() - .Select(a => a.GetType(typeName, false)) - .FirstOrDefault(t => t != null); - } - } -} diff --git a/MediaBrowser.Controller/Plugins/PluginSecurityManager.cs b/MediaBrowser.Controller/Plugins/PluginSecurityManager.cs index a08eb457d4..db3f20ee3f 100644 --- a/MediaBrowser.Controller/Plugins/PluginSecurityManager.cs +++ b/MediaBrowser.Controller/Plugins/PluginSecurityManager.cs @@ -1,4 +1,5 @@ -using Mediabrowser.Model.Entities; +using MediaBrowser.Model.Serialization; +using Mediabrowser.Model.Entities; using Mediabrowser.PluginSecurity; using MediaBrowser.Common.Kernel; using MediaBrowser.Common.Net; @@ -39,10 +40,8 @@ namespace MediaBrowser.Controller.Plugins } } - /// - /// The _network manager - /// - private INetworkManager _networkManager; + private IHttpClient _httpClient; + private IJsonSerializer _jsonSerializer; /// /// The _kernel @@ -53,21 +52,22 @@ namespace MediaBrowser.Controller.Plugins /// Initializes a new instance of the class. /// /// The kernel. - /// The network manager. - public PluginSecurityManager(IKernel kernel, INetworkManager networkManager) + public PluginSecurityManager(IKernel kernel, IHttpClient httpClient, IJsonSerializer jsonSerializer, IApplicationPaths appPaths) { if (kernel == null) { throw new ArgumentNullException("kernel"); } - if (networkManager == null) + if (httpClient == null) { - throw new ArgumentNullException("networkManager"); + throw new ArgumentNullException("httpClient"); } _kernel = kernel; - _networkManager = networkManager; + _httpClient = httpClient; + _jsonSerializer = jsonSerializer; + MBRegistration.Init(appPaths); } /// @@ -78,7 +78,7 @@ namespace MediaBrowser.Controller.Plugins /// Task{MBRegistrationRecord}. public async Task GetRegistrationStatus(string feature, string mb2Equivalent = null) { - return await MBRegistration.GetRegistrationStatus(feature, mb2Equivalent).ConfigureAwait(false); + return await MBRegistration.GetRegistrationStatus(_httpClient, _jsonSerializer, feature, mb2Equivalent).ConfigureAwait(false); } /// diff --git a/MediaBrowser.Controller/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Controller/ScheduledTasks/ChapterImagesTask.cs deleted file mode 100644 index 5360337199..0000000000 --- a/MediaBrowser.Controller/ScheduledTasks/ChapterImagesTask.cs +++ /dev/null @@ -1,111 +0,0 @@ -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.ScheduledTasks -{ - /// - /// Class ChapterImagesTask - /// - class ChapterImagesTask : BaseScheduledTask - { - /// - /// Initializes a new instance of the class. - /// - /// The kernel. - /// - public ChapterImagesTask(Kernel kernel, ITaskManager taskManager, ILogger logger) - : base(kernel, taskManager, logger) - { - } - - /// - /// Creates the triggers that define when the task will run - /// - /// IEnumerable{BaseTaskTrigger}. - public override IEnumerable GetDefaultTriggers() - { - return new ITaskTrigger[] - { - new DailyTrigger { TimeOfDay = TimeSpan.FromHours(4) } - }; - } - - /// - /// Returns the task to be executed - /// - /// The cancellation token. - /// The progress. - /// Task. - protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) - { - var videos = Kernel.RootFolder.RecursiveChildren.OfType /// The description. - public override string Description + public string Description { get { return "Updates metadata for actors, artists and directors in your media library."; } } @@ -69,7 +69,7 @@ namespace MediaBrowser.Controller.ScheduledTasks /// Gets the category. /// /// The category. - public override string Category + public string Category { get { diff --git a/MediaBrowser.Controller/ScheduledTasks/PluginUpdateTask.cs b/MediaBrowser.Controller/ScheduledTasks/PluginUpdateTask.cs deleted file mode 100644 index 7a1007f1bb..0000000000 --- a/MediaBrowser.Controller/ScheduledTasks/PluginUpdateTask.cs +++ /dev/null @@ -1,121 +0,0 @@ -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.ScheduledTasks -{ - /// - /// Plugin Update Task - /// - public class PluginUpdateTask : BaseScheduledTask - { - /// - /// Initializes a new instance of the class. - /// - /// The kernel. - /// - public PluginUpdateTask(Kernel kernel, ITaskManager taskManager, ILogger logger) - : base(kernel, taskManager, logger) - { - } - - /// - /// Creates the triggers that define when the task will run - /// - /// IEnumerable{BaseTaskTrigger}. - public override IEnumerable GetDefaultTriggers() - { - return new ITaskTrigger[] { - - // 1:30am - new DailyTrigger { TimeOfDay = TimeSpan.FromHours(1.5) }, - - new IntervalTrigger { Interval = TimeSpan.FromHours(2)} - }; - } - - /// - /// Update installed plugins - /// - /// The cancellation token. - /// The progress. - /// Task. - protected override async Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) - { - progress.Report(0); - - var packagesToInstall = (await Kernel.InstallationManager.GetAvailablePluginUpdates(true, cancellationToken).ConfigureAwait(false)).ToList(); - - progress.Report(10); - - var numComplete = 0; - - // Create tasks for each one - var tasks = packagesToInstall.Select(i => Task.Run(async () => - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - await Kernel.InstallationManager.InstallPackage(i, new Progress { }, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // InstallPackage has it's own inner cancellation token, so only throw this if it's ours - if (cancellationToken.IsCancellationRequested) - { - throw; - } - } - catch (HttpException ex) - { - Logger.ErrorException("Error downloading {0}", ex, i.name); - } - catch (IOException ex) - { - Logger.ErrorException("Error updating {0}", ex, i.name); - } - - // Update progress - lock (progress) - { - numComplete++; - double percent = numComplete; - percent /= packagesToInstall.Count; - - progress.Report((90 * percent) + 10); - } - })); - - cancellationToken.ThrowIfCancellationRequested(); - - await Task.WhenAll(tasks).ConfigureAwait(false); - - progress.Report(100); - } - - /// - /// Gets the name of the task - /// - /// The name. - public override string Name - { - get { return "Check for plugin updates"; } - } - - /// - /// Gets the description. - /// - /// The description. - public override string Description - { - get { return "Downloads and installs updates for plugins that are configured to update automatically."; } - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/ScheduledTasks/RefreshMediaLibraryTask.cs b/MediaBrowser.Controller/ScheduledTasks/RefreshMediaLibraryTask.cs index 104b432f4c..c5e36afb8f 100644 --- a/MediaBrowser.Controller/ScheduledTasks/RefreshMediaLibraryTask.cs +++ b/MediaBrowser.Controller/ScheduledTasks/RefreshMediaLibraryTask.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Model.Logging; using MediaBrowser.Model.Tasks; using System; using System.Collections.Generic; @@ -11,23 +10,27 @@ namespace MediaBrowser.Controller.ScheduledTasks /// /// Class RefreshMediaLibraryTask /// - public class RefreshMediaLibraryTask : BaseScheduledTask + public class RefreshMediaLibraryTask : IScheduledTask { + /// + /// The _kernel + /// + private readonly Kernel _kernel; + /// /// Initializes a new instance of the class. /// /// The kernel. - /// - public RefreshMediaLibraryTask(Kernel kernel, ITaskManager taskManager, ILogger logger) - : base(kernel, taskManager, logger) + public RefreshMediaLibraryTask(Kernel kernel) { + _kernel = kernel; } /// /// Gets the default triggers. /// /// IEnumerable{BaseTaskTrigger}. - public override IEnumerable GetDefaultTriggers() + public IEnumerable GetDefaultTriggers() { return new ITaskTrigger[] { @@ -45,20 +48,20 @@ namespace MediaBrowser.Controller.ScheduledTasks /// The cancellation token. /// The progress. /// Task. - protected override Task ExecuteInternal(CancellationToken cancellationToken, IProgress progress) + public Task Execute(CancellationToken cancellationToken, IProgress progress) { cancellationToken.ThrowIfCancellationRequested(); progress.Report(0); - return Kernel.LibraryManager.ValidateMediaLibrary(progress, cancellationToken); + return _kernel.LibraryManager.ValidateMediaLibrary(progress, cancellationToken); } /// /// Gets the name. /// /// The name. - public override string Name + public string Name { get { return "Scan media library"; } } @@ -67,7 +70,7 @@ namespace MediaBrowser.Controller.ScheduledTasks /// Gets the description. /// /// The description. - public override string Description + public string Description { get { return "Scans your media library and refreshes metatata based on configuration."; } } @@ -76,7 +79,7 @@ namespace MediaBrowser.Controller.ScheduledTasks /// Gets the category. /// /// The category. - public override string Category + public string Category { get { diff --git a/MediaBrowser.Controller/Updates/InstallationManager.cs b/MediaBrowser.Controller/Updates/InstallationManager.cs index 15d626b2fc..8751bd4274 100644 --- a/MediaBrowser.Controller/Updates/InstallationManager.cs +++ b/MediaBrowser.Controller/Updates/InstallationManager.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Events; +using MediaBrowser.Common.Kernel; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Progress; @@ -119,6 +120,12 @@ namespace MediaBrowser.Controller.Updates /// The HTTP client. protected IHttpClient HttpClient { get; private set; } + /// + /// Gets the application host. + /// + /// The application host. + protected IApplicationHost ApplicationHost { get; private set; } + /// /// Initializes a new instance of the class. /// @@ -128,8 +135,9 @@ namespace MediaBrowser.Controller.Updates /// The network manager. /// The json serializer. /// The logger. + /// The app host. /// zipClient - public InstallationManager(Kernel kernel, IHttpClient httpClient, IZipClient zipClient, INetworkManager networkManager, IJsonSerializer jsonSerializer, ILogger logger) + public InstallationManager(Kernel kernel, IHttpClient httpClient, IZipClient zipClient, INetworkManager networkManager, IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost) : base(kernel) { if (zipClient == null) @@ -155,6 +163,7 @@ namespace MediaBrowser.Controller.Updates JsonSerializer = jsonSerializer; HttpClient = httpClient; + ApplicationHost = appHost; _networkManager = networkManager; _logger = logger; ZipClient = zipClient; @@ -276,7 +285,7 @@ namespace MediaBrowser.Controller.Updates return package.versions .OrderByDescending(v => v.version) - .FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, Kernel.ApplicationVersion)); + .FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, ApplicationHost.ApplicationVersion)); } /// diff --git a/MediaBrowser.Model/Serialization/IJsonSerializer.cs b/MediaBrowser.Model/Serialization/IJsonSerializer.cs index 056389d2e5..77a14c1e5d 100644 --- a/MediaBrowser.Model/Serialization/IJsonSerializer.cs +++ b/MediaBrowser.Model/Serialization/IJsonSerializer.cs @@ -8,22 +8,18 @@ namespace MediaBrowser.Model.Serialization /// /// Serializes to stream. /// - /// /// The obj. /// The stream. /// obj - void SerializeToStream(T obj, Stream stream) - where T : class; + void SerializeToStream(object obj, Stream stream); /// /// Serializes to file. /// - /// /// The obj. /// The file. /// obj - void SerializeToFile(T obj, string file) - where T : class; + void SerializeToFile(object obj, string file); /// /// Deserializes from file. @@ -83,21 +79,17 @@ namespace MediaBrowser.Model.Serialization /// /// Serializes to string. /// - /// /// The obj. /// System.String. /// obj - string SerializeToString(T obj) - where T : class; + string SerializeToString(object obj); /// /// Serializes to bytes. /// - /// /// The obj. /// System.Byte[][]. /// obj - byte[] SerializeToBytes(T obj) - where T : class; + byte[] SerializeToBytes(object obj); } } \ No newline at end of file diff --git a/MediaBrowser.Networking/HttpServer/BaseRestService.cs b/MediaBrowser.Networking/HttpServer/BaseRestService.cs new file mode 100644 index 0000000000..d499f07814 --- /dev/null +++ b/MediaBrowser.Networking/HttpServer/BaseRestService.cs @@ -0,0 +1,453 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Kernel; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; +using ServiceStack.Common; +using ServiceStack.Common.Web; +using ServiceStack.ServiceHost; +using ServiceStack.ServiceInterface; +using ServiceStack.WebHost.Endpoints; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using MimeTypes = MediaBrowser.Common.Net.MimeTypes; +using StreamWriter = MediaBrowser.Common.Net.StreamWriter; + +namespace MediaBrowser.Networking.HttpServer +{ + /// + /// Class BaseRestService + /// + public class BaseRestService : Service, IRestfulService + { + /// + /// Gets or sets the kernel. + /// + /// The kernel. + public IKernel Kernel { get; set; } + + /// + /// Gets or sets the logger. + /// + /// The logger. + public ILogger Logger { get; set; } + + /// + /// Gets a value indicating whether this instance is range request. + /// + /// true if this instance is range request; otherwise, false. + protected bool IsRangeRequest + { + get + { + return Request.Headers.AllKeys.Contains("Range"); + } + } + + /// + /// To the optimized result. + /// + /// + /// The result. + /// System.Object. + /// result + protected object ToOptimizedResult(T result) + where T : class + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + Response.AddHeader("Vary", "Accept-Encoding"); + + return RequestContext.ToOptimizedResult(result); + } + + /// + /// To the optimized result using cache. + /// + /// + /// The cache key. + /// The last date modified. + /// Duration of the cache. + /// The factory fn. + /// System.Object. + /// cacheKey + protected object ToOptimizedResultUsingCache(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func factoryFn) + where T : class + { + if (cacheKey == Guid.Empty) + { + throw new ArgumentNullException("cacheKey"); + } + if (factoryFn == null) + { + throw new ArgumentNullException("factoryFn"); + } + + var key = cacheKey.ToString("N"); + + var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, string.Empty); + + if (result != null) + { + return result; + } + + return ToOptimizedResult(factoryFn()); + } + + /// + /// To the cached result. + /// + /// + /// The cache key. + /// The last date modified. + /// Duration of the cache. + /// The factory fn. + /// Type of the content. + /// System.Object. + /// cacheKey + protected object ToCachedResult(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func factoryFn, string contentType) + where T : class + { + if (cacheKey == Guid.Empty) + { + throw new ArgumentNullException("cacheKey"); + } + if (factoryFn == null) + { + throw new ArgumentNullException("factoryFn"); + } + + var key = cacheKey.ToString("N"); + + var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, contentType); + + if (result != null) + { + return result; + } + + return factoryFn(); + } + + /// + /// To the static file result. + /// + /// The path. + /// System.Object. + /// path + protected object ToStaticFileResult(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + var dateModified = File.GetLastWriteTimeUtc(path); + + var cacheKey = path + dateModified.Ticks; + + return ToStaticResult(cacheKey.GetMD5(), dateModified, null, MimeTypes.GetMimeType(path), () => Task.FromResult(GetFileStream(path))); + } + + /// + /// Gets the file stream. + /// + /// The path. + /// Stream. + private Stream GetFileStream(string path) + { + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous); + } + + /// + /// To the static result. + /// + /// The cache key. + /// The last date modified. + /// Duration of the cache. + /// Type of the content. + /// The factory fn. + /// System.Object. + /// cacheKey + protected object ToStaticResult(Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func> factoryFn) + { + if (cacheKey == Guid.Empty) + { + throw new ArgumentNullException("cacheKey"); + } + if (factoryFn == null) + { + throw new ArgumentNullException("factoryFn"); + } + + var key = cacheKey.ToString("N"); + + var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, contentType); + + if (result != null) + { + return result; + } + + var compress = ShouldCompressResponse(contentType); + + if (compress) + { + Response.AddHeader("Vary", "Accept-Encoding"); + } + + return ToStaticResult(contentType, factoryFn, compress).Result; + } + + /// + /// Shoulds the compress response. + /// + /// Type of the content. + /// true if XXXX, false otherwise + private bool ShouldCompressResponse(string contentType) + { + // It will take some work to support compression with byte range requests + if (IsRangeRequest) + { + return false; + } + + // Don't compress media + if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Don't compress images + if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + + /// + /// To the static result. + /// + /// Type of the content. + /// The factory fn. + /// if set to true [compress]. + /// System.Object. + private async Task ToStaticResult(string contentType, Func> factoryFn, bool compress) + { + if (!compress || string.IsNullOrEmpty(RequestContext.CompressionType)) + { + Response.ContentType = contentType; + + var stream = await factoryFn().ConfigureAwait(false); + + return new StreamWriter(stream); + } + + string content; + + using (var stream = await factoryFn().ConfigureAwait(false)) + { + using (var reader = new StreamReader(stream)) + { + content = await reader.ReadToEndAsync().ConfigureAwait(false); + } + } + + var contents = content.Compress(RequestContext.CompressionType); + + return new CompressedResult(contents, RequestContext.CompressionType, contentType); + } + + /// + /// Pres the process optimized result. + /// + /// The cache key. + /// The cache key string. + /// The last date modified. + /// Duration of the cache. + /// Type of the content. + /// System.Object. + private object PreProcessCachedResult(Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType) + { + Response.AddHeader("ETag", cacheKeyString); + + if (IsNotModified(cacheKey, lastDateModified, cacheDuration)) + { + AddAgeHeader(lastDateModified); + AddExpiresHeader(cacheKeyString, cacheDuration); + //ctx.Response.SendChunked = false; + + if (!string.IsNullOrEmpty(contentType)) + { + Response.ContentType = contentType; + } + + return new HttpResult(new byte[] { }, HttpStatusCode.NotModified); + } + + SetCachingHeaders(cacheKeyString, lastDateModified, cacheDuration); + + return null; + } + + /// + /// Determines whether [is not modified] [the specified cache key]. + /// + /// The cache key. + /// The last date modified. + /// Duration of the cache. + /// true if [is not modified] [the specified cache key]; otherwise, false. + private bool IsNotModified(Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) + { + var isNotModified = true; + + if (Request.Headers.AllKeys.Contains("If-Modified-Since")) + { + DateTime ifModifiedSince; + + if (DateTime.TryParse(Request.Headers["If-Modified-Since"], out ifModifiedSince)) + { + isNotModified = IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified); + } + } + + // Validate If-None-Match + if (isNotModified && (cacheKey.HasValue || !string.IsNullOrEmpty(Request.Headers["If-None-Match"]))) + { + Guid ifNoneMatch; + + if (Guid.TryParse(Request.Headers["If-None-Match"] ?? string.Empty, out ifNoneMatch)) + { + if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch) + { + return true; + } + } + } + + return false; + } + + /// + /// Determines whether [is not modified] [the specified if modified since]. + /// + /// If modified since. + /// Duration of the cache. + /// The date modified. + /// true if [is not modified] [the specified if modified since]; otherwise, false. + private bool IsNotModified(DateTime ifModifiedSince, TimeSpan? cacheDuration, DateTime? dateModified) + { + if (dateModified.HasValue) + { + var lastModified = NormalizeDateForComparison(dateModified.Value); + ifModifiedSince = NormalizeDateForComparison(ifModifiedSince); + + return lastModified <= ifModifiedSince; + } + + if (cacheDuration.HasValue) + { + var cacheExpirationDate = ifModifiedSince.Add(cacheDuration.Value); + + if (DateTime.UtcNow < cacheExpirationDate) + { + return true; + } + } + + return false; + } + + + /// + /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that + /// + /// The date. + /// DateTime. + private DateTime NormalizeDateForComparison(DateTime date) + { + return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind); + } + + /// + /// Sets the caching headers. + /// + /// The cache key. + /// The last date modified. + /// Duration of the cache. + private void SetCachingHeaders(string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) + { + // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant + // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching + if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue)) + { + AddAgeHeader(lastDateModified); + Response.AddHeader("LastModified", lastDateModified.Value.ToString("r")); + } + + if (cacheDuration.HasValue) + { + Response.AddHeader("Cache-Control", "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds)); + } + else if (!string.IsNullOrEmpty(cacheKey)) + { + Response.AddHeader("Cache-Control", "public"); + } + else + { + Response.AddHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + Response.AddHeader("pragma", "no-cache, no-store, must-revalidate"); + } + + AddExpiresHeader(cacheKey, cacheDuration); + } + + /// + /// Adds the expires header. + /// + /// The cache key. + /// Duration of the cache. + private void AddExpiresHeader(string cacheKey, TimeSpan? cacheDuration) + { + if (cacheDuration.HasValue) + { + Response.AddHeader("Expires", DateTime.UtcNow.Add(cacheDuration.Value).ToString("r")); + } + else if (string.IsNullOrEmpty(cacheKey)) + { + Response.AddHeader("Expires", "-1"); + } + } + + /// + /// Adds the age header. + /// + /// The last date modified. + private void AddAgeHeader(DateTime? lastDateModified) + { + if (lastDateModified.HasValue) + { + Response.AddHeader("Age", Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture)); + } + } + } +} diff --git a/MediaBrowser.Networking/HttpServer/HttpServer.cs b/MediaBrowser.Networking/HttpServer/HttpServer.cs index b6250527db..08a6b3561e 100644 --- a/MediaBrowser.Networking/HttpServer/HttpServer.cs +++ b/MediaBrowser.Networking/HttpServer/HttpServer.cs @@ -1,4 +1,3 @@ -using System.Net.WebSockets; using Funq; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Kernel; @@ -16,10 +15,12 @@ using ServiceStack.WebHost.Endpoints; using ServiceStack.WebHost.Endpoints.Extensions; using ServiceStack.WebHost.Endpoints.Support; using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Net.WebSockets; using System.Reactive.Linq; using System.Reflection; using System.Text; @@ -44,10 +45,9 @@ namespace MediaBrowser.Networking.HttpServer public string UrlPrefix { get; private set; } /// - /// Gets or sets the kernel. + /// The _rest services /// - /// The kernel. - private IKernel Kernel { get; set; } + private readonly List _restServices = new List(); /// /// Gets or sets the application host. @@ -88,19 +88,14 @@ namespace MediaBrowser.Networking.HttpServer /// Initializes a new instance of the class. /// /// The application host. - /// The kernel. /// The protobuf serializer. /// The logger. /// Name of the server. /// The default redirectpath. /// urlPrefix - public HttpServer(IApplicationHost applicationHost, IKernel kernel, IProtobufSerializer protobufSerializer, ILogger logger, string serverName, string defaultRedirectpath) + public HttpServer(IApplicationHost applicationHost, IProtobufSerializer protobufSerializer, ILogger logger, string serverName, string defaultRedirectpath) : base() { - if (kernel == null) - { - throw new ArgumentNullException("kernel"); - } if (protobufSerializer == null) { throw new ArgumentNullException("protobufSerializer"); @@ -130,13 +125,6 @@ namespace MediaBrowser.Networking.HttpServer EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath = null; EndpointHostConfig.Instance.MetadataRedirectPath = "metadata"; - - Kernel = kernel; - - EndpointHost.ConfigureHost(this, ServerName, CreateServiceManager()); - ContentTypeFilters.Register(ContentType.ProtoBuf, (reqCtx, res, stream) => ProtobufSerializer.SerializeToStream(res, stream), (type, stream) => ProtobufSerializer.DeserializeFromStream(stream, type)); - - Init(); } /// @@ -161,11 +149,6 @@ namespace MediaBrowser.Networking.HttpServer container.Adapter = new ContainerAdapter(ApplicationHost); - foreach (var service in Kernel.RestServices) - { - service.Configure(this); - } - Plugins.Add(new SwaggerFeature()); Plugins.Add(new CorsFeature()); @@ -450,7 +433,7 @@ namespace MediaBrowser.Networking.HttpServer /// ServiceManager. protected override ServiceManager CreateServiceManager(params Assembly[] assembliesWithServices) { - var types = Kernel.RestServices.Select(r => r.GetType()).ToArray(); + var types = _restServices.Select(r => r.GetType()).ToArray(); return new ServiceManager(new Container(), new ServiceController(() => types)); } @@ -511,6 +494,20 @@ namespace MediaBrowser.Networking.HttpServer /// /// true if [enable HTTP request logging]; otherwise, false. public bool EnableHttpRequestLogging { get; set; } + + /// + /// Adds the rest handlers. + /// + /// The services. + public void Init(IEnumerable services) + { + _restServices.AddRange(services); + + EndpointHost.ConfigureHost(this, ServerName, CreateServiceManager()); + ContentTypeFilters.Register(ContentType.ProtoBuf, (reqCtx, res, stream) => ProtobufSerializer.SerializeToStream(res, stream), (type, stream) => ProtobufSerializer.DeserializeFromStream(stream, type)); + + Init(); + } } /// diff --git a/MediaBrowser.Networking/HttpServer/ServerFactory.cs b/MediaBrowser.Networking/HttpServer/ServerFactory.cs index e853a6ec22..716fd450a5 100644 --- a/MediaBrowser.Networking/HttpServer/ServerFactory.cs +++ b/MediaBrowser.Networking/HttpServer/ServerFactory.cs @@ -14,15 +14,14 @@ namespace MediaBrowser.Networking.HttpServer /// Creates the server. /// /// The application host. - /// The kernel. /// The protobuf serializer. /// The logger. /// Name of the server. /// The default redirectpath. /// IHttpServer. - public static IHttpServer CreateServer(IApplicationHost applicationHost, IKernel kernel, IProtobufSerializer protobufSerializer, ILogger logger, string serverName, string defaultRedirectpath) + public static IHttpServer CreateServer(IApplicationHost applicationHost, IProtobufSerializer protobufSerializer, ILogger logger, string serverName, string defaultRedirectpath) { - return new HttpServer(applicationHost, kernel, protobufSerializer, logger, serverName, defaultRedirectpath); + return new HttpServer(applicationHost, protobufSerializer, logger, serverName, defaultRedirectpath); } } } diff --git a/MediaBrowser.Networking/MediaBrowser.Networking.csproj b/MediaBrowser.Networking/MediaBrowser.Networking.csproj index 7bb3fc930d..3ef7832dfc 100644 --- a/MediaBrowser.Networking/MediaBrowser.Networking.csproj +++ b/MediaBrowser.Networking/MediaBrowser.Networking.csproj @@ -107,6 +107,7 @@ Properties\SharedVersion.cs + diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 416a7d7464..ec29ca4d30 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -51,6 +51,10 @@ Properties\SharedVersion.cs + + + + diff --git a/MediaBrowser.Server.Implementations/Reflection/TypeMapper.cs b/MediaBrowser.Server.Implementations/Reflection/TypeMapper.cs new file mode 100644 index 0000000000..5f411a0023 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Reflection/TypeMapper.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Reflection +{ + /// + /// Class TypeMapper + /// + public class TypeMapper + { + /// + /// This holds all the types in the running assemblies so that we can de-serialize properly when we don't have strong types + /// + private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary(); + + /// + /// Gets the type. + /// + /// Name of the type. + /// Type. + /// + public Type GetType(string typeName) + { + if (string.IsNullOrEmpty(typeName)) + { + throw new ArgumentNullException(); + } + + return _typeMap.GetOrAdd(typeName, LookupType); + } + + /// + /// Lookups the type. + /// + /// Name of the type. + /// Type. + private Type LookupType(string typeName) + { + return AppDomain + .CurrentDomain + .GetAssemblies() + .Select(a => a.GetType(typeName, false)) + .FirstOrDefault(t => t != null); + } + } +} diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs new file mode 100644 index 0000000000..d3854f9d79 --- /dev/null +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -0,0 +1,122 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.ScheduledTasks.Tasks +{ + /// + /// Class ChapterImagesTask + /// + class ChapterImagesTask : IScheduledTask + { + /// + /// The _kernel + /// + private readonly Kernel _kernel; + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The kernel. + /// The logger. + public ChapterImagesTask(Kernel kernel, ILogger logger) + { + _kernel = kernel; + _logger = logger; + } + + /// + /// Creates the triggers that define when the task will run + /// + /// IEnumerable{BaseTaskTrigger}. + public IEnumerable GetDefaultTriggers() + { + return new ITaskTrigger[] + { + new DailyTrigger { TimeOfDay = TimeSpan.FromHours(4) } + }; + } + + /// + /// Returns the task to be executed + /// + /// The cancellation token. + /// The progress. + /// Task. + public Task Execute(CancellationToken cancellationToken, IProgress progress) + { + var videos = _kernel.RootFolder.RecursiveChildren.OfType