mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-07-11 08:10:34 +02:00
move common dependencies
This commit is contained in:
parent
ce38e98791
commit
2729301bff
773
Emby.Common.Implementations/BaseApplicationHost.cs
Normal file
773
Emby.Common.Implementations/BaseApplicationHost.cs
Normal file
|
@ -0,0 +1,773 @@
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Events;
|
||||||
|
using Emby.Common.Implementations.Devices;
|
||||||
|
using Emby.Common.Implementations.IO;
|
||||||
|
using Emby.Common.Implementations.ScheduledTasks;
|
||||||
|
using Emby.Common.Implementations.Serialization;
|
||||||
|
using Emby.Common.Implementations.Updates;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Common.Plugins;
|
||||||
|
using MediaBrowser.Common.Progress;
|
||||||
|
using MediaBrowser.Common.Security;
|
||||||
|
using MediaBrowser.Common.Updates;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
using MediaBrowser.Model.Updates;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using Emby.Common.Implementations.Cryptography;
|
||||||
|
using MediaBrowser.Common;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
using MediaBrowser.Model.System;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class BaseApplicationHost
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TApplicationPathsType">The type of the T application paths type.</typeparam>
|
||||||
|
public abstract class BaseApplicationHost<TApplicationPathsType> : IApplicationHost
|
||||||
|
where TApplicationPathsType : class, IApplicationPaths
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [has pending restart changed].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler HasPendingRestartChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [application updated].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericEventArgs<PackageVersionInfo>> ApplicationUpdated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this instance has changes that require the entire application to restart.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance has pending application restart; otherwise, <c>false</c>.</value>
|
||||||
|
public bool HasPendingRestart { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The logger.</value>
|
||||||
|
protected ILogger Logger { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the plugins.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The plugins.</value>
|
||||||
|
public IPlugin[] Plugins { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the log manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The log manager.</value>
|
||||||
|
public ILogManager LogManager { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the application paths.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The application paths.</value>
|
||||||
|
protected TApplicationPathsType ApplicationPaths { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The json serializer
|
||||||
|
/// </summary>
|
||||||
|
public IJsonSerializer JsonSerializer { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _XML serializer
|
||||||
|
/// </summary>
|
||||||
|
protected readonly IXmlSerializer XmlSerializer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets assemblies that failed to load
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The failed assemblies.</value>
|
||||||
|
public List<string> FailedAssemblies { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all concrete types.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>All concrete types.</value>
|
||||||
|
public Type[] AllConcreteTypes { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The disposable parts
|
||||||
|
/// </summary>
|
||||||
|
protected readonly List<IDisposable> DisposableParts = new List<IDisposable>();
|
||||||
|
|
||||||
|
/// <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 the kernel.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The kernel.</value>
|
||||||
|
protected ITaskManager TaskManager { get; private set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HTTP client.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The HTTP client.</value>
|
||||||
|
public IHttpClient HttpClient { get; private set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the network manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The network manager.</value>
|
||||||
|
protected INetworkManager NetworkManager { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configuration manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The configuration manager.</value>
|
||||||
|
protected IConfigurationManager ConfigurationManager { get; private set; }
|
||||||
|
|
||||||
|
protected IFileSystem FileSystemManager { get; private set; }
|
||||||
|
|
||||||
|
protected IIsoManager IsoManager { get; private set; }
|
||||||
|
|
||||||
|
protected ISystemEvents SystemEvents { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public abstract string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this instance is running as service.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance is running as service; otherwise, <c>false</c>.</value>
|
||||||
|
public abstract bool IsRunningAsService { get; }
|
||||||
|
|
||||||
|
protected ICryptographyProvider CryptographyProvider = new CryptographyProvider();
|
||||||
|
|
||||||
|
private DeviceId _deviceId;
|
||||||
|
public string SystemId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_deviceId == null)
|
||||||
|
{
|
||||||
|
_deviceId = new DeviceId(ApplicationPaths, LogManager.GetLogger("SystemId"), FileSystemManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _deviceId.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string OperatingSystemDisplayName
|
||||||
|
{
|
||||||
|
get { return Environment.OSVersion.VersionString; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMemoryStreamProvider MemoryStreamProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
protected BaseApplicationHost(TApplicationPathsType applicationPaths,
|
||||||
|
ILogManager logManager,
|
||||||
|
IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
// hack alert, until common can target .net core
|
||||||
|
BaseExtensions.CryptographyProvider = CryptographyProvider;
|
||||||
|
|
||||||
|
XmlSerializer = new XmlSerializer(fileSystem, logManager.GetLogger("XmlSerializer"));
|
||||||
|
FailedAssemblies = new List<string>();
|
||||||
|
|
||||||
|
ApplicationPaths = applicationPaths;
|
||||||
|
LogManager = logManager;
|
||||||
|
FileSystemManager = fileSystem;
|
||||||
|
|
||||||
|
ConfigurationManager = GetConfigurationManager();
|
||||||
|
|
||||||
|
// Initialize this early in case the -v command line option is used
|
||||||
|
Logger = LogManager.GetLogger("App");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inits this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
public virtual async Task Init(IProgress<double> progress)
|
||||||
|
{
|
||||||
|
progress.Report(1);
|
||||||
|
|
||||||
|
JsonSerializer = CreateJsonSerializer();
|
||||||
|
|
||||||
|
MemoryStreamProvider = CreateMemoryStreamProvider();
|
||||||
|
SystemEvents = CreateSystemEvents();
|
||||||
|
|
||||||
|
OnLoggerLoaded(true);
|
||||||
|
LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false);
|
||||||
|
|
||||||
|
IsFirstRun = !ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted;
|
||||||
|
progress.Report(2);
|
||||||
|
|
||||||
|
LogManager.LogSeverity = ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging
|
||||||
|
? LogSeverity.Debug
|
||||||
|
: LogSeverity.Info;
|
||||||
|
|
||||||
|
progress.Report(3);
|
||||||
|
|
||||||
|
DiscoverTypes();
|
||||||
|
progress.Report(14);
|
||||||
|
|
||||||
|
SetHttpLimit();
|
||||||
|
progress.Report(15);
|
||||||
|
|
||||||
|
var innerProgress = new ActionableProgress<double>();
|
||||||
|
innerProgress.RegisterAction(p => progress.Report(.8 * p + 15));
|
||||||
|
|
||||||
|
await RegisterResources(innerProgress).ConfigureAwait(false);
|
||||||
|
|
||||||
|
FindParts();
|
||||||
|
progress.Report(95);
|
||||||
|
|
||||||
|
await InstallIsoMounters(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
|
progress.Report(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract IMemoryStreamProvider CreateMemoryStreamProvider();
|
||||||
|
protected abstract ISystemEvents CreateSystemEvents();
|
||||||
|
|
||||||
|
protected virtual void OnLoggerLoaded(bool isFirstLoad)
|
||||||
|
{
|
||||||
|
Logger.Info("Application version: {0}", ApplicationVersion);
|
||||||
|
|
||||||
|
if (!isFirstLoad)
|
||||||
|
{
|
||||||
|
LogEnvironmentInfo(Logger, ApplicationPaths, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the app config in the log for troubleshooting purposes
|
||||||
|
Logger.LogMultiline("Application configuration:", LogSeverity.Info, new StringBuilder(JsonSerializer.SerializeToString(ConfigurationManager.CommonConfiguration)));
|
||||||
|
|
||||||
|
if (Plugins != null)
|
||||||
|
{
|
||||||
|
var pluginBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
|
{
|
||||||
|
pluginBuilder.AppendLine(string.Format("{0} {1}", plugin.Name, plugin.Version));
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogMultiline("Plugins:", LogSeverity.Info, pluginBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, bool isStartup)
|
||||||
|
{
|
||||||
|
logger.LogMultiline("Emby", LogSeverity.Info, GetBaseExceptionMessage(appPaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static StringBuilder GetBaseExceptionMessage(IApplicationPaths appPaths)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.AppendLine(string.Format("Command line: {0}", string.Join(" ", Environment.GetCommandLineArgs())));
|
||||||
|
|
||||||
|
builder.AppendLine(string.Format("Operating system: {0}", Environment.OSVersion));
|
||||||
|
builder.AppendLine(string.Format("Processor count: {0}", Environment.ProcessorCount));
|
||||||
|
builder.AppendLine(string.Format("64-Bit OS: {0}", Environment.Is64BitOperatingSystem));
|
||||||
|
builder.AppendLine(string.Format("64-Bit Process: {0}", Environment.Is64BitProcess));
|
||||||
|
builder.AppendLine(string.Format("Program data path: {0}", appPaths.ProgramDataPath));
|
||||||
|
|
||||||
|
Type type = Type.GetType("Mono.Runtime");
|
||||||
|
if (type != null)
|
||||||
|
{
|
||||||
|
MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
|
if (displayName != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine("Mono: " + displayName.Invoke(null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine(string.Format("Application Path: {0}", appPaths.ApplicationPath));
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract IJsonSerializer CreateJsonSerializer();
|
||||||
|
|
||||||
|
private void SetHttpLimit()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Increase the max http request limit
|
||||||
|
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error setting http limit", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Installs the iso mounters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
private async Task InstallIsoMounters(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var list = new List<IIsoMounter>();
|
||||||
|
|
||||||
|
foreach (var isoMounter in GetExports<IIsoMounter>())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isoMounter.RequiresInstallation && !isoMounter.IsInstalled)
|
||||||
|
{
|
||||||
|
Logger.Info("Installing {0}", isoMounter.Name);
|
||||||
|
|
||||||
|
await isoMounter.Install(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(isoMounter);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("{0} failed to load.", ex, isoMounter.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IsoManager.AddParts(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the startup tasks.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
public virtual Task RunStartupTasks()
|
||||||
|
{
|
||||||
|
Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
|
||||||
|
|
||||||
|
ConfigureAutorun();
|
||||||
|
|
||||||
|
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the autorun.
|
||||||
|
/// </summary>
|
||||||
|
private void ConfigureAutorun()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConfigureAutoRunAtStartup(ConfigurationManager.CommonConfiguration.RunAtStartup);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error configuring autorun", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the composable part assemblies.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IEnumerable{Assembly}.</returns>
|
||||||
|
protected abstract IEnumerable<Assembly> GetComposablePartAssemblies();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configuration manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IConfigurationManager.</returns>
|
||||||
|
protected abstract IConfigurationManager GetConfigurationManager();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the parts.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void FindParts()
|
||||||
|
{
|
||||||
|
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||||
|
Plugins = GetExports<IPlugin>().Select(LoadPlugin).Where(i => i != null).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IPlugin LoadPlugin(IPlugin plugin)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assemblyPlugin = plugin as IPluginAssembly;
|
||||||
|
|
||||||
|
if (assemblyPlugin != null)
|
||||||
|
{
|
||||||
|
var assembly = plugin.GetType().Assembly;
|
||||||
|
var assemblyName = assembly.GetName();
|
||||||
|
|
||||||
|
var attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute), true)[0];
|
||||||
|
var assemblyId = new Guid(attribute.Value);
|
||||||
|
|
||||||
|
var assemblyFileName = assemblyName.Name + ".dll";
|
||||||
|
var assemblyFilePath = Path.Combine(ApplicationPaths.PluginsPath, assemblyFileName);
|
||||||
|
|
||||||
|
assemblyPlugin.SetAttributes(assemblyFilePath, assemblyFileName, assemblyName.Version, assemblyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var isFirstRun = !File.Exists(plugin.ConfigurationFilePath);
|
||||||
|
|
||||||
|
plugin.SetStartupInfo(isFirstRun, File.GetLastWriteTimeUtc, s => Directory.CreateDirectory(s));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error loading plugin {0}", ex, plugin.GetType().FullName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers the types.
|
||||||
|
/// </summary>
|
||||||
|
protected void DiscoverTypes()
|
||||||
|
{
|
||||||
|
FailedAssemblies.Clear();
|
||||||
|
|
||||||
|
var assemblies = GetComposablePartAssemblies().ToList();
|
||||||
|
|
||||||
|
foreach (var assembly in assemblies)
|
||||||
|
{
|
||||||
|
Logger.Info("Loading {0}", assembly.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
AllConcreteTypes = assemblies
|
||||||
|
.SelectMany(GetTypes)
|
||||||
|
.Where(t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers resources that classes will depend on
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
protected virtual Task RegisterResources(IProgress<double> progress)
|
||||||
|
{
|
||||||
|
RegisterSingleInstance(ConfigurationManager);
|
||||||
|
RegisterSingleInstance<IApplicationHost>(this);
|
||||||
|
|
||||||
|
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
|
||||||
|
|
||||||
|
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LogManager.GetLogger("TaskManager"), FileSystemManager, SystemEvents);
|
||||||
|
|
||||||
|
RegisterSingleInstance(JsonSerializer);
|
||||||
|
RegisterSingleInstance(XmlSerializer);
|
||||||
|
RegisterSingleInstance(MemoryStreamProvider);
|
||||||
|
RegisterSingleInstance(SystemEvents);
|
||||||
|
|
||||||
|
RegisterSingleInstance(LogManager);
|
||||||
|
RegisterSingleInstance(Logger);
|
||||||
|
|
||||||
|
RegisterSingleInstance(TaskManager);
|
||||||
|
|
||||||
|
RegisterSingleInstance(FileSystemManager);
|
||||||
|
|
||||||
|
HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamProvider);
|
||||||
|
RegisterSingleInstance(HttpClient);
|
||||||
|
|
||||||
|
NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager"));
|
||||||
|
RegisterSingleInstance(NetworkManager);
|
||||||
|
|
||||||
|
IsoManager = new IsoManager();
|
||||||
|
RegisterSingleInstance(IsoManager);
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of types within an assembly
|
||||||
|
/// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly">The assembly.</param>
|
||||||
|
/// <returns>IEnumerable{Type}.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">assembly</exception>
|
||||||
|
protected IEnumerable<Type> GetTypes(Assembly assembly)
|
||||||
|
{
|
||||||
|
if (assembly == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("assembly");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return assembly.GetTypes();
|
||||||
|
}
|
||||||
|
catch (ReflectionTypeLoadException ex)
|
||||||
|
{
|
||||||
|
if (ex.LoaderExceptions != null)
|
||||||
|
{
|
||||||
|
foreach (var loaderException in ex.LoaderExceptions)
|
||||||
|
{
|
||||||
|
Logger.Error("LoaderException: " + loaderException.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it fails we can still get a list of the Types it was able to resolve
|
||||||
|
return ex.Types.Where(t => t != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract INetworkManager CreateNetworkManager(ILogger logger);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance of type and resolves all constructor dependancies
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
public abstract object CreateInstance(Type type);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the instance safe.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
protected abstract object CreateInstanceSafe(Type type);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the specified obj.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="obj">The obj.</param>
|
||||||
|
/// <param name="manageLifetime">if set to <c>true</c> [manage lifetime].</param>
|
||||||
|
protected abstract void RegisterSingleInstance<T>(T obj, bool manageLifetime = true)
|
||||||
|
where T : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the single instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="func">The func.</param>
|
||||||
|
protected abstract void RegisterSingleInstance<T>(Func<T> func)
|
||||||
|
where T : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns>``0.</returns>
|
||||||
|
public abstract T Resolve<T>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns>``0.</returns>
|
||||||
|
public abstract T TryResolve<T>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the assembly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The file.</param>
|
||||||
|
/// <returns>Assembly.</returns>
|
||||||
|
protected Assembly LoadAssembly(string file)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Assembly.Load(File.ReadAllBytes(file));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
FailedAssemblies.Add(file);
|
||||||
|
Logger.ErrorException("Error loading assembly {0}", ex, file);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the export types.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns>IEnumerable{Type}.</returns>
|
||||||
|
public IEnumerable<Type> GetExportTypes<T>()
|
||||||
|
{
|
||||||
|
var currentType = typeof(T);
|
||||||
|
|
||||||
|
return AllConcreteTypes.AsParallel().Where(currentType.IsAssignableFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the exports.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="manageLiftime">if set to <c>true</c> [manage liftime].</param>
|
||||||
|
/// <returns>IEnumerable{``0}.</returns>
|
||||||
|
public IEnumerable<T> GetExports<T>(bool manageLiftime = true)
|
||||||
|
{
|
||||||
|
var parts = GetExportTypes<T>()
|
||||||
|
.Select(CreateInstanceSafe)
|
||||||
|
.Where(i => i != null)
|
||||||
|
.Cast<T>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (manageLiftime)
|
||||||
|
{
|
||||||
|
lock (DisposableParts)
|
||||||
|
{
|
||||||
|
DisposableParts.AddRange(parts.OfType<IDisposable>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the application version.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The application version.</value>
|
||||||
|
public abstract Version ApplicationVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the ConfigurationUpdated event of the ConfigurationManager control.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The source of the event.</param>
|
||||||
|
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
|
||||||
|
/// <exception cref="System.NotImplementedException"></exception>
|
||||||
|
protected virtual void OnConfigurationUpdated(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ConfigureAutorun();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void ConfigureAutoRunAtStartup(bool autorun);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the plugin.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">The plugin.</param>
|
||||||
|
public void RemovePlugin(IPlugin plugin)
|
||||||
|
{
|
||||||
|
var list = Plugins.ToList();
|
||||||
|
list.Remove(plugin);
|
||||||
|
Plugins = list.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this instance can self restart.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
|
||||||
|
public abstract bool CanSelfRestart { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notifies that the kernel that a change has been made that requires a restart
|
||||||
|
/// </summary>
|
||||||
|
public void NotifyPendingRestart()
|
||||||
|
{
|
||||||
|
var changed = !HasPendingRestart;
|
||||||
|
|
||||||
|
HasPendingRestart = true;
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, Logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
var type = GetType();
|
||||||
|
|
||||||
|
Logger.Info("Disposing " + type.Name);
|
||||||
|
|
||||||
|
var parts = DisposableParts.Distinct().Where(i => i.GetType() != type).ToList();
|
||||||
|
DisposableParts.Clear();
|
||||||
|
|
||||||
|
foreach (var part in parts)
|
||||||
|
{
|
||||||
|
Logger.Info("Disposing " + part.GetType().Name);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
part.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error disposing {0}", ex, part.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restarts this instance.
|
||||||
|
/// </summary>
|
||||||
|
public abstract Task Restart();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this instance can self update.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
|
||||||
|
public abstract bool CanSelfUpdate { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks for update.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <returns>Task{CheckForUpdateResult}.</returns>
|
||||||
|
public abstract Task<CheckForUpdateResult> CheckForApplicationUpdate(CancellationToken cancellationToken,
|
||||||
|
IProgress<double> progress);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the application.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="package">The package that contains the update</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
public abstract Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken,
|
||||||
|
IProgress<double> progress);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shuts down.
|
||||||
|
/// </summary>
|
||||||
|
public abstract Task Shutdown();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [application updated].
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="package">The package.</param>
|
||||||
|
protected void OnApplicationUpdated(PackageVersionInfo package)
|
||||||
|
{
|
||||||
|
Logger.Info("Application has been updated to version {0}", package.versionStr);
|
||||||
|
|
||||||
|
EventHelper.FireEventIfNotNull(ApplicationUpdated, this, new GenericEventArgs<PackageVersionInfo>
|
||||||
|
{
|
||||||
|
Argument = package
|
||||||
|
|
||||||
|
}, Logger);
|
||||||
|
|
||||||
|
NotifyPendingRestart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
178
Emby.Common.Implementations/BaseApplicationPaths.cs
Normal file
178
Emby.Common.Implementations/BaseApplicationPaths.cs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
using System.IO;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a base class to hold common application paths used by both the Ui and Server.
|
||||||
|
/// This can be subclassed to add application-specific paths.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BaseApplicationPaths : IApplicationPaths
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
||||||
|
/// </summary>
|
||||||
|
protected BaseApplicationPaths(string programDataPath, string applicationPath)
|
||||||
|
{
|
||||||
|
ProgramDataPath = programDataPath;
|
||||||
|
ApplicationPath = applicationPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ApplicationPath { get; private set; }
|
||||||
|
public string ProgramDataPath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to the system folder
|
||||||
|
/// </summary>
|
||||||
|
public string ProgramSystemPath
|
||||||
|
{
|
||||||
|
get { return Path.GetDirectoryName(ApplicationPath); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _data directory
|
||||||
|
/// </summary>
|
||||||
|
private string _dataDirectory;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the folder path to the data directory
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The data directory.</value>
|
||||||
|
public string DataPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_dataDirectory == null)
|
||||||
|
{
|
||||||
|
_dataDirectory = Path.Combine(ProgramDataPath, "data");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(_dataDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _dataDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the image cache path.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The image cache path.</value>
|
||||||
|
public string ImageCachePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(CachePath, "images");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to the plugin directory
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The plugins path.</value>
|
||||||
|
public string PluginsPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(ProgramDataPath, "plugins");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to the plugin configurations directory
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The plugin configurations path.</value>
|
||||||
|
public string PluginConfigurationsPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(PluginsPath, "configurations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to where temporary update files will be stored
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The plugin configurations path.</value>
|
||||||
|
public string TempUpdatePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(ProgramDataPath, "updates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to the log directory
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The log directory path.</value>
|
||||||
|
public string LogDirectoryPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(ProgramDataPath, "logs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to the application configuration root directory
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The configuration directory path.</value>
|
||||||
|
public string ConfigurationDirectoryPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(ProgramDataPath, "config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to the system configuration file
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The system configuration file path.</value>
|
||||||
|
public string SystemConfigurationFilePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(ConfigurationDirectoryPath, "system.xml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _cache directory
|
||||||
|
/// </summary>
|
||||||
|
private string _cachePath;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the folder path to the cache directory
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The cache directory.</value>
|
||||||
|
public string CachePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_cachePath))
|
||||||
|
{
|
||||||
|
_cachePath = Path.Combine(ProgramDataPath, "cache");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(_cachePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _cachePath;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_cachePath = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the folder path to the temp directory within the cache folder
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The temp directory.</value>
|
||||||
|
public string TempDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(CachePath, "temp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Events;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using Emby.Common.Implementations;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.Configuration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class BaseConfigurationManager
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BaseConfigurationManager : IConfigurationManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The type of the configuration.</value>
|
||||||
|
protected abstract Type ConfigurationType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [configuration updated].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> ConfigurationUpdated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [configuration updating].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdating;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [named configuration updated].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The logger.</value>
|
||||||
|
protected ILogger Logger { get; private set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the XML serializer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The XML serializer.</value>
|
||||||
|
protected IXmlSerializer XmlSerializer { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the application paths.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The application paths.</value>
|
||||||
|
public IApplicationPaths CommonApplicationPaths { get; private set; }
|
||||||
|
public readonly IFileSystem FileSystem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _configuration loaded
|
||||||
|
/// </summary>
|
||||||
|
private bool _configurationLoaded;
|
||||||
|
/// <summary>
|
||||||
|
/// The _configuration sync lock
|
||||||
|
/// </summary>
|
||||||
|
private object _configurationSyncLock = new object();
|
||||||
|
/// <summary>
|
||||||
|
/// The _configuration
|
||||||
|
/// </summary>
|
||||||
|
private BaseApplicationConfiguration _configuration;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the system configuration
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The configuration.</value>
|
||||||
|
public BaseApplicationConfiguration CommonConfiguration
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// Lazy load
|
||||||
|
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem));
|
||||||
|
return _configuration;
|
||||||
|
}
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
_configuration = value;
|
||||||
|
|
||||||
|
_configurationLoaded = value != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigurationStore[] _configurationStores = { };
|
||||||
|
private IConfigurationFactory[] _configurationFactories = { };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="applicationPaths">The application paths.</param>
|
||||||
|
/// <param name="logManager">The log manager.</param>
|
||||||
|
/// <param name="xmlSerializer">The XML serializer.</param>
|
||||||
|
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILogManager logManager, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
CommonApplicationPaths = applicationPaths;
|
||||||
|
XmlSerializer = xmlSerializer;
|
||||||
|
FileSystem = fileSystem;
|
||||||
|
Logger = logManager.GetLogger(GetType().Name);
|
||||||
|
|
||||||
|
UpdateCachePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void AddParts(IEnumerable<IConfigurationFactory> factories)
|
||||||
|
{
|
||||||
|
_configurationFactories = factories.ToArray();
|
||||||
|
|
||||||
|
_configurationStores = _configurationFactories
|
||||||
|
.SelectMany(i => i.GetConfigurations())
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the configuration.
|
||||||
|
/// </summary>
|
||||||
|
public void SaveConfiguration()
|
||||||
|
{
|
||||||
|
Logger.Info("Saving system configuration");
|
||||||
|
var path = CommonApplicationPaths.SystemConfigurationFilePath;
|
||||||
|
|
||||||
|
FileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
|
lock (_configurationSyncLock)
|
||||||
|
{
|
||||||
|
XmlSerializer.SerializeToFile(CommonConfiguration, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnConfigurationUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [configuration updated].
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnConfigurationUpdated()
|
||||||
|
{
|
||||||
|
UpdateCachePath();
|
||||||
|
|
||||||
|
EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces the configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newConfiguration">The new configuration.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">newConfiguration</exception>
|
||||||
|
public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
|
||||||
|
{
|
||||||
|
if (newConfiguration == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("newConfiguration");
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateCachePath(newConfiguration);
|
||||||
|
|
||||||
|
CommonConfiguration = newConfiguration;
|
||||||
|
SaveConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the items by name path.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateCachePath()
|
||||||
|
{
|
||||||
|
string cachePath;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
|
||||||
|
{
|
||||||
|
cachePath = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cachePath = Path.Combine(CommonConfiguration.CachePath, "cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces the cache path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newConfig">The new configuration.</param>
|
||||||
|
/// <exception cref="System.IO.DirectoryNotFoundException"></exception>
|
||||||
|
private void ValidateCachePath(BaseApplicationConfiguration newConfig)
|
||||||
|
{
|
||||||
|
var newPath = newConfig.CachePath;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(newPath)
|
||||||
|
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
|
||||||
|
{
|
||||||
|
// Validate
|
||||||
|
if (!FileSystem.DirectoryExists(newPath))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureWriteAccess(newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void EnsureWriteAccess(string path)
|
||||||
|
{
|
||||||
|
var file = Path.Combine(path, Guid.NewGuid().ToString());
|
||||||
|
|
||||||
|
FileSystem.WriteAllText(file, string.Empty);
|
||||||
|
FileSystem.DeleteFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
|
||||||
|
|
||||||
|
private string GetConfigurationFile(string key)
|
||||||
|
{
|
||||||
|
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLower() + ".xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public object GetConfiguration(string key)
|
||||||
|
{
|
||||||
|
return _configurations.GetOrAdd(key, k =>
|
||||||
|
{
|
||||||
|
var file = GetConfigurationFile(key);
|
||||||
|
|
||||||
|
var configurationInfo = _configurationStores
|
||||||
|
.FirstOrDefault(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (configurationInfo == null)
|
||||||
|
{
|
||||||
|
throw new ResourceNotFoundException("Configuration with key " + key + " not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var configurationType = configurationInfo.ConfigurationType;
|
||||||
|
|
||||||
|
lock (_configurationSyncLock)
|
||||||
|
{
|
||||||
|
return LoadConfiguration(file, configurationType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private object LoadConfiguration(string path, Type configurationType)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return XmlSerializer.DeserializeFromFile(configurationType, path);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
return Activator.CreateInstance(configurationType);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
return Activator.CreateInstance(configurationType);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error loading configuration file: {0}", ex, path);
|
||||||
|
|
||||||
|
return Activator.CreateInstance(configurationType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveConfiguration(string key, object configuration)
|
||||||
|
{
|
||||||
|
var configurationStore = GetConfigurationStore(key);
|
||||||
|
var configurationType = configurationStore.ConfigurationType;
|
||||||
|
|
||||||
|
if (configuration.GetType() != configurationType)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Expected configuration type is " + configurationType.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
var validatingStore = configurationStore as IValidatingConfiguration;
|
||||||
|
if (validatingStore != null)
|
||||||
|
{
|
||||||
|
var currentConfiguration = GetConfiguration(key);
|
||||||
|
|
||||||
|
validatingStore.Validate(currentConfiguration, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventHelper.FireEventIfNotNull(NamedConfigurationUpdating, this, new ConfigurationUpdateEventArgs
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
NewConfiguration = configuration
|
||||||
|
|
||||||
|
}, Logger);
|
||||||
|
|
||||||
|
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
||||||
|
|
||||||
|
var path = GetConfigurationFile(key);
|
||||||
|
FileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
|
lock (_configurationSyncLock)
|
||||||
|
{
|
||||||
|
XmlSerializer.SerializeToFile(configuration, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnNamedConfigurationUpdated(key, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnNamedConfigurationUpdated(string key, object configuration)
|
||||||
|
{
|
||||||
|
EventHelper.FireEventIfNotNull(NamedConfigurationUpdated, this, new ConfigurationUpdateEventArgs
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
NewConfiguration = configuration
|
||||||
|
|
||||||
|
}, Logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type GetConfigurationType(string key)
|
||||||
|
{
|
||||||
|
return GetConfigurationStore(key)
|
||||||
|
.ConfigurationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigurationStore GetConfigurationStore(string key)
|
||||||
|
{
|
||||||
|
return _configurationStores
|
||||||
|
.First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.Configuration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class ConfigurationHelper
|
||||||
|
/// </summary>
|
||||||
|
public static class ConfigurationHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads an xml configuration file from the file system
|
||||||
|
/// It will immediately re-serialize and save if new serialization data is available due to property changes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <param name="xmlSerializer">The XML serializer.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
object configuration;
|
||||||
|
|
||||||
|
byte[] buffer = null;
|
||||||
|
|
||||||
|
// Use try/catch to avoid the extra file system lookup using File.Exists
|
||||||
|
try
|
||||||
|
{
|
||||||
|
buffer = fileSystem.ReadAllBytes(path);
|
||||||
|
|
||||||
|
configuration = xmlSerializer.DeserializeFromBytes(type, buffer);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
configuration = Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
xmlSerializer.SerializeToStream(configuration, stream);
|
||||||
|
|
||||||
|
// Take the object we just got and serialize it back to bytes
|
||||||
|
var newBytes = stream.ToArray();
|
||||||
|
|
||||||
|
// If the file didn't exist before, or if something has changed, re-save
|
||||||
|
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
||||||
|
{
|
||||||
|
fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
|
// Save it after load in case we got new items
|
||||||
|
fileSystem.WriteAllBytes(path, newBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.Cryptography
|
||||||
|
{
|
||||||
|
public class CryptographyProvider : ICryptographyProvider
|
||||||
|
{
|
||||||
|
public Guid GetMD5(string str)
|
||||||
|
{
|
||||||
|
return new Guid(GetMD5Bytes(str));
|
||||||
|
}
|
||||||
|
public byte[] GetMD5Bytes(string str)
|
||||||
|
{
|
||||||
|
using (var provider = MD5.Create())
|
||||||
|
{
|
||||||
|
return provider.ComputeHash(Encoding.Unicode.GetBytes(str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public byte[] GetMD5Bytes(Stream str)
|
||||||
|
{
|
||||||
|
using (var provider = MD5.Create())
|
||||||
|
{
|
||||||
|
return provider.ComputeHash(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
Emby.Common.Implementations/Devices/DeviceId.cs
Normal file
109
Emby.Common.Implementations/Devices/DeviceId.cs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.Devices
|
||||||
|
{
|
||||||
|
public class DeviceId
|
||||||
|
{
|
||||||
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
|
private readonly object _syncLock = new object();
|
||||||
|
|
||||||
|
private string CachePath
|
||||||
|
{
|
||||||
|
get { return Path.Combine(_appPaths.DataPath, "device.txt"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCachedId()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
var value = File.ReadAllText(CachePath, Encoding.UTF8);
|
||||||
|
|
||||||
|
Guid guid;
|
||||||
|
if (Guid.TryParse(value, out guid))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Error("Invalid value found in device id file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error reading file", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveId(string id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = CachePath;
|
||||||
|
|
||||||
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_fileSystem.WriteAllText(path, id, Encoding.UTF8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error writing to file", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetNewId()
|
||||||
|
{
|
||||||
|
return Guid.NewGuid().ToString("N");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDeviceId()
|
||||||
|
{
|
||||||
|
var id = GetCachedId();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
{
|
||||||
|
id = GetNewId();
|
||||||
|
SaveId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _id;
|
||||||
|
|
||||||
|
public DeviceId(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
if (fileSystem == null) {
|
||||||
|
throw new ArgumentNullException ("fileSystem");
|
||||||
|
}
|
||||||
|
|
||||||
|
_appPaths = appPaths;
|
||||||
|
_logger = logger;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get { return _id ?? (_id = GetDeviceId()); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>5a27010a-09c6-4e86-93ea-437484c10917</ProjectGuid>
|
||||||
|
<RootNamespace>Emby.Common.Implementations</RootNamespace>
|
||||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||||
|
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||||
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.HttpClientManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class HttpClientInfo
|
||||||
|
/// </summary>
|
||||||
|
public class HttpClientInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the last timeout.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The last timeout.</value>
|
||||||
|
public DateTime LastTimeout { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,936 @@
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Cache;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Emby.Common.Implementations.HttpClientManager;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.HttpClientManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class HttpClientManager
|
||||||
|
/// </summary>
|
||||||
|
public class HttpClientManager : IHttpClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When one request to a host times out, we'll ban all other requests for this period of time, to prevent scans from stalling
|
||||||
|
/// </summary>
|
||||||
|
private const int TimeoutSeconds = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _logger
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _app paths
|
||||||
|
/// </summary>
|
||||||
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IMemoryStreamProvider _memoryStreamProvider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="HttpClientManager" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appPaths">The app paths.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="fileSystem">The file system.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">appPaths
|
||||||
|
/// or
|
||||||
|
/// logger</exception>
|
||||||
|
public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamProvider memoryStreamProvider)
|
||||||
|
{
|
||||||
|
if (appPaths == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("appPaths");
|
||||||
|
}
|
||||||
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("logger");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger = logger;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_memoryStreamProvider = memoryStreamProvider;
|
||||||
|
_appPaths = appPaths;
|
||||||
|
|
||||||
|
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
|
||||||
|
ServicePointManager.Expect100Continue = false;
|
||||||
|
|
||||||
|
// Trakt requests sometimes fail without this
|
||||||
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
|
||||||
|
/// DON'T dispose it after use.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The HTTP clients.</value>
|
||||||
|
private readonly ConcurrentDictionary<string, HttpClientInfo> _httpClients = new ConcurrentDictionary<string, HttpClientInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">The host.</param>
|
||||||
|
/// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param>
|
||||||
|
/// <returns>HttpClient.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">host</exception>
|
||||||
|
private HttpClientInfo GetHttpClient(string host, bool enableHttpCompression)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(host))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("host");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClientInfo client;
|
||||||
|
|
||||||
|
var key = host + enableHttpCompression;
|
||||||
|
|
||||||
|
if (!_httpClients.TryGetValue(key, out client))
|
||||||
|
{
|
||||||
|
client = new HttpClientInfo();
|
||||||
|
|
||||||
|
_httpClients.TryAdd(key, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebRequest CreateWebRequest(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return WebRequest.Create(url);
|
||||||
|
}
|
||||||
|
catch (NotSupportedException)
|
||||||
|
{
|
||||||
|
//Webrequest creation does fail on MONO randomly when using WebRequest.Create
|
||||||
|
//the issue occurs in the GetCreator method here: http://www.oschina.net/code/explore/mono-2.8.1/mcs/class/System/System.Net/WebRequest.cs
|
||||||
|
|
||||||
|
var type = Type.GetType("System.Net.HttpRequestCreator, System, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089");
|
||||||
|
var creator = Activator.CreateInstance(type, nonPublic: true) as IWebRequestCreate;
|
||||||
|
return creator.Create(new Uri(url)) as HttpWebRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddIpv4Option(HttpWebRequest request, HttpRequestOptions options)
|
||||||
|
{
|
||||||
|
request.ServicePoint.BindIPEndPointDelegate = (servicePount, remoteEndPoint, retryCount) =>
|
||||||
|
{
|
||||||
|
if (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
return new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException("no IPv4 address");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebRequest GetRequest(HttpRequestOptions options, string method)
|
||||||
|
{
|
||||||
|
var url = options.Url;
|
||||||
|
|
||||||
|
var uriAddress = new Uri(url);
|
||||||
|
var userInfo = uriAddress.UserInfo;
|
||||||
|
if (!string.IsNullOrWhiteSpace(userInfo))
|
||||||
|
{
|
||||||
|
_logger.Info("Found userInfo in url: {0} ... url: {1}", userInfo, url);
|
||||||
|
url = url.Replace(userInfo + "@", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = CreateWebRequest(url);
|
||||||
|
var httpWebRequest = request as HttpWebRequest;
|
||||||
|
|
||||||
|
if (httpWebRequest != null)
|
||||||
|
{
|
||||||
|
if (options.PreferIpv4)
|
||||||
|
{
|
||||||
|
AddIpv4Option(httpWebRequest, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddRequestHeaders(httpWebRequest, options);
|
||||||
|
|
||||||
|
httpWebRequest.AutomaticDecompression = options.EnableHttpCompression ?
|
||||||
|
(options.DecompressionMethod ?? DecompressionMethods.Deflate) :
|
||||||
|
DecompressionMethods.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
|
||||||
|
|
||||||
|
if (httpWebRequest != null)
|
||||||
|
{
|
||||||
|
if (options.EnableKeepAlive)
|
||||||
|
{
|
||||||
|
httpWebRequest.KeepAlive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Method = method;
|
||||||
|
request.Timeout = options.TimeoutMs;
|
||||||
|
|
||||||
|
if (httpWebRequest != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(options.Host))
|
||||||
|
{
|
||||||
|
httpWebRequest.Host = options.Host;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(options.Referer))
|
||||||
|
{
|
||||||
|
httpWebRequest.Referer = options.Referer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(userInfo))
|
||||||
|
{
|
||||||
|
var parts = userInfo.Split(':');
|
||||||
|
if (parts.Length == 2)
|
||||||
|
{
|
||||||
|
request.Credentials = GetCredential(url, parts[0], parts[1]);
|
||||||
|
request.PreAuthenticate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialCache GetCredential(string url, string username, string password)
|
||||||
|
{
|
||||||
|
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
|
||||||
|
CredentialCache credentialCache = new CredentialCache();
|
||||||
|
credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
|
||||||
|
return credentialCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
|
||||||
|
{
|
||||||
|
foreach (var header in options.RequestHeaders.ToList())
|
||||||
|
{
|
||||||
|
if (string.Equals(header.Key, "Accept", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
request.Accept = header.Value;
|
||||||
|
}
|
||||||
|
else if (string.Equals(header.Key, "User-Agent", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
request.UserAgent = header.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
request.Headers.Set(header.Key, header.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the response internal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The options.</param>
|
||||||
|
/// <returns>Task{HttpResponseInfo}.</returns>
|
||||||
|
public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
|
||||||
|
{
|
||||||
|
return SendAsync(options, "GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a GET request and returns the resulting stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The options.</param>
|
||||||
|
/// <returns>Task{Stream}.</returns>
|
||||||
|
public async Task<Stream> Get(HttpRequestOptions options)
|
||||||
|
{
|
||||||
|
var response = await GetResponse(options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return response.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a GET request and returns the resulting stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The URL.</param>
|
||||||
|
/// <param name="resourcePool">The resource pool.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task{Stream}.</returns>
|
||||||
|
public Task<Stream> Get(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Get(new HttpRequestOptions
|
||||||
|
{
|
||||||
|
Url = url,
|
||||||
|
ResourcePool = resourcePool,
|
||||||
|
CancellationToken = cancellationToken,
|
||||||
|
BufferContent = resourcePool != null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified URL.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The URL.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task{Stream}.</returns>
|
||||||
|
public Task<Stream> Get(string url, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Get(url, null, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// send as an asynchronous operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The options.</param>
|
||||||
|
/// <param name="httpMethod">The HTTP method.</param>
|
||||||
|
/// <returns>Task{HttpResponseInfo}.</returns>
|
||||||
|
/// <exception cref="HttpException">
|
||||||
|
/// </exception>
|
||||||
|
public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
|
||||||
|
{
|
||||||
|
if (options.CacheMode == CacheMode.None)
|
||||||
|
{
|
||||||
|
return await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = options.Url;
|
||||||
|
var urlHash = url.ToLower().GetMD5().ToString("N");
|
||||||
|
|
||||||
|
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
|
||||||
|
|
||||||
|
var response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false);
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (response.StatusCode == HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
await CacheResponse(response, responseCachePath).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<HttpResponseInfo> GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
|
||||||
|
{
|
||||||
|
_logger.Info("Checking for cache file {0}", responseCachePath);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
using (var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
|
||||||
|
{
|
||||||
|
var memoryStream = _memoryStreamProvider.CreateNew();
|
||||||
|
|
||||||
|
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
|
return new HttpResponseInfo
|
||||||
|
{
|
||||||
|
ResponseUrl = url,
|
||||||
|
Content = memoryStream,
|
||||||
|
StatusCode = HttpStatusCode.OK,
|
||||||
|
ContentLength = memoryStream.Length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CacheResponse(HttpResponseInfo response, string responseCachePath)
|
||||||
|
{
|
||||||
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(responseCachePath));
|
||||||
|
|
||||||
|
using (var responseStream = response.Content)
|
||||||
|
{
|
||||||
|
var memoryStream = _memoryStreamProvider.CreateNew();
|
||||||
|
await responseStream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
|
using (var fileStream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
|
||||||
|
{
|
||||||
|
await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||||
|
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
response.Content = memoryStream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, string httpMethod)
|
||||||
|
{
|
||||||
|
ValidateParams(options);
|
||||||
|
|
||||||
|
options.CancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression);
|
||||||
|
|
||||||
|
if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds)
|
||||||
|
{
|
||||||
|
throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url))
|
||||||
|
{
|
||||||
|
IsTimedOut = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpWebRequest = GetRequest(options, httpMethod);
|
||||||
|
|
||||||
|
if (options.RequestContentBytes != null ||
|
||||||
|
!string.IsNullOrEmpty(options.RequestContent) ||
|
||||||
|
string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var bytes = options.RequestContentBytes ??
|
||||||
|
Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty);
|
||||||
|
|
||||||
|
httpWebRequest.ContentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
|
||||||
|
|
||||||
|
httpWebRequest.ContentLength = bytes.Length;
|
||||||
|
httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.ResourcePool != null)
|
||||||
|
{
|
||||||
|
await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds)
|
||||||
|
{
|
||||||
|
if (options.ResourcePool != null)
|
||||||
|
{
|
||||||
|
options.ResourcePool.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HttpException(string.Format("Connection to {0} timed out", options.Url)) { IsTimedOut = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.LogRequest)
|
||||||
|
{
|
||||||
|
_logger.Info("HttpClientManager {0}: {1}", httpMethod.ToUpper(), options.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
options.CancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
if (!options.BufferContent)
|
||||||
|
{
|
||||||
|
var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var httpResponse = (HttpWebResponse)response;
|
||||||
|
|
||||||
|
EnsureSuccessStatusCode(client, httpResponse, options);
|
||||||
|
|
||||||
|
options.CancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse), httpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var httpResponse = (HttpWebResponse)response;
|
||||||
|
|
||||||
|
EnsureSuccessStatusCode(client, httpResponse, options);
|
||||||
|
|
||||||
|
options.CancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
using (var stream = httpResponse.GetResponseStream())
|
||||||
|
{
|
||||||
|
var memoryStream = _memoryStreamProvider.CreateNew();
|
||||||
|
|
||||||
|
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||||
|
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
|
return GetResponseInfo(httpResponse, memoryStream, memoryStream.Length, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex)
|
||||||
|
{
|
||||||
|
throw GetCancellationException(options, client, options.CancellationToken, ex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw GetException(ex, options, client);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (options.ResourcePool != null)
|
||||||
|
{
|
||||||
|
options.ResourcePool.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable)
|
||||||
|
{
|
||||||
|
var responseInfo = new HttpResponseInfo(disposable)
|
||||||
|
{
|
||||||
|
Content = content,
|
||||||
|
|
||||||
|
StatusCode = httpResponse.StatusCode,
|
||||||
|
|
||||||
|
ContentType = httpResponse.ContentType,
|
||||||
|
|
||||||
|
ContentLength = contentLength,
|
||||||
|
|
||||||
|
ResponseUrl = httpResponse.ResponseUri.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (httpResponse.Headers != null)
|
||||||
|
{
|
||||||
|
SetHeaders(httpResponse.Headers, responseInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, string tempFile, long? contentLength)
|
||||||
|
{
|
||||||
|
var responseInfo = new HttpResponseInfo
|
||||||
|
{
|
||||||
|
TempFilePath = tempFile,
|
||||||
|
|
||||||
|
StatusCode = httpResponse.StatusCode,
|
||||||
|
|
||||||
|
ContentType = httpResponse.ContentType,
|
||||||
|
|
||||||
|
ContentLength = contentLength
|
||||||
|
};
|
||||||
|
|
||||||
|
if (httpResponse.Headers != null)
|
||||||
|
{
|
||||||
|
SetHeaders(httpResponse.Headers, responseInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetHeaders(WebHeaderCollection headers, HttpResponseInfo responseInfo)
|
||||||
|
{
|
||||||
|
foreach (var key in headers.AllKeys)
|
||||||
|
{
|
||||||
|
responseInfo.Headers[key] = headers[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<HttpResponseInfo> Post(HttpRequestOptions options)
|
||||||
|
{
|
||||||
|
return SendAsync(options, "POST");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a POST request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The options.</param>
|
||||||
|
/// <param name="postData">Params to add to the POST data.</param>
|
||||||
|
/// <returns>stream on success, null on failure</returns>
|
||||||
|
public async Task<Stream> Post(HttpRequestOptions options, Dictionary<string, string> postData)
|
||||||
|
{
|
||||||
|
options.SetPostData(postData);
|
||||||
|
|
||||||
|
var response = await Post(options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return response.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a POST request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The URL.</param>
|
||||||
|
/// <param name="postData">Params to add to the POST data.</param>
|
||||||
|
/// <param name="resourcePool">The resource pool.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>stream on success, null on failure</returns>
|
||||||
|
public Task<Stream> Post(string url, Dictionary<string, string> postData, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Post(new HttpRequestOptions
|
||||||
|
{
|
||||||
|
Url = url,
|
||||||
|
ResourcePool = resourcePool,
|
||||||
|
CancellationToken = cancellationToken,
|
||||||
|
BufferContent = resourcePool != null
|
||||||
|
|
||||||
|
}, postData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads the contents of a given url into a temporary location
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The options.</param>
|
||||||
|
/// <returns>Task{System.String}.</returns>
|
||||||
|
public async Task<string> GetTempFile(HttpRequestOptions options)
|
||||||
|
{
|
||||||
|
var response = await GetTempFileResponse(options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return response.TempFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponseInfo> GetTempFileResponse(HttpRequestOptions options)
|
||||||
|
{
|
||||||
|
ValidateParams(options);
|
||||||
|
|
||||||
|
_fileSystem.CreateDirectory(_appPaths.TempDirectory);
|
||||||
|
|
||||||
|
var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
|
||||||
|
|
||||||
|
if (options.Progress == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("progress");
|
||||||
|
}
|
||||||
|
|
||||||
|
options.CancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var httpWebRequest = GetRequest(options, "GET");
|
||||||
|
|
||||||
|
if (options.ResourcePool != null)
|
||||||
|
{
|
||||||
|
await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
options.Progress.Report(0);
|
||||||
|
|
||||||
|
if (options.LogRequest)
|
||||||
|
{
|
||||||
|
_logger.Info("HttpClientManager.GetTempFileResponse url: {0}", options.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
options.CancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var httpResponse = (HttpWebResponse)response;
|
||||||
|
|
||||||
|
EnsureSuccessStatusCode(client, httpResponse, options);
|
||||||
|
|
||||||
|
options.CancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var contentLength = GetContentLength(httpResponse);
|
||||||
|
|
||||||
|
if (!contentLength.HasValue)
|
||||||
|
{
|
||||||
|
// We're not able to track progress
|
||||||
|
using (var stream = httpResponse.GetResponseStream())
|
||||||
|
{
|
||||||
|
using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
|
||||||
|
{
|
||||||
|
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var stream = ProgressStream.CreateReadProgressStream(httpResponse.GetResponseStream(), options.Progress.Report, contentLength.Value))
|
||||||
|
{
|
||||||
|
using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
|
||||||
|
{
|
||||||
|
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.Progress.Report(100);
|
||||||
|
|
||||||
|
return GetResponseInfo(httpResponse, tempFile, contentLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
DeleteTempFile(tempFile);
|
||||||
|
throw GetException(ex, options, client);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (options.ResourcePool != null)
|
||||||
|
{
|
||||||
|
options.ResourcePool.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long? GetContentLength(HttpWebResponse response)
|
||||||
|
{
|
||||||
|
var length = response.ContentLength;
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
|
private Exception GetException(Exception ex, HttpRequestOptions options, HttpClientInfo client)
|
||||||
|
{
|
||||||
|
if (ex is HttpException)
|
||||||
|
{
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
var webException = ex as WebException
|
||||||
|
?? ex.InnerException as WebException;
|
||||||
|
|
||||||
|
if (webException != null)
|
||||||
|
{
|
||||||
|
if (options.LogErrors)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error getting response from " + options.Url, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
var exception = new HttpException(ex.Message, ex);
|
||||||
|
|
||||||
|
var response = webException.Response as HttpWebResponse;
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
exception.StatusCode = response.StatusCode;
|
||||||
|
|
||||||
|
if ((int)response.StatusCode == 429)
|
||||||
|
{
|
||||||
|
client.LastTimeout = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
var operationCanceledException = ex as OperationCanceledException
|
||||||
|
?? ex.InnerException as OperationCanceledException;
|
||||||
|
|
||||||
|
if (operationCanceledException != null)
|
||||||
|
{
|
||||||
|
return GetCancellationException(options, client, options.CancellationToken, operationCanceledException);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.LogErrors)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error getting response from " + options.Url, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteTempFile(string file)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_fileSystem.DeleteFile(file);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
// Might not have been created at all. No need to worry.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateParams(HttpRequestOptions options)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(options.Url))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("options");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the host from URL.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The URL.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
private string GetHostFromUrl(string url)
|
||||||
|
{
|
||||||
|
var index = url.IndexOf("://", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
url = url.Substring(index + 3);
|
||||||
|
var host = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(host))
|
||||||
|
{
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
_httpClients.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws the cancellation exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The options.</param>
|
||||||
|
/// <param name="client">The client.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <param name="exception">The exception.</param>
|
||||||
|
/// <returns>Exception.</returns>
|
||||||
|
private Exception GetCancellationException(HttpRequestOptions options, HttpClientInfo client, CancellationToken cancellationToken, OperationCanceledException exception)
|
||||||
|
{
|
||||||
|
// If the HttpClient's timeout is reached, it will cancel the Task internally
|
||||||
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var msg = string.Format("Connection to {0} timed out", options.Url);
|
||||||
|
|
||||||
|
if (options.LogErrors)
|
||||||
|
{
|
||||||
|
_logger.Error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.LastTimeout = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// Throw an HttpException so that the caller doesn't think it was cancelled by user code
|
||||||
|
return new HttpException(msg, exception)
|
||||||
|
{
|
||||||
|
IsTimedOut = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureSuccessStatusCode(HttpClientInfo client, HttpWebResponse response, HttpRequestOptions options)
|
||||||
|
{
|
||||||
|
var statusCode = response.StatusCode;
|
||||||
|
|
||||||
|
var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299;
|
||||||
|
|
||||||
|
if (!isSuccessful)
|
||||||
|
{
|
||||||
|
if (options.LogErrorResponseBody)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = response.GetResponseStream())
|
||||||
|
{
|
||||||
|
if (stream != null)
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
var msg = reader.ReadToEnd();
|
||||||
|
|
||||||
|
_logger.Error(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new HttpException(response.StatusDescription)
|
||||||
|
{
|
||||||
|
StatusCode = response.StatusCode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Posts the specified URL.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The URL.</param>
|
||||||
|
/// <param name="postData">The post data.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task{Stream}.</returns>
|
||||||
|
public Task<Stream> Post(string url, Dictionary<string, string> postData, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Post(url, postData, null, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<WebResponse> GetResponseAsync(WebRequest request, TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var taskCompletion = new TaskCompletionSource<WebResponse>();
|
||||||
|
|
||||||
|
Task<WebResponse> asyncTask = Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null);
|
||||||
|
|
||||||
|
ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true);
|
||||||
|
var callback = new TaskCallback { taskCompletion = taskCompletion };
|
||||||
|
asyncTask.ContinueWith(callback.OnSuccess, TaskContinuationOptions.NotOnFaulted);
|
||||||
|
|
||||||
|
// Handle errors
|
||||||
|
asyncTask.ContinueWith(callback.OnError, TaskContinuationOptions.OnlyOnFaulted);
|
||||||
|
|
||||||
|
return taskCompletion.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TimeoutCallback(object state, bool timedOut)
|
||||||
|
{
|
||||||
|
if (timedOut)
|
||||||
|
{
|
||||||
|
WebRequest request = (WebRequest)state;
|
||||||
|
if (state != null)
|
||||||
|
{
|
||||||
|
request.Abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TaskCallback
|
||||||
|
{
|
||||||
|
public TaskCompletionSource<WebResponse> taskCompletion;
|
||||||
|
|
||||||
|
public void OnSuccess(Task<WebResponse> task)
|
||||||
|
{
|
||||||
|
taskCompletion.TrySetResult(task.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnError(Task<WebResponse> task)
|
||||||
|
{
|
||||||
|
if (task.Exception != null)
|
||||||
|
{
|
||||||
|
taskCompletion.TrySetException(task.Exception);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
taskCompletion.TrySetException(new List<Exception>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
Emby.Common.Implementations/IO/IsoManager.cs
Normal file
75
Emby.Common.Implementations/IO/IsoManager.cs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.IO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class IsoManager
|
||||||
|
/// </summary>
|
||||||
|
public class IsoManager : IIsoManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The _mounters
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<IIsoMounter> _mounters = new List<IIsoMounter>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mounts the specified iso path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isoPath">The iso path.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>IsoMount.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">isoPath</exception>
|
||||||
|
/// <exception cref="System.ArgumentException"></exception>
|
||||||
|
public Task<IIsoMount> Mount(string isoPath, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(isoPath))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("isoPath");
|
||||||
|
}
|
||||||
|
|
||||||
|
var mounter = _mounters.FirstOrDefault(i => i.CanMount(isoPath));
|
||||||
|
|
||||||
|
if (mounter == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(string.Format("No mounters are able to mount {0}", isoPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mounter.Mount(isoPath, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this instance can mount the specified path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <returns><c>true</c> if this instance can mount the specified path; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool CanMount(string path)
|
||||||
|
{
|
||||||
|
return _mounters.Any(i => i.CanMount(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the parts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mounters">The mounters.</param>
|
||||||
|
public void AddParts(IEnumerable<IIsoMounter> mounters)
|
||||||
|
{
|
||||||
|
_mounters.AddRange(mounters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var mounter in _mounters)
|
||||||
|
{
|
||||||
|
mounter.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
705
Emby.Common.Implementations/IO/ManagedFileSystem.cs
Normal file
705
Emby.Common.Implementations/IO/ManagedFileSystem.cs
Normal file
|
@ -0,0 +1,705 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.IO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class ManagedFileSystem
|
||||||
|
/// </summary>
|
||||||
|
public class ManagedFileSystem : IFileSystem
|
||||||
|
{
|
||||||
|
protected ILogger Logger;
|
||||||
|
|
||||||
|
private readonly bool _supportsAsyncFileStreams;
|
||||||
|
private char[] _invalidFileNameChars;
|
||||||
|
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
|
||||||
|
protected bool EnableFileSystemRequestConcat = true;
|
||||||
|
|
||||||
|
public ManagedFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
_supportsAsyncFileStreams = supportsAsyncFileStreams;
|
||||||
|
SetInvalidFileNameChars(enableManagedInvalidFileNameChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddShortcutHandler(IShortcutHandler handler)
|
||||||
|
{
|
||||||
|
_shortcutHandlers.Add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetInvalidFileNameChars(bool enableManagedInvalidFileNameChars)
|
||||||
|
{
|
||||||
|
if (enableManagedInvalidFileNameChars)
|
||||||
|
{
|
||||||
|
_invalidFileNameChars = Path.GetInvalidFileNameChars();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// GetInvalidFileNameChars is less restrictive in Linux/Mac than Windows, this mimic Windows behavior for mono under Linux/Mac.
|
||||||
|
_invalidFileNameChars = new char[41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
|
||||||
|
'\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
|
||||||
|
'\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
|
||||||
|
'\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public char DirectorySeparatorChar
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.DirectorySeparatorChar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFullPath(string path)
|
||||||
|
{
|
||||||
|
return Path.GetFullPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the specified filename is shortcut.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">The filename.</param>
|
||||||
|
/// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">filename</exception>
|
||||||
|
public virtual bool IsShortcut(string filename)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
var extension = Path.GetExtension(filename);
|
||||||
|
return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the shortcut.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">The filename.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">filename</exception>
|
||||||
|
public virtual string ResolveShortcut(string filename)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
var extension = Path.GetExtension(filename);
|
||||||
|
var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
return handler.Resolve(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the shortcut.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="shortcutPath">The shortcut path.</param>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// shortcutPath
|
||||||
|
/// or
|
||||||
|
/// target
|
||||||
|
/// </exception>
|
||||||
|
public void CreateShortcut(string shortcutPath, string target)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(shortcutPath))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("shortcutPath");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(target))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("target");
|
||||||
|
}
|
||||||
|
|
||||||
|
var extension = Path.GetExtension(shortcutPath);
|
||||||
|
var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler.Create(shortcutPath, target);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a <see cref="FileSystemMetadata"/> object for the specified file or directory path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">A path to a file or directory.</param>
|
||||||
|
/// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
|
||||||
|
/// <remarks>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
|
||||||
|
/// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and all other properties will reflect the properties of the directory.</remarks>
|
||||||
|
public FileSystemMetadata GetFileSystemInfo(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
|
||||||
|
if (Path.HasExtension(path))
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(path);
|
||||||
|
|
||||||
|
if (fileInfo.Exists)
|
||||||
|
{
|
||||||
|
return GetFileSystemMetadata(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetFileSystemMetadata(new DirectoryInfo(path));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var fileInfo = new DirectoryInfo(path);
|
||||||
|
|
||||||
|
if (fileInfo.Exists)
|
||||||
|
{
|
||||||
|
return GetFileSystemMetadata(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetFileSystemMetadata(new FileInfo(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a <see cref="FileSystemMetadata"/> object for the specified file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">A path to a file.</param>
|
||||||
|
/// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
|
||||||
|
/// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
|
||||||
|
/// <see cref="FileSystemMetadata.IsDirectory"/> property and the <see cref="FileSystemMetadata.Exists"/> property will both be set to false.</para>
|
||||||
|
/// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
|
||||||
|
public FileSystemMetadata GetFileInfo(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfo = new FileInfo(path);
|
||||||
|
|
||||||
|
return GetFileSystemMetadata(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a <see cref="FileSystemMetadata"/> object for the specified directory path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">A path to a directory.</param>
|
||||||
|
/// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
|
||||||
|
/// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata"/> object's
|
||||||
|
/// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and the <see cref="FileSystemMetadata.Exists"/> property will be set to false.</para>
|
||||||
|
/// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
|
||||||
|
public FileSystemMetadata GetDirectoryInfo(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfo = new DirectoryInfo(path);
|
||||||
|
|
||||||
|
return GetFileSystemMetadata(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info)
|
||||||
|
{
|
||||||
|
var result = new FileSystemMetadata();
|
||||||
|
|
||||||
|
result.Exists = info.Exists;
|
||||||
|
result.FullName = info.FullName;
|
||||||
|
result.Extension = info.Extension;
|
||||||
|
result.Name = info.Name;
|
||||||
|
|
||||||
|
if (result.Exists)
|
||||||
|
{
|
||||||
|
var attributes = info.Attributes;
|
||||||
|
result.IsDirectory = info is DirectoryInfo || (attributes & FileAttributes.Directory) == FileAttributes.Directory;
|
||||||
|
result.IsHidden = (attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||||
|
result.IsReadOnly = (attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
|
||||||
|
|
||||||
|
var fileInfo = info as FileInfo;
|
||||||
|
if (fileInfo != null)
|
||||||
|
{
|
||||||
|
result.Length = fileInfo.Length;
|
||||||
|
result.DirectoryName = fileInfo.DirectoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.CreationTimeUtc = GetCreationTimeUtc(info);
|
||||||
|
result.LastWriteTimeUtc = GetLastWriteTimeUtc(info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.IsDirectory = info is DirectoryInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The space char
|
||||||
|
/// </summary>
|
||||||
|
private const char SpaceChar = ' ';
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes a filename and removes invalid characters
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">The filename.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">filename</exception>
|
||||||
|
public string GetValidFilename(string filename)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder(filename);
|
||||||
|
|
||||||
|
foreach (var c in _invalidFileNameChars)
|
||||||
|
{
|
||||||
|
builder = builder.Replace(c, SpaceChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the creation time UTC.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The info.</param>
|
||||||
|
/// <returns>DateTime.</returns>
|
||||||
|
public DateTime GetCreationTimeUtc(FileSystemInfo info)
|
||||||
|
{
|
||||||
|
// This could throw an error on some file systems that have dates out of range
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return info.CreationTimeUtc;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error determining CreationTimeUtc for {0}", ex, info.FullName);
|
||||||
|
return DateTime.MinValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the creation time UTC.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <returns>DateTime.</returns>
|
||||||
|
public DateTime GetCreationTimeUtc(string path)
|
||||||
|
{
|
||||||
|
return GetCreationTimeUtc(GetFileSystemInfo(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime GetCreationTimeUtc(FileSystemMetadata info)
|
||||||
|
{
|
||||||
|
return info.CreationTimeUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
|
||||||
|
{
|
||||||
|
return info.LastWriteTimeUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the creation time UTC.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The info.</param>
|
||||||
|
/// <returns>DateTime.</returns>
|
||||||
|
public DateTime GetLastWriteTimeUtc(FileSystemInfo info)
|
||||||
|
{
|
||||||
|
// This could throw an error on some file systems that have dates out of range
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return info.LastWriteTimeUtc;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error determining LastAccessTimeUtc for {0}", ex, info.FullName);
|
||||||
|
return DateTime.MinValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last write time UTC.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <returns>DateTime.</returns>
|
||||||
|
public DateTime GetLastWriteTimeUtc(string path)
|
||||||
|
{
|
||||||
|
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <param name="mode">The mode.</param>
|
||||||
|
/// <param name="access">The access.</param>
|
||||||
|
/// <param name="share">The share.</param>
|
||||||
|
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
|
||||||
|
/// <returns>FileStream.</returns>
|
||||||
|
public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
|
||||||
|
{
|
||||||
|
if (_supportsAsyncFileStreams && isAsync)
|
||||||
|
{
|
||||||
|
return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 262144, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 262144);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileMode GetFileMode(FileOpenMode mode)
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case FileOpenMode.Append:
|
||||||
|
return FileMode.Append;
|
||||||
|
case FileOpenMode.Create:
|
||||||
|
return FileMode.Create;
|
||||||
|
case FileOpenMode.CreateNew:
|
||||||
|
return FileMode.CreateNew;
|
||||||
|
case FileOpenMode.Open:
|
||||||
|
return FileMode.Open;
|
||||||
|
case FileOpenMode.OpenOrCreate:
|
||||||
|
return FileMode.OpenOrCreate;
|
||||||
|
case FileOpenMode.Truncate:
|
||||||
|
return FileMode.Truncate;
|
||||||
|
default:
|
||||||
|
throw new Exception("Unrecognized FileOpenMode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileAccess GetFileAccess(FileAccessMode mode)
|
||||||
|
{
|
||||||
|
var val = (int)mode;
|
||||||
|
|
||||||
|
return (FileAccess)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileShare GetFileShare(FileShareMode mode)
|
||||||
|
{
|
||||||
|
var val = (int)mode;
|
||||||
|
|
||||||
|
return (FileShare)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHidden(string path, bool isHidden)
|
||||||
|
{
|
||||||
|
var info = GetFileInfo(path);
|
||||||
|
|
||||||
|
if (info.Exists && info.IsHidden != isHidden)
|
||||||
|
{
|
||||||
|
if (isHidden)
|
||||||
|
{
|
||||||
|
FileAttributes attributes = File.GetAttributes(path);
|
||||||
|
attributes = RemoveAttribute(attributes, FileAttributes.Hidden);
|
||||||
|
File.SetAttributes(path, attributes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Hidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
|
||||||
|
{
|
||||||
|
return attributes & ~attributesToRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swaps the files.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file1">The file1.</param>
|
||||||
|
/// <param name="file2">The file2.</param>
|
||||||
|
public void SwapFiles(string file1, string file2)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(file1))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("file1");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(file2))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("file2");
|
||||||
|
}
|
||||||
|
|
||||||
|
var temp1 = Path.GetTempFileName();
|
||||||
|
var temp2 = Path.GetTempFileName();
|
||||||
|
|
||||||
|
// Copying over will fail against hidden files
|
||||||
|
RemoveHiddenAttribute(file1);
|
||||||
|
RemoveHiddenAttribute(file2);
|
||||||
|
|
||||||
|
CopyFile(file1, temp1, true);
|
||||||
|
CopyFile(file2, temp2, true);
|
||||||
|
|
||||||
|
CopyFile(temp1, file2, true);
|
||||||
|
CopyFile(temp2, file1, true);
|
||||||
|
|
||||||
|
DeleteFile(temp1);
|
||||||
|
DeleteFile(temp2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the hidden attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
private void RemoveHiddenAttribute(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentFile = new FileInfo(path);
|
||||||
|
|
||||||
|
// This will fail if the file is hidden
|
||||||
|
if (currentFile.Exists)
|
||||||
|
{
|
||||||
|
if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
|
||||||
|
{
|
||||||
|
currentFile.Attributes &= ~FileAttributes.Hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsSubPath(string parentPath, string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(parentPath))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("parentPath");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.IndexOf(parentPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRootPath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(parent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NormalizePath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.EndsWith(":\\", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFileNameWithoutExtension(FileSystemMetadata info)
|
||||||
|
{
|
||||||
|
if (info.IsDirectory)
|
||||||
|
{
|
||||||
|
return info.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.GetFileNameWithoutExtension(info.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFileNameWithoutExtension(string path)
|
||||||
|
{
|
||||||
|
return Path.GetFileNameWithoutExtension(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPathFile(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
|
||||||
|
|
||||||
|
if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
|
||||||
|
!path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
//return Path.IsPathRooted(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteFile(string path)
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteDirectory(string path, bool recursive)
|
||||||
|
{
|
||||||
|
Directory.Delete(path, recursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateDirectory(string path)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
|
||||||
|
{
|
||||||
|
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||||
|
|
||||||
|
return ToMetadata(path, new DirectoryInfo(path).EnumerateDirectories("*", searchOption));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
|
||||||
|
{
|
||||||
|
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||||
|
|
||||||
|
return ToMetadata(path, new DirectoryInfo(path).EnumerateFiles("*", searchOption));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
|
||||||
|
{
|
||||||
|
var directoryInfo = new DirectoryInfo(path);
|
||||||
|
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||||
|
|
||||||
|
if (EnableFileSystemRequestConcat)
|
||||||
|
{
|
||||||
|
return ToMetadata(path, directoryInfo.EnumerateDirectories("*", searchOption))
|
||||||
|
.Concat(ToMetadata(path, directoryInfo.EnumerateFiles("*", searchOption)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToMetadata(path, directoryInfo.EnumerateFileSystemInfos("*", searchOption));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<FileSystemMetadata> ToMetadata(string parentPath, IEnumerable<FileSystemInfo> infos)
|
||||||
|
{
|
||||||
|
return infos.Select(i =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return GetFileSystemMetadata(i);
|
||||||
|
}
|
||||||
|
catch (PathTooLongException)
|
||||||
|
{
|
||||||
|
// Can't log using the FullName because it will throw the PathTooLongExceptiona again
|
||||||
|
//Logger.Warn("Path too long: {0}", i.FullName);
|
||||||
|
Logger.Warn("File or directory path too long. Parent folder: {0}", parentPath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}).Where(i => i != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream OpenRead(string path)
|
||||||
|
{
|
||||||
|
return File.OpenRead(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyFile(string source, string target, bool overwrite)
|
||||||
|
{
|
||||||
|
File.Copy(source, target, overwrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveFile(string source, string target)
|
||||||
|
{
|
||||||
|
File.Move(source, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveDirectory(string source, string target)
|
||||||
|
{
|
||||||
|
Directory.Move(source, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DirectoryExists(string path)
|
||||||
|
{
|
||||||
|
return Directory.Exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FileExists(string path)
|
||||||
|
{
|
||||||
|
return File.Exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadAllText(string path)
|
||||||
|
{
|
||||||
|
return File.ReadAllText(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ReadAllBytes(string path)
|
||||||
|
{
|
||||||
|
return File.ReadAllBytes(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteAllText(string path, string text, Encoding encoding)
|
||||||
|
{
|
||||||
|
File.WriteAllText(path, text, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteAllText(string path, string text)
|
||||||
|
{
|
||||||
|
File.WriteAllText(path, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteAllBytes(string path, byte[] bytes)
|
||||||
|
{
|
||||||
|
File.WriteAllBytes(path, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadAllText(string path, Encoding encoding)
|
||||||
|
{
|
||||||
|
return File.ReadAllText(path, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
|
||||||
|
{
|
||||||
|
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||||
|
return Directory.EnumerateDirectories(path, "*", searchOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetFilePaths(string path, bool recursive = false)
|
||||||
|
{
|
||||||
|
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||||
|
return Directory.EnumerateFiles(path, "*", searchOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
|
||||||
|
{
|
||||||
|
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||||
|
return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
Emby.Common.Implementations/IO/WindowsFileSystem.cs
Normal file
13
Emby.Common.Implementations/IO/WindowsFileSystem.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.IO
|
||||||
|
{
|
||||||
|
public class WindowsFileSystem : ManagedFileSystem
|
||||||
|
{
|
||||||
|
public WindowsFileSystem(ILogger logger)
|
||||||
|
: base(logger, true, true)
|
||||||
|
{
|
||||||
|
EnableFileSystemRequestConcat = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
385
Emby.Common.Implementations/Networking/BaseNetworkManager.cs
Normal file
385
Emby.Common.Implementations/Networking/BaseNetworkManager.cs
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using MediaBrowser.Model.Extensions;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.Networking
|
||||||
|
{
|
||||||
|
public abstract class BaseNetworkManager
|
||||||
|
{
|
||||||
|
protected ILogger Logger { get; private set; }
|
||||||
|
private DateTime _lastRefresh;
|
||||||
|
|
||||||
|
protected BaseNetworkManager(ILogger logger)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IPAddress> _localIpAddresses;
|
||||||
|
private readonly object _localIpAddressSyncLock = new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the machine's local ip address
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IPAddress.</returns>
|
||||||
|
public IEnumerable<IPAddress> GetLocalIpAddresses()
|
||||||
|
{
|
||||||
|
const int cacheMinutes = 5;
|
||||||
|
|
||||||
|
lock (_localIpAddressSyncLock)
|
||||||
|
{
|
||||||
|
var forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes;
|
||||||
|
|
||||||
|
if (_localIpAddresses == null || forceRefresh)
|
||||||
|
{
|
||||||
|
var addresses = GetLocalIpAddressesInternal().ToList();
|
||||||
|
|
||||||
|
_localIpAddresses = addresses;
|
||||||
|
_lastRefresh = DateTime.UtcNow;
|
||||||
|
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _localIpAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<IPAddress> GetLocalIpAddressesInternal()
|
||||||
|
{
|
||||||
|
var list = GetIPsDefault()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (list.Count == 0)
|
||||||
|
{
|
||||||
|
list.AddRange(GetLocalIpAddressesFallback());
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.Where(FilterIpAddress).DistinctBy(i => i.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FilterIpAddress(IPAddress address)
|
||||||
|
{
|
||||||
|
var addressString = address.ToString ();
|
||||||
|
|
||||||
|
if (addressString.StartsWith("169.", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInPrivateAddressSpace(string endpoint)
|
||||||
|
{
|
||||||
|
if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ipv4 mapped to ipv6
|
||||||
|
endpoint = endpoint.Replace("::ffff:", string.Empty);
|
||||||
|
|
||||||
|
// Private address space:
|
||||||
|
// http://en.wikipedia.org/wiki/Private_network
|
||||||
|
|
||||||
|
if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return Is172AddressPrivate(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Is172AddressPrivate(string endpoint)
|
||||||
|
{
|
||||||
|
for (var i = 16; i <= 31; i++)
|
||||||
|
{
|
||||||
|
if (endpoint.StartsWith("172." + i.ToString(CultureInfo.InvariantCulture) + ".", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInLocalNetwork(string endpoint)
|
||||||
|
{
|
||||||
|
return IsInLocalNetworkInternal(endpoint, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInLocalNetworkInternal(string endpoint, bool resolveHost)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(endpoint))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("endpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress address;
|
||||||
|
if (IPAddress.TryParse(endpoint, out address))
|
||||||
|
{
|
||||||
|
var addressString = address.ToString();
|
||||||
|
|
||||||
|
int lengthMatch = 100;
|
||||||
|
if (address.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
lengthMatch = 4;
|
||||||
|
if (IsInPrivateAddressSpace(addressString))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||||
|
{
|
||||||
|
lengthMatch = 10;
|
||||||
|
if (IsInPrivateAddressSpace(endpoint))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be even be doing this with ipv6?
|
||||||
|
if (addressString.Length >= lengthMatch)
|
||||||
|
{
|
||||||
|
var prefix = addressString.Substring(0, lengthMatch);
|
||||||
|
|
||||||
|
if (GetLocalIpAddresses().Any(i => i.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (resolveHost)
|
||||||
|
{
|
||||||
|
Uri uri;
|
||||||
|
if (Uri.TryCreate(endpoint, UriKind.RelativeOrAbsolute, out uri))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var host = uri.DnsSafeHost;
|
||||||
|
Logger.Debug("Resolving host {0}", host);
|
||||||
|
|
||||||
|
address = GetIpAddresses(host).FirstOrDefault();
|
||||||
|
|
||||||
|
if (address != null)
|
||||||
|
{
|
||||||
|
Logger.Debug("{0} resolved to {1}", host, address);
|
||||||
|
|
||||||
|
return IsInLocalNetworkInternal(address.ToString(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// Can happen with reverse proxy or IIS url rewriting
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error resovling hostname", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IPAddress> GetIpAddresses(string hostName)
|
||||||
|
{
|
||||||
|
return Dns.GetHostAddresses(hostName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IPAddress> GetIPsDefault()
|
||||||
|
{
|
||||||
|
NetworkInterface[] interfaces;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
interfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error in GetAllNetworkInterfaces", ex);
|
||||||
|
return new List<IPAddress>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return interfaces.SelectMany(network => {
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.Debug("Querying interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
|
||||||
|
|
||||||
|
var properties = network.GetIPProperties();
|
||||||
|
|
||||||
|
return properties.UnicastAddresses
|
||||||
|
.Where(i => i.IsDnsEligible)
|
||||||
|
.Select(i => i.Address)
|
||||||
|
.Where(i => i.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error querying network interface", ex);
|
||||||
|
return new List<IPAddress>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}).DistinctBy(i => i.ToString())
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<IPAddress> GetLocalIpAddressesFallback()
|
||||||
|
{
|
||||||
|
var host = Dns.GetHostEntry(Dns.GetHostName());
|
||||||
|
|
||||||
|
// Reverse them because the last one is usually the correct one
|
||||||
|
// It's not fool-proof so ultimately the consumer will have to examine them and decide
|
||||||
|
return host.AddressList
|
||||||
|
.Where(i => i.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|
.Reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a random port number that is currently available
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>System.Int32.</returns>
|
||||||
|
public int GetRandomUnusedPort()
|
||||||
|
{
|
||||||
|
var listener = new TcpListener(IPAddress.Any, 0);
|
||||||
|
listener.Start();
|
||||||
|
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||||
|
listener.Stop();
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns MAC Address from first Network Card in Computer
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>[string] MAC Address</returns>
|
||||||
|
public string GetMacAddress()
|
||||||
|
{
|
||||||
|
return NetworkInterface.GetAllNetworkInterfaces()
|
||||||
|
.Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
||||||
|
.Select(i => BitConverter.ToString(i.GetPhysicalAddress().GetAddressBytes()))
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the specified endpointstring.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpointstring">The endpointstring.</param>
|
||||||
|
/// <returns>IPEndPoint.</returns>
|
||||||
|
public IPEndPoint Parse(string endpointstring)
|
||||||
|
{
|
||||||
|
return Parse(endpointstring, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the specified endpointstring.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpointstring">The endpointstring.</param>
|
||||||
|
/// <param name="defaultport">The defaultport.</param>
|
||||||
|
/// <returns>IPEndPoint.</returns>
|
||||||
|
/// <exception cref="System.ArgumentException">Endpoint descriptor may not be empty.</exception>
|
||||||
|
/// <exception cref="System.FormatException"></exception>
|
||||||
|
private static IPEndPoint Parse(string endpointstring, int defaultport)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrEmpty(endpointstring)
|
||||||
|
|| endpointstring.Trim().Length == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Endpoint descriptor may not be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultport != -1 &&
|
||||||
|
(defaultport < IPEndPoint.MinPort
|
||||||
|
|| defaultport > IPEndPoint.MaxPort))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(String.Format("Invalid default port '{0}'", defaultport));
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] values = endpointstring.Split(new char[] { ':' });
|
||||||
|
IPAddress ipaddy;
|
||||||
|
int port = -1;
|
||||||
|
|
||||||
|
//check if we have an IPv6 or ports
|
||||||
|
if (values.Length <= 2) // ipv4 or hostname
|
||||||
|
{
|
||||||
|
port = values.Length == 1 ? defaultport : GetPort(values[1]);
|
||||||
|
|
||||||
|
//try to use the address as IPv4, otherwise get hostname
|
||||||
|
if (!IPAddress.TryParse(values[0], out ipaddy))
|
||||||
|
ipaddy = GetIPfromHost(values[0]);
|
||||||
|
}
|
||||||
|
else if (values.Length > 2) //ipv6
|
||||||
|
{
|
||||||
|
//could [a:b:c]:d
|
||||||
|
if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
|
||||||
|
{
|
||||||
|
string ipaddressstring = String.Join(":", values.Take(values.Length - 1).ToArray());
|
||||||
|
ipaddy = IPAddress.Parse(ipaddressstring);
|
||||||
|
port = GetPort(values[values.Length - 1]);
|
||||||
|
}
|
||||||
|
else //[a:b:c] or a:b:c
|
||||||
|
{
|
||||||
|
ipaddy = IPAddress.Parse(endpointstring);
|
||||||
|
port = defaultport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new FormatException(String.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port == -1)
|
||||||
|
throw new ArgumentException(String.Format("No port specified: '{0}'", endpointstring));
|
||||||
|
|
||||||
|
return new IPEndPoint(ipaddy, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the port.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="p">The p.</param>
|
||||||
|
/// <returns>System.Int32.</returns>
|
||||||
|
/// <exception cref="System.FormatException"></exception>
|
||||||
|
private static int GetPort(string p)
|
||||||
|
{
|
||||||
|
int port;
|
||||||
|
|
||||||
|
if (!Int32.TryParse(p, out port)
|
||||||
|
|| port < IPEndPoint.MinPort
|
||||||
|
|| port > IPEndPoint.MaxPort)
|
||||||
|
{
|
||||||
|
throw new FormatException(String.Format("Invalid end point port '{0}'", p));
|
||||||
|
}
|
||||||
|
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the I pfrom host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="p">The p.</param>
|
||||||
|
/// <returns>IPAddress.</returns>
|
||||||
|
/// <exception cref="System.ArgumentException"></exception>
|
||||||
|
private static IPAddress GetIPfromHost(string p)
|
||||||
|
{
|
||||||
|
var hosts = Dns.GetHostAddresses(p);
|
||||||
|
|
||||||
|
if (hosts == null || hosts.Length == 0)
|
||||||
|
throw new ArgumentException(String.Format("Host not found: {0}", p));
|
||||||
|
|
||||||
|
return hosts[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
Emby.Common.Implementations/Properties/AssemblyInfo.cs
Normal file
19
Emby.Common.Implementations/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Emby.Common.Implementations")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("5a27010a-09c6-4e86-93ea-437484c10917")]
|
91
Emby.Common.Implementations/ScheduledTasks/DailyTrigger.cs
Normal file
91
Emby.Common.Implementations/ScheduledTasks/DailyTrigger.cs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a task trigger that fires everyday
|
||||||
|
/// </summary>
|
||||||
|
public class DailyTrigger : ITaskTrigger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the time of day to trigger the task to run
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The time of day.</value>
|
||||||
|
public TimeSpan TimeOfDay { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The timer.</value>
|
||||||
|
private Timer Timer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the execution properties of this task.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The execution properties of this task.
|
||||||
|
/// </value>
|
||||||
|
public TaskExecutionOptions TaskOptions { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stars waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lastResult">The last result.</param>
|
||||||
|
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
|
||||||
|
public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
|
||||||
|
var triggerDate = now.TimeOfDay > TimeOfDay ? now.Date.AddDays(1) : now.Date;
|
||||||
|
triggerDate = triggerDate.Add(TimeOfDay);
|
||||||
|
|
||||||
|
var dueTime = triggerDate - now;
|
||||||
|
|
||||||
|
logger.Info("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes the timer.
|
||||||
|
/// </summary>
|
||||||
|
private void DisposeTimer()
|
||||||
|
{
|
||||||
|
if (Timer != null)
|
||||||
|
{
|
||||||
|
Timer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
private void OnTriggered()
|
||||||
|
{
|
||||||
|
if (Triggered != null)
|
||||||
|
{
|
||||||
|
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
112
Emby.Common.Implementations/ScheduledTasks/IntervalTrigger.cs
Normal file
112
Emby.Common.Implementations/ScheduledTasks/IntervalTrigger.cs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a task trigger that runs repeatedly on an interval
|
||||||
|
/// </summary>
|
||||||
|
public class IntervalTrigger : ITaskTrigger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the interval.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The interval.</value>
|
||||||
|
public TimeSpan Interval { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The timer.</value>
|
||||||
|
private Timer Timer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the execution properties of this task.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The execution properties of this task.
|
||||||
|
/// </value>
|
||||||
|
public TaskExecutionOptions TaskOptions { get; set; }
|
||||||
|
|
||||||
|
private DateTime _lastStartDate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stars waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lastResult">The last result.</param>
|
||||||
|
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
|
||||||
|
public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
|
||||||
|
DateTime triggerDate;
|
||||||
|
|
||||||
|
if (lastResult == null)
|
||||||
|
{
|
||||||
|
// Task has never been completed before
|
||||||
|
triggerDate = DateTime.UtcNow.AddHours(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
triggerDate = new[] { lastResult.EndTimeUtc, _lastStartDate }.Max().Add(Interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.UtcNow > triggerDate)
|
||||||
|
{
|
||||||
|
triggerDate = DateTime.UtcNow.AddMinutes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dueTime = triggerDate - DateTime.UtcNow;
|
||||||
|
var maxDueTime = TimeSpan.FromDays(7);
|
||||||
|
|
||||||
|
if (dueTime > maxDueTime)
|
||||||
|
{
|
||||||
|
dueTime = maxDueTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes the timer.
|
||||||
|
/// </summary>
|
||||||
|
private void DisposeTimer()
|
||||||
|
{
|
||||||
|
if (Timer != null)
|
||||||
|
{
|
||||||
|
Timer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
private void OnTriggered()
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
|
||||||
|
if (Triggered != null)
|
||||||
|
{
|
||||||
|
_lastStartDate = DateTime.UtcNow;
|
||||||
|
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,782 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Events;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
using MediaBrowser.Model.System;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class ScheduledTaskWorker
|
||||||
|
/// </summary>
|
||||||
|
public class ScheduledTaskWorker : IScheduledTaskWorker
|
||||||
|
{
|
||||||
|
public event EventHandler<GenericEventArgs<double>> TaskProgress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the scheduled task.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The scheduled task.</value>
|
||||||
|
public IScheduledTask ScheduledTask { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the json serializer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The json serializer.</value>
|
||||||
|
private IJsonSerializer JsonSerializer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the application paths.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The application paths.</value>
|
||||||
|
private IApplicationPaths ApplicationPaths { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The logger.</value>
|
||||||
|
private ILogger Logger { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the task manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The task manager.</value>
|
||||||
|
private ITaskManager TaskManager { get; set; }
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly ISystemEvents _systemEvents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scheduledTask">The scheduled task.</param>
|
||||||
|
/// <param name="applicationPaths">The application paths.</param>
|
||||||
|
/// <param name="taskManager">The task manager.</param>
|
||||||
|
/// <param name="jsonSerializer">The json serializer.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">
|
||||||
|
/// scheduledTask
|
||||||
|
/// or
|
||||||
|
/// applicationPaths
|
||||||
|
/// or
|
||||||
|
/// taskManager
|
||||||
|
/// or
|
||||||
|
/// jsonSerializer
|
||||||
|
/// or
|
||||||
|
/// logger
|
||||||
|
/// </exception>
|
||||||
|
public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem, ISystemEvents systemEvents)
|
||||||
|
{
|
||||||
|
if (scheduledTask == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("scheduledTask");
|
||||||
|
}
|
||||||
|
if (applicationPaths == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("applicationPaths");
|
||||||
|
}
|
||||||
|
if (taskManager == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("taskManager");
|
||||||
|
}
|
||||||
|
if (jsonSerializer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("jsonSerializer");
|
||||||
|
}
|
||||||
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("logger");
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduledTask = scheduledTask;
|
||||||
|
ApplicationPaths = applicationPaths;
|
||||||
|
TaskManager = taskManager;
|
||||||
|
JsonSerializer = jsonSerializer;
|
||||||
|
Logger = logger;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_systemEvents = systemEvents;
|
||||||
|
|
||||||
|
InitTriggerEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _readFromFile = false;
|
||||||
|
/// <summary>
|
||||||
|
/// The _last execution result
|
||||||
|
/// </summary>
|
||||||
|
private TaskResult _lastExecutionResult;
|
||||||
|
/// <summary>
|
||||||
|
/// The _last execution result sync lock
|
||||||
|
/// </summary>
|
||||||
|
private readonly object _lastExecutionResultSyncLock = new object();
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last execution result.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The last execution result.</value>
|
||||||
|
public TaskResult LastExecutionResult
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var path = GetHistoryFilePath();
|
||||||
|
|
||||||
|
lock (_lastExecutionResultSyncLock)
|
||||||
|
{
|
||||||
|
if (_lastExecutionResult == null && !_readFromFile)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lastExecutionResult = JsonSerializer.DeserializeFromFile<TaskResult>(path);
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
// File doesn't exist. No biggie
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
// File doesn't exist. No biggie
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error deserializing {0}", ex, path);
|
||||||
|
}
|
||||||
|
_readFromFile = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _lastExecutionResult;
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
_lastExecutionResult = value;
|
||||||
|
|
||||||
|
var path = GetHistoryFilePath();
|
||||||
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
|
lock (_lastExecutionResultSyncLock)
|
||||||
|
{
|
||||||
|
JsonSerializer.SerializeToFile(value, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return ScheduledTask.Name; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The description.</value>
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get { return ScheduledTask.Description; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the category.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The category.</value>
|
||||||
|
public string Category
|
||||||
|
{
|
||||||
|
get { return ScheduledTask.Category; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 Tuple<TaskTriggerInfo,ITaskTrigger>[] _triggers;
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the triggers that define when the task will run
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The triggers.</value>
|
||||||
|
private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _triggers;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup current triggers
|
||||||
|
if (_triggers != null)
|
||||||
|
{
|
||||||
|
DisposeTriggers();
|
||||||
|
}
|
||||||
|
|
||||||
|
_triggers = value.ToArray();
|
||||||
|
|
||||||
|
ReloadTriggerEvents(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the triggers that define when the task will run
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The triggers.</value>
|
||||||
|
/// <exception cref="System.ArgumentNullException">value</exception>
|
||||||
|
public TaskTriggerInfo[] Triggers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return InternalTriggers.Select(i => i.Item1).ToArray();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveTriggers(value);
|
||||||
|
|
||||||
|
InternalTriggers = value.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _id
|
||||||
|
/// </summary>
|
||||||
|
private string _id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The unique id.</value>
|
||||||
|
public string Id
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_id == null)
|
||||||
|
{
|
||||||
|
_id = ScheduledTask.GetType().FullName.GetMD5().ToString("N");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitTriggerEvents()
|
||||||
|
{
|
||||||
|
_triggers = LoadTriggers();
|
||||||
|
ReloadTriggerEvents(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadTriggerEvents()
|
||||||
|
{
|
||||||
|
ReloadTriggerEvents(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 triggerInfo in InternalTriggers)
|
||||||
|
{
|
||||||
|
var trigger = triggerInfo.Item2;
|
||||||
|
|
||||||
|
trigger.Stop();
|
||||||
|
|
||||||
|
trigger.Triggered -= trigger_Triggered;
|
||||||
|
trigger.Triggered += trigger_Triggered;
|
||||||
|
trigger.Start(LastExecutionResult, Logger, Name, 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, GenericEventArgs<TaskExecutionOptions> e)
|
||||||
|
{
|
||||||
|
var trigger = (ITaskTrigger)sender;
|
||||||
|
|
||||||
|
var configurableTask = ScheduledTask as IConfigurableScheduledTask;
|
||||||
|
|
||||||
|
if (configurableTask != null && !configurableTask.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info("{0} fired for task: {1}", trigger.GetType().Name, Name);
|
||||||
|
|
||||||
|
trigger.Stop();
|
||||||
|
|
||||||
|
TaskManager.QueueScheduledTask(ScheduledTask, e.Argument);
|
||||||
|
|
||||||
|
await Task.Delay(1000).ConfigureAwait(false);
|
||||||
|
|
||||||
|
trigger.Start(LastExecutionResult, Logger, Name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task _currentTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the task
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">Task options.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
/// <exception cref="System.InvalidOperationException">Cannot execute a Task that is already running</exception>
|
||||||
|
public async Task Execute(TaskExecutionOptions options)
|
||||||
|
{
|
||||||
|
var task = ExecuteInternal(options);
|
||||||
|
|
||||||
|
_currentTask = task;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_currentTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteInternal(TaskExecutionOptions options)
|
||||||
|
{
|
||||||
|
// Cancel the current execution, if any
|
||||||
|
if (CurrentCancellationTokenSource != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot execute a Task that is already running");
|
||||||
|
}
|
||||||
|
|
||||||
|
var progress = new Progress<double>();
|
||||||
|
|
||||||
|
CurrentCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
Logger.Info("Executing {0}", Name);
|
||||||
|
|
||||||
|
((TaskManager)TaskManager).OnTaskExecuting(this);
|
||||||
|
|
||||||
|
progress.ProgressChanged += progress_ProgressChanged;
|
||||||
|
|
||||||
|
TaskCompletionStatus status;
|
||||||
|
CurrentExecutionStartTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
Exception failureException = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (options != null && options.MaxRuntimeMs.HasValue)
|
||||||
|
{
|
||||||
|
CurrentCancellationTokenSource.CancelAfter(options.MaxRuntimeMs.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var localTask = ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress);
|
||||||
|
|
||||||
|
await localTask.ConfigureAwait(false);
|
||||||
|
|
||||||
|
status = TaskCompletionStatus.Completed;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
status = TaskCompletionStatus.Cancelled;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error", ex);
|
||||||
|
|
||||||
|
failureException = ex;
|
||||||
|
|
||||||
|
status = TaskCompletionStatus.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTime = CurrentExecutionStartTime;
|
||||||
|
var endTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
progress.ProgressChanged -= progress_ProgressChanged;
|
||||||
|
CurrentCancellationTokenSource.Dispose();
|
||||||
|
CurrentCancellationTokenSource = null;
|
||||||
|
CurrentProgress = null;
|
||||||
|
|
||||||
|
OnTaskCompleted(startTime, endTime, status, failureException);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
EventHelper.FireEventIfNotNull(TaskProgress, this, new GenericEventArgs<double>
|
||||||
|
{
|
||||||
|
Argument = e
|
||||||
|
|
||||||
|
}, Logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// Gets the scheduled tasks configuration directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
private string GetScheduledTasksConfigurationDirectory()
|
||||||
|
{
|
||||||
|
return Path.Combine(ApplicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the scheduled tasks data directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
private string GetScheduledTasksDataDirectory()
|
||||||
|
{
|
||||||
|
return Path.Combine(ApplicationPaths.DataPath, "ScheduledTasks");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the history file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The history file path.</value>
|
||||||
|
private string GetHistoryFilePath()
|
||||||
|
{
|
||||||
|
return Path.Combine(GetScheduledTasksDataDirectory(), new Guid(Id) + ".js");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configuration file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
private string GetConfigurationFilePath()
|
||||||
|
{
|
||||||
|
return Path.Combine(GetScheduledTasksConfigurationDirectory(), new Guid(Id) + ".js");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the triggers.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
|
private Tuple<TaskTriggerInfo, ITaskTrigger>[] LoadTriggers()
|
||||||
|
{
|
||||||
|
var settings = LoadTriggerSettings();
|
||||||
|
|
||||||
|
return settings.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskTriggerInfo[] LoadTriggerSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.DeserializeFromFile<IEnumerable<TaskTriggerInfo>>(GetConfigurationFilePath())
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
// File doesn't exist. No biggie. Return defaults.
|
||||||
|
return ScheduledTask.GetDefaultTriggers().ToArray();
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
// File doesn't exist. No biggie. Return defaults.
|
||||||
|
return ScheduledTask.GetDefaultTriggers().ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the triggers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="triggers">The triggers.</param>
|
||||||
|
private void SaveTriggers(TaskTriggerInfo[] triggers)
|
||||||
|
{
|
||||||
|
var path = GetConfigurationFilePath();
|
||||||
|
|
||||||
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
|
JsonSerializer.SerializeToFile(triggers, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [task completed].
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startTime">The start time.</param>
|
||||||
|
/// <param name="endTime">The end time.</param>
|
||||||
|
/// <param name="status">The status.</param>
|
||||||
|
private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex)
|
||||||
|
{
|
||||||
|
var elapsedTime = endTime - startTime;
|
||||||
|
|
||||||
|
Logger.Info("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds);
|
||||||
|
|
||||||
|
var result = new TaskResult
|
||||||
|
{
|
||||||
|
StartTimeUtc = startTime,
|
||||||
|
EndTimeUtc = endTime,
|
||||||
|
Status = status,
|
||||||
|
Name = Name,
|
||||||
|
Id = Id
|
||||||
|
};
|
||||||
|
|
||||||
|
result.Key = ScheduledTask.Key;
|
||||||
|
|
||||||
|
if (ex != null)
|
||||||
|
{
|
||||||
|
result.ErrorMessage = ex.Message;
|
||||||
|
result.LongErrorMessage = ex.StackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
LastExecutionResult = result;
|
||||||
|
|
||||||
|
((TaskManager)TaskManager).OnTaskCompleted(this, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
|
||||||
|
var wassRunning = State == TaskState.Running;
|
||||||
|
var startTime = CurrentExecutionStartTime;
|
||||||
|
|
||||||
|
var token = CurrentCancellationTokenSource;
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.Info(Name + ": Cancelling");
|
||||||
|
token.Cancel();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error calling CancellationToken.Cancel();", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var task = _currentTask;
|
||||||
|
if (task != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.Info(Name + ": Waiting on Task");
|
||||||
|
var exited = Task.WaitAll(new[] { task }, 2000);
|
||||||
|
|
||||||
|
if (exited)
|
||||||
|
{
|
||||||
|
Logger.Info(Name + ": Task exited");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info(Name + ": Timed out waiting for task to stop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error calling Task.WaitAll();", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.Debug(Name + ": Disposing CancellationToken");
|
||||||
|
token.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error calling CancellationToken.Dispose();", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wassRunning)
|
||||||
|
{
|
||||||
|
OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">The info.</param>
|
||||||
|
/// <returns>BaseTaskTrigger.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException"></exception>
|
||||||
|
/// <exception cref="System.ArgumentException">Invalid trigger type: + info.Type</exception>
|
||||||
|
private ITaskTrigger GetTrigger(TaskTriggerInfo info)
|
||||||
|
{
|
||||||
|
var options = new TaskExecutionOptions
|
||||||
|
{
|
||||||
|
MaxRuntimeMs = info.MaxRuntimeMs
|
||||||
|
};
|
||||||
|
|
||||||
|
if (info.Type.Equals(typeof(DailyTrigger).Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (!info.TimeOfDayTicks.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DailyTrigger
|
||||||
|
{
|
||||||
|
TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value),
|
||||||
|
TaskOptions = options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.Type.Equals(typeof(WeeklyTrigger).Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (!info.TimeOfDayTicks.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.DayOfWeek.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WeeklyTrigger
|
||||||
|
{
|
||||||
|
TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value),
|
||||||
|
DayOfWeek = info.DayOfWeek.Value,
|
||||||
|
TaskOptions = options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.Type.Equals(typeof(IntervalTrigger).Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (!info.IntervalTicks.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new IntervalTrigger
|
||||||
|
{
|
||||||
|
Interval = TimeSpan.FromTicks(info.IntervalTicks.Value),
|
||||||
|
TaskOptions = options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.Type.Equals(typeof(SystemEventTrigger).Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (!info.SystemEvent.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SystemEventTrigger(_systemEvents)
|
||||||
|
{
|
||||||
|
SystemEvent = info.SystemEvent.Value,
|
||||||
|
TaskOptions = options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.Type.Equals(typeof(StartupTrigger).Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return new StartupTrigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("Unrecognized trigger type: " + info.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes each trigger
|
||||||
|
/// </summary>
|
||||||
|
private void DisposeTriggers()
|
||||||
|
{
|
||||||
|
foreach (var triggerInfo in InternalTriggers)
|
||||||
|
{
|
||||||
|
var trigger = triggerInfo.Item2;
|
||||||
|
trigger.Triggered -= trigger_Triggered;
|
||||||
|
trigger.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
Emby.Common.Implementations/ScheduledTasks/StartupTrigger.cs
Normal file
67
Emby.Common.Implementations/ScheduledTasks/StartupTrigger.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class StartupTaskTrigger
|
||||||
|
/// </summary>
|
||||||
|
public class StartupTrigger : ITaskTrigger
|
||||||
|
{
|
||||||
|
public int DelayMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the execution properties of this task.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The execution properties of this task.
|
||||||
|
/// </value>
|
||||||
|
public TaskExecutionOptions TaskOptions { get; set; }
|
||||||
|
|
||||||
|
public StartupTrigger()
|
||||||
|
{
|
||||||
|
DelayMs = 3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stars waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lastResult">The last result.</param>
|
||||||
|
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
|
||||||
|
public async void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
|
||||||
|
{
|
||||||
|
if (isApplicationStartup)
|
||||||
|
{
|
||||||
|
await Task.Delay(DelayMs).ConfigureAwait(false);
|
||||||
|
|
||||||
|
OnTriggered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
private void OnTriggered()
|
||||||
|
{
|
||||||
|
if (Triggered != null)
|
||||||
|
{
|
||||||
|
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.System;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class SystemEventTrigger
|
||||||
|
/// </summary>
|
||||||
|
public class SystemEventTrigger : ITaskTrigger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the system event.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The system event.</value>
|
||||||
|
public SystemEvent SystemEvent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the execution properties of this task.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The execution properties of this task.
|
||||||
|
/// </value>
|
||||||
|
public TaskExecutionOptions TaskOptions { get; set; }
|
||||||
|
|
||||||
|
private readonly ISystemEvents _systemEvents;
|
||||||
|
|
||||||
|
public SystemEventTrigger(ISystemEvents systemEvents)
|
||||||
|
{
|
||||||
|
_systemEvents = systemEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stars waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lastResult">The last result.</param>
|
||||||
|
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
|
||||||
|
public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
|
||||||
|
{
|
||||||
|
switch (SystemEvent)
|
||||||
|
{
|
||||||
|
case SystemEvent.WakeFromSleep:
|
||||||
|
_systemEvents.Resume += _systemEvents_Resume;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void _systemEvents_Resume(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (SystemEvent == SystemEvent.WakeFromSleep)
|
||||||
|
{
|
||||||
|
// This value is a bit arbitrary, but add a delay to help ensure network connections have been restored before running the task
|
||||||
|
await Task.Delay(10000).ConfigureAwait(false);
|
||||||
|
|
||||||
|
OnTriggered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_systemEvents.Resume -= _systemEvents_Resume;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
private void OnTriggered()
|
||||||
|
{
|
||||||
|
if (Triggered != null)
|
||||||
|
{
|
||||||
|
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
358
Emby.Common.Implementations/ScheduledTasks/TaskManager.cs
Normal file
358
Emby.Common.Implementations/ScheduledTasks/TaskManager.cs
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Events;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.System;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class TaskManager
|
||||||
|
/// </summary>
|
||||||
|
public class TaskManager : ITaskManager
|
||||||
|
{
|
||||||
|
public event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting;
|
||||||
|
public event EventHandler<TaskCompletionEventArgs> TaskCompleted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of Scheduled Tasks
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The scheduled tasks.</value>
|
||||||
|
public IScheduledTaskWorker[] ScheduledTasks { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _task queue
|
||||||
|
/// </summary>
|
||||||
|
private readonly ConcurrentQueue<Tuple<Type, TaskExecutionOptions>> _taskQueue =
|
||||||
|
new ConcurrentQueue<Tuple<Type, TaskExecutionOptions>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the json serializer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The json serializer.</value>
|
||||||
|
private IJsonSerializer JsonSerializer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the application paths.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The application paths.</value>
|
||||||
|
private IApplicationPaths ApplicationPaths { get; set; }
|
||||||
|
|
||||||
|
private readonly ISystemEvents _systemEvents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The logger.</value>
|
||||||
|
private ILogger Logger { get; set; }
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
|
private bool _suspendTriggers;
|
||||||
|
|
||||||
|
public bool SuspendTriggers
|
||||||
|
{
|
||||||
|
get { return _suspendTriggers; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Logger.Info("Setting SuspendTriggers to {0}", value);
|
||||||
|
var executeQueued = _suspendTriggers && !value;
|
||||||
|
|
||||||
|
_suspendTriggers = value;
|
||||||
|
|
||||||
|
if (executeQueued)
|
||||||
|
{
|
||||||
|
ExecuteQueuedTasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TaskManager" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="applicationPaths">The application paths.</param>
|
||||||
|
/// <param name="jsonSerializer">The json serializer.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <exception cref="System.ArgumentException">kernel</exception>
|
||||||
|
public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem, ISystemEvents systemEvents)
|
||||||
|
{
|
||||||
|
ApplicationPaths = applicationPaths;
|
||||||
|
JsonSerializer = jsonSerializer;
|
||||||
|
Logger = logger;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_systemEvents = systemEvents;
|
||||||
|
|
||||||
|
ScheduledTasks = new IScheduledTaskWorker[] { };
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BindToSystemEvent()
|
||||||
|
{
|
||||||
|
_systemEvents.Resume += _systemEvents_Resume;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _systemEvents_Resume(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
foreach (var task in ScheduledTasks)
|
||||||
|
{
|
||||||
|
task.ReloadTriggerEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancels if running and queue.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="options">Task options.</param>
|
||||||
|
public void CancelIfRunningAndQueue<T>(TaskExecutionOptions options)
|
||||||
|
where T : IScheduledTask
|
||||||
|
{
|
||||||
|
var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
|
||||||
|
((ScheduledTaskWorker)task).CancelIfRunning();
|
||||||
|
|
||||||
|
QueueScheduledTask<T>(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelIfRunningAndQueue<T>()
|
||||||
|
where T : IScheduledTask
|
||||||
|
{
|
||||||
|
CancelIfRunningAndQueue<T>(new TaskExecutionOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancels if running
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public void CancelIfRunning<T>()
|
||||||
|
where T : IScheduledTask
|
||||||
|
{
|
||||||
|
var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
|
||||||
|
((ScheduledTaskWorker)task).CancelIfRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues the scheduled task.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="options">Task options</param>
|
||||||
|
public void QueueScheduledTask<T>(TaskExecutionOptions options)
|
||||||
|
where T : IScheduledTask
|
||||||
|
{
|
||||||
|
var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
|
||||||
|
|
||||||
|
if (scheduledTask == null)
|
||||||
|
{
|
||||||
|
Logger.Error("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QueueScheduledTask(scheduledTask, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueScheduledTask<T>()
|
||||||
|
where T : IScheduledTask
|
||||||
|
{
|
||||||
|
QueueScheduledTask<T>(new TaskExecutionOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueIfNotRunning<T>()
|
||||||
|
where T : IScheduledTask
|
||||||
|
{
|
||||||
|
var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
|
||||||
|
|
||||||
|
if (task.State != TaskState.Running)
|
||||||
|
{
|
||||||
|
QueueScheduledTask<T>(new TaskExecutionOptions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute<T>()
|
||||||
|
where T : IScheduledTask
|
||||||
|
{
|
||||||
|
var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
|
||||||
|
|
||||||
|
if (scheduledTask == null)
|
||||||
|
{
|
||||||
|
Logger.Error("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var type = scheduledTask.ScheduledTask.GetType();
|
||||||
|
|
||||||
|
Logger.Info("Queueing task {0}", type.Name);
|
||||||
|
|
||||||
|
lock (_taskQueue)
|
||||||
|
{
|
||||||
|
if (scheduledTask.State == TaskState.Idle)
|
||||||
|
{
|
||||||
|
Execute(scheduledTask, new TaskExecutionOptions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues the scheduled task.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">The task.</param>
|
||||||
|
/// <param name="options">The task options.</param>
|
||||||
|
public void QueueScheduledTask(IScheduledTask task, TaskExecutionOptions options)
|
||||||
|
{
|
||||||
|
var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == task.GetType());
|
||||||
|
|
||||||
|
if (scheduledTask == null)
|
||||||
|
{
|
||||||
|
Logger.Error("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QueueScheduledTask(scheduledTask, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues the scheduled task.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">The task.</param>
|
||||||
|
/// <param name="options">The task options.</param>
|
||||||
|
private void QueueScheduledTask(IScheduledTaskWorker task, TaskExecutionOptions options)
|
||||||
|
{
|
||||||
|
var type = task.ScheduledTask.GetType();
|
||||||
|
|
||||||
|
Logger.Info("Queueing task {0}", type.Name);
|
||||||
|
|
||||||
|
lock (_taskQueue)
|
||||||
|
{
|
||||||
|
if (task.State == TaskState.Idle && !SuspendTriggers)
|
||||||
|
{
|
||||||
|
Execute(task, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_taskQueue.Enqueue(new Tuple<Type, TaskExecutionOptions>(type, options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the tasks.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tasks">The tasks.</param>
|
||||||
|
public void AddTasks(IEnumerable<IScheduledTask> tasks)
|
||||||
|
{
|
||||||
|
var myTasks = ScheduledTasks.ToList();
|
||||||
|
|
||||||
|
var list = tasks.ToList();
|
||||||
|
myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)));
|
||||||
|
|
||||||
|
ScheduledTasks = myTasks.ToArray();
|
||||||
|
|
||||||
|
BindToSystemEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
foreach (var task in ScheduledTasks)
|
||||||
|
{
|
||||||
|
task.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cancel(IScheduledTaskWorker task)
|
||||||
|
{
|
||||||
|
((ScheduledTaskWorker)task).Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Execute(IScheduledTaskWorker task, TaskExecutionOptions options)
|
||||||
|
{
|
||||||
|
return ((ScheduledTaskWorker)task).Execute(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [task executing].
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">The task.</param>
|
||||||
|
internal void OnTaskExecuting(IScheduledTaskWorker task)
|
||||||
|
{
|
||||||
|
EventHelper.FireEventIfNotNull(TaskExecuting, this, new GenericEventArgs<IScheduledTaskWorker>
|
||||||
|
{
|
||||||
|
Argument = task
|
||||||
|
|
||||||
|
}, Logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [task completed].
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">The task.</param>
|
||||||
|
/// <param name="result">The result.</param>
|
||||||
|
internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result)
|
||||||
|
{
|
||||||
|
EventHelper.FireEventIfNotNull(TaskCompleted, task, new TaskCompletionEventArgs
|
||||||
|
{
|
||||||
|
Result = result,
|
||||||
|
Task = task
|
||||||
|
|
||||||
|
}, Logger);
|
||||||
|
|
||||||
|
ExecuteQueuedTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the queued tasks.
|
||||||
|
/// </summary>
|
||||||
|
private void ExecuteQueuedTasks()
|
||||||
|
{
|
||||||
|
if (SuspendTriggers)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info("ExecuteQueuedTasks");
|
||||||
|
|
||||||
|
// Execute queued tasks
|
||||||
|
lock (_taskQueue)
|
||||||
|
{
|
||||||
|
var list = new List<Tuple<Type, TaskExecutionOptions>>();
|
||||||
|
|
||||||
|
Tuple<Type, TaskExecutionOptions> item;
|
||||||
|
while (_taskQueue.TryDequeue(out item))
|
||||||
|
{
|
||||||
|
if (list.All(i => i.Item1 != item.Item1))
|
||||||
|
{
|
||||||
|
list.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var enqueuedType in list)
|
||||||
|
{
|
||||||
|
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1);
|
||||||
|
|
||||||
|
if (scheduledTask.State == TaskState.Idle)
|
||||||
|
{
|
||||||
|
Execute(scheduledTask, enqueuedType.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks.Tasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes old cache files
|
||||||
|
/// </summary>
|
||||||
|
public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the application paths.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The application paths.</value>
|
||||||
|
private IApplicationPaths ApplicationPaths { get; set; }
|
||||||
|
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
|
||||||
|
/// </summary>
|
||||||
|
public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
ApplicationPaths = appPaths;
|
||||||
|
_logger = logger;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the triggers that define when the task will run
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
|
{
|
||||||
|
return new[] {
|
||||||
|
|
||||||
|
// Every so often
|
||||||
|
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the task to be executed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||||
|
{
|
||||||
|
var minDateModified = DateTime.UtcNow.AddDays(-30);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.CachePath, minDateModified, progress);
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
// No biggie here. Nothing to delete
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.Report(90);
|
||||||
|
|
||||||
|
minDateModified = DateTime.UtcNow.AddDays(-1);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, minDateModified, progress);
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
// No biggie here. Nothing to delete
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes the cache files from directory with a last write time less than a given date
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">The task cancellation token.</param>
|
||||||
|
/// <param name="directory">The directory.</param>
|
||||||
|
/// <param name="minDateModified">The min date modified.</param>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress)
|
||||||
|
{
|
||||||
|
var filesToDelete = _fileSystem.GetFiles(directory, true)
|
||||||
|
.Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
foreach (var file in filesToDelete)
|
||||||
|
{
|
||||||
|
double percent = index;
|
||||||
|
percent /= filesToDelete.Count;
|
||||||
|
|
||||||
|
progress.Report(100 * percent);
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
DeleteFile(file.FullName);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteEmptyFolders(directory);
|
||||||
|
|
||||||
|
progress.Report(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteEmptyFolders(string parent)
|
||||||
|
{
|
||||||
|
foreach (var directory in _fileSystem.GetDirectoryPaths(parent))
|
||||||
|
{
|
||||||
|
DeleteEmptyFolders(directory);
|
||||||
|
if (!_fileSystem.GetFileSystemEntryPaths(directory).Any())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_fileSystem.DeleteDirectory(directory, false);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error deleting directory {0}", ex, directory);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error deleting directory {0}", ex, directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteFile(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_fileSystem.DeleteFile(path);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error deleting file {0}", ex, path);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error deleting file {0}", ex, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the task
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return "Cache file cleanup"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key
|
||||||
|
{
|
||||||
|
get { return "DeleteCacheFiles"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The description.</value>
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get { return "Deletes cache files no longer needed by the system"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the category.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The category.</value>
|
||||||
|
public string Category
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return "Maintenance";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this instance is hidden.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
|
||||||
|
public bool IsHidden
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLogged
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks.Tasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes old log files
|
||||||
|
/// </summary>
|
||||||
|
public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the configuration manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The configuration manager.</value>
|
||||||
|
private IConfigurationManager ConfigurationManager { get; set; }
|
||||||
|
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
|
public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
ConfigurationManager = configurationManager;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the triggers that define when the task will run
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
|
{
|
||||||
|
return new[] {
|
||||||
|
|
||||||
|
// Every so often
|
||||||
|
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the task to be executed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||||
|
{
|
||||||
|
// Delete log files more than n days old
|
||||||
|
var minDateModified = DateTime.UtcNow.AddDays(-ConfigurationManager.CommonConfiguration.LogFileRetentionDays);
|
||||||
|
|
||||||
|
var filesToDelete = _fileSystem.GetFiles(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, true)
|
||||||
|
.Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
foreach (var file in filesToDelete)
|
||||||
|
{
|
||||||
|
double percent = index;
|
||||||
|
percent /= filesToDelete.Count;
|
||||||
|
|
||||||
|
progress.Report(100 * percent);
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
_fileSystem.DeleteFile(file.FullName);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.Report(100);
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key
|
||||||
|
{
|
||||||
|
get { return "CleanLogFiles"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the task
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return "Log file cleanup"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The description.</value>
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get { return string.Format("Deletes log files that are more than {0} days old.", ConfigurationManager.CommonConfiguration.LogFileRetentionDays); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the category.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The category.</value>
|
||||||
|
public string Category
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return "Maintenance";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this instance is hidden.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
|
||||||
|
public bool IsHidden
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLogged
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks.Tasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class ReloadLoggerFileTask
|
||||||
|
/// </summary>
|
||||||
|
public class ReloadLoggerFileTask : IScheduledTask, IConfigurableScheduledTask
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the log manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The log manager.</value>
|
||||||
|
private ILogManager LogManager { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the configuration manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The configuration manager.</value>
|
||||||
|
private IConfigurationManager ConfigurationManager { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ReloadLoggerFileTask" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logManager">The logManager.</param>
|
||||||
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
|
public ReloadLoggerFileTask(ILogManager logManager, IConfigurationManager configurationManager)
|
||||||
|
{
|
||||||
|
LogManager = logManager;
|
||||||
|
ConfigurationManager = configurationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the default triggers.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
|
{
|
||||||
|
var trigger = new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerDaily, TimeOfDayTicks = TimeSpan.FromHours(0).Ticks }; //12am
|
||||||
|
|
||||||
|
return new[] { trigger };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the internal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
progress.Report(0);
|
||||||
|
|
||||||
|
LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging
|
||||||
|
? LogSeverity.Debug
|
||||||
|
: LogSeverity.Info);
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return "Start new log file"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The description.</value>
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get { return "Moves logging to a new file to help reduce log file sizes."; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the category.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The category.</value>
|
||||||
|
public string Category
|
||||||
|
{
|
||||||
|
get { return "Application"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHidden
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLogged
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
Emby.Common.Implementations/ScheduledTasks/WeeklyTrigger.cs
Normal file
116
Emby.Common.Implementations/ScheduledTasks/WeeklyTrigger.cs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.ScheduledTasks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a task trigger that fires on a weekly basis
|
||||||
|
/// </summary>
|
||||||
|
public class WeeklyTrigger : ITaskTrigger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the time of day to trigger the task to run
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The time of day.</value>
|
||||||
|
public TimeSpan TimeOfDay { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the day of week.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The day of week.</value>
|
||||||
|
public DayOfWeek DayOfWeek { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the execution properties of this task.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The execution properties of this task.
|
||||||
|
/// </value>
|
||||||
|
public TaskExecutionOptions TaskOptions { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timer.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The timer.</value>
|
||||||
|
private Timer Timer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stars waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lastResult">The last result.</param>
|
||||||
|
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
|
||||||
|
public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
|
||||||
|
var triggerDate = GetNextTriggerDateTime();
|
||||||
|
|
||||||
|
Timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next trigger date time.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>DateTime.</returns>
|
||||||
|
private DateTime GetNextTriggerDateTime()
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
|
||||||
|
// If it's on the same day
|
||||||
|
if (now.DayOfWeek == DayOfWeek)
|
||||||
|
{
|
||||||
|
// It's either later today, or a week from now
|
||||||
|
return now.TimeOfDay < TimeOfDay ? now.Date.Add(TimeOfDay) : now.Date.AddDays(7).Add(TimeOfDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
var triggerDate = now.Date;
|
||||||
|
|
||||||
|
// Walk the date forward until we get to the trigger day
|
||||||
|
while (triggerDate.DayOfWeek != DayOfWeek)
|
||||||
|
{
|
||||||
|
triggerDate = triggerDate.AddDays(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the trigger date plus the time offset
|
||||||
|
return triggerDate.Add(TimeOfDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops waiting for the trigger action
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes the timer.
|
||||||
|
/// </summary>
|
||||||
|
private void DisposeTimer()
|
||||||
|
{
|
||||||
|
if (Timer != null)
|
||||||
|
{
|
||||||
|
Timer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
private void OnTriggered()
|
||||||
|
{
|
||||||
|
if (Triggered != null)
|
||||||
|
{
|
||||||
|
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
Emby.Common.Implementations/Serialization/XmlSerializer.cs
Normal file
130
Emby.Common.Implementations/Serialization/XmlSerializer.cs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.Serialization
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a wrapper around third party xml serialization.
|
||||||
|
/// </summary>
|
||||||
|
public class XmlSerializer : IXmlSerializer
|
||||||
|
{
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public XmlSerializer(IFileSystem fileSystem, ILogger logger)
|
||||||
|
{
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to cache these
|
||||||
|
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
|
||||||
|
private readonly Dictionary<string, System.Xml.Serialization.XmlSerializer> _serializers =
|
||||||
|
new Dictionary<string, System.Xml.Serialization.XmlSerializer>();
|
||||||
|
|
||||||
|
private System.Xml.Serialization.XmlSerializer GetSerializer(Type type)
|
||||||
|
{
|
||||||
|
var key = type.FullName;
|
||||||
|
lock (_serializers)
|
||||||
|
{
|
||||||
|
System.Xml.Serialization.XmlSerializer serializer;
|
||||||
|
if (!_serializers.TryGetValue(key, out serializer))
|
||||||
|
{
|
||||||
|
serializer = new System.Xml.Serialization.XmlSerializer(type);
|
||||||
|
_serializers[key] = serializer;
|
||||||
|
}
|
||||||
|
return serializer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes to writer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The obj.</param>
|
||||||
|
/// <param name="writer">The writer.</param>
|
||||||
|
private void SerializeToWriter(object obj, XmlWriter writer)
|
||||||
|
{
|
||||||
|
//writer.Formatting = Formatting.Indented;
|
||||||
|
var netSerializer = GetSerializer(obj.GetType());
|
||||||
|
netSerializer.Serialize(writer, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializes from stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <param name="stream">The stream.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
public object DeserializeFromStream(Type type, Stream stream)
|
||||||
|
{
|
||||||
|
using (var reader = XmlReader.Create(stream))
|
||||||
|
{
|
||||||
|
var netSerializer = GetSerializer(type);
|
||||||
|
return netSerializer.Deserialize(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes to stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The obj.</param>
|
||||||
|
/// <param name="stream">The stream.</param>
|
||||||
|
public void SerializeToStream(object obj, Stream stream)
|
||||||
|
{
|
||||||
|
using (var writer = XmlWriter.Create(stream))
|
||||||
|
{
|
||||||
|
SerializeToWriter(obj, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes to file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The obj.</param>
|
||||||
|
/// <param name="file">The file.</param>
|
||||||
|
public void SerializeToFile(object obj, string file)
|
||||||
|
{
|
||||||
|
_logger.Debug("Serializing to file {0}", file);
|
||||||
|
using (var stream = new FileStream(file, FileMode.Create))
|
||||||
|
{
|
||||||
|
SerializeToStream(obj, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializes from file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <param name="file">The file.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
public object DeserializeFromFile(Type type, string file)
|
||||||
|
{
|
||||||
|
_logger.Debug("Deserializing file {0}", file);
|
||||||
|
using (var stream = _fileSystem.OpenRead(file))
|
||||||
|
{
|
||||||
|
return DeserializeFromStream(type, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializes from bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <param name="buffer">The buffer.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
|
public object DeserializeFromBytes(Type type, byte[] buffer)
|
||||||
|
{
|
||||||
|
using (var stream = new MemoryStream(buffer))
|
||||||
|
{
|
||||||
|
return DeserializeFromStream(type, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
269
Emby.Common.Implementations/Updates/GithubUpdater.cs
Normal file
269
Emby.Common.Implementations/Updates/GithubUpdater.cs
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
using MediaBrowser.Model.Updates;
|
||||||
|
|
||||||
|
namespace Emby.Common.Implementations.Updates
|
||||||
|
{
|
||||||
|
public class GithubUpdater
|
||||||
|
{
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
|
|
||||||
|
public GithubUpdater(IHttpClient httpClient, IJsonSerializer jsonSerializer)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_jsonSerializer = jsonSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CheckForUpdateResult> CheckForUpdateResult(string organzation, string repository, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename, TimeSpan cacheLength, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
|
||||||
|
|
||||||
|
var options = new HttpRequestOptions
|
||||||
|
{
|
||||||
|
Url = url,
|
||||||
|
EnableKeepAlive = false,
|
||||||
|
CancellationToken = cancellationToken,
|
||||||
|
UserAgent = "Emby/3.0",
|
||||||
|
BufferContent = false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cacheLength.Ticks > 0)
|
||||||
|
{
|
||||||
|
options.CacheMode = CacheMode.Unconditional;
|
||||||
|
options.CacheLength = cacheLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var obj = _jsonSerializer.DeserializeFromStream<RootObject[]>(stream);
|
||||||
|
|
||||||
|
return CheckForUpdateResult(obj, minVersion, updateLevel, assetFilename, packageName, targetFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CheckForUpdateResult CheckForUpdateResult(RootObject[] obj, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename)
|
||||||
|
{
|
||||||
|
if (updateLevel == PackageVersionClass.Release)
|
||||||
|
{
|
||||||
|
// Technically all we need to do is check that it's not pre-release
|
||||||
|
// But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
|
||||||
|
obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||||
|
}
|
||||||
|
else if (updateLevel == PackageVersionClass.Beta)
|
||||||
|
{
|
||||||
|
obj = obj.Where(i => !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||||
|
}
|
||||||
|
else if (updateLevel == PackageVersionClass.Dev)
|
||||||
|
{
|
||||||
|
obj = obj.Where(i => !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) || i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
var availableUpdate = obj
|
||||||
|
.Select(i => CheckForUpdateResult(i, minVersion, assetFilename, packageName, targetFilename))
|
||||||
|
.Where(i => i != null)
|
||||||
|
.OrderByDescending(i => Version.Parse(i.AvailableVersion))
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return availableUpdate ?? new CheckForUpdateResult
|
||||||
|
{
|
||||||
|
IsUpdateAvailable = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MatchesUpdateLevel(RootObject i, PackageVersionClass updateLevel)
|
||||||
|
{
|
||||||
|
if (updateLevel == PackageVersionClass.Beta)
|
||||||
|
{
|
||||||
|
return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
if (updateLevel == PackageVersionClass.Dev)
|
||||||
|
{
|
||||||
|
return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Technically all we need to do is check that it's not pre-release
|
||||||
|
// But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
|
||||||
|
return !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<RootObject>> GetLatestReleases(string organzation, string repository, string assetFilename, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var list = new List<RootObject>();
|
||||||
|
|
||||||
|
var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
|
||||||
|
|
||||||
|
var options = new HttpRequestOptions
|
||||||
|
{
|
||||||
|
Url = url,
|
||||||
|
EnableKeepAlive = false,
|
||||||
|
CancellationToken = cancellationToken,
|
||||||
|
UserAgent = "Emby/3.0",
|
||||||
|
BufferContent = false
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var obj = _jsonSerializer.DeserializeFromStream<RootObject[]>(stream);
|
||||||
|
|
||||||
|
obj = obj.Where(i => (i.assets ?? new List<Asset>()).Any(a => IsAsset(a, assetFilename))).ToArray();
|
||||||
|
|
||||||
|
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Release)).OrderByDescending(GetVersion).Take(1));
|
||||||
|
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Beta)).OrderByDescending(GetVersion).Take(1));
|
||||||
|
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Dev)).OrderByDescending(GetVersion).Take(1));
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version GetVersion(RootObject obj)
|
||||||
|
{
|
||||||
|
Version version;
|
||||||
|
if (!Version.TryParse(obj.tag_name, out version))
|
||||||
|
{
|
||||||
|
return new Version(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CheckForUpdateResult CheckForUpdateResult(RootObject obj, Version minVersion, string assetFilename, string packageName, string targetFilename)
|
||||||
|
{
|
||||||
|
Version version;
|
||||||
|
if (!Version.TryParse(obj.tag_name, out version))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < minVersion)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var asset = (obj.assets ?? new List<Asset>()).FirstOrDefault(i => IsAsset(i, assetFilename));
|
||||||
|
|
||||||
|
if (asset == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CheckForUpdateResult
|
||||||
|
{
|
||||||
|
AvailableVersion = version.ToString(),
|
||||||
|
IsUpdateAvailable = version > minVersion,
|
||||||
|
Package = new PackageVersionInfo
|
||||||
|
{
|
||||||
|
classification = obj.prerelease ?
|
||||||
|
(obj.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase) ? PackageVersionClass.Dev : PackageVersionClass.Beta) :
|
||||||
|
PackageVersionClass.Release,
|
||||||
|
name = packageName,
|
||||||
|
sourceUrl = asset.browser_download_url,
|
||||||
|
targetFilename = targetFilename,
|
||||||
|
versionStr = version.ToString(),
|
||||||
|
requiredVersionStr = "1.0.0",
|
||||||
|
description = obj.body,
|
||||||
|
infoUrl = obj.html_url
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsAsset(Asset asset, string assetFilename)
|
||||||
|
{
|
||||||
|
var downloadFilename = Path.GetFileName(asset.browser_download_url) ?? string.Empty;
|
||||||
|
|
||||||
|
if (downloadFilename.IndexOf(assetFilename, StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(assetFilename, downloadFilename, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Uploader
|
||||||
|
{
|
||||||
|
public string login { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public string avatar_url { get; set; }
|
||||||
|
public string gravatar_id { get; set; }
|
||||||
|
public string url { get; set; }
|
||||||
|
public string html_url { get; set; }
|
||||||
|
public string followers_url { get; set; }
|
||||||
|
public string following_url { get; set; }
|
||||||
|
public string gists_url { get; set; }
|
||||||
|
public string starred_url { get; set; }
|
||||||
|
public string subscriptions_url { get; set; }
|
||||||
|
public string organizations_url { get; set; }
|
||||||
|
public string repos_url { get; set; }
|
||||||
|
public string events_url { get; set; }
|
||||||
|
public string received_events_url { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
public bool site_admin { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Asset
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public object label { get; set; }
|
||||||
|
public Uploader uploader { get; set; }
|
||||||
|
public string content_type { get; set; }
|
||||||
|
public string state { get; set; }
|
||||||
|
public int size { get; set; }
|
||||||
|
public int download_count { get; set; }
|
||||||
|
public string created_at { get; set; }
|
||||||
|
public string updated_at { get; set; }
|
||||||
|
public string browser_download_url { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Author
|
||||||
|
{
|
||||||
|
public string login { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public string avatar_url { get; set; }
|
||||||
|
public string gravatar_id { get; set; }
|
||||||
|
public string url { get; set; }
|
||||||
|
public string html_url { get; set; }
|
||||||
|
public string followers_url { get; set; }
|
||||||
|
public string following_url { get; set; }
|
||||||
|
public string gists_url { get; set; }
|
||||||
|
public string starred_url { get; set; }
|
||||||
|
public string subscriptions_url { get; set; }
|
||||||
|
public string organizations_url { get; set; }
|
||||||
|
public string repos_url { get; set; }
|
||||||
|
public string events_url { get; set; }
|
||||||
|
public string received_events_url { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
public bool site_admin { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RootObject
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
public string assets_url { get; set; }
|
||||||
|
public string upload_url { get; set; }
|
||||||
|
public string html_url { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
public string tag_name { get; set; }
|
||||||
|
public string target_commitish { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public bool draft { get; set; }
|
||||||
|
public Author author { get; set; }
|
||||||
|
public bool prerelease { get; set; }
|
||||||
|
public string created_at { get; set; }
|
||||||
|
public string published_at { get; set; }
|
||||||
|
public List<Asset> assets { get; set; }
|
||||||
|
public string tarball_url { get; set; }
|
||||||
|
public string zipball_url { get; set; }
|
||||||
|
public string body { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
Emby.Common.Implementations/project.fragment.lock.json
Normal file
39
Emby.Common.Implementations/project.fragment.lock.json
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"exports": {
|
||||||
|
"MediaBrowser.Common/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"framework": ".NETPortable,Version=v4.5,Profile=Profile7",
|
||||||
|
"compile": {
|
||||||
|
"bin/Debug/MediaBrowser.Common.dll": {}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"bin/Debug/MediaBrowser.Common.dll": {}
|
||||||
|
},
|
||||||
|
"contentFiles": {
|
||||||
|
"bin/Debug/MediaBrowser.Common.pdb": {
|
||||||
|
"buildAction": "None",
|
||||||
|
"codeLanguage": "any",
|
||||||
|
"copyToOutput": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MediaBrowser.Model/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"framework": ".NETPortable,Version=v4.5,Profile=Profile7",
|
||||||
|
"compile": {
|
||||||
|
"bin/Debug/MediaBrowser.Model.dll": {}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"bin/Debug/MediaBrowser.Model.dll": {}
|
||||||
|
},
|
||||||
|
"contentFiles": {
|
||||||
|
"bin/Debug/MediaBrowser.Model.pdb": {
|
||||||
|
"buildAction": "None",
|
||||||
|
"codeLanguage": "any",
|
||||||
|
"copyToOutput": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Emby.Common.Implementations/project.json
Normal file
48
Emby.Common.Implementations/project.json
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0-*",
|
||||||
|
|
||||||
|
"dependencies": {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
"frameworks": {
|
||||||
|
"net46": {
|
||||||
|
"frameworkAssemblies": {
|
||||||
|
"System.Collections": "4.0.0.0",
|
||||||
|
"System.IO": "4.0.0.0",
|
||||||
|
"System.Net": "4.0.0.0",
|
||||||
|
"System.Net.Http": "4.0.0.0",
|
||||||
|
"System.Net.Http.WebRequest": "4.0.0.0",
|
||||||
|
"System.Net.Primitives": "4.0.0.0",
|
||||||
|
"System.Runtime": "4.0.0.0",
|
||||||
|
"System.Text.Encoding": "4.0.0.0",
|
||||||
|
"System.Threading": "4.0.0.0",
|
||||||
|
"System.Threading.Tasks": "4.0.0.0",
|
||||||
|
"System.Xml": "4.0.0.0",
|
||||||
|
"System.Xml.Serialization": "4.0.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"MediaBrowser.Common": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.Model": {
|
||||||
|
"target": "project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"netstandard1.6": {
|
||||||
|
"imports": "dnxcore50",
|
||||||
|
"dependencies": {
|
||||||
|
"NETStandard.Library": "1.6.0",
|
||||||
|
"MediaBrowser.Common": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"MediaBrowser.Model": {
|
||||||
|
"target": "project"
|
||||||
|
},
|
||||||
|
"System.Net.Requests": "4.0.11",
|
||||||
|
"System.Xml.XmlSerializer": "4.0.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4403
Emby.Common.Implementations/project.lock.json
Normal file
4403
Emby.Common.Implementations/project.lock.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,6 @@ using MediaBrowser.Common.Events;
|
||||||
using MediaBrowser.Common.Implementations.Devices;
|
using MediaBrowser.Common.Implementations.Devices;
|
||||||
using MediaBrowser.Common.Implementations.IO;
|
using MediaBrowser.Common.Implementations.IO;
|
||||||
using MediaBrowser.Common.Implementations.ScheduledTasks;
|
using MediaBrowser.Common.Implementations.ScheduledTasks;
|
||||||
using MediaBrowser.Common.Implementations.Security;
|
|
||||||
using MediaBrowser.Common.Implementations.Serialization;
|
using MediaBrowser.Common.Implementations.Serialization;
|
||||||
using MediaBrowser.Common.Implementations.Updates;
|
using MediaBrowser.Common.Implementations.Updates;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
@ -31,6 +30,7 @@ using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Implementations.Cryptography;
|
using MediaBrowser.Common.Implementations.Cryptography;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
using MediaBrowser.Model.System;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations
|
namespace MediaBrowser.Common.Implementations
|
||||||
|
@ -121,11 +121,6 @@ namespace MediaBrowser.Common.Implementations
|
||||||
/// <value>The kernel.</value>
|
/// <value>The kernel.</value>
|
||||||
protected ITaskManager TaskManager { get; private set; }
|
protected ITaskManager TaskManager { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the security manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The security manager.</value>
|
|
||||||
protected ISecurityManager SecurityManager { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the HTTP client.
|
/// Gets the HTTP client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The HTTP client.</value>
|
/// <value>The HTTP client.</value>
|
||||||
|
@ -142,16 +137,12 @@ namespace MediaBrowser.Common.Implementations
|
||||||
/// <value>The configuration manager.</value>
|
/// <value>The configuration manager.</value>
|
||||||
protected IConfigurationManager ConfigurationManager { get; private set; }
|
protected IConfigurationManager ConfigurationManager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the installation manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The installation manager.</value>
|
|
||||||
protected IInstallationManager InstallationManager { get; private set; }
|
|
||||||
|
|
||||||
protected IFileSystem FileSystemManager { get; private set; }
|
protected IFileSystem FileSystemManager { get; private set; }
|
||||||
|
|
||||||
protected IIsoManager IsoManager { get; private set; }
|
protected IIsoManager IsoManager { get; private set; }
|
||||||
|
|
||||||
|
protected ISystemEvents SystemEvents { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name.
|
/// Gets the name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -221,6 +212,7 @@ namespace MediaBrowser.Common.Implementations
|
||||||
JsonSerializer = CreateJsonSerializer();
|
JsonSerializer = CreateJsonSerializer();
|
||||||
|
|
||||||
MemoryStreamProvider = CreateMemoryStreamProvider();
|
MemoryStreamProvider = CreateMemoryStreamProvider();
|
||||||
|
SystemEvents = CreateSystemEvents();
|
||||||
|
|
||||||
OnLoggerLoaded(true);
|
OnLoggerLoaded(true);
|
||||||
LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false);
|
LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false);
|
||||||
|
@ -254,6 +246,7 @@ namespace MediaBrowser.Common.Implementations
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IMemoryStreamProvider CreateMemoryStreamProvider();
|
protected abstract IMemoryStreamProvider CreateMemoryStreamProvider();
|
||||||
|
protected abstract ISystemEvents CreateSystemEvents();
|
||||||
|
|
||||||
protected virtual void OnLoggerLoaded(bool isFirstLoad)
|
protected virtual void OnLoggerLoaded(bool isFirstLoad)
|
||||||
{
|
{
|
||||||
|
@ -473,11 +466,12 @@ namespace MediaBrowser.Common.Implementations
|
||||||
|
|
||||||
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
|
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
|
||||||
|
|
||||||
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LogManager.GetLogger("TaskManager"), FileSystemManager);
|
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LogManager.GetLogger("TaskManager"), FileSystemManager, SystemEvents);
|
||||||
|
|
||||||
RegisterSingleInstance(JsonSerializer);
|
RegisterSingleInstance(JsonSerializer);
|
||||||
RegisterSingleInstance(XmlSerializer);
|
RegisterSingleInstance(XmlSerializer);
|
||||||
RegisterSingleInstance(MemoryStreamProvider);
|
RegisterSingleInstance(MemoryStreamProvider);
|
||||||
|
RegisterSingleInstance(SystemEvents);
|
||||||
|
|
||||||
RegisterSingleInstance(LogManager);
|
RegisterSingleInstance(LogManager);
|
||||||
RegisterSingleInstance(Logger);
|
RegisterSingleInstance(Logger);
|
||||||
|
@ -492,12 +486,6 @@ namespace MediaBrowser.Common.Implementations
|
||||||
NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager"));
|
NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager"));
|
||||||
RegisterSingleInstance(NetworkManager);
|
RegisterSingleInstance(NetworkManager);
|
||||||
|
|
||||||
SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager);
|
|
||||||
RegisterSingleInstance(SecurityManager);
|
|
||||||
|
|
||||||
InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager);
|
|
||||||
RegisterSingleInstance(InstallationManager);
|
|
||||||
|
|
||||||
IsoManager = new IsoManager();
|
IsoManager = new IsoManager();
|
||||||
RegisterSingleInstance(IsoManager);
|
RegisterSingleInstance(IsoManager);
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// Lazy load
|
// Lazy load
|
||||||
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
|
LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem));
|
||||||
return _configuration;
|
return _configuration;
|
||||||
}
|
}
|
||||||
protected set
|
protected set
|
||||||
|
@ -126,7 +126,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
Logger.Info("Saving system configuration");
|
Logger.Info("Saving system configuration");
|
||||||
var path = CommonApplicationPaths.SystemConfigurationFilePath;
|
var path = CommonApplicationPaths.SystemConfigurationFilePath;
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
FileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
lock (_configurationSyncLock)
|
lock (_configurationSyncLock)
|
||||||
{
|
{
|
||||||
|
@ -196,9 +196,9 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
|
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
|
||||||
{
|
{
|
||||||
// Validate
|
// Validate
|
||||||
if (!Directory.Exists(newPath))
|
if (!FileSystem.DirectoryExists(newPath))
|
||||||
{
|
{
|
||||||
throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
|
throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureWriteAccess(newPath);
|
EnsureWriteAccess(newPath);
|
||||||
|
@ -253,7 +253,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
{
|
{
|
||||||
return Activator.CreateInstance(configurationType);
|
return Activator.CreateInstance(configurationType);
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
return Activator.CreateInstance(configurationType);
|
return Activator.CreateInstance(configurationType);
|
||||||
}
|
}
|
||||||
|
@ -293,7 +293,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
||||||
|
|
||||||
var path = GetConfigurationFile(key);
|
var path = GetConfigurationFile(key);
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
FileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
lock (_configurationSyncLock)
|
lock (_configurationSyncLock)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.Configuration
|
namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
{
|
{
|
||||||
|
@ -18,7 +19,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="xmlSerializer">The XML serializer.</param>
|
/// <param name="xmlSerializer">The XML serializer.</param>
|
||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer)
|
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
object configuration;
|
object configuration;
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
// Use try/catch to avoid the extra file system lookup using File.Exists
|
// Use try/catch to avoid the extra file system lookup using File.Exists
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
buffer = File.ReadAllBytes(path);
|
buffer = fileSystem.ReadAllBytes(path);
|
||||||
|
|
||||||
configuration = xmlSerializer.DeserializeFromBytes(type, buffer);
|
configuration = xmlSerializer.DeserializeFromBytes(type, buffer);
|
||||||
}
|
}
|
||||||
|
@ -46,10 +47,10 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
||||||
// If the file didn't exist before, or if something has changed, re-save
|
// If the file didn't exist before, or if something has changed, re-save
|
||||||
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
// Save it after load in case we got new items
|
// Save it after load in case we got new items
|
||||||
File.WriteAllBytes(path, newBytes);
|
fileSystem.WriteAllBytes(path, newBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuration;
|
return configuration;
|
||||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MediaBrowser.Common.IO;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
|
@ -660,6 +659,11 @@ namespace MediaBrowser.Common.Implementations.IO
|
||||||
return File.ReadAllText(path);
|
return File.ReadAllText(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] ReadAllBytes(string path)
|
||||||
|
{
|
||||||
|
return File.ReadAllBytes(path);
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteAllText(string path, string text, Encoding encoding)
|
public void WriteAllText(string path, string text, Encoding encoding)
|
||||||
{
|
{
|
||||||
File.WriteAllText(path, text, encoding);
|
File.WriteAllText(path, text, encoding);
|
||||||
|
@ -670,6 +674,11 @@ namespace MediaBrowser.Common.Implementations.IO
|
||||||
File.WriteAllText(path, text);
|
File.WriteAllText(path, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteAllBytes(string path, byte[] bytes)
|
||||||
|
{
|
||||||
|
File.WriteAllBytes(path, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
public string ReadAllText(string path, Encoding encoding)
|
public string ReadAllText(string path, Encoding encoding)
|
||||||
{
|
{
|
||||||
return File.ReadAllText(path, encoding);
|
return File.ReadAllText(path, encoding);
|
||||||
|
|
|
@ -79,14 +79,8 @@
|
||||||
<Compile Include="ScheduledTasks\Tasks\DeleteLogFileTask.cs" />
|
<Compile Include="ScheduledTasks\Tasks\DeleteLogFileTask.cs" />
|
||||||
<Compile Include="ScheduledTasks\Tasks\ReloadLoggerFileTask.cs" />
|
<Compile Include="ScheduledTasks\Tasks\ReloadLoggerFileTask.cs" />
|
||||||
<Compile Include="ScheduledTasks\WeeklyTrigger.cs" />
|
<Compile Include="ScheduledTasks\WeeklyTrigger.cs" />
|
||||||
<Compile Include="Security\MbAdmin.cs" />
|
|
||||||
<Compile Include="Security\MBLicenseFile.cs" />
|
|
||||||
<Compile Include="Security\PluginSecurityManager.cs" />
|
|
||||||
<Compile Include="Security\RegRecord.cs" />
|
|
||||||
<Compile Include="Security\SuppporterInfoResponse.cs" />
|
|
||||||
<Compile Include="Serialization\XmlSerializer.cs" />
|
<Compile Include="Serialization\XmlSerializer.cs" />
|
||||||
<Compile Include="Updates\GithubUpdater.cs" />
|
<Compile Include="Updates\GithubUpdater.cs" />
|
||||||
<Compile Include="Updates\InstallationManager.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using MediaBrowser.Model.Events;
|
using System;
|
||||||
using MediaBrowser.Model.Tasks;
|
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.ScheduledTasks
|
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a task trigger that fires everyday
|
/// Represents a task trigger that fires everyday
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using MediaBrowser.Model.Events;
|
using System;
|
||||||
using MediaBrowser.Model.Tasks;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.ScheduledTasks
|
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a task trigger that runs repeatedly on an interval
|
/// Represents a task trigger that runs repeatedly on an interval
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
using MediaBrowser.Model.Events;
|
using System;
|
||||||
using MediaBrowser.Model.Tasks;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.ScheduledTasks
|
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class StartupTaskTrigger
|
/// Class StartupTaskTrigger
|
||||||
|
|
|
@ -12,6 +12,7 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.System;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
|
@ -48,6 +49,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
/// <value>The application paths.</value>
|
/// <value>The application paths.</value>
|
||||||
private IApplicationPaths ApplicationPaths { get; set; }
|
private IApplicationPaths ApplicationPaths { get; set; }
|
||||||
|
|
||||||
|
private readonly ISystemEvents _systemEvents;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the logger.
|
/// Gets the logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -81,29 +84,23 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
/// <param name="jsonSerializer">The json serializer.</param>
|
/// <param name="jsonSerializer">The json serializer.</param>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <exception cref="System.ArgumentException">kernel</exception>
|
/// <exception cref="System.ArgumentException">kernel</exception>
|
||||||
public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem)
|
public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem, ISystemEvents systemEvents)
|
||||||
{
|
{
|
||||||
ApplicationPaths = applicationPaths;
|
ApplicationPaths = applicationPaths;
|
||||||
JsonSerializer = jsonSerializer;
|
JsonSerializer = jsonSerializer;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
_systemEvents = systemEvents;
|
||||||
|
|
||||||
ScheduledTasks = new IScheduledTaskWorker[] { };
|
ScheduledTasks = new IScheduledTaskWorker[] { };
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BindToSystemEvent()
|
private void BindToSystemEvent()
|
||||||
{
|
{
|
||||||
try
|
_systemEvents.Resume += _systemEvents_Resume;
|
||||||
{
|
|
||||||
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
private void _systemEvents_Resume(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
foreach (var task in ScheduledTasks)
|
foreach (var task in ScheduledTasks)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@ using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.ScheduledTasks
|
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a task trigger that fires on a weekly basis
|
/// Represents a task trigger that fires on a weekly basis
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.Security
|
|
||||||
{
|
|
||||||
public class MbAdmin
|
|
||||||
{
|
|
||||||
public const string HttpUrl = "https://www.mb3admin.com/admin/";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Leaving as http for now until we get it squared away
|
|
||||||
/// </summary>
|
|
||||||
public const string HttpsUrl = "https://www.mb3admin.com/admin/";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.Security
|
|
||||||
{
|
|
||||||
internal class SuppporterInfoResponse
|
|
||||||
{
|
|
||||||
public string email { get; set; }
|
|
||||||
public string supporterKey { get; set; }
|
|
||||||
public int totalRegs { get; set; }
|
|
||||||
public int totalMachines { get; set; }
|
|
||||||
public string expDate { get; set; }
|
|
||||||
public string regDate { get; set; }
|
|
||||||
public string planType { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -249,6 +249,10 @@ namespace MediaBrowser.Model.IO
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
string ReadAllText(string path);
|
string ReadAllText(string path);
|
||||||
|
|
||||||
|
byte[] ReadAllBytes(string path);
|
||||||
|
|
||||||
|
void WriteAllBytes(string path, byte[] bytes);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes all text.
|
/// Writes all text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -408,6 +408,7 @@
|
||||||
<Compile Include="Sync\SyncQualityOption.cs" />
|
<Compile Include="Sync\SyncQualityOption.cs" />
|
||||||
<Compile Include="Sync\SyncTarget.cs" />
|
<Compile Include="Sync\SyncTarget.cs" />
|
||||||
<Compile Include="System\Architecture.cs" />
|
<Compile Include="System\Architecture.cs" />
|
||||||
|
<Compile Include="System\ISystemEvents.cs" />
|
||||||
<Compile Include="System\LogFile.cs" />
|
<Compile Include="System\LogFile.cs" />
|
||||||
<Compile Include="System\PublicSystemInfo.cs" />
|
<Compile Include="System\PublicSystemInfo.cs" />
|
||||||
<Compile Include="Tasks\IConfigurableScheduledTask.cs" />
|
<Compile Include="Tasks\IConfigurableScheduledTask.cs" />
|
||||||
|
|
10
MediaBrowser.Model/System/ISystemEvents.cs
Normal file
10
MediaBrowser.Model/System/ISystemEvents.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Model.System
|
||||||
|
{
|
||||||
|
public interface ISystemEvents
|
||||||
|
{
|
||||||
|
event EventHandler Resume;
|
||||||
|
event EventHandler Suspend;
|
||||||
|
}
|
||||||
|
}
|
|
@ -287,6 +287,9 @@
|
||||||
<Compile Include="Persistence\IDbConnector.cs" />
|
<Compile Include="Persistence\IDbConnector.cs" />
|
||||||
<Compile Include="Persistence\MediaStreamColumns.cs" />
|
<Compile Include="Persistence\MediaStreamColumns.cs" />
|
||||||
<Compile Include="Reflection\AssemblyInfo.cs" />
|
<Compile Include="Reflection\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Security\MBLicenseFile.cs" />
|
||||||
|
<Compile Include="Security\PluginSecurityManager.cs" />
|
||||||
|
<Compile Include="Security\RegRecord.cs" />
|
||||||
<Compile Include="Serialization\JsonSerializer.cs" />
|
<Compile Include="Serialization\JsonSerializer.cs" />
|
||||||
<Compile Include="Social\SharingManager.cs" />
|
<Compile Include="Social\SharingManager.cs" />
|
||||||
<Compile Include="Social\SharingRepository.cs" />
|
<Compile Include="Social\SharingRepository.cs" />
|
||||||
|
@ -296,6 +299,7 @@
|
||||||
<Compile Include="Sync\SyncNotificationEntryPoint.cs" />
|
<Compile Include="Sync\SyncNotificationEntryPoint.cs" />
|
||||||
<Compile Include="Threading\PeriodicTimer.cs" />
|
<Compile Include="Threading\PeriodicTimer.cs" />
|
||||||
<Compile Include="TV\SeriesPostScanTask.cs" />
|
<Compile Include="TV\SeriesPostScanTask.cs" />
|
||||||
|
<Compile Include="Updates\InstallationManager.cs" />
|
||||||
<Compile Include="UserViews\CollectionFolderImageProvider.cs" />
|
<Compile Include="UserViews\CollectionFolderImageProvider.cs" />
|
||||||
<Compile Include="UserViews\DynamicImageProvider.cs" />
|
<Compile Include="UserViews\DynamicImageProvider.cs" />
|
||||||
<Compile Include="News\NewsEntryPoint.cs" />
|
<Compile Include="News\NewsEntryPoint.cs" />
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using MediaBrowser.Common.Configuration;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
@ -7,8 +6,9 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.Security
|
namespace MediaBrowser.Server.Implementations.Security
|
||||||
{
|
{
|
||||||
internal class MBLicenseFile
|
internal class MBLicenseFile
|
||||||
{
|
{
|
|
@ -1,26 +1,29 @@
|
||||||
using System.IO;
|
using System;
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Common.Security;
|
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Common.Security;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.Security
|
namespace MediaBrowser.Server.Implementations.Security
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class PluginSecurityManager
|
/// Class PluginSecurityManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PluginSecurityManager : ISecurityManager
|
public class PluginSecurityManager : ISecurityManager
|
||||||
{
|
{
|
||||||
private const string MBValidateUrl = MbAdmin.HttpsUrl + "service/registration/validate";
|
private const string MBValidateUrl = "https://mb3admin.com/admin/service/registration/validate";
|
||||||
private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "https://mb3admin.com/admin/service/appstore/register";
|
private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "https://mb3admin.com/admin/service/appstore/register";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -57,9 +60,10 @@ namespace MediaBrowser.Common.Implementations.Security
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
private IEnumerable<IRequiresRegistration> _registeredEntities;
|
private IEnumerable<IRequiresRegistration> _registeredEntities;
|
||||||
protected IEnumerable<IRequiresRegistration> RegisteredEntities
|
protected IEnumerable<IRequiresRegistration> RegisteredEntities
|
||||||
|
@ -73,8 +77,8 @@ namespace MediaBrowser.Common.Implementations.Security
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginSecurityManager" /> class.
|
/// Initializes a new instance of the <see cref="PluginSecurityManager" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PluginSecurityManager(IApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer,
|
public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer,
|
||||||
IApplicationPaths appPaths, ILogManager logManager)
|
IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
if (httpClient == null)
|
if (httpClient == null)
|
||||||
{
|
{
|
||||||
|
@ -85,6 +89,7 @@ namespace MediaBrowser.Common.Implementations.Security
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
_logger = logManager.GetLogger("SecurityManager");
|
_logger = logManager.GetLogger("SecurityManager");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +231,7 @@ namespace MediaBrowser.Common.Implementations.Security
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.WriteAllText(Path.Combine(_appPaths.ProgramDataPath, "apptrans-error.txt"), info);
|
_fileSystem.WriteAllText(Path.Combine(_appPaths.ProgramDataPath, "apptrans-error.txt"), info);
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.Security
|
namespace MediaBrowser.Server.Implementations.Security
|
||||||
{
|
{
|
||||||
class RegRecord
|
class RegRecord
|
||||||
{
|
{
|
|
@ -1,16 +1,4 @@
|
||||||
using MediaBrowser.Common.Configuration;
|
using System;
|
||||||
using MediaBrowser.Common.Events;
|
|
||||||
using MediaBrowser.Common.Implementations.Security;
|
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Common.Plugins;
|
|
||||||
using MediaBrowser.Common.Progress;
|
|
||||||
using MediaBrowser.Common.Security;
|
|
||||||
using MediaBrowser.Common.Updates;
|
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using MediaBrowser.Model.Updates;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -18,10 +6,21 @@ using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Events;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Common.Plugins;
|
||||||
|
using MediaBrowser.Common.Progress;
|
||||||
|
using MediaBrowser.Common.Security;
|
||||||
|
using MediaBrowser.Common.Updates;
|
||||||
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
using MediaBrowser.Model.Updates;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.Updates
|
namespace MediaBrowser.Server.Implementations.Updates
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages all install, uninstall and update operations (both plugins and system)
|
/// Manages all install, uninstall and update operations (both plugins and system)
|
||||||
|
@ -164,7 +163,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
||||||
|
|
||||||
if (withRegistration)
|
if (withRegistration)
|
||||||
{
|
{
|
||||||
using (var json = await _httpClient.Post(MbAdmin.HttpsUrl + "service/package/retrieveall", data, cancellationToken).ConfigureAwait(false))
|
using (var json = await _httpClient.Post("https://www.mb3admin.com/admin/service/package/retrieveall", data, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
@ -237,7 +236,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
||||||
|
|
||||||
var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
|
var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
|
||||||
{
|
{
|
||||||
Url = MbAdmin.HttpUrl + "service/MB3Packages.json",
|
Url = "https://www.mb3admin.com/admin/service/MB3Packages.json",
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
Progress = new Progress<Double>()
|
Progress = new Progress<Double>()
|
||||||
|
|
||||||
|
@ -619,7 +618,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
||||||
//If it is an archive - write out a version file so we know what it is
|
//If it is an archive - write out a version file so we know what it is
|
||||||
if (isArchive)
|
if (isArchive)
|
||||||
{
|
{
|
||||||
File.WriteAllText(target + ".ver", package.versionStr);
|
_fileSystem.WriteAllText(target + ".ver", package.versionStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
|
@ -72,7 +72,7 @@ namespace MediaBrowser.Server.Mono
|
||||||
|
|
||||||
private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, StartupOptions options)
|
private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, StartupOptions options)
|
||||||
{
|
{
|
||||||
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
||||||
|
|
||||||
// Allow all https requests
|
// Allow all https requests
|
||||||
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
|
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
|
||||||
|
|
|
@ -104,6 +104,8 @@ using MediaBrowser.Common.Implementations.Serialization;
|
||||||
using MediaBrowser.Common.Implementations.Updates;
|
using MediaBrowser.Common.Implementations.Updates;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
|
using MediaBrowser.Common.Security;
|
||||||
|
using MediaBrowser.Common.Updates;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
|
@ -120,6 +122,7 @@ using MediaBrowser.Model.Xml;
|
||||||
using MediaBrowser.Server.Implementations.Archiving;
|
using MediaBrowser.Server.Implementations.Archiving;
|
||||||
using MediaBrowser.Server.Implementations.Reflection;
|
using MediaBrowser.Server.Implementations.Reflection;
|
||||||
using MediaBrowser.Server.Implementations.Serialization;
|
using MediaBrowser.Server.Implementations.Serialization;
|
||||||
|
using MediaBrowser.Server.Implementations.Updates;
|
||||||
using MediaBrowser.Server.Implementations.Xml;
|
using MediaBrowser.Server.Implementations.Xml;
|
||||||
using OpenSubtitlesHandler;
|
using OpenSubtitlesHandler;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
|
@ -226,6 +229,17 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
private IMediaSourceManager MediaSourceManager { get; set; }
|
private IMediaSourceManager MediaSourceManager { get; set; }
|
||||||
private IPlaylistManager PlaylistManager { get; set; }
|
private IPlaylistManager PlaylistManager { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the installation manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The installation manager.</value>
|
||||||
|
protected IInstallationManager InstallationManager { get; private set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the security manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The security manager.</value>
|
||||||
|
protected ISecurityManager SecurityManager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the zip client.
|
/// Gets or sets the zip client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -405,6 +419,11 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
return new MemoryStreamProvider();
|
return new MemoryStreamProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override ISystemEvents CreateSystemEvents()
|
||||||
|
{
|
||||||
|
return new SystemEvents(LogManager.GetLogger("SystemEvents"));
|
||||||
|
}
|
||||||
|
|
||||||
protected override IJsonSerializer CreateJsonSerializer()
|
protected override IJsonSerializer CreateJsonSerializer()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -636,7 +655,6 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
{
|
{
|
||||||
var migrations = new List<IVersionMigration>
|
var migrations = new List<IVersionMigration>
|
||||||
{
|
{
|
||||||
new MovieDbEpisodeProviderMigration(ServerConfigurationManager),
|
|
||||||
new DbMigration(ServerConfigurationManager, TaskManager)
|
new DbMigration(ServerConfigurationManager, TaskManager)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -660,6 +678,12 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
{
|
{
|
||||||
await base.RegisterResources(progress).ConfigureAwait(false);
|
await base.RegisterResources(progress).ConfigureAwait(false);
|
||||||
|
|
||||||
|
SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager, FileSystemManager);
|
||||||
|
RegisterSingleInstance(SecurityManager);
|
||||||
|
|
||||||
|
InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager);
|
||||||
|
RegisterSingleInstance(InstallationManager);
|
||||||
|
|
||||||
ZipClient = new ZipClient(FileSystemManager);
|
ZipClient = new ZipClient(FileSystemManager);
|
||||||
RegisterSingleInstance(ZipClient);
|
RegisterSingleInstance(ZipClient);
|
||||||
|
|
||||||
|
|
|
@ -77,11 +77,11 @@
|
||||||
<Compile Include="MbLinkShortcutHandler.cs" />
|
<Compile Include="MbLinkShortcutHandler.cs" />
|
||||||
<Compile Include="Migrations\IVersionMigration.cs" />
|
<Compile Include="Migrations\IVersionMigration.cs" />
|
||||||
<Compile Include="Migrations\DbMigration.cs" />
|
<Compile Include="Migrations\DbMigration.cs" />
|
||||||
<Compile Include="Migrations\MovieDbEpisodeProviderMigration.cs" />
|
|
||||||
<Compile Include="Migrations\UpdateLevelMigration.cs" />
|
<Compile Include="Migrations\UpdateLevelMigration.cs" />
|
||||||
<Compile Include="NativeEnvironment.cs" />
|
<Compile Include="NativeEnvironment.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="StartupOptions.cs" />
|
<Compile Include="StartupOptions.cs" />
|
||||||
|
<Compile Include="SystemEvents.cs" />
|
||||||
<Compile Include="UnhandledExceptionWriter.cs" />
|
<Compile Include="UnhandledExceptionWriter.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Startup.Common.Migrations
|
|
||||||
{
|
|
||||||
class MovieDbEpisodeProviderMigration : IVersionMigration
|
|
||||||
{
|
|
||||||
private readonly IServerConfigurationManager _config;
|
|
||||||
private const string _providerName = "TheMovieDb";
|
|
||||||
|
|
||||||
public MovieDbEpisodeProviderMigration(IServerConfigurationManager config)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Run()
|
|
||||||
{
|
|
||||||
var migrationKey = this.GetType().FullName;
|
|
||||||
var migrationKeyList = _config.Configuration.Migrations.ToList();
|
|
||||||
|
|
||||||
if (!migrationKeyList.Contains(migrationKey))
|
|
||||||
{
|
|
||||||
foreach (var metaDataOption in _config.Configuration.MetadataOptions)
|
|
||||||
{
|
|
||||||
if (metaDataOption.ItemType == "Episode")
|
|
||||||
{
|
|
||||||
var disabledFetchers = metaDataOption.DisabledMetadataFetchers.ToList();
|
|
||||||
if (!disabledFetchers.Contains(_providerName))
|
|
||||||
{
|
|
||||||
disabledFetchers.Add(_providerName);
|
|
||||||
metaDataOption.DisabledMetadataFetchers = disabledFetchers.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
migrationKeyList.Add(migrationKey);
|
|
||||||
_config.Configuration.Migrations = migrationKeyList.ToArray();
|
|
||||||
_config.SaveConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
34
MediaBrowser.Server.Startup.Common/SystemEvents.cs
Normal file
34
MediaBrowser.Server.Startup.Common/SystemEvents.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using MediaBrowser.Common.Events;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Startup.Common
|
||||||
|
{
|
||||||
|
public class SystemEvents : ISystemEvents
|
||||||
|
{
|
||||||
|
public event EventHandler Resume;
|
||||||
|
public event EventHandler Suspend;
|
||||||
|
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public SystemEvents(ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Mode)
|
||||||
|
{
|
||||||
|
case Microsoft.Win32.PowerModes.Resume:
|
||||||
|
EventHelper.FireEventIfNotNull(Resume, this, EventArgs.Empty, _logger);
|
||||||
|
break;
|
||||||
|
case Microsoft.Win32.PowerModes.Suspend:
|
||||||
|
EventHelper.FireEventIfNotNull(Suspend, this, EventArgs.Empty, _logger);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -351,8 +351,8 @@ namespace MediaBrowser.ServerApplication
|
||||||
task = InstallVcredist2013IfNeeded(_appHost, _logger);
|
task = InstallVcredist2013IfNeeded(_appHost, _logger);
|
||||||
Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
|
|
||||||
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
||||||
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
|
Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
|
||||||
|
|
||||||
HideSplashScreen();
|
HideSplashScreen();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue