move common dependencies

This commit is contained in:
Luke Pulverenti 2016-10-29 01:40:15 -04:00
parent ce38e98791
commit 2729301bff
55 changed files with 10787 additions and 171 deletions

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

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

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

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

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

View 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];
}
}
}

View 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")]

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

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

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

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

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

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

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

View 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"
}
}
}
}

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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/";
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,10 @@
using System;
namespace MediaBrowser.Model.System
{
public interface ISystemEvents
{
event EventHandler Resume;
event EventHandler Suspend;
}
}

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
using System;
namespace MediaBrowser.Common.Implementations.Security
namespace MediaBrowser.Server.Implementations.Security
{
class RegRecord
{

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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