mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-28 18:43:46 +01:00
plugin security fixes and other abstractions
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is first run.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is first run; otherwise, <c>false</c>.</value>
|
||||
public bool IsFirstRun { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has changes that require the entire application to restart.
|
||||
/// </summary>
|
||||
@@ -176,12 +167,6 @@ namespace MediaBrowser.Common.Kernel
|
||||
/// <value>The TCP manager.</value>
|
||||
public TcpManager TcpManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rest services.
|
||||
/// </summary>
|
||||
/// <value>The rest services.</value>
|
||||
public IEnumerable<IRestfulService> RestServices { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
public Task Init()
|
||||
{
|
||||
IsFirstRun = !File.Exists(ApplicationPaths.SystemConfigurationFilePath);
|
||||
|
||||
// Performs initializations that can be reloaded at anytime
|
||||
return Reload();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs initializations that can be reloaded at anytime
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
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
|
||||
/// </summary>
|
||||
protected virtual void FindParts()
|
||||
{
|
||||
RestServices = ApplicationHost.GetExports<IRestfulService>();
|
||||
WebSocketListeners = ApplicationHost.GetExports<IWebSocketListener>();
|
||||
Plugins = ApplicationHost.GetExports<IPlugin>();
|
||||
}
|
||||
@@ -425,18 +395,6 @@ namespace MediaBrowser.Common.Kernel
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current application version
|
||||
/// </summary>
|
||||
/// <value>The application version.</value>
|
||||
public Version ApplicationVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetType().Assembly.GetName().Version;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the pending restart.
|
||||
/// </summary>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the application.
|
||||
/// </summary>
|
||||
protected void RestartApplication()
|
||||
{
|
||||
Logger.Info("Restarting the application");
|
||||
|
||||
ApplicationHost.Restart();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system status.
|
||||
/// </summary>
|
||||
@@ -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,
|
||||
|
||||
@@ -21,6 +21,12 @@ namespace MediaBrowser.Common.Kernel
|
||||
/// </summary>
|
||||
void ReloadLogger();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the application version.
|
||||
/// </summary>
|
||||
/// <value>The application version.</value>
|
||||
Version ApplicationVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the log file path.
|
||||
/// </summary>
|
||||
@@ -33,11 +39,17 @@ namespace MediaBrowser.Common.Kernel
|
||||
/// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
|
||||
bool CanSelfUpdate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is first run.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is first run; otherwise, <c>false</c>.</value>
|
||||
bool IsFirstRun { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the failed assemblies.
|
||||
/// </summary>
|
||||
/// <value>The failed assemblies.</value>
|
||||
IEnumerable<string> FailedAssemblies { get; }
|
||||
List<string> FailedAssemblies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all concrete types.
|
||||
@@ -72,34 +84,6 @@ namespace MediaBrowser.Common.Kernel
|
||||
/// <returns>System.Object.</returns>
|
||||
object CreateInstance(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a service that other classes can use as a dependancy.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="obj">The obj.</param>
|
||||
void RegisterSingleInstance<T>(T obj) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// Registers the single instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="func">The func.</param>
|
||||
void RegisterSingleInstance<T>(Func<T> func) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// Registers the specified func.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="func">The func.</param>
|
||||
void Register<T>(Func<T> func) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// Registers the specified service type.
|
||||
/// </summary>
|
||||
/// <param name="serviceType">Type of the service.</param>
|
||||
/// <param name="implementation">Type of the implementation.</param>
|
||||
void Register(Type serviceType, Type implementation);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -37,12 +37,6 @@ namespace MediaBrowser.Common.Kernel
|
||||
/// <returns>Task.</returns>
|
||||
Task Init();
|
||||
|
||||
/// <summary>
|
||||
/// Reloads this instance.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
Task Reload();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has pending kernel reload.
|
||||
/// </summary>
|
||||
@@ -106,12 +100,6 @@ namespace MediaBrowser.Common.Kernel
|
||||
/// <value>The HTTP server URL prefix.</value>
|
||||
string HttpServerUrlPrefix { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is first run.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is first run; otherwise, <c>false</c>.</value>
|
||||
bool IsFirstRun { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TCP manager.
|
||||
/// </summary>
|
||||
@@ -139,12 +127,6 @@ namespace MediaBrowser.Common.Kernel
|
||||
/// </summary>
|
||||
event EventHandler<EventArgs> ConfigurationUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rest services.
|
||||
/// </summary>
|
||||
/// <value>The rest services.</value>
|
||||
IEnumerable<IRestfulService> RestServices { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the pending restart.
|
||||
/// </summary>
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace MediaBrowser.Common.Kernel
|
||||
/// </summary>
|
||||
/// <value>The json serializer.</value>
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This subscribes to HttpListener requests and finds the appropriate BaseHandler to process it
|
||||
/// </summary>
|
||||
@@ -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
|
||||
/// <param name="e">The <see cref="WebSocketConnectEventArgs" /> instance containing the event data.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -95,8 +95,6 @@
|
||||
<Compile Include="Kernel\IApplicationHost.cs" />
|
||||
<Compile Include="Kernel\IKernel.cs" />
|
||||
<Compile Include="Kernel\TcpManager.cs" />
|
||||
<Compile Include="Net\BaseRestService.cs" />
|
||||
<Compile Include="Net\Handlers\BaseActionHandler.cs" />
|
||||
<Compile Include="Net\Handlers\IHttpServerHandler.cs" />
|
||||
<Compile Include="Net\Handlers\StaticFileHandler.cs" />
|
||||
<Compile Include="Net\IHttpClient.cs" />
|
||||
@@ -122,6 +120,8 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ScheduledTasks\IScheduledTask.cs" />
|
||||
<Compile Include="ScheduledTasks\IScheduledTaskWorker.cs" />
|
||||
<Compile Include="ScheduledTasks\ITaskManager.cs" />
|
||||
<Compile Include="ScheduledTasks\ITaskTrigger.cs" />
|
||||
<Compile Include="ScheduledTasks\ScheduledTaskHelpers.cs" />
|
||||
@@ -130,13 +130,10 @@
|
||||
<Compile Include="Kernel\BaseKernel.cs" />
|
||||
<Compile Include="Kernel\KernelContext.cs" />
|
||||
<Compile Include="Net\Handlers\BaseHandler.cs" />
|
||||
<Compile Include="Net\Handlers\BaseSerializationHandler.cs" />
|
||||
<Compile Include="Plugins\BasePlugin.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ScheduledTasks\BaseScheduledTask.cs" />
|
||||
<Compile Include="ScheduledTasks\DailyTrigger.cs" />
|
||||
<Compile Include="ScheduledTasks\IntervalTrigger.cs" />
|
||||
<Compile Include="ScheduledTasks\IScheduledTask.cs" />
|
||||
<Compile Include="ScheduledTasks\WeeklyTrigger.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Class BaseRestService
|
||||
/// </summary>
|
||||
public class BaseRestService : Service, IRestfulService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the kernel.
|
||||
/// </summary>
|
||||
/// <value>The kernel.</value>
|
||||
public IKernel Kernel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger.
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is range request.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is range request; otherwise, <c>false</c>.</value>
|
||||
protected bool IsRangeRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
return Request.Headers.AllKeys.Contains("Range");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the routes.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The app host.</param>
|
||||
public virtual void Configure(IAppHost appHost)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the optimized result.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">result</exception>
|
||||
protected object ToOptimizedResult<T>(T result)
|
||||
where T : class
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
throw new ArgumentNullException("result");
|
||||
}
|
||||
|
||||
Response.AddHeader("Vary", "Accept-Encoding");
|
||||
|
||||
return RequestContext.ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the optimized result using cache.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="factoryFn">The factory fn.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
|
||||
protected object ToOptimizedResultUsingCache<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> 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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the cached result.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="factoryFn">The factory fn.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
|
||||
protected object ToCachedResult<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the static file result.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">path</exception>
|
||||
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)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file stream.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>Stream.</returns>
|
||||
private Stream GetFileStream(string path)
|
||||
{
|
||||
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the static result.
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <param name="factoryFn">The factory fn.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
|
||||
protected object ToStaticResult(Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func<Task<Stream>> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shoulds the compress response.
|
||||
/// </summary>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the static result.
|
||||
/// </summary>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <param name="factoryFn">The factory fn.</param>
|
||||
/// <param name="compress">if set to <c>true</c> [compress].</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
private async Task<object> ToStaticResult(string contentType, Func<Task<Stream>> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pres the process optimized result.
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="cacheKeyString">The cache key string.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is not modified] [the specified cache key].
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is not modified] [the specified if modified since].
|
||||
/// </summary>
|
||||
/// <param name="ifModifiedSince">If modified since.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="dateModified">The date modified.</param>
|
||||
/// <returns><c>true</c> if [is not modified] [the specified if modified since]; otherwise, <c>false</c>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
|
||||
/// </summary>
|
||||
/// <param name="date">The date.</param>
|
||||
/// <returns>DateTime.</returns>
|
||||
private DateTime NormalizeDateForComparison(DateTime date)
|
||||
{
|
||||
return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the caching headers.
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the expires header.
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the age header.
|
||||
/// </summary>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
private void AddAgeHeader(DateTime? lastDateModified)
|
||||
{
|
||||
if (lastDateModified.HasValue)
|
||||
{
|
||||
Response.AddHeader("Age", Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using MediaBrowser.Common.Kernel;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.Net.Handlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Class BaseActionHandler
|
||||
/// </summary>
|
||||
/// <typeparam name="TKernelType">The type of the T kernel type.</typeparam>
|
||||
public abstract class BaseActionHandler<TKernelType> : BaseSerializationHandler<TKernelType, EmptyRequestResult>
|
||||
where TKernelType : IKernel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the object to serialize.
|
||||
/// </summary>
|
||||
/// <returns>Task{EmptyRequestResult}.</returns>
|
||||
protected override async Task<EmptyRequestResult> GetObjectToSerialize()
|
||||
{
|
||||
await ExecuteAction();
|
||||
|
||||
return new EmptyRequestResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the action.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
protected abstract Task ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Class BaseSerializationHandler
|
||||
/// </summary>
|
||||
/// <typeparam name="TKernelType">The type of the T kernel type.</typeparam>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class BaseSerializationHandler<TKernelType, T> : BaseHandler<TKernelType>
|
||||
where TKernelType : IKernel
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the serialization format.
|
||||
/// </summary>
|
||||
/// <value>The serialization format.</value>
|
||||
public SerializationFormat SerializationFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
var format = QueryString["dataformat"];
|
||||
|
||||
if (string.IsNullOrEmpty(format))
|
||||
{
|
||||
return SerializationFormat.Json;
|
||||
}
|
||||
|
||||
return (SerializationFormat)Enum.Parse(typeof(SerializationFormat), format, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the content.
|
||||
/// </summary>
|
||||
/// <value>The type of the content.</value>
|
||||
protected string ContentType
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (SerializationFormat)
|
||||
{
|
||||
case SerializationFormat.Protobuf:
|
||||
return "application/x-protobuf";
|
||||
default:
|
||||
return MimeTypes.JsonMimeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response info.
|
||||
/// </summary>
|
||||
/// <returns>Task{ResponseInfo}.</returns>
|
||||
protected override Task<ResponseInfo> GetResponseInfo()
|
||||
{
|
||||
return Task.FromResult(new ResponseInfo
|
||||
{
|
||||
ContentType = ContentType
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when [processing request].
|
||||
/// </summary>
|
||||
/// <param name="responseInfo">The response info.</param>
|
||||
/// <returns>Task.</returns>
|
||||
protected override async Task OnProcessingRequest(ResponseInfo responseInfo)
|
||||
{
|
||||
_objectToSerialize = await GetObjectToSerialize().ConfigureAwait(false);
|
||||
|
||||
if (_objectToSerialize == null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
await base.OnProcessingRequest(responseInfo).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _object to serialize
|
||||
/// </summary>
|
||||
private T _objectToSerialize;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object to serialize.
|
||||
/// </summary>
|
||||
/// <returns>Task{`0}.</returns>
|
||||
protected abstract Task<T> GetObjectToSerialize();
|
||||
|
||||
/// <summary>
|
||||
/// Writes the response to output stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="responseInfo">The response info.</param>
|
||||
/// <param name="contentLength">Length of the content.</param>
|
||||
/// <returns>Task.</returns>
|
||||
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;
|
||||
//}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum SerializationFormat
|
||||
/// </summary>
|
||||
public enum SerializationFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// The json
|
||||
/// </summary>
|
||||
Json,
|
||||
/// <summary>
|
||||
/// The protobuf
|
||||
/// </summary>
|
||||
Protobuf
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,9 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface IHttpClient
|
||||
/// </summary>
|
||||
public interface IHttpClient : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
@@ -52,10 +55,5 @@ namespace MediaBrowser.Common.Net
|
||||
/// <returns>Task{MemoryStream}.</returns>
|
||||
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
|
||||
Task<MemoryStream> GetMemoryStream(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
}
|
||||
}
|
||||
@@ -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].
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Inits this instance.
|
||||
/// </summary>
|
||||
void Init(IEnumerable<IRestfulService> services);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public interface IRestfulService : IService, IRequiresRequestContext, IDisposable
|
||||
{
|
||||
void Configure(IAppHost appHost);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,17 +41,22 @@ namespace MediaBrowser.Common.Net
|
||||
/// The _json serializer
|
||||
/// </summary>
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the receive action.
|
||||
/// </summary>
|
||||
/// <value>The receive action.</value>
|
||||
public Action<WebSocketMessageInfo> OnReceive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
|
||||
/// </summary>
|
||||
/// <param name="socket">The socket.</param>
|
||||
/// <param name="remoteEndPoint">The remote end point.</param>
|
||||
/// <param name="receiveAction">The receive action.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <exception cref="System.ArgumentNullException">socket</exception>
|
||||
public WebSocketConnection(IWebSocket socket, string remoteEndPoint, Action<WebSocketMessageInfo> 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].
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes.</param>
|
||||
/// <param name="callback">The callback.</param>
|
||||
private void OnReceive(byte[] bytes, Action<WebSocketMessageInfo> callback)
|
||||
private void OnReceiveInternal(byte[] bytes)
|
||||
{
|
||||
if (OnReceive == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
WebSocketMessageInfo info;
|
||||
|
||||
using (var memoryStream = new MemoryStream(bytes))
|
||||
{
|
||||
info = _jsonSerializer.DeserializeFromStream<WebSocketMessageInfo>(memoryStream);
|
||||
info = (WebSocketMessageInfo)_jsonSerializer.DeserializeFromStream(memoryStream, typeof(WebSocketMessageInfo));
|
||||
}
|
||||
|
||||
info.Connection = this;
|
||||
|
||||
callback(info);
|
||||
OnReceive(info);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a task that can be executed at a scheduled time
|
||||
/// </summary>
|
||||
/// <typeparam name="TKernelType">The type of the T kernel type.</typeparam>
|
||||
public abstract class BaseScheduledTask<TKernelType> : IScheduledTask
|
||||
where TKernelType : class, IKernel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the kernel.
|
||||
/// </summary>
|
||||
/// <value>The kernel.</value>
|
||||
protected TKernelType Kernel { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
protected ILogger Logger { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task manager.
|
||||
/// </summary>
|
||||
/// <value>The task manager.</value>
|
||||
protected ITaskManager TaskManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseScheduledTask{TKernelType}" /> class.
|
||||
/// </summary>
|
||||
/// <param name="kernel">The kernel.</param>
|
||||
/// <param name="taskManager">The task manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <exception cref="System.ArgumentNullException">kernel</exception>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _last execution result
|
||||
/// </summary>
|
||||
private TaskResult _lastExecutionResult;
|
||||
/// <summary>
|
||||
/// The _last execution resultinitialized
|
||||
/// </summary>
|
||||
private bool _lastExecutionResultinitialized;
|
||||
/// <summary>
|
||||
/// The _last execution result sync lock
|
||||
/// </summary>
|
||||
private object _lastExecutionResultSyncLock = new object();
|
||||
/// <summary>
|
||||
/// Gets the last execution result.
|
||||
/// </summary>
|
||||
/// <value>The last execution result.</value>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current cancellation token
|
||||
/// </summary>
|
||||
/// <value>The current cancellation token source.</value>
|
||||
private CancellationTokenSource CurrentCancellationTokenSource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current execution start time.
|
||||
/// </summary>
|
||||
/// <value>The current execution start time.</value>
|
||||
private DateTime CurrentExecutionStartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state.
|
||||
/// </summary>
|
||||
/// <value>The state.</value>
|
||||
public TaskState State
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentCancellationTokenSource != null)
|
||||
{
|
||||
return CurrentCancellationTokenSource.IsCancellationRequested
|
||||
? TaskState.Cancelling
|
||||
: TaskState.Running;
|
||||
}
|
||||
|
||||
return TaskState.Idle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current progress.
|
||||
/// </summary>
|
||||
/// <value>The current progress.</value>
|
||||
public double? CurrentProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The _triggers
|
||||
/// </summary>
|
||||
private IEnumerable<ITaskTrigger> _triggers;
|
||||
/// <summary>
|
||||
/// The _triggers initialized
|
||||
/// </summary>
|
||||
private bool _triggersInitialized;
|
||||
/// <summary>
|
||||
/// The _triggers sync lock
|
||||
/// </summary>
|
||||
private object _triggersSyncLock = new object();
|
||||
/// <summary>
|
||||
/// Gets the triggers that define when the task will run
|
||||
/// </summary>
|
||||
/// <value>The triggers.</value>
|
||||
/// <exception cref="System.ArgumentNullException">value</exception>
|
||||
public IEnumerable<ITaskTrigger> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the triggers that define when the task will run
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||
public abstract IEnumerable<ITaskTrigger> GetDefaultTriggers();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the task to be executed
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <returns>Task.</returns>
|
||||
protected abstract Task ExecuteInternal(CancellationToken cancellationToken, IProgress<double> progress);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the task
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description.
|
||||
/// </summary>
|
||||
/// <value>The description.</value>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the category.
|
||||
/// </summary>
|
||||
/// <value>The category.</value>
|
||||
public virtual string Category
|
||||
{
|
||||
get { return "Application"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _id
|
||||
/// </summary>
|
||||
private Guid? _id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique id.
|
||||
/// </summary>
|
||||
/// <value>The unique id.</value>
|
||||
public Guid Id
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_id.HasValue)
|
||||
{
|
||||
_id = GetType().FullName.GetMD5();
|
||||
}
|
||||
|
||||
return _id.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the trigger events.
|
||||
/// </summary>
|
||||
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
|
||||
private void ReloadTriggerEvents(bool isApplicationStartup)
|
||||
{
|
||||
foreach (var trigger in Triggers)
|
||||
{
|
||||
trigger.Stop();
|
||||
|
||||
trigger.Triggered -= trigger_Triggered;
|
||||
trigger.Triggered += trigger_Triggered;
|
||||
trigger.Start(isApplicationStartup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the Triggered event of the trigger control.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the task
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">Cannot execute a Task that is already running</exception>
|
||||
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<double>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Progress_s the progress changed.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <param name="e">The e.</param>
|
||||
void progress_ProgressChanged(object sender, double e)
|
||||
{
|
||||
CurrentProgress = e;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the task if it is currently executing
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">Cannot cancel a Task unless it is in the Running state.</exception>
|
||||
public void Cancel()
|
||||
{
|
||||
if (State != TaskState.Running)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot cancel a Task unless it is in the Running state.");
|
||||
}
|
||||
|
||||
CancelIfRunning();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels if running.
|
||||
/// </summary>
|
||||
public void CancelIfRunning()
|
||||
{
|
||||
if (State == TaskState.Running)
|
||||
{
|
||||
Logger.Info("Attempting to cancel Scheduled Task {0}", Name);
|
||||
CurrentCancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes each trigger
|
||||
/// </summary>
|
||||
private void DisposeTriggers()
|
||||
{
|
||||
foreach (var trigger in Triggers)
|
||||
{
|
||||
trigger.Triggered -= trigger_Triggered;
|
||||
trigger.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface IScheduledTask
|
||||
/// Interface IScheduledTaskWorker
|
||||
/// </summary>
|
||||
public interface IScheduledTask : IDisposable
|
||||
public interface IScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the triggers.
|
||||
/// </summary>
|
||||
/// <value>The triggers.</value>
|
||||
IEnumerable<ITaskTrigger> Triggers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last execution result.
|
||||
/// </summary>
|
||||
/// <value>The last execution result.</value>
|
||||
TaskResult LastExecutionResult { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state.
|
||||
/// </summary>
|
||||
/// <value>The state.</value>
|
||||
TaskState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current progress.
|
||||
/// </summary>
|
||||
/// <value>The current progress.</value>
|
||||
double? CurrentProgress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the task
|
||||
/// </summary>
|
||||
@@ -52,29 +28,13 @@ namespace MediaBrowser.Common.ScheduledTasks
|
||||
/// <value>The category.</value>
|
||||
string Category { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique id.
|
||||
/// </summary>
|
||||
/// <value>The unique id.</value>
|
||||
Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the task
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">Cannot execute a Task that is already running</exception>
|
||||
Task Execute();
|
||||
|
||||
/// <summary>
|
||||
/// Stops the task if it is currently executing
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">Cannot cancel a Task unless it is in the Running state.</exception>
|
||||
void Cancel();
|
||||
|
||||
/// <summary>
|
||||
/// Cancels if running.
|
||||
/// </summary>
|
||||
void CancelIfRunning();
|
||||
Task Execute(CancellationToken cancellationToken, IProgress<double> progress);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default triggers.
|
||||
@@ -82,4 +42,4 @@ namespace MediaBrowser.Common.ScheduledTasks
|
||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||
IEnumerable<ITaskTrigger> GetDefaultTriggers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
86
MediaBrowser.Common/ScheduledTasks/IScheduledTaskWorker.cs
Normal file
86
MediaBrowser.Common/ScheduledTasks/IScheduledTaskWorker.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface IScheduledTaskWorker
|
||||
/// </summary>
|
||||
public interface IScheduledTaskWorker : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the scheduled task.
|
||||
/// </summary>
|
||||
/// <value>The scheduled task.</value>
|
||||
IScheduledTask ScheduledTask { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last execution result.
|
||||
/// </summary>
|
||||
/// <value>The last execution result.</value>
|
||||
TaskResult LastExecutionResult { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description.
|
||||
/// </summary>
|
||||
/// <value>The description.</value>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the category.
|
||||
/// </summary>
|
||||
/// <value>The category.</value>
|
||||
string Category { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state.
|
||||
/// </summary>
|
||||
/// <value>The state.</value>
|
||||
TaskState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current progress.
|
||||
/// </summary>
|
||||
/// <value>The current progress.</value>
|
||||
double? CurrentProgress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triggers that define when the task will run
|
||||
/// </summary>
|
||||
/// <value>The triggers.</value>
|
||||
/// <exception cref="System.ArgumentNullException">value</exception>
|
||||
IEnumerable<ITaskTrigger> Triggers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique id.
|
||||
/// </summary>
|
||||
/// <value>The unique id.</value>
|
||||
Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the task
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">Cannot execute a Task that is already running</exception>
|
||||
Task Execute();
|
||||
|
||||
/// <summary>
|
||||
/// Stops the task if it is currently executing
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">Cannot cancel a Task unless it is in the Running state.</exception>
|
||||
void Cancel();
|
||||
|
||||
/// <summary>
|
||||
/// Cancels if running.
|
||||
/// </summary>
|
||||
void CancelIfRunning();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/// </summary>
|
||||
/// <value>The scheduled tasks.</value>
|
||||
IScheduledTask[] ScheduledTasks { get; }
|
||||
IScheduledTaskWorker[] ScheduledTasks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cancels if running and queue.
|
||||
@@ -37,35 +36,5 @@ namespace MediaBrowser.Common.ScheduledTasks
|
||||
/// </summary>
|
||||
/// <param name="tasks">The tasks.</param>
|
||||
void AddTasks(IEnumerable<IScheduledTask> tasks);
|
||||
|
||||
/// <summary>
|
||||
/// Called when [task completed].
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
/// <param name="startTime">The start time.</param>
|
||||
/// <param name="endTime">The end time.</param>
|
||||
/// <param name="status">The status.</param>
|
||||
void OnTaskCompleted(IScheduledTask task, DateTime startTime, DateTime endTime, TaskCompletionStatus status);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last execution result.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
/// <returns>TaskResult.</returns>
|
||||
TaskResult GetLastExecutionResult(IScheduledTask task);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the triggers.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||
IEnumerable<ITaskTrigger> LoadTriggers(IScheduledTask task);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the triggers.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
/// <param name="triggers">The triggers.</param>
|
||||
void SaveTriggers(IScheduledTask task, IEnumerable<ITaskTrigger> triggers);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace MediaBrowser.Common.ScheduledTasks
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
/// <returns>TaskInfo.</returns>
|
||||
public static TaskInfo GetTaskInfo(IScheduledTask task)
|
||||
public static TaskInfo GetTaskInfo(IScheduledTaskWorker task)
|
||||
{
|
||||
return new TaskInfo
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user