plugin security fixes and other abstractions

This commit is contained in:
LukePulverenti
2013-02-25 22:43:04 -05:00
parent 364fbb9e0c
commit 2d06095447
79 changed files with 1271 additions and 1388 deletions

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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));
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -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>

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}

View 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();
}
}

View File

@@ -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);
}
}

View File

@@ -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
{