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.IO;
|
||||
using MediaBrowser.Common.Implementations.ScheduledTasks;
|
||||
using MediaBrowser.Common.Implementations.Security;
|
||||
using MediaBrowser.Common.Implementations.Serialization;
|
||||
using MediaBrowser.Common.Implementations.Updates;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
@ -31,6 +30,7 @@ using MediaBrowser.Common.Extensions;
|
|||
using MediaBrowser.Common.Implementations.Cryptography;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.Implementations
|
||||
|
@ -121,11 +121,6 @@ namespace MediaBrowser.Common.Implementations
|
|||
/// <value>The kernel.</value>
|
||||
protected ITaskManager TaskManager { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the security manager.
|
||||
/// </summary>
|
||||
/// <value>The security manager.</value>
|
||||
protected ISecurityManager SecurityManager { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the HTTP client.
|
||||
/// </summary>
|
||||
/// <value>The HTTP client.</value>
|
||||
|
@ -142,16 +137,12 @@ namespace MediaBrowser.Common.Implementations
|
|||
/// <value>The configuration manager.</value>
|
||||
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 IIsoManager IsoManager { get; private set; }
|
||||
|
||||
protected ISystemEvents SystemEvents { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
|
@ -221,6 +212,7 @@ namespace MediaBrowser.Common.Implementations
|
|||
JsonSerializer = CreateJsonSerializer();
|
||||
|
||||
MemoryStreamProvider = CreateMemoryStreamProvider();
|
||||
SystemEvents = CreateSystemEvents();
|
||||
|
||||
OnLoggerLoaded(true);
|
||||
LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false);
|
||||
|
@ -254,6 +246,7 @@ namespace MediaBrowser.Common.Implementations
|
|||
}
|
||||
|
||||
protected abstract IMemoryStreamProvider CreateMemoryStreamProvider();
|
||||
protected abstract ISystemEvents CreateSystemEvents();
|
||||
|
||||
protected virtual void OnLoggerLoaded(bool isFirstLoad)
|
||||
{
|
||||
|
@ -473,11 +466,12 @@ namespace MediaBrowser.Common.Implementations
|
|||
|
||||
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(XmlSerializer);
|
||||
RegisterSingleInstance(MemoryStreamProvider);
|
||||
RegisterSingleInstance(SystemEvents);
|
||||
|
||||
RegisterSingleInstance(LogManager);
|
||||
RegisterSingleInstance(Logger);
|
||||
|
@ -492,12 +486,6 @@ namespace MediaBrowser.Common.Implementations
|
|||
NetworkManager = CreateNetworkManager(LogManager.GetLogger("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();
|
||||
RegisterSingleInstance(IsoManager);
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||
get
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
protected set
|
||||
|
@ -126,7 +126,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||
Logger.Info("Saving system configuration");
|
||||
var path = CommonApplicationPaths.SystemConfigurationFilePath;
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
FileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
lock (_configurationSyncLock)
|
||||
{
|
||||
|
@ -196,9 +196,9 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
|
||||
{
|
||||
// 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);
|
||||
|
@ -253,7 +253,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||
{
|
||||
return Activator.CreateInstance(configurationType);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
catch (IOException)
|
||||
{
|
||||
return Activator.CreateInstance(configurationType);
|
||||
}
|
||||
|
@ -293,7 +293,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
||||
|
||||
var path = GetConfigurationFile(key);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
FileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
lock (_configurationSyncLock)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace MediaBrowser.Common.Implementations.Configuration
|
||||
{
|
||||
|
@ -18,7 +19,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||
/// <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)
|
||||
public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
|
||||
{
|
||||
object configuration;
|
||||
|
||||
|
@ -27,7 +28,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
|
|||
// Use try/catch to avoid the extra file system lookup using File.Exists
|
||||
try
|
||||
{
|
||||
buffer = File.ReadAllBytes(path);
|
||||
buffer = fileSystem.ReadAllBytes(path);
|
||||
|
||||
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 (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
|
||||
File.WriteAllBytes(path, newBytes);
|
||||
fileSystem.WriteAllBytes(path, newBytes);
|
||||
}
|
||||
|
||||
return configuration;
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
||||
|
@ -660,6 +659,11 @@ namespace MediaBrowser.Common.Implementations.IO
|
|||
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);
|
||||
|
@ -670,6 +674,11 @@ namespace MediaBrowser.Common.Implementations.IO
|
|||
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);
|
||||
|
|
|
@ -79,14 +79,8 @@
|
|||
<Compile Include="ScheduledTasks\Tasks\DeleteLogFileTask.cs" />
|
||||
<Compile Include="ScheduledTasks\Tasks\ReloadLoggerFileTask.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="Updates\GithubUpdater.cs" />
|
||||
<Compile Include="Updates\InstallationManager.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using System;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.ScheduledTasks
|
||||
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a task trigger that fires everyday
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.ScheduledTasks
|
||||
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a task trigger that runs repeatedly on an interval
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.ScheduledTasks
|
||||
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Class StartupTaskTrigger
|
||||
|
|
|
@ -12,6 +12,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||
|
@ -48,6 +49,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
|||
/// <value>The application paths.</value>
|
||||
private IApplicationPaths ApplicationPaths { get; set; }
|
||||
|
||||
private readonly ISystemEvents _systemEvents;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
|
@ -81,29 +84,23 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
|||
/// <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)
|
||||
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()
|
||||
{
|
||||
try
|
||||
{
|
||||
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
_systemEvents.Resume += _systemEvents_Resume;
|
||||
}
|
||||
|
||||
void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||||
private void _systemEvents_Resume(object sender, EventArgs e)
|
||||
{
|
||||
foreach (var task in ScheduledTasks)
|
||||
{
|
||||
|
|
|
@ -4,7 +4,7 @@ using MediaBrowser.Model.Events;
|
|||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.ScheduledTasks
|
||||
namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// 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>
|
||||
string ReadAllText(string path);
|
||||
|
||||
byte[] ReadAllBytes(string path);
|
||||
|
||||
void WriteAllBytes(string path, byte[] bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Writes all text.
|
||||
/// </summary>
|
||||
|
|
|
@ -408,6 +408,7 @@
|
|||
<Compile Include="Sync\SyncQualityOption.cs" />
|
||||
<Compile Include="Sync\SyncTarget.cs" />
|
||||
<Compile Include="System\Architecture.cs" />
|
||||
<Compile Include="System\ISystemEvents.cs" />
|
||||
<Compile Include="System\LogFile.cs" />
|
||||
<Compile Include="System\PublicSystemInfo.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\MediaStreamColumns.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="Social\SharingManager.cs" />
|
||||
<Compile Include="Social\SharingRepository.cs" />
|
||||
|
@ -296,6 +299,7 @@
|
|||
<Compile Include="Sync\SyncNotificationEntryPoint.cs" />
|
||||
<Compile Include="Threading\PeriodicTimer.cs" />
|
||||
<Compile Include="TV\SeriesPostScanTask.cs" />
|
||||
<Compile Include="Updates\InstallationManager.cs" />
|
||||
<Compile Include="UserViews\CollectionFolderImageProvider.cs" />
|
||||
<Compile Include="UserViews\DynamicImageProvider.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.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -7,8 +6,9 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
namespace MediaBrowser.Common.Implementations.Security
|
||||
namespace MediaBrowser.Server.Implementations.Security
|
||||
{
|
||||
internal class MBLicenseFile
|
||||
{
|
|
@ -1,26 +1,29 @@
|
|||
using System.IO;
|
||||
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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
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.Serialization;
|
||||
|
||||
namespace MediaBrowser.Common.Implementations.Security
|
||||
namespace MediaBrowser.Server.Implementations.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Class PluginSecurityManager
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
|
@ -57,9 +60,10 @@ namespace MediaBrowser.Common.Implementations.Security
|
|||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IApplicationHost _appHost;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private IEnumerable<IRequiresRegistration> _registeredEntities;
|
||||
protected IEnumerable<IRequiresRegistration> RegisteredEntities
|
||||
|
@ -73,8 +77,8 @@ namespace MediaBrowser.Common.Implementations.Security
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginSecurityManager" /> class.
|
||||
/// </summary>
|
||||
public PluginSecurityManager(IApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer,
|
||||
IApplicationPaths appPaths, ILogManager logManager)
|
||||
public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer,
|
||||
IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem)
|
||||
{
|
||||
if (httpClient == null)
|
||||
{
|
||||
|
@ -85,6 +89,7 @@ namespace MediaBrowser.Common.Implementations.Security
|
|||
_httpClient = httpClient;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_appPaths = appPaths;
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logManager.GetLogger("SecurityManager");
|
||||
}
|
||||
|
||||
|
@ -226,7 +231,7 @@ namespace MediaBrowser.Common.Implementations.Security
|
|||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(_appPaths.ProgramDataPath, "apptrans-error.txt"), info);
|
||||
_fileSystem.WriteAllText(Path.Combine(_appPaths.ProgramDataPath, "apptrans-error.txt"), info);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace MediaBrowser.Common.Implementations.Security
|
||||
namespace MediaBrowser.Server.Implementations.Security
|
||||
{
|
||||
class RegRecord
|
||||
{
|
|
@ -1,16 +1,4 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
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;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -18,10 +6,21 @@ using System.Linq;
|
|||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
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.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Updates;
|
||||
|
||||
namespace MediaBrowser.Common.Implementations.Updates
|
||||
namespace MediaBrowser.Server.Implementations.Updates
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages all install, uninstall and update operations (both plugins and system)
|
||||
|
@ -164,7 +163,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||
|
||||
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();
|
||||
|
||||
|
@ -237,7 +236,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
|||
|
||||
var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
|
||||
{
|
||||
Url = MbAdmin.HttpUrl + "service/MB3Packages.json",
|
||||
Url = "https://www.mb3admin.com/admin/service/MB3Packages.json",
|
||||
CancellationToken = cancellationToken,
|
||||
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 (isArchive)
|
||||
{
|
||||
File.WriteAllText(target + ".ver", package.versionStr);
|
||||
_fileSystem.WriteAllText(target + ".ver", package.versionStr);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
|
@ -72,7 +72,7 @@ namespace MediaBrowser.Server.Mono
|
|||
|
||||
private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, StartupOptions options)
|
||||
{
|
||||
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
||||
Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
||||
|
||||
// Allow all https requests
|
||||
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
|
||||
|
|
|
@ -104,6 +104,8 @@ using MediaBrowser.Common.Implementations.Serialization;
|
|||
using MediaBrowser.Common.Implementations.Updates;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Common.Security;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
|
@ -120,6 +122,7 @@ using MediaBrowser.Model.Xml;
|
|||
using MediaBrowser.Server.Implementations.Archiving;
|
||||
using MediaBrowser.Server.Implementations.Reflection;
|
||||
using MediaBrowser.Server.Implementations.Serialization;
|
||||
using MediaBrowser.Server.Implementations.Updates;
|
||||
using MediaBrowser.Server.Implementations.Xml;
|
||||
using OpenSubtitlesHandler;
|
||||
using ServiceStack;
|
||||
|
@ -226,6 +229,17 @@ namespace MediaBrowser.Server.Startup.Common
|
|||
private IMediaSourceManager MediaSourceManager { 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>
|
||||
/// Gets or sets the zip client.
|
||||
/// </summary>
|
||||
|
@ -405,6 +419,11 @@ namespace MediaBrowser.Server.Startup.Common
|
|||
return new MemoryStreamProvider();
|
||||
}
|
||||
|
||||
protected override ISystemEvents CreateSystemEvents()
|
||||
{
|
||||
return new SystemEvents(LogManager.GetLogger("SystemEvents"));
|
||||
}
|
||||
|
||||
protected override IJsonSerializer CreateJsonSerializer()
|
||||
{
|
||||
try
|
||||
|
@ -636,7 +655,6 @@ namespace MediaBrowser.Server.Startup.Common
|
|||
{
|
||||
var migrations = new List<IVersionMigration>
|
||||
{
|
||||
new MovieDbEpisodeProviderMigration(ServerConfigurationManager),
|
||||
new DbMigration(ServerConfigurationManager, TaskManager)
|
||||
};
|
||||
|
||||
|
@ -660,6 +678,12 @@ namespace MediaBrowser.Server.Startup.Common
|
|||
{
|
||||
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);
|
||||
RegisterSingleInstance(ZipClient);
|
||||
|
||||
|
|
|
@ -77,11 +77,11 @@
|
|||
<Compile Include="MbLinkShortcutHandler.cs" />
|
||||
<Compile Include="Migrations\IVersionMigration.cs" />
|
||||
<Compile Include="Migrations\DbMigration.cs" />
|
||||
<Compile Include="Migrations\MovieDbEpisodeProviderMigration.cs" />
|
||||
<Compile Include="Migrations\UpdateLevelMigration.cs" />
|
||||
<Compile Include="NativeEnvironment.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="StartupOptions.cs" />
|
||||
<Compile Include="SystemEvents.cs" />
|
||||
<Compile Include="UnhandledExceptionWriter.cs" />
|
||||
</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.WaitAll(task);
|
||||
|
||||
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
||||
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
|
||||
Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding;
|
||||
Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
|
||||
|
||||
HideSplashScreen();
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user