Merge branch 'master' into perf
This commit is contained in:
commit
4811e76860
|
@ -55,15 +55,77 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
|
|||
###############################
|
||||
# Naming Conventions #
|
||||
###############################
|
||||
# Style Definitions
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
# Use PascalCase for constant fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
# Style Definitions (From Roslyn)
|
||||
|
||||
# Non-private static fields are PascalCase
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
||||
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
|
||||
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
||||
|
||||
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
||||
|
||||
# Constants are PascalCase
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
|
||||
|
||||
dotnet_naming_symbols.constants.applicable_kinds = field, local
|
||||
dotnet_naming_symbols.constants.required_modifiers = const
|
||||
|
||||
dotnet_naming_style.constant_style.capitalization = pascal_case
|
||||
|
||||
# Static fields are camelCase and start with s_
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
|
||||
|
||||
dotnet_naming_symbols.static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_fields.required_modifiers = static
|
||||
|
||||
dotnet_naming_style.static_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.static_field_style.required_prefix = _
|
||||
|
||||
# Instance fields are camelCase and start with _
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
|
||||
|
||||
dotnet_naming_symbols.instance_fields.applicable_kinds = field
|
||||
|
||||
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||
|
||||
# Locals and parameters are camelCase
|
||||
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
|
||||
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
|
||||
|
||||
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
|
||||
|
||||
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
||||
|
||||
# Local functions are PascalCase
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
|
||||
|
||||
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
||||
|
||||
dotnet_naming_style.local_function_style.capitalization = pascal_case
|
||||
|
||||
# By default, name items with PascalCase
|
||||
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
|
||||
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
|
||||
|
||||
dotnet_naming_symbols.all_members.applicable_kinds = *
|
||||
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
###############################
|
||||
# C# Coding Conventions #
|
||||
###############################
|
||||
|
|
|
@ -38,7 +38,9 @@ namespace Emby.Dlna
|
|||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
|
||||
IJsonSerializer jsonSerializer,
|
||||
IServerApplicationHost appHost,
|
||||
IAssemblyInfo assemblyInfo)
|
||||
{
|
||||
_xmlSerializer = xmlSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
|
|
|
@ -36,7 +36,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||
};
|
||||
}
|
||||
|
||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
|
||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
||||
: base(config, logger, xmlReaderSettingsFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
|
@ -14,50 +15,44 @@ namespace Emby.Server.Implementations.AppBase
|
|||
/// </summary>
|
||||
protected BaseApplicationPaths(
|
||||
string programDataPath,
|
||||
string appFolderPath,
|
||||
string logDirectoryPath = null,
|
||||
string configurationDirectoryPath = null,
|
||||
string cacheDirectoryPath = null)
|
||||
string logDirectoryPath,
|
||||
string configurationDirectoryPath,
|
||||
string cacheDirectoryPath)
|
||||
{
|
||||
ProgramDataPath = programDataPath;
|
||||
ProgramSystemPath = appFolderPath;
|
||||
LogDirectoryPath = logDirectoryPath;
|
||||
ConfigurationDirectoryPath = configurationDirectoryPath;
|
||||
CachePath = cacheDirectoryPath;
|
||||
|
||||
DataPath = Path.Combine(ProgramDataPath, "data");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the program data folder
|
||||
/// </summary>
|
||||
/// <value>The program data path.</value>
|
||||
public string ProgramDataPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system folder
|
||||
/// </summary>
|
||||
public string ProgramSystemPath { get; private set; }
|
||||
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// The _data directory
|
||||
/// </summary>
|
||||
private string _dataDirectory;
|
||||
/// <summary>
|
||||
/// Gets the folder path to the data directory
|
||||
/// </summary>
|
||||
/// <value>The data directory.</value>
|
||||
private string _dataPath;
|
||||
public string DataPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_dataDirectory == null)
|
||||
{
|
||||
_dataDirectory = Path.Combine(ProgramDataPath, "data");
|
||||
|
||||
Directory.CreateDirectory(_dataDirectory);
|
||||
}
|
||||
|
||||
return _dataDirectory;
|
||||
}
|
||||
get => _dataPath;
|
||||
private set => _dataPath = Directory.CreateDirectory(value).FullName;
|
||||
}
|
||||
|
||||
private const string _virtualDataPath = "%AppDataPath%";
|
||||
public string VirtualDataPath => _virtualDataPath;
|
||||
/// <summary>
|
||||
/// Gets the magic strings used for virtual path manipulation.
|
||||
/// </summary>
|
||||
public string VirtualDataPath { get; } = "%AppDataPath%";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image cache path.
|
||||
|
@ -83,55 +78,17 @@ namespace Emby.Server.Implementations.AppBase
|
|||
/// <value>The plugin configurations path.</value>
|
||||
public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates");
|
||||
|
||||
/// <summary>
|
||||
/// The _log directory
|
||||
/// </summary>
|
||||
private string _logDirectoryPath;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the log directory
|
||||
/// </summary>
|
||||
/// <value>The log directory path.</value>
|
||||
public string LogDirectoryPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_logDirectoryPath))
|
||||
{
|
||||
_logDirectoryPath = Path.Combine(ProgramDataPath, "logs");
|
||||
|
||||
Directory.CreateDirectory(_logDirectoryPath);
|
||||
}
|
||||
|
||||
return _logDirectoryPath;
|
||||
}
|
||||
set => _logDirectoryPath = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _config directory
|
||||
/// </summary>
|
||||
private string _configurationDirectoryPath;
|
||||
public string LogDirectoryPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the application configuration root directory
|
||||
/// </summary>
|
||||
/// <value>The configuration directory path.</value>
|
||||
public string ConfigurationDirectoryPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_configurationDirectoryPath))
|
||||
{
|
||||
_configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
|
||||
|
||||
Directory.CreateDirectory(_configurationDirectoryPath);
|
||||
}
|
||||
|
||||
return _configurationDirectoryPath;
|
||||
}
|
||||
set => _configurationDirectoryPath = value;
|
||||
}
|
||||
public string ConfigurationDirectoryPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system configuration file
|
||||
|
@ -139,29 +96,11 @@ namespace Emby.Server.Implementations.AppBase
|
|||
/// <value>The system configuration file path.</value>
|
||||
public string SystemConfigurationFilePath => 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;
|
||||
}
|
||||
public string CachePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path to the temp directory within the cache folder
|
||||
|
|
|
@ -105,6 +105,7 @@ using MediaBrowser.Providers.Subtitles;
|
|||
using MediaBrowser.WebDashboard.Api;
|
||||
using MediaBrowser.XbmcMetadata.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ServiceStack;
|
||||
using ServiceStack.Text.Jsv;
|
||||
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
|
||||
|
@ -202,7 +203,7 @@ namespace Emby.Server.Implementations
|
|||
/// Gets all concrete types.
|
||||
/// </summary>
|
||||
/// <value>All concrete types.</value>
|
||||
public Tuple<Type, string>[] AllConcreteTypes { get; protected set; }
|
||||
public Type[] AllConcreteTypes { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The disposable parts
|
||||
|
@ -219,8 +220,6 @@ namespace Emby.Server.Implementations
|
|||
|
||||
protected IEnvironmentInfo EnvironmentInfo { get; set; }
|
||||
|
||||
private IBlurayExaminer BlurayExaminer { get; set; }
|
||||
|
||||
public PackageVersionClass SystemUpdateLevel
|
||||
{
|
||||
get
|
||||
|
@ -232,12 +231,7 @@ namespace Emby.Server.Implementations
|
|||
}
|
||||
}
|
||||
|
||||
public virtual string OperatingSystemDisplayName => EnvironmentInfo.OperatingSystemName;
|
||||
|
||||
/// <summary>
|
||||
/// The container
|
||||
/// </summary>
|
||||
protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
|
||||
protected IServiceProvider _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server configuration manager.
|
||||
|
@ -309,7 +303,6 @@ namespace Emby.Server.Implementations
|
|||
/// <value>The user data repository.</value>
|
||||
private IUserDataManager UserDataManager { get; set; }
|
||||
private IUserRepository UserRepository { get; set; }
|
||||
internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
|
||||
internal SqliteItemRepository ItemRepository { get; set; }
|
||||
|
||||
private INotificationManager NotificationManager { get; set; }
|
||||
|
@ -453,138 +446,58 @@ namespace Emby.Server.Implementations
|
|||
/// <value>The name.</value>
|
||||
public string Name => ApplicationProductName;
|
||||
|
||||
private static Tuple<Assembly, string> GetAssembly(Type type)
|
||||
{
|
||||
var assembly = type.GetTypeInfo().Assembly;
|
||||
|
||||
return new Tuple<Assembly, string>(assembly, null);
|
||||
}
|
||||
|
||||
public virtual IStreamHelper CreateStreamHelper()
|
||||
{
|
||||
return new StreamHelper();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of type and resolves all constructor dependancies
|
||||
/// Creates an instance of type and resolves all constructor dependencies
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public object CreateInstance(Type type)
|
||||
{
|
||||
return Container.GetInstance(type);
|
||||
}
|
||||
=> ActivatorUtilities.CreateInstance(_serviceProvider, type);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of type and resolves all constructor dependencies
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public T CreateInstance<T>()
|
||||
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the instance safe.
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">The type information.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
protected object CreateInstanceSafe(Tuple<Type, string> typeInfo)
|
||||
protected object CreateInstanceSafe(Type type)
|
||||
{
|
||||
var type = typeInfo.Item1;
|
||||
|
||||
try
|
||||
{
|
||||
return Container.GetInstance(type);
|
||||
Logger.LogWarning("Creating instance of {Type}", type);
|
||||
return ActivatorUtilities.CreateInstance(_serviceProvider, type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error creating {type}", type.FullName);
|
||||
// Don't blow up in release mode
|
||||
Logger.LogError(ex, "Error creating {Type}", type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 void RegisterSingleInstance<T>(T obj, bool manageLifetime = true)
|
||||
where T : class
|
||||
{
|
||||
Container.RegisterInstance<T>(obj);
|
||||
|
||||
if (manageLifetime)
|
||||
{
|
||||
var disposable = obj as IDisposable;
|
||||
|
||||
if (disposable != null)
|
||||
{
|
||||
DisposableParts.Add(disposable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the single instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="func">The func.</param>
|
||||
protected void RegisterSingleInstance<T>(Func<T> func)
|
||||
where T : class
|
||||
{
|
||||
Container.RegisterSingleton(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves this instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
public T Resolve<T>()
|
||||
{
|
||||
return (T)Container.GetRegistration(typeof(T), true).GetInstance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves this instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
public T TryResolve<T>()
|
||||
{
|
||||
var result = Container.GetRegistration(typeof(T), false);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return (T)result.GetInstance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the assembly.
|
||||
/// </summary>
|
||||
/// <param name="file">The file.</param>
|
||||
/// <returns>Assembly.</returns>
|
||||
protected Tuple<Assembly, string> LoadAssembly(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.Load(File.ReadAllBytes(file));
|
||||
|
||||
return new Tuple<Assembly, string>(assembly, file);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading assembly {File}", file);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public T Resolve<T>() => _serviceProvider.GetService<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the export types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>IEnumerable{Type}.</returns>
|
||||
public IEnumerable<Tuple<Type, string>> GetExportTypes<T>()
|
||||
public IEnumerable<Type> GetExportTypes<T>()
|
||||
{
|
||||
var currentType = typeof(T);
|
||||
|
||||
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i.Item1));
|
||||
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -596,9 +509,10 @@ namespace Emby.Server.Implementations
|
|||
public IEnumerable<T> GetExports<T>(bool manageLifetime = true)
|
||||
{
|
||||
var parts = GetExportTypes<T>()
|
||||
.Select(CreateInstanceSafe)
|
||||
.Select(x => CreateInstanceSafe(x))
|
||||
.Where(i => i != null)
|
||||
.Cast<T>();
|
||||
.Cast<T>()
|
||||
.ToList(); // Convert to list so this isn't executed for each iteration
|
||||
|
||||
if (manageLifetime)
|
||||
{
|
||||
|
@ -611,33 +525,6 @@ namespace Emby.Server.Implementations
|
|||
return parts;
|
||||
}
|
||||
|
||||
public List<Tuple<T, string>> GetExportsWithInfo<T>(bool manageLifetime = true)
|
||||
{
|
||||
var parts = GetExportTypes<T>()
|
||||
.Select(i =>
|
||||
{
|
||||
var obj = CreateInstanceSafe(i);
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new Tuple<T, string>((T)obj, i.Item2);
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
|
||||
if (manageLifetime)
|
||||
{
|
||||
lock (DisposableParts)
|
||||
{
|
||||
DisposableParts.AddRange(parts.Select(i => i.Item1).OfType<IDisposable>());
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the startup tasks.
|
||||
/// </summary>
|
||||
|
@ -691,7 +578,7 @@ namespace Emby.Server.Implementations
|
|||
}
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
public async Task Init(IServiceCollection serviceCollection)
|
||||
{
|
||||
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
||||
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
||||
|
@ -721,7 +608,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
SetHttpLimit();
|
||||
|
||||
await RegisterResources();
|
||||
await RegisterResources(serviceCollection);
|
||||
|
||||
FindParts();
|
||||
}
|
||||
|
@ -736,104 +623,103 @@ namespace Emby.Server.Implementations
|
|||
/// <summary>
|
||||
/// Registers resources that classes will depend on
|
||||
/// </summary>
|
||||
protected async Task RegisterResources()
|
||||
protected async Task RegisterResources(IServiceCollection serviceCollection)
|
||||
{
|
||||
RegisterSingleInstance(ConfigurationManager);
|
||||
RegisterSingleInstance<IApplicationHost>(this);
|
||||
serviceCollection.AddSingleton(ConfigurationManager);
|
||||
serviceCollection.AddSingleton<IApplicationHost>(this);
|
||||
|
||||
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
|
||||
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||
|
||||
RegisterSingleInstance(JsonSerializer);
|
||||
|
||||
RegisterSingleInstance(LoggerFactory, false);
|
||||
RegisterSingleInstance(Logger);
|
||||
serviceCollection.AddSingleton(JsonSerializer);
|
||||
|
||||
RegisterSingleInstance(EnvironmentInfo);
|
||||
serviceCollection.AddSingleton(LoggerFactory);
|
||||
serviceCollection.AddLogging();
|
||||
serviceCollection.AddSingleton(Logger);
|
||||
|
||||
RegisterSingleInstance(FileSystemManager);
|
||||
serviceCollection.AddSingleton(EnvironmentInfo);
|
||||
|
||||
serviceCollection.AddSingleton(FileSystemManager);
|
||||
|
||||
HttpClient = CreateHttpClient();
|
||||
RegisterSingleInstance(HttpClient);
|
||||
serviceCollection.AddSingleton(HttpClient);
|
||||
|
||||
RegisterSingleInstance(NetworkManager);
|
||||
serviceCollection.AddSingleton(NetworkManager);
|
||||
|
||||
IsoManager = new IsoManager();
|
||||
RegisterSingleInstance(IsoManager);
|
||||
serviceCollection.AddSingleton(IsoManager);
|
||||
|
||||
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
|
||||
RegisterSingleInstance(TaskManager);
|
||||
serviceCollection.AddSingleton(TaskManager);
|
||||
|
||||
RegisterSingleInstance(XmlSerializer);
|
||||
serviceCollection.AddSingleton(XmlSerializer);
|
||||
|
||||
ProcessFactory = new ProcessFactory();
|
||||
RegisterSingleInstance(ProcessFactory);
|
||||
serviceCollection.AddSingleton(ProcessFactory);
|
||||
|
||||
var streamHelper = CreateStreamHelper();
|
||||
ApplicationHost.StreamHelper = streamHelper;
|
||||
RegisterSingleInstance(streamHelper);
|
||||
ApplicationHost.StreamHelper = new StreamHelper();
|
||||
serviceCollection.AddSingleton(StreamHelper);
|
||||
|
||||
RegisterSingleInstance(CryptographyProvider);
|
||||
serviceCollection.AddSingleton(CryptographyProvider);
|
||||
|
||||
SocketFactory = new SocketFactory();
|
||||
RegisterSingleInstance(SocketFactory);
|
||||
serviceCollection.AddSingleton(SocketFactory);
|
||||
|
||||
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime);
|
||||
RegisterSingleInstance(InstallationManager);
|
||||
serviceCollection.AddSingleton(InstallationManager);
|
||||
|
||||
ZipClient = new ZipClient(FileSystemManager);
|
||||
RegisterSingleInstance(ZipClient);
|
||||
serviceCollection.AddSingleton(ZipClient);
|
||||
|
||||
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
|
||||
RegisterSingleInstance(HttpResultFactory);
|
||||
serviceCollection.AddSingleton(HttpResultFactory);
|
||||
|
||||
RegisterSingleInstance<IServerApplicationHost>(this);
|
||||
RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);
|
||||
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
||||
|
||||
RegisterSingleInstance(ServerConfigurationManager);
|
||||
serviceCollection.AddSingleton(ServerConfigurationManager);
|
||||
|
||||
IAssemblyInfo assemblyInfo = new AssemblyInfo();
|
||||
RegisterSingleInstance(assemblyInfo);
|
||||
var assemblyInfo = new AssemblyInfo();
|
||||
serviceCollection.AddSingleton<IAssemblyInfo>(assemblyInfo);
|
||||
|
||||
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory);
|
||||
await LocalizationManager.LoadAll();
|
||||
RegisterSingleInstance<ILocalizationManager>(LocalizationManager);
|
||||
serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
|
||||
|
||||
BlurayExaminer = new BdInfoExaminer(FileSystemManager);
|
||||
RegisterSingleInstance(BlurayExaminer);
|
||||
serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
|
||||
|
||||
RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
|
||||
serviceCollection.AddSingleton<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
|
||||
|
||||
UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
|
||||
RegisterSingleInstance(UserDataManager);
|
||||
serviceCollection.AddSingleton(UserDataManager);
|
||||
|
||||
UserRepository = GetUserRepository();
|
||||
// This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it
|
||||
RegisterSingleInstance(UserRepository);
|
||||
serviceCollection.AddSingleton(UserRepository);
|
||||
|
||||
var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager);
|
||||
DisplayPreferencesRepository = displayPreferencesRepo;
|
||||
RegisterSingleInstance(DisplayPreferencesRepository);
|
||||
serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo);
|
||||
|
||||
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo);
|
||||
RegisterSingleInstance<IItemRepository>(ItemRepository);
|
||||
serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
|
||||
|
||||
AuthenticationRepository = GetAuthenticationRepository();
|
||||
RegisterSingleInstance(AuthenticationRepository);
|
||||
serviceCollection.AddSingleton(AuthenticationRepository);
|
||||
|
||||
UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager, CryptographyProvider);
|
||||
RegisterSingleInstance(UserManager);
|
||||
serviceCollection.AddSingleton(UserManager);
|
||||
|
||||
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
|
||||
RegisterSingleInstance(LibraryManager);
|
||||
serviceCollection.AddSingleton(LibraryManager);
|
||||
|
||||
// TODO wtaylor: investigate use of second music manager
|
||||
var musicManager = new MusicManager(LibraryManager);
|
||||
RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
|
||||
serviceCollection.AddSingleton<IMusicManager>(new MusicManager(LibraryManager));
|
||||
|
||||
LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
|
||||
RegisterSingleInstance(LibraryMonitor);
|
||||
serviceCollection.AddSingleton(LibraryMonitor);
|
||||
|
||||
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager));
|
||||
serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
|
||||
|
||||
CertificateInfo = GetCertificateInfo(true);
|
||||
Certificate = GetCertificate(CertificateInfo);
|
||||
|
@ -848,81 +734,82 @@ namespace Emby.Server.Implementations
|
|||
GetParseFn);
|
||||
|
||||
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
|
||||
RegisterSingleInstance(HttpServer);
|
||||
serviceCollection.AddSingleton(HttpServer);
|
||||
|
||||
ImageProcessor = GetImageProcessor();
|
||||
RegisterSingleInstance(ImageProcessor);
|
||||
serviceCollection.AddSingleton(ImageProcessor);
|
||||
|
||||
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
|
||||
RegisterSingleInstance(TVSeriesManager);
|
||||
serviceCollection.AddSingleton(TVSeriesManager);
|
||||
|
||||
var encryptionManager = new EncryptionManager();
|
||||
RegisterSingleInstance<IEncryptionManager>(encryptionManager);
|
||||
serviceCollection.AddSingleton<IEncryptionManager>(encryptionManager);
|
||||
|
||||
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager);
|
||||
RegisterSingleInstance(DeviceManager);
|
||||
serviceCollection.AddSingleton(DeviceManager);
|
||||
|
||||
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder);
|
||||
RegisterSingleInstance(MediaSourceManager);
|
||||
serviceCollection.AddSingleton(MediaSourceManager);
|
||||
|
||||
SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager);
|
||||
RegisterSingleInstance(SubtitleManager);
|
||||
serviceCollection.AddSingleton(SubtitleManager);
|
||||
|
||||
ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
|
||||
RegisterSingleInstance(ProviderManager);
|
||||
serviceCollection.AddSingleton(ProviderManager);
|
||||
|
||||
DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager);
|
||||
RegisterSingleInstance(DtoService);
|
||||
serviceCollection.AddSingleton(DtoService);
|
||||
|
||||
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
|
||||
RegisterSingleInstance(ChannelManager);
|
||||
serviceCollection.AddSingleton(ChannelManager);
|
||||
|
||||
SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
|
||||
RegisterSingleInstance(SessionManager);
|
||||
serviceCollection.AddSingleton(SessionManager);
|
||||
|
||||
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo);
|
||||
RegisterSingleInstance<IDlnaManager>(dlnaManager);
|
||||
serviceCollection.AddSingleton<IDlnaManager>(
|
||||
new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo));
|
||||
|
||||
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
|
||||
RegisterSingleInstance(CollectionManager);
|
||||
serviceCollection.AddSingleton(CollectionManager);
|
||||
|
||||
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
|
||||
RegisterSingleInstance(PlaylistManager);
|
||||
serviceCollection.AddSingleton(PlaylistManager);
|
||||
|
||||
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
|
||||
RegisterSingleInstance(LiveTvManager);
|
||||
serviceCollection.AddSingleton(LiveTvManager);
|
||||
|
||||
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
|
||||
RegisterSingleInstance(UserViewManager);
|
||||
serviceCollection.AddSingleton(UserViewManager);
|
||||
|
||||
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
||||
RegisterSingleInstance(NotificationManager);
|
||||
serviceCollection.AddSingleton(NotificationManager);
|
||||
|
||||
RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
|
||||
serviceCollection.AddSingleton<IDeviceDiscovery>(
|
||||
new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
|
||||
|
||||
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
|
||||
RegisterSingleInstance(ChapterManager);
|
||||
serviceCollection.AddSingleton(ChapterManager);
|
||||
|
||||
RegisterMediaEncoder(assemblyInfo);
|
||||
RegisterMediaEncoder(serviceCollection);
|
||||
|
||||
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
|
||||
RegisterSingleInstance(EncodingManager);
|
||||
serviceCollection.AddSingleton(EncodingManager);
|
||||
|
||||
var activityLogRepo = GetActivityLogRepository();
|
||||
RegisterSingleInstance(activityLogRepo);
|
||||
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
|
||||
serviceCollection.AddSingleton(activityLogRepo);
|
||||
serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
|
||||
|
||||
var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
|
||||
RegisterSingleInstance<IAuthorizationContext>(authContext);
|
||||
RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
|
||||
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
|
||||
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
|
||||
|
||||
AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager);
|
||||
RegisterSingleInstance(AuthService);
|
||||
serviceCollection.AddSingleton(AuthService);
|
||||
|
||||
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
|
||||
RegisterSingleInstance(SubtitleEncoder);
|
||||
serviceCollection.AddSingleton(SubtitleEncoder);
|
||||
|
||||
RegisterSingleInstance(CreateResourceFileManager());
|
||||
serviceCollection.AddSingleton(CreateResourceFileManager());
|
||||
|
||||
displayPreferencesRepo.Initialize();
|
||||
|
||||
|
@ -935,6 +822,8 @@ namespace Emby.Server.Implementations
|
|||
((UserDataManager)UserDataManager).Repository = userDataRepo;
|
||||
ItemRepository.Initialize(userDataRepo, UserManager);
|
||||
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
|
||||
|
||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
}
|
||||
|
||||
protected virtual IBrotliCompressor CreateBrotliCompressor()
|
||||
|
@ -1066,7 +955,7 @@ namespace Emby.Server.Implementations
|
|||
/// Registers the media encoder.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo)
|
||||
private void RegisterMediaEncoder(IServiceCollection serviceCollection)
|
||||
{
|
||||
string encoderPath = null;
|
||||
string probePath = null;
|
||||
|
@ -1098,7 +987,7 @@ namespace Emby.Server.Implementations
|
|||
5000);
|
||||
|
||||
MediaEncoder = mediaEncoder;
|
||||
RegisterSingleInstance(MediaEncoder);
|
||||
serviceCollection.AddSingleton(MediaEncoder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1174,7 +1063,10 @@ namespace Emby.Server.Implementations
|
|||
}
|
||||
|
||||
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||
Plugins = GetExportsWithInfo<IPlugin>().Select(LoadPlugin).Where(i => i != null).ToArray();
|
||||
Plugins = GetExports<IPlugin>()
|
||||
.Select(LoadPlugin)
|
||||
.Where(i => i != null)
|
||||
.ToArray();
|
||||
|
||||
HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>());
|
||||
|
||||
|
@ -1208,19 +1100,15 @@ namespace Emby.Server.Implementations
|
|||
IsoManager.AddParts(GetExports<IIsoMounter>());
|
||||
}
|
||||
|
||||
private IPlugin LoadPlugin(Tuple<IPlugin, string> info)
|
||||
private IPlugin LoadPlugin(IPlugin plugin)
|
||||
{
|
||||
var plugin = info.Item1;
|
||||
var assemblyFilePath = info.Item2;
|
||||
|
||||
try
|
||||
{
|
||||
var assemblyPlugin = plugin as IPluginAssembly;
|
||||
|
||||
if (assemblyPlugin != null)
|
||||
if (plugin is IPluginAssembly assemblyPlugin)
|
||||
{
|
||||
var assembly = plugin.GetType().Assembly;
|
||||
var assemblyName = assembly.GetName();
|
||||
var assemblyFilePath = assembly.Location;
|
||||
|
||||
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
|
||||
|
||||
|
@ -1264,78 +1152,15 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
Logger.LogInformation("Loading assemblies");
|
||||
|
||||
var assemblyInfos = GetComposablePartAssemblies();
|
||||
|
||||
foreach (var assemblyInfo in assemblyInfos)
|
||||
{
|
||||
var assembly = assemblyInfo.Item1;
|
||||
var path = assemblyInfo.Item2;
|
||||
|
||||
if (path == null)
|
||||
AllConcreteTypes = GetComposablePartAssemblies()
|
||||
.SelectMany(x => x.ExportedTypes)
|
||||
.Where(type =>
|
||||
{
|
||||
Logger.LogInformation("Loading {assemblyName}", assembly.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation("Loading {assemblyName} from {path}", assembly.FullName, path);
|
||||
}
|
||||
}
|
||||
|
||||
AllConcreteTypes = assemblyInfos
|
||||
.SelectMany(GetTypes)
|
||||
.Where(info =>
|
||||
{
|
||||
var t = info.Item1;
|
||||
return t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType;
|
||||
return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType;
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <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>
|
||||
protected List<Tuple<Type, string>> GetTypes(Tuple<Assembly, string> assemblyInfo)
|
||||
{
|
||||
if (assemblyInfo == null)
|
||||
{
|
||||
return new List<Tuple<Type, string>>();
|
||||
}
|
||||
|
||||
var assembly = assemblyInfo.Item1;
|
||||
|
||||
try
|
||||
{
|
||||
// This null checking really shouldn't be needed but adding it due to some
|
||||
// unhandled exceptions in mono 5.0 that are a little hard to hunt down
|
||||
var types = assembly.GetTypes() ?? new Type[] { };
|
||||
return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
if (ex.LoaderExceptions != null)
|
||||
{
|
||||
foreach (var loaderException in ex.LoaderExceptions)
|
||||
{
|
||||
if (loaderException != null)
|
||||
{
|
||||
Logger.LogError("LoaderException: " + loaderException.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it fails we can still get a list of the Types it was able to resolve
|
||||
var types = ex.Types ?? new Type[] { };
|
||||
return types.Where(t => t != null).Select(i => new Tuple<Type, string>(i, assemblyInfo.Item2)).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading types from assembly");
|
||||
|
||||
return new List<Tuple<Type, string>>();
|
||||
}
|
||||
}
|
||||
|
||||
private CertificateInfo CertificateInfo { get; set; }
|
||||
protected X509Certificate Certificate { get; private set; }
|
||||
|
||||
|
@ -1546,151 +1371,64 @@ namespace Emby.Server.Implementations
|
|||
/// Gets the composable part assemblies.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{Assembly}.</returns>
|
||||
protected List<Tuple<Assembly, string>> GetComposablePartAssemblies()
|
||||
protected IEnumerable<Assembly> GetComposablePartAssemblies()
|
||||
{
|
||||
var list = GetPluginAssemblies(ApplicationPaths.PluginsPath);
|
||||
|
||||
// Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
|
||||
// This will prevent the .dll file from getting locked, and allow us to replace it when needed
|
||||
if (Directory.Exists(ApplicationPaths.PluginsPath))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
Logger.LogInformation("Loading assembly {Path}", file);
|
||||
yield return Assembly.LoadFrom(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Include composable parts in the Api assembly
|
||||
list.Add(GetAssembly(typeof(ApiEntryPoint)));
|
||||
yield return typeof(ApiEntryPoint).Assembly;
|
||||
|
||||
// Include composable parts in the Dashboard assembly
|
||||
list.Add(GetAssembly(typeof(DashboardService)));
|
||||
yield return typeof(DashboardService).Assembly;
|
||||
|
||||
// Include composable parts in the Model assembly
|
||||
list.Add(GetAssembly(typeof(SystemInfo)));
|
||||
yield return typeof(SystemInfo).Assembly;
|
||||
|
||||
// Include composable parts in the Common assembly
|
||||
list.Add(GetAssembly(typeof(IApplicationHost)));
|
||||
yield return typeof(IApplicationHost).Assembly;
|
||||
|
||||
// Include composable parts in the Controller assembly
|
||||
list.Add(GetAssembly(typeof(IServerApplicationHost)));
|
||||
yield return typeof(IServerApplicationHost).Assembly;
|
||||
|
||||
// Include composable parts in the Providers assembly
|
||||
list.Add(GetAssembly(typeof(ProviderUtils)));
|
||||
yield return typeof(ProviderUtils).Assembly;
|
||||
|
||||
// Include composable parts in the Photos assembly
|
||||
list.Add(GetAssembly(typeof(PhotoProvider)));
|
||||
yield return typeof(PhotoProvider).Assembly;
|
||||
|
||||
// Emby.Server implementations
|
||||
list.Add(GetAssembly(typeof(InstallationManager)));
|
||||
yield return typeof(InstallationManager).Assembly;
|
||||
|
||||
// MediaEncoding
|
||||
list.Add(GetAssembly(typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder)));
|
||||
yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
|
||||
|
||||
// Dlna
|
||||
list.Add(GetAssembly(typeof(DlnaEntryPoint)));
|
||||
yield return typeof(DlnaEntryPoint).Assembly;
|
||||
|
||||
// Local metadata
|
||||
list.Add(GetAssembly(typeof(BoxSetXmlSaver)));
|
||||
yield return typeof(BoxSetXmlSaver).Assembly;
|
||||
|
||||
// Notifications
|
||||
list.Add(GetAssembly(typeof(NotificationManager)));
|
||||
yield return typeof(NotificationManager).Assembly;
|
||||
|
||||
// Xbmc
|
||||
list.Add(GetAssembly(typeof(ArtistNfoProvider)));
|
||||
yield return typeof(ArtistNfoProvider).Assembly;
|
||||
|
||||
list.AddRange(GetAssembliesWithPartsInternal().Select(i => new Tuple<Assembly, string>(i, null)));
|
||||
|
||||
return list.ToList();
|
||||
foreach (var i in GetAssembliesWithPartsInternal())
|
||||
{
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
|
||||
|
||||
private List<Tuple<Assembly, string>> GetPluginAssemblies(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly))
|
||||
.Select(LoadAssembly)
|
||||
.Where(a => a != null)
|
||||
.ToList();
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return new List<Tuple<Assembly, string>>();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> FilterAssembliesToLoad(IEnumerable<string> paths)
|
||||
{
|
||||
|
||||
var exclude = new[]
|
||||
{
|
||||
"mbplus.dll",
|
||||
"mbintros.dll",
|
||||
"embytv.dll",
|
||||
"Messenger.dll",
|
||||
"Messages.dll",
|
||||
"MediaBrowser.Plugins.TvMazeProvider.dll",
|
||||
"MBBookshelf.dll",
|
||||
"MediaBrowser.Channels.Adult.YouJizz.dll",
|
||||
"MediaBrowser.Channels.Vine-co.dll",
|
||||
"MediaBrowser.Plugins.Vimeo.dll",
|
||||
"MediaBrowser.Channels.Vevo.dll",
|
||||
"MediaBrowser.Plugins.Twitch.dll",
|
||||
"MediaBrowser.Channels.SvtPlay.dll",
|
||||
"MediaBrowser.Plugins.SoundCloud.dll",
|
||||
"MediaBrowser.Plugins.SnesBox.dll",
|
||||
"MediaBrowser.Plugins.RottenTomatoes.dll",
|
||||
"MediaBrowser.Plugins.Revision3.dll",
|
||||
"MediaBrowser.Plugins.NesBox.dll",
|
||||
"MBChapters.dll",
|
||||
"MediaBrowser.Channels.LeagueOfLegends.dll",
|
||||
"MediaBrowser.Plugins.ADEProvider.dll",
|
||||
"MediaBrowser.Channels.BallStreams.dll",
|
||||
"MediaBrowser.Channels.Adult.Beeg.dll",
|
||||
"ChannelDownloader.dll",
|
||||
"Hamstercat.Emby.EmbyBands.dll",
|
||||
"EmbyTV.dll",
|
||||
"MediaBrowser.Channels.HitboxTV.dll",
|
||||
"MediaBrowser.Channels.HockeyStreams.dll",
|
||||
"MediaBrowser.Plugins.ITV.dll",
|
||||
"MediaBrowser.Plugins.Lastfm.dll",
|
||||
"ServerRestart.dll",
|
||||
"MediaBrowser.Plugins.NotifyMyAndroidNotifications.dll",
|
||||
"MetadataViewer.dll"
|
||||
};
|
||||
|
||||
var minRequiredVersions = new Dictionary<string, Version>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "moviethemesongs.dll", new Version(1, 6) },
|
||||
{ "themesongs.dll", new Version(1, 2) }
|
||||
};
|
||||
|
||||
return paths.Where(path =>
|
||||
{
|
||||
var filename = Path.GetFileName(path);
|
||||
if (exclude.Contains(filename ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (minRequiredVersions.TryGetValue(filename, out Version minRequiredVersion))
|
||||
{
|
||||
try
|
||||
{
|
||||
var version = Version.Parse(FileVersionInfo.GetVersionInfo(path).FileVersion);
|
||||
|
||||
if (version < minRequiredVersion)
|
||||
{
|
||||
Logger.LogInformation("Not loading {filename} {version} because the minimum supported version is {minRequiredVersion}. Please update to the newer version", filename, version, minRequiredVersion);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error getting version number from {path}", path);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system status.
|
||||
/// </summary>
|
||||
|
@ -1718,7 +1456,7 @@ namespace Emby.Server.Implementations
|
|||
SupportsHttps = SupportsHttps,
|
||||
HttpsPortNumber = HttpsPort,
|
||||
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
|
||||
OperatingSystemDisplayName = OperatingSystemDisplayName,
|
||||
OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName,
|
||||
CanSelfRestart = CanSelfRestart,
|
||||
CanSelfUpdate = CanSelfUpdate,
|
||||
CanLaunchWebBrowser = CanLaunchWebBrowser,
|
||||
|
@ -1788,7 +1526,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
public async Task<string> GetWanApiUrl(CancellationToken cancellationToken)
|
||||
{
|
||||
var url = "http://ipv4.icanhazip.com";
|
||||
const string url = "http://ipv4.icanhazip.com";
|
||||
try
|
||||
{
|
||||
using (var response = await HttpClient.Get(new HttpRequestOptions
|
||||
|
|
|
@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
|
|
@ -22,9 +22,10 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.22.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="4.4.2" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
|
||||
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
class UserDataChangeNotifier : IServerEntryPoint
|
||||
public class UserDataChangeNotifier : IServerEntryPoint
|
||||
{
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILogger _logger;
|
||||
|
|
|
@ -9,7 +9,7 @@ using MediaBrowser.Model.IO;
|
|||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
class SpecialFolderResolver : FolderResolver<Folder>
|
||||
public class SpecialFolderResolver : FolderResolver<Folder>
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
|
|
|
@ -157,56 +157,56 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null;
|
||||
|
||||
string numberString = null;
|
||||
string attributeValue;
|
||||
double doubleValue;
|
||||
|
||||
// Check for channel number with the format from SatIp
|
||||
// #EXTINF:0,84. VOX Schweiz
|
||||
// #EXTINF:0,84.0 - VOX Schweiz
|
||||
if (!string.IsNullOrWhiteSpace(nameInExtInf))
|
||||
if (attributes.TryGetValue("tvg-chno", out attributeValue))
|
||||
{
|
||||
var numberIndex = nameInExtInf.IndexOf(' ');
|
||||
if (numberIndex > 0)
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
|
||||
{
|
||||
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
|
||||
|
||||
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
|
||||
{
|
||||
numberString = numberPart;
|
||||
}
|
||||
numberString = attributeValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = numberString.Trim();
|
||||
}
|
||||
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
{
|
||||
if (attributes.TryGetValue("tvg-id", out string value))
|
||||
if (attributes.TryGetValue("tvg-id", out attributeValue))
|
||||
{
|
||||
if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleValue))
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
|
||||
{
|
||||
numberString = value;
|
||||
numberString = attributeValue;
|
||||
}
|
||||
else if (attributes.TryGetValue("channel-id", out attributeValue))
|
||||
{
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
|
||||
{
|
||||
numberString = attributeValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = numberString.Trim();
|
||||
}
|
||||
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
{
|
||||
if (attributes.TryGetValue("channel-id", out string value))
|
||||
if (String.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = value;
|
||||
}
|
||||
}
|
||||
// Using this as a fallback now as this leads to Problems with channels like "5 USA"
|
||||
// where 5 isnt ment to be the channel number
|
||||
// Check for channel number with the format from SatIp
|
||||
// #EXTINF:0,84. VOX Schweiz
|
||||
// #EXTINF:0,84.0 - VOX Schweiz
|
||||
if (!string.IsNullOrWhiteSpace(nameInExtInf))
|
||||
{
|
||||
var numberIndex = nameInExtInf.IndexOf(' ');
|
||||
if (numberIndex > 0)
|
||||
{
|
||||
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
|
||||
|
||||
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
|
||||
{
|
||||
numberString = numberPart;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = numberString.Trim();
|
||||
}
|
||||
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
|
@ -214,7 +214,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
numberString = null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(numberString))
|
||||
if (!string.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
numberString = numberString.Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaUrl))
|
||||
{
|
||||
|
|
|
@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
|
||||
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
//OpenedMediaSource.Path = tempFile;
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
/// <summary>
|
||||
/// Class ChapterImagesTask
|
||||
/// </summary>
|
||||
class ChapterImagesTask : IScheduledTask
|
||||
public class ChapterImagesTask : IScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
|
|
|
@ -15,21 +15,17 @@ namespace Emby.Server.Implementations
|
|||
/// </summary>
|
||||
public ServerApplicationPaths(
|
||||
string programDataPath,
|
||||
string appFolderPath,
|
||||
string applicationResourcesPath,
|
||||
string logDirectoryPath = null,
|
||||
string configurationDirectoryPath = null,
|
||||
string cacheDirectoryPath = null)
|
||||
string logDirectoryPath,
|
||||
string configurationDirectoryPath,
|
||||
string cacheDirectoryPath)
|
||||
: base(programDataPath,
|
||||
appFolderPath,
|
||||
logDirectoryPath,
|
||||
configurationDirectoryPath,
|
||||
cacheDirectoryPath)
|
||||
{
|
||||
ApplicationResourcesPath = applicationResourcesPath;
|
||||
}
|
||||
|
||||
public string ApplicationResourcesPath { get; private set; }
|
||||
public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the base root media directory
|
||||
|
@ -148,7 +144,6 @@ namespace Emby.Server.Implementations
|
|||
set => _internalMetadataPath = value;
|
||||
}
|
||||
|
||||
private const string _virtualInternalMetadataPath = "%MetadataPath%";
|
||||
public string VirtualInternalMetadataPath => _virtualInternalMetadataPath;
|
||||
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
|
|||
|
||||
namespace Emby.Server.Implementations.Sorting
|
||||
{
|
||||
class AiredEpisodeOrderComparer : IBaseItemComparer
|
||||
public class AiredEpisodeOrderComparer : IBaseItemComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares the specified x.
|
||||
|
|
|
@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying;
|
|||
|
||||
namespace Emby.Server.Implementations.Sorting
|
||||
{
|
||||
class SeriesSortNameComparer : IBaseItemComparer
|
||||
public class SeriesSortNameComparer : IBaseItemComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares the specified x.
|
||||
|
|
|
@ -282,7 +282,7 @@ namespace Jellyfin.Drawing.Skia
|
|||
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
||||
|
||||
// decode
|
||||
var _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||
_ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||
|
||||
origin = codec.EncodedOrigin;
|
||||
|
||||
|
|
|
@ -18,15 +18,17 @@ namespace Jellyfin.Server
|
|||
|
||||
public override bool CanSelfRestart => StartupOptions.RestartPath != null;
|
||||
|
||||
protected override bool SupportsDualModeSockets => true;
|
||||
|
||||
protected override void RestartInternal() => Program.Restart();
|
||||
|
||||
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
|
||||
=> new[] { typeof(CoreAppHost).Assembly };
|
||||
{
|
||||
yield return typeof(CoreAppHost).Assembly;
|
||||
}
|
||||
|
||||
protected override void ShutdownInternal() => Program.Shutdown();
|
||||
|
||||
protected override bool SupportsDualModeSockets => true;
|
||||
|
||||
protected override IHttpListener CreateHttpListener()
|
||||
=> new WebSocketSharpListener(
|
||||
Logger,
|
||||
|
@ -37,7 +39,6 @@ namespace Jellyfin.Server
|
|||
CryptographyProvider,
|
||||
SupportsDualModeSockets,
|
||||
FileSystemManager,
|
||||
EnvironmentInfo
|
||||
);
|
||||
EnvironmentInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- We need C# 7.1 for async main-->
|
||||
<LangVersion>latest</LangVersion>
|
||||
<!-- Disable documentation warnings (for now) -->
|
||||
<NoWarn>SA1600;CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -20,6 +23,10 @@
|
|||
<EmbeddedResource Include="Resources/Configuration/*" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code analysers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
|
||||
|
|
|
@ -21,6 +21,7 @@ using MediaBrowser.Controller.Drawing;
|
|||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Serilog.AspNetCore;
|
||||
|
@ -56,13 +57,28 @@ namespace Jellyfin.Server
|
|||
errs => Task.FromResult(0)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
if (!_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Restart()
|
||||
{
|
||||
_restartOnShutdown = true;
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
private static async Task StartApp(StartupOptions options)
|
||||
{
|
||||
ServerApplicationPaths appPaths = CreateApplicationPaths(options);
|
||||
|
||||
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
|
||||
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
|
||||
await CreateLogger(appPaths);
|
||||
await CreateLogger(appPaths).ConfigureAwait(false);
|
||||
_logger = _loggerFactory.CreateLogger("Main");
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e)
|
||||
|
@ -75,6 +91,7 @@ namespace Jellyfin.Server
|
|||
{
|
||||
return; // Already shutting down
|
||||
}
|
||||
|
||||
e.Cancel = true;
|
||||
_logger.LogInformation("Ctrl+C, shutting down");
|
||||
Environment.ExitCode = 128 + 2;
|
||||
|
@ -88,6 +105,7 @@ namespace Jellyfin.Server
|
|||
{
|
||||
return; // Already shutting down
|
||||
}
|
||||
|
||||
_logger.LogInformation("Received a SIGTERM signal, shutting down");
|
||||
Environment.ExitCode = 128 + 15;
|
||||
Shutdown();
|
||||
|
@ -101,7 +119,7 @@ namespace Jellyfin.Server
|
|||
SQLitePCL.Batteries_V2.Init();
|
||||
|
||||
// Allow all https requests
|
||||
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
|
||||
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; } );
|
||||
|
||||
var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, null, appPaths.TempDirectory, true);
|
||||
|
||||
|
@ -114,18 +132,18 @@ namespace Jellyfin.Server
|
|||
new NullImageEncoder(),
|
||||
new NetworkManager(_loggerFactory, environmentInfo)))
|
||||
{
|
||||
await appHost.Init();
|
||||
await appHost.Init(new ServiceCollection()).ConfigureAwait(false);
|
||||
|
||||
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
|
||||
|
||||
await appHost.RunStartupTasks();
|
||||
await appHost.RunStartupTasks().ConfigureAwait(false);
|
||||
|
||||
// TODO: read input for a stop command
|
||||
|
||||
try
|
||||
{
|
||||
// Block main thread until shutdown
|
||||
await Task.Delay(-1, _tokenSource.Token);
|
||||
await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
|
@ -139,112 +157,156 @@ namespace Jellyfin.Server
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the data, config and log paths from the variety of inputs(command line args,
|
||||
/// environment variables) or decide on what default to use. For Windows it's %AppPath%
|
||||
/// for everything else the XDG approach is followed:
|
||||
/// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
/// </summary>
|
||||
/// <param name="options">StartupOptions</param>
|
||||
/// <returns>ServerApplicationPaths</returns>
|
||||
private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
|
||||
{
|
||||
string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
|
||||
if (string.IsNullOrEmpty(programDataPath))
|
||||
// dataDir
|
||||
// IF --datadir
|
||||
// ELSE IF $JELLYFIN_DATA_PATH
|
||||
// ELSE IF windows, use <%APPDATA%>/jellyfin
|
||||
// ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin
|
||||
// ELSE use $HOME/.local/share/jellyfin
|
||||
var dataDir = options.DataDir;
|
||||
|
||||
if (string.IsNullOrEmpty(dataDir))
|
||||
{
|
||||
if (options.DataDir != null)
|
||||
{
|
||||
programDataPath = options.DataDir;
|
||||
}
|
||||
else
|
||||
dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
|
||||
|
||||
if (string.IsNullOrEmpty(dataDir))
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
|
||||
programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
|
||||
// If $XDG_DATA_HOME is either not set or empty, $HOME/.local/share should be used.
|
||||
if (string.IsNullOrEmpty(programDataPath))
|
||||
dataDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
|
||||
|
||||
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
|
||||
if (string.IsNullOrEmpty(dataDir))
|
||||
{
|
||||
programDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
|
||||
dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
|
||||
}
|
||||
}
|
||||
|
||||
programDataPath = Path.Combine(programDataPath, "jellyfin");
|
||||
dataDir = Path.Combine(dataDir, "jellyfin");
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(programDataPath))
|
||||
{
|
||||
Console.WriteLine("Cannot continue without path to program data folder (try -programdata)");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(programDataPath);
|
||||
}
|
||||
// configDir
|
||||
// IF --configdir
|
||||
// ELSE IF $JELLYFIN_CONFIG_DIR
|
||||
// ELSE IF --datadir, use <datadir>/config (assume portable run)
|
||||
// ELSE IF <datadir>/config exists, use that
|
||||
// ELSE IF windows, use <datadir>/config
|
||||
// ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
|
||||
// ELSE $HOME/.config/jellyfin
|
||||
var configDir = options.ConfigDir;
|
||||
|
||||
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
|
||||
if (string.IsNullOrEmpty(configDir))
|
||||
{
|
||||
if (options.ConfigDir != null)
|
||||
configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
|
||||
|
||||
if (string.IsNullOrEmpty(configDir))
|
||||
{
|
||||
configDir = options.ConfigDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let BaseApplicationPaths set up the default value
|
||||
configDir = null;
|
||||
if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Hang config folder off already set dataDir
|
||||
configDir = Path.Combine(dataDir, "config");
|
||||
}
|
||||
else
|
||||
{
|
||||
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
|
||||
configDir = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
|
||||
|
||||
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME /.config should be used.
|
||||
if (string.IsNullOrEmpty(configDir))
|
||||
{
|
||||
configDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
|
||||
}
|
||||
|
||||
configDir = Path.Combine(configDir, "jellyfin");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (configDir != null)
|
||||
{
|
||||
Directory.CreateDirectory(configDir);
|
||||
}
|
||||
// cacheDir
|
||||
// IF --cachedir
|
||||
// ELSE IF $JELLYFIN_CACHE_DIR
|
||||
// ELSE IF windows, use <datadir>/cache
|
||||
// ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin
|
||||
// ELSE HOME/.cache/jellyfin
|
||||
var cacheDir = options.CacheDir;
|
||||
|
||||
string cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
|
||||
if (string.IsNullOrEmpty(cacheDir))
|
||||
{
|
||||
if (options.CacheDir != null)
|
||||
cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
|
||||
|
||||
if (string.IsNullOrEmpty(cacheDir))
|
||||
{
|
||||
cacheDir = options.CacheDir;
|
||||
}
|
||||
else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
|
||||
cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
|
||||
// If $XDG_CACHE_HOME is either not set or empty, $HOME/.cache should be used.
|
||||
if (string.IsNullOrEmpty(cacheDir))
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
|
||||
// Hang cache folder off already set dataDir
|
||||
cacheDir = Path.Combine(dataDir, "cache");
|
||||
}
|
||||
else
|
||||
{
|
||||
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
|
||||
cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
|
||||
|
||||
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
|
||||
if (string.IsNullOrEmpty(cacheDir))
|
||||
{
|
||||
cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
|
||||
}
|
||||
|
||||
cacheDir = Path.Combine(cacheDir, "jellyfin");
|
||||
}
|
||||
cacheDir = Path.Combine(cacheDir, "jellyfin");
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheDir != null)
|
||||
{
|
||||
Directory.CreateDirectory(cacheDir);
|
||||
}
|
||||
// logDir
|
||||
// IF --logdir
|
||||
// ELSE IF $JELLYFIN_LOG_DIR
|
||||
// ELSE IF --datadir, use <datadir>/log (assume portable run)
|
||||
// ELSE <datadir>/log
|
||||
var logDir = options.LogDir;
|
||||
|
||||
string logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
{
|
||||
if (options.LogDir != null)
|
||||
logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
|
||||
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
{
|
||||
logDir = options.LogDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let BaseApplicationPaths set up the default value
|
||||
logDir = null;
|
||||
// Hang log folder off already set dataDir
|
||||
logDir = Path.Combine(dataDir, "log");
|
||||
}
|
||||
}
|
||||
|
||||
if (logDir != null)
|
||||
// Ensure the main folders exist before we continue
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(dataDir);
|
||||
Directory.CreateDirectory(logDir);
|
||||
Directory.CreateDirectory(configDir);
|
||||
Directory.CreateDirectory(cacheDir);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Console.Error.WriteLine("Error whilst attempting to create folder");
|
||||
Console.Error.WriteLine(ex.ToString());
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
string appPath = AppContext.BaseDirectory;
|
||||
|
||||
return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir);
|
||||
return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir);
|
||||
}
|
||||
|
||||
private static async Task CreateLogger(IApplicationPaths appPaths)
|
||||
|
@ -263,6 +325,7 @@ namespace Jellyfin.Server
|
|||
await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(appPaths.ConfigurationDirectoryPath)
|
||||
.AddJsonFile("logging.json")
|
||||
|
@ -290,7 +353,7 @@ namespace Jellyfin.Server
|
|||
}
|
||||
}
|
||||
|
||||
public static IImageEncoder GetImageEncoder(
|
||||
private static IImageEncoder GetImageEncoder(
|
||||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILocalizationManager localizationManager)
|
||||
|
@ -331,26 +394,12 @@ namespace Jellyfin.Server
|
|||
{
|
||||
return MediaBrowser.Model.System.OperatingSystem.BSD;
|
||||
}
|
||||
|
||||
throw new Exception($"Can't resolve OS with description: '{osDescription}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
if (!_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Restart()
|
||||
{
|
||||
_restartOnShutdown = true;
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
private static void StartNewInstance(StartupOptions options)
|
||||
{
|
||||
_logger.LogInformation("Starting new instance");
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
internal static string GetParameter(string header, string attr)
|
||||
{
|
||||
int ap = header.IndexOf(attr);
|
||||
int ap = header.IndexOf(attr, StringComparison.Ordinal);
|
||||
if (ap == -1)
|
||||
{
|
||||
return null;
|
||||
|
@ -82,9 +82,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// We use a substream, as in 2.x we will support large uploads streamed to disk,
|
||||
//
|
||||
var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
|
||||
files[e.Name] = sub;
|
||||
}
|
||||
|
@ -127,8 +125,12 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
|
||||
|
||||
protected bool validate_cookies, validate_query_string, validate_form;
|
||||
protected bool checked_cookies, checked_query_string, checked_form;
|
||||
protected bool validate_cookies { get; set; }
|
||||
protected bool validate_query_string { get; set; }
|
||||
protected bool validate_form { get; set; }
|
||||
protected bool checked_cookies { get; set; }
|
||||
protected bool checked_query_string { get; set; }
|
||||
protected bool checked_form { get; set; }
|
||||
|
||||
private static void ThrowValidationException(string name, string key, string value)
|
||||
{
|
||||
|
@ -138,8 +140,12 @@ namespace Jellyfin.Server.SocketSharp
|
|||
v = v.Substring(0, 16) + "...\"";
|
||||
}
|
||||
|
||||
string msg = string.Format("A potentially dangerous Request.{0} value was " +
|
||||
"detected from the client ({1}={2}).", name, key, v);
|
||||
string msg = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
|
||||
name,
|
||||
key,
|
||||
v);
|
||||
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
@ -179,6 +185,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
for (int idx = 1; idx < len; idx++)
|
||||
{
|
||||
char next = val[idx];
|
||||
|
||||
// See http://secunia.com/advisories/14325
|
||||
if (current == '<' || current == '\xff1c')
|
||||
{
|
||||
|
@ -256,6 +263,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
value.Append((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == -1)
|
||||
{
|
||||
AddRawKeyValue(form, key, value);
|
||||
|
@ -271,6 +279,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
key.Append((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == -1)
|
||||
{
|
||||
AddRawKeyValue(form, key, value);
|
||||
|
@ -308,6 +317,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
result.Append(key);
|
||||
result.Append('=');
|
||||
}
|
||||
|
||||
result.Append(pair.Value);
|
||||
}
|
||||
|
||||
|
@ -429,13 +439,13 @@ namespace Jellyfin.Server.SocketSharp
|
|||
real = position + d;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(nameof(origin));
|
||||
throw new ArgumentException("Unknown SeekOrigin value", nameof(origin));
|
||||
}
|
||||
|
||||
long virt = real - offset;
|
||||
if (virt < 0 || virt > Length)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
throw new ArgumentException("Invalid position", nameof(d));
|
||||
}
|
||||
|
||||
position = s.Seek(real, SeekOrigin.Begin);
|
||||
|
@ -491,11 +501,6 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public Stream InputStream => stream;
|
||||
}
|
||||
|
||||
private class Helpers
|
||||
{
|
||||
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
|
||||
}
|
||||
|
||||
internal static class StrUtils
|
||||
{
|
||||
public static bool StartsWith(string str1, string str2, bool ignore_case)
|
||||
|
@ -533,12 +538,17 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public class Element
|
||||
{
|
||||
public string ContentType;
|
||||
public string Name;
|
||||
public string Filename;
|
||||
public Encoding Encoding;
|
||||
public long Start;
|
||||
public long Length;
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Filename { get; set; }
|
||||
|
||||
public Encoding Encoding { get; set; }
|
||||
|
||||
public long Start { get; set; }
|
||||
|
||||
public long Length { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
@ -547,15 +557,23 @@ namespace Jellyfin.Server.SocketSharp
|
|||
}
|
||||
}
|
||||
|
||||
private Stream data;
|
||||
private string boundary;
|
||||
private byte[] boundary_bytes;
|
||||
private byte[] buffer;
|
||||
private bool at_eof;
|
||||
private Encoding encoding;
|
||||
private StringBuilder sb;
|
||||
private const byte LF = (byte)'\n';
|
||||
|
||||
private const byte LF = (byte)'\n', CR = (byte)'\r';
|
||||
private const byte CR = (byte)'\r';
|
||||
|
||||
private Stream data;
|
||||
|
||||
private string boundary;
|
||||
|
||||
private byte[] boundaryBytes;
|
||||
|
||||
private byte[] buffer;
|
||||
|
||||
private bool atEof;
|
||||
|
||||
private Encoding encoding;
|
||||
|
||||
private StringBuilder sb;
|
||||
|
||||
// See RFC 2046
|
||||
// In the case of multipart entities, in which one or more different
|
||||
|
@ -570,18 +588,48 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public HttpMultipart(Stream data, string b, Encoding encoding)
|
||||
{
|
||||
this.data = data;
|
||||
//DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET
|
||||
//var ms = new MemoryStream(32 * 1024);
|
||||
//data.CopyTo(ms);
|
||||
//this.data = ms;
|
||||
|
||||
boundary = b;
|
||||
boundary_bytes = encoding.GetBytes(b);
|
||||
buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--'
|
||||
boundaryBytes = encoding.GetBytes(b);
|
||||
buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
|
||||
this.encoding = encoding;
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
public Element ReadNextElement()
|
||||
{
|
||||
if (atEof || ReadBoundary())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var elem = new Element();
|
||||
string header;
|
||||
while ((header = ReadHeaders()) != null)
|
||||
{
|
||||
if (StrUtils.StartsWith(header, "Content-Disposition:", true))
|
||||
{
|
||||
elem.Name = GetContentDispositionAttribute(header, "name");
|
||||
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
|
||||
}
|
||||
else if (StrUtils.StartsWith(header, "Content-Type:", true))
|
||||
{
|
||||
elem.ContentType = header.Substring("Content-Type:".Length).Trim();
|
||||
elem.Encoding = GetEncoding(elem.ContentType);
|
||||
}
|
||||
}
|
||||
|
||||
long start = data.Position;
|
||||
elem.Start = start;
|
||||
long pos = MoveToNextBoundary();
|
||||
if (pos == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
elem.Length = pos - start;
|
||||
return elem;
|
||||
}
|
||||
|
||||
private string ReadLine()
|
||||
{
|
||||
// CRLF or LF are ok as line endings.
|
||||
|
@ -600,6 +648,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
break;
|
||||
}
|
||||
|
||||
got_cr = b == CR;
|
||||
sb.Append((char)b);
|
||||
}
|
||||
|
@ -769,7 +818,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (!CompareBytes(boundary_bytes, buffer))
|
||||
if (!CompareBytes(boundaryBytes, buffer))
|
||||
{
|
||||
state = 0;
|
||||
data.Position = retval + 2;
|
||||
|
@ -785,7 +834,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
|
||||
{
|
||||
at_eof = true;
|
||||
atEof = true;
|
||||
}
|
||||
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
|
||||
{
|
||||
|
@ -800,6 +849,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
c = data.ReadByte();
|
||||
continue;
|
||||
}
|
||||
|
||||
data.Position = retval + 2;
|
||||
if (got_cr)
|
||||
{
|
||||
|
@ -818,42 +868,6 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return retval;
|
||||
}
|
||||
|
||||
public Element ReadNextElement()
|
||||
{
|
||||
if (at_eof || ReadBoundary())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var elem = new Element();
|
||||
string header;
|
||||
while ((header = ReadHeaders()) != null)
|
||||
{
|
||||
if (StrUtils.StartsWith(header, "Content-Disposition:", true))
|
||||
{
|
||||
elem.Name = GetContentDispositionAttribute(header, "name");
|
||||
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
|
||||
}
|
||||
else if (StrUtils.StartsWith(header, "Content-Type:", true))
|
||||
{
|
||||
elem.ContentType = header.Substring("Content-Type:".Length).Trim();
|
||||
elem.Encoding = GetEncoding(elem.ContentType);
|
||||
}
|
||||
}
|
||||
|
||||
long start = 0;
|
||||
start = data.Position;
|
||||
elem.Start = start;
|
||||
long pos = MoveToNextBoundary();
|
||||
if (pos == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
elem.Length = pos - start;
|
||||
return elem;
|
||||
}
|
||||
|
||||
private static string StripPath(string path)
|
||||
{
|
||||
if (path == null || path.Length == 0)
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private bool _disposed = false;
|
||||
|
||||
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
|
||||
{
|
||||
|
@ -40,9 +41,9 @@ namespace Jellyfin.Server.SocketSharp
|
|||
_logger = logger;
|
||||
WebSocket = socket;
|
||||
|
||||
socket.OnMessage += socket_OnMessage;
|
||||
socket.OnClose += socket_OnClose;
|
||||
socket.OnError += socket_OnError;
|
||||
socket.OnMessage += OnSocketMessage;
|
||||
socket.OnClose += OnSocketClose;
|
||||
socket.OnError += OnSocketError;
|
||||
|
||||
WebSocket.ConnectAsServer();
|
||||
}
|
||||
|
@ -52,29 +53,22 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return _taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e)
|
||||
private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e)
|
||||
{
|
||||
_logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty);
|
||||
//Closed?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
// Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e)
|
||||
private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e)
|
||||
{
|
||||
_taskCompletionSource.TrySetResult(true);
|
||||
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e)
|
||||
private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e)
|
||||
{
|
||||
//if (!string.IsNullOrEmpty(e.Data))
|
||||
//{
|
||||
// if (OnReceive != null)
|
||||
// {
|
||||
// OnReceive(e.Data);
|
||||
// }
|
||||
// return;
|
||||
//}
|
||||
if (OnReceiveBytes != null)
|
||||
{
|
||||
OnReceiveBytes(e.RawData);
|
||||
|
@ -117,6 +111,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -125,16 +120,23 @@ namespace Jellyfin.Server.SocketSharp
|
|||
/// <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 (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispose)
|
||||
{
|
||||
WebSocket.OnMessage -= socket_OnMessage;
|
||||
WebSocket.OnClose -= socket_OnClose;
|
||||
WebSocket.OnError -= socket_OnError;
|
||||
WebSocket.OnMessage -= OnSocketMessage;
|
||||
WebSocket.OnClose -= OnSocketClose;
|
||||
WebSocket.OnError -= OnSocketError;
|
||||
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
||||
WebSocket.Close();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -142,11 +144,5 @@ namespace Jellyfin.Server.SocketSharp
|
|||
/// </summary>
|
||||
/// <value>The receive action.</value>
|
||||
public Action<byte[]> OnReceiveBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the on receive.
|
||||
/// </summary>
|
||||
/// <value>The on receive.</value>
|
||||
public Action<string> OnReceive { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,9 +34,16 @@ namespace Jellyfin.Server.SocketSharp
|
|||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||
private CancellationToken _disposeCancellationToken;
|
||||
|
||||
public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IStreamHelper streamHelper,
|
||||
INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider,
|
||||
bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment)
|
||||
public WebSocketSharpListener(
|
||||
ILogger logger,
|
||||
X509Certificate certificate,
|
||||
IStreamHelper streamHelper,
|
||||
INetworkManager networkManager,
|
||||
ISocketFactory socketFactory,
|
||||
ICryptoProvider cryptoProvider,
|
||||
bool enableDualMode,
|
||||
IFileSystem fileSystem,
|
||||
IEnvironmentInfo environment)
|
||||
{
|
||||
_logger = logger;
|
||||
_certificate = certificate;
|
||||
|
@ -61,7 +68,9 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public void Start(IEnumerable<string> urlPrefixes)
|
||||
{
|
||||
if (_listener == null)
|
||||
{
|
||||
_listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment);
|
||||
}
|
||||
|
||||
_listener.EnableDualMode = _enableDualMode;
|
||||
|
||||
|
@ -83,15 +92,18 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
private void ProcessContext(HttpListenerContext context)
|
||||
{
|
||||
var _ = Task.Run(async () => await InitTask(context, _disposeCancellationToken));
|
||||
_ = Task.Run(async () => await InitTask(context, _disposeCancellationToken).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
private static void LogRequest(ILogger logger, HttpListenerRequest request)
|
||||
{
|
||||
var url = request.Url.ToString();
|
||||
|
||||
logger.LogInformation("{0} {1}. UserAgent: {2}",
|
||||
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
|
||||
logger.LogInformation(
|
||||
"{0} {1}. UserAgent: {2}",
|
||||
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod,
|
||||
url,
|
||||
request.UserAgent ?? string.Empty);
|
||||
}
|
||||
|
||||
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
|
||||
|
@ -201,7 +213,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
//TODO Investigate and properly fix.
|
||||
// TODO: Investigate and properly fix.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -223,38 +235,39 @@ namespace Jellyfin.Server.SocketSharp
|
|||
public Task Stop()
|
||||
{
|
||||
_disposeCancellationTokenSource.Cancel();
|
||||
|
||||
if (_listener != null)
|
||||
{
|
||||
_listener.Close();
|
||||
}
|
||||
_listener?.Close();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources and disposes of the managed resources used.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
private readonly object _disposeLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources and disposes of the managed resources used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">Whether or not the managed resources should be disposed</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
lock (_disposeLock)
|
||||
if (_disposed)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
//release unmanaged resources here...
|
||||
_disposed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Stop().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
|
@ -24,7 +25,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
this.request = httpContext.Request;
|
||||
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
|
||||
|
||||
//HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
|
||||
// HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
|
||||
}
|
||||
|
||||
public HttpListenerRequest HttpRequest => request;
|
||||
|
@ -45,9 +46,11 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public string UserHostAddress => request.UserHostAddress;
|
||||
|
||||
public string XForwardedFor => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
|
||||
public string XForwardedFor
|
||||
=> string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
|
||||
|
||||
public int? XForwardedPort => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]);
|
||||
public int? XForwardedPort
|
||||
=> string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture);
|
||||
|
||||
public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"];
|
||||
|
||||
|
@ -83,6 +86,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
switch (crlf)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if (c == '\r')
|
||||
{
|
||||
crlf = 1;
|
||||
|
@ -97,29 +101,39 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
throw new ArgumentException("net_WebHeaderInvalidControlChars");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
crlf = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
if (c == ' ' || c == '\t')
|
||||
{
|
||||
crlf = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (crlf != 0)
|
||||
{
|
||||
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
@ -132,6 +146,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -337,6 +352,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
|
||||
this.pathInfo = NormalizePathInfo(pathInfo, mode);
|
||||
}
|
||||
|
||||
return this.pathInfo;
|
||||
}
|
||||
}
|
||||
|
@ -438,7 +454,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public string ContentType => request.ContentType;
|
||||
|
||||
public Encoding contentEncoding;
|
||||
private Encoding contentEncoding;
|
||||
public Encoding ContentEncoding
|
||||
{
|
||||
get => contentEncoding ?? request.ContentEncoding;
|
||||
|
@ -496,6 +512,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return httpFiles;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@ using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
|
|||
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
|
||||
using IRequest = MediaBrowser.Model.Services.IRequest;
|
||||
|
||||
|
||||
namespace Jellyfin.Server.SocketSharp
|
||||
{
|
||||
public class WebSocketSharpResponse : IHttpResponse
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly HttpListenerResponse _response;
|
||||
|
||||
public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request)
|
||||
|
@ -30,7 +30,9 @@ namespace Jellyfin.Server.SocketSharp
|
|||
}
|
||||
|
||||
public IRequest Request { get; private set; }
|
||||
|
||||
public Dictionary<string, object> Items { get; private set; }
|
||||
|
||||
public object OriginalResponse => _response;
|
||||
|
||||
public int StatusCode
|
||||
|
@ -51,7 +53,7 @@ namespace Jellyfin.Server.SocketSharp
|
|||
set => _response.ContentType = value;
|
||||
}
|
||||
|
||||
//public ICookies Cookies { get; set; }
|
||||
public QueryParamCollection Headers => _response.Headers;
|
||||
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
|
@ -64,8 +66,6 @@ namespace Jellyfin.Server.SocketSharp
|
|||
_response.AddHeader(name, value);
|
||||
}
|
||||
|
||||
public QueryParamCollection Headers => _response.Headers;
|
||||
|
||||
public string GetHeader(string name)
|
||||
{
|
||||
return _response.Headers[name];
|
||||
|
@ -114,9 +114,9 @@ namespace Jellyfin.Server.SocketSharp
|
|||
|
||||
public void SetContentLength(long contentLength)
|
||||
{
|
||||
//you can happily set the Content-Length header in Asp.Net
|
||||
//but HttpListener will complain if you do - you have to set ContentLength64 on the response.
|
||||
//workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
|
||||
// you can happily set the Content-Length header in Asp.Net
|
||||
// but HttpListener will complain if you do - you have to set ContentLength64 on the response.
|
||||
// workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
|
||||
_response.ContentLength64 = contentLength;
|
||||
}
|
||||
|
||||
|
@ -147,15 +147,12 @@ namespace Jellyfin.Server.SocketSharp
|
|||
{
|
||||
sb.Append($";domain={cookie.Domain}");
|
||||
}
|
||||
//else if (restrictAllCookiesToDomain != null)
|
||||
//{
|
||||
// sb.Append($";domain={restrictAllCookiesToDomain}");
|
||||
//}
|
||||
|
||||
if (cookie.Secure)
|
||||
{
|
||||
sb.Append(";Secure");
|
||||
}
|
||||
|
||||
if (cookie.HttpOnly)
|
||||
{
|
||||
sb.Append(";HttpOnly");
|
||||
|
@ -164,7 +161,6 @@ namespace Jellyfin.Server.SocketSharp
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
public bool SendChunked
|
||||
{
|
||||
get => _response.SendChunked;
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace MediaBrowser.Api.Session
|
|||
/// <summary>
|
||||
/// Class SessionInfoWebSocketListener
|
||||
/// </summary>
|
||||
class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>
|
||||
public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace MediaBrowser.Api.System
|
|||
/// <summary>
|
||||
/// Class SessionInfoWebSocketListener
|
||||
/// </summary>
|
||||
class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState>
|
||||
public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
|||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace MediaBrowser.Common
|
||||
{
|
||||
|
@ -13,12 +14,6 @@ namespace MediaBrowser.Common
|
|||
/// </summary>
|
||||
public interface IApplicationHost
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the display name of the operating system.
|
||||
/// </summary>
|
||||
/// <value>The display name of the operating system.</value>
|
||||
string OperatingSystemDisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
|
@ -104,13 +99,6 @@ namespace MediaBrowser.Common
|
|||
/// <returns>``0.</returns>
|
||||
T Resolve<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Resolves this instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
T TryResolve<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down.
|
||||
/// </summary>
|
||||
|
@ -131,7 +119,7 @@ namespace MediaBrowser.Common
|
|||
/// <summary>
|
||||
/// Inits this instance.
|
||||
/// </summary>
|
||||
Task Init();
|
||||
Task Init(IServiceCollection serviceCollection);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the instance.
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace MediaBrowser.LocalMetadata.Providers
|
||||
{
|
||||
class PlaylistXmlProvider : BaseXmlProvider<Playlist>
|
||||
public class PlaylistXmlProvider : BaseXmlProvider<Playlist>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IProviderManager _providerManager;
|
||||
|
|
|
@ -24,12 +24,12 @@ namespace MediaBrowser.Model.System
|
|||
/// Gets or sets the server version.
|
||||
/// </summary>
|
||||
/// <value>The version.</value>
|
||||
public string Version { get; set; }
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the operating sytem.
|
||||
/// Gets or sets the operating system.
|
||||
/// </summary>
|
||||
/// <value>The operating sytem.</value>
|
||||
/// <value>The operating system.</value>
|
||||
public string OperatingSystem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MediaBrowser.Model.Updates;
|
||||
|
||||
|
@ -136,7 +137,7 @@ namespace MediaBrowser.Model.System
|
|||
/// </summary>
|
||||
public SystemInfo()
|
||||
{
|
||||
CompletedInstallations = new InstallationInfo[] { };
|
||||
CompletedInstallations = Array.Empty<InstallationInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ using MediaBrowser.Providers.Movies;
|
|||
|
||||
namespace MediaBrowser.Providers.BoxSets
|
||||
{
|
||||
class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
|
||||
public class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ using MediaBrowser.Model.Serialization;
|
|||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
|
||||
public class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
|
|
@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo>
|
||||
public class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo>
|
||||
{
|
||||
protected override void MergeData(MetadataResult<MusicVideo> source, MetadataResult<MusicVideo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace MediaBrowser.Providers.Photos
|
||||
{
|
||||
class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo>
|
||||
public class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo>
|
||||
{
|
||||
protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace MediaBrowser.Providers.Photos
|
||||
{
|
||||
class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo>
|
||||
public class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo>
|
||||
{
|
||||
protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
|
|
|
@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace MediaBrowser.Providers.Playlists
|
||||
{
|
||||
class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo>
|
||||
public class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo>
|
||||
{
|
||||
protected override IList<BaseItem> GetChildrenForMetadataUpdates(Playlist item)
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace MediaBrowser.Providers.TV.Omdb
|
||||
{
|
||||
class OmdbEpisodeProvider :
|
||||
public class OmdbEpisodeProvider :
|
||||
IRemoteMetadataProvider<Episode, EpisodeInfo>,
|
||||
IHasOrder
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace MediaBrowser.Providers.TV.TheMovieDb
|
||||
{
|
||||
class MovieDbEpisodeProvider :
|
||||
public class MovieDbEpisodeProvider :
|
||||
MovieDbProviderBase,
|
||||
IRemoteMetadataProvider<Episode, EpisodeInfo>,
|
||||
IHasOrder
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 094c1deae91c51b8bbf8ebb16a55758af110f04d
|
||||
Subproject commit c7ce1ac8eccd50f1bd759b30fbe60ea797ffe86e
|
|
@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace MediaBrowser.XbmcMetadata.Parsers
|
||||
{
|
||||
class MovieNfoParser : BaseNfoParser<Video>
|
||||
public class MovieNfoParser : BaseNfoParser<Video>
|
||||
{
|
||||
protected override bool SupportsUrlAfterClosingXmlTag => true;
|
||||
|
||||
|
|
|
@ -3,11 +3,19 @@
|
|||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
|
||||
<!-- disable warning SA1101: Prefix local calls with 'this.' -->
|
||||
<Rule Id="SA1101" Action="None" />
|
||||
<!-- disable warning SA1130: Use lambda syntax -->
|
||||
<Rule Id="SA1130" Action="None" />
|
||||
<!-- disable warning SA1200: 'using' directive must appear within a namespace declaration -->
|
||||
<Rule Id="SA1200" Action="None" />
|
||||
<!-- disable warning SA1309: Fields must not begin with an underscore -->
|
||||
<Rule Id="SA1309" Action="None" />
|
||||
<!-- disable warning SA1512: Single-line comments must not be followed by blank line -->
|
||||
<Rule Id="SA1512" Action="None" />
|
||||
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
|
||||
<Rule Id="SA1633" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
|
||||
<!-- disable warning CA1054: Change the type of parameter url from string to System.Uri -->
|
||||
<Rule Id="CA1054" Action="None" />
|
||||
</Rules>
|
||||
</RuleSet>
|
||||
|
|
Loading…
Reference in New Issue
Block a user