Merge branch 'master' into perf

This commit is contained in:
Bond-009 2019-02-16 17:05:44 +01:00 committed by GitHub
commit 4811e76860
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 643 additions and 807 deletions

View file

@ -55,15 +55,77 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
############################### ###############################
# Naming Conventions # # Naming Conventions #
############################### ###############################
# Style Definitions # Style Definitions (From Roslyn)
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields # Non-private static fields are PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = * dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const 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 # # C# Coding Conventions #
############################### ###############################

View file

@ -38,7 +38,9 @@ namespace Emby.Dlna
IFileSystem fileSystem, IFileSystem fileSystem,
IApplicationPaths appPaths, IApplicationPaths appPaths,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo) IJsonSerializer jsonSerializer,
IServerApplicationHost appHost,
IAssemblyInfo assemblyInfo)
{ {
_xmlSerializer = xmlSerializer; _xmlSerializer = xmlSerializer;
_fileSystem = fileSystem; _fileSystem = fileSystem;

View file

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

View file

@ -1,3 +1,4 @@
using System;
using System.IO; using System.IO;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -14,50 +15,44 @@ namespace Emby.Server.Implementations.AppBase
/// </summary> /// </summary>
protected BaseApplicationPaths( protected BaseApplicationPaths(
string programDataPath, string programDataPath,
string appFolderPath, string logDirectoryPath,
string logDirectoryPath = null, string configurationDirectoryPath,
string configurationDirectoryPath = null, string cacheDirectoryPath)
string cacheDirectoryPath = null)
{ {
ProgramDataPath = programDataPath; ProgramDataPath = programDataPath;
ProgramSystemPath = appFolderPath;
LogDirectoryPath = logDirectoryPath; LogDirectoryPath = logDirectoryPath;
ConfigurationDirectoryPath = configurationDirectoryPath; ConfigurationDirectoryPath = configurationDirectoryPath;
CachePath = cacheDirectoryPath; 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; } public string ProgramDataPath { get; private set; }
/// <summary> /// <summary>
/// Gets the path to the system folder /// Gets the path to the system folder
/// </summary> /// </summary>
public string ProgramSystemPath { get; private set; } public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
/// <summary>
/// The _data directory
/// </summary>
private string _dataDirectory;
/// <summary> /// <summary>
/// Gets the folder path to the data directory /// Gets the folder path to the data directory
/// </summary> /// </summary>
/// <value>The data directory.</value> /// <value>The data directory.</value>
private string _dataPath;
public string DataPath public string DataPath
{ {
get get => _dataPath;
{ private set => _dataPath = Directory.CreateDirectory(value).FullName;
if (_dataDirectory == null)
{
_dataDirectory = Path.Combine(ProgramDataPath, "data");
Directory.CreateDirectory(_dataDirectory);
}
return _dataDirectory;
}
} }
private const string _virtualDataPath = "%AppDataPath%"; /// <summary>
public string VirtualDataPath => _virtualDataPath; /// Gets the magic strings used for virtual path manipulation.
/// </summary>
public string VirtualDataPath { get; } = "%AppDataPath%";
/// <summary> /// <summary>
/// Gets the image cache path. /// Gets the image cache path.
@ -83,55 +78,17 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The plugin configurations path.</value> /// <value>The plugin configurations path.</value>
public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates"); public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates");
/// <summary>
/// The _log directory
/// </summary>
private string _logDirectoryPath;
/// <summary> /// <summary>
/// Gets the path to the log directory /// Gets the path to the log directory
/// </summary> /// </summary>
/// <value>The log directory path.</value> /// <value>The log directory path.</value>
public string LogDirectoryPath public string LogDirectoryPath { get; private set; }
{
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;
/// <summary> /// <summary>
/// Gets the path to the application configuration root directory /// Gets the path to the application configuration root directory
/// </summary> /// </summary>
/// <value>The configuration directory path.</value> /// <value>The configuration directory path.</value>
public string ConfigurationDirectoryPath public string ConfigurationDirectoryPath { get; private set; }
{
get
{
if (string.IsNullOrEmpty(_configurationDirectoryPath))
{
_configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
Directory.CreateDirectory(_configurationDirectoryPath);
}
return _configurationDirectoryPath;
}
set => _configurationDirectoryPath = value;
}
/// <summary> /// <summary>
/// Gets the path to the system configuration file /// Gets the path to the system configuration file
@ -139,29 +96,11 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The system configuration file path.</value> /// <value>The system configuration file path.</value>
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml"); public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
/// <summary>
/// The _cache directory
/// </summary>
private string _cachePath;
/// <summary> /// <summary>
/// Gets the folder path to the cache directory /// Gets the folder path to the cache directory
/// </summary> /// </summary>
/// <value>The cache directory.</value> /// <value>The cache directory.</value>
public string CachePath public string CachePath { get; set; }
{
get
{
if (string.IsNullOrEmpty(_cachePath))
{
_cachePath = Path.Combine(ProgramDataPath, "cache");
Directory.CreateDirectory(_cachePath);
}
return _cachePath;
}
set => _cachePath = value;
}
/// <summary> /// <summary>
/// Gets the folder path to the temp directory within the cache folder /// Gets the folder path to the temp directory within the cache folder

View file

@ -105,6 +105,7 @@ using MediaBrowser.Providers.Subtitles;
using MediaBrowser.WebDashboard.Api; using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers; using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using ServiceStack; using ServiceStack;
using ServiceStack.Text.Jsv; using ServiceStack.Text.Jsv;
using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
@ -202,7 +203,7 @@ namespace Emby.Server.Implementations
/// Gets all concrete types. /// Gets all concrete types.
/// </summary> /// </summary>
/// <value>All concrete types.</value> /// <value>All concrete types.</value>
public Tuple<Type, string>[] AllConcreteTypes { get; protected set; } public Type[] AllConcreteTypes { get; protected set; }
/// <summary> /// <summary>
/// The disposable parts /// The disposable parts
@ -219,8 +220,6 @@ namespace Emby.Server.Implementations
protected IEnvironmentInfo EnvironmentInfo { get; set; } protected IEnvironmentInfo EnvironmentInfo { get; set; }
private IBlurayExaminer BlurayExaminer { get; set; }
public PackageVersionClass SystemUpdateLevel public PackageVersionClass SystemUpdateLevel
{ {
get get
@ -232,12 +231,7 @@ namespace Emby.Server.Implementations
} }
} }
public virtual string OperatingSystemDisplayName => EnvironmentInfo.OperatingSystemName; protected IServiceProvider _serviceProvider;
/// <summary>
/// The container
/// </summary>
protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
/// <summary> /// <summary>
/// Gets the server configuration manager. /// Gets the server configuration manager.
@ -309,7 +303,6 @@ namespace Emby.Server.Implementations
/// <value>The user data repository.</value> /// <value>The user data repository.</value>
private IUserDataManager UserDataManager { get; set; } private IUserDataManager UserDataManager { get; set; }
private IUserRepository UserRepository { get; set; } private IUserRepository UserRepository { get; set; }
internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
internal SqliteItemRepository ItemRepository { get; set; } internal SqliteItemRepository ItemRepository { get; set; }
private INotificationManager NotificationManager { get; set; } private INotificationManager NotificationManager { get; set; }
@ -453,138 +446,58 @@ namespace Emby.Server.Implementations
/// <value>The name.</value> /// <value>The name.</value>
public string Name => ApplicationProductName; 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> /// <summary>
/// Creates an instance of type and resolves all constructor dependancies /// Creates an instance of type and resolves all constructor dependencies
/// </summary> /// </summary>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
public object CreateInstance(Type type) public object CreateInstance(Type type)
{ => ActivatorUtilities.CreateInstance(_serviceProvider, type);
return Container.GetInstance(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> /// <summary>
/// Creates the instance safe. /// Creates the instance safe.
/// </summary> /// </summary>
/// <param name="typeInfo">The type information.</param> /// <param name="typeInfo">The type information.</param>
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
protected object CreateInstanceSafe(Tuple<Type, string> typeInfo) protected object CreateInstanceSafe(Type type)
{ {
var type = typeInfo.Item1;
try try
{ {
return Container.GetInstance(type); Logger.LogWarning("Creating instance of {Type}", type);
return ActivatorUtilities.CreateInstance(_serviceProvider, type);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogError(ex, "Error creating {type}", type.FullName); Logger.LogError(ex, "Error creating {Type}", type);
// Don't blow up in release mode
return null; 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> /// <summary>
/// Resolves this instance. /// Resolves this instance.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns>``0.</returns> /// <returns>``0.</returns>
public T Resolve<T>() public T Resolve<T>() => _serviceProvider.GetService<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;
}
}
/// <summary> /// <summary>
/// Gets the export types. /// Gets the export types.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns>IEnumerable{Type}.</returns> /// <returns>IEnumerable{Type}.</returns>
public IEnumerable<Tuple<Type, string>> GetExportTypes<T>() public IEnumerable<Type> GetExportTypes<T>()
{ {
var currentType = typeof(T); var currentType = typeof(T);
return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i.Item1)); return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i));
} }
/// <summary> /// <summary>
@ -596,9 +509,10 @@ namespace Emby.Server.Implementations
public IEnumerable<T> GetExports<T>(bool manageLifetime = true) public IEnumerable<T> GetExports<T>(bool manageLifetime = true)
{ {
var parts = GetExportTypes<T>() var parts = GetExportTypes<T>()
.Select(CreateInstanceSafe) .Select(x => CreateInstanceSafe(x))
.Where(i => i != null) .Where(i => i != null)
.Cast<T>(); .Cast<T>()
.ToList(); // Convert to list so this isn't executed for each iteration
if (manageLifetime) if (manageLifetime)
{ {
@ -611,33 +525,6 @@ namespace Emby.Server.Implementations
return parts; 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> /// <summary>
/// Runs the startup tasks. /// Runs the startup tasks.
/// </summary> /// </summary>
@ -691,7 +578,7 @@ namespace Emby.Server.Implementations
} }
} }
public async Task Init() public async Task Init(IServiceCollection serviceCollection)
{ {
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@ -721,7 +608,7 @@ namespace Emby.Server.Implementations
SetHttpLimit(); SetHttpLimit();
await RegisterResources(); await RegisterResources(serviceCollection);
FindParts(); FindParts();
} }
@ -736,104 +623,103 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// Registers resources that classes will depend on /// Registers resources that classes will depend on
/// </summary> /// </summary>
protected async Task RegisterResources() protected async Task RegisterResources(IServiceCollection serviceCollection)
{ {
RegisterSingleInstance(ConfigurationManager); serviceCollection.AddSingleton(ConfigurationManager);
RegisterSingleInstance<IApplicationHost>(this); serviceCollection.AddSingleton<IApplicationHost>(this);
RegisterSingleInstance<IApplicationPaths>(ApplicationPaths); serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
RegisterSingleInstance(JsonSerializer);
RegisterSingleInstance(LoggerFactory, false); serviceCollection.AddSingleton(JsonSerializer);
RegisterSingleInstance(Logger);
RegisterSingleInstance(EnvironmentInfo); serviceCollection.AddSingleton(LoggerFactory);
serviceCollection.AddLogging();
serviceCollection.AddSingleton(Logger);
RegisterSingleInstance(FileSystemManager); serviceCollection.AddSingleton(EnvironmentInfo);
serviceCollection.AddSingleton(FileSystemManager);
HttpClient = CreateHttpClient(); HttpClient = CreateHttpClient();
RegisterSingleInstance(HttpClient); serviceCollection.AddSingleton(HttpClient);
RegisterSingleInstance(NetworkManager); serviceCollection.AddSingleton(NetworkManager);
IsoManager = new IsoManager(); IsoManager = new IsoManager();
RegisterSingleInstance(IsoManager); serviceCollection.AddSingleton(IsoManager);
TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager); TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
RegisterSingleInstance(TaskManager); serviceCollection.AddSingleton(TaskManager);
RegisterSingleInstance(XmlSerializer); serviceCollection.AddSingleton(XmlSerializer);
ProcessFactory = new ProcessFactory(); ProcessFactory = new ProcessFactory();
RegisterSingleInstance(ProcessFactory); serviceCollection.AddSingleton(ProcessFactory);
var streamHelper = CreateStreamHelper(); ApplicationHost.StreamHelper = new StreamHelper();
ApplicationHost.StreamHelper = streamHelper; serviceCollection.AddSingleton(StreamHelper);
RegisterSingleInstance(streamHelper);
RegisterSingleInstance(CryptographyProvider); serviceCollection.AddSingleton(CryptographyProvider);
SocketFactory = new SocketFactory(); SocketFactory = new SocketFactory();
RegisterSingleInstance(SocketFactory); serviceCollection.AddSingleton(SocketFactory);
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime); InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime);
RegisterSingleInstance(InstallationManager); serviceCollection.AddSingleton(InstallationManager);
ZipClient = new ZipClient(FileSystemManager); ZipClient = new ZipClient(FileSystemManager);
RegisterSingleInstance(ZipClient); serviceCollection.AddSingleton(ZipClient);
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor()); HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
RegisterSingleInstance(HttpResultFactory); serviceCollection.AddSingleton(HttpResultFactory);
RegisterSingleInstance<IServerApplicationHost>(this); serviceCollection.AddSingleton<IServerApplicationHost>(this);
RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths); serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
RegisterSingleInstance(ServerConfigurationManager); serviceCollection.AddSingleton(ServerConfigurationManager);
IAssemblyInfo assemblyInfo = new AssemblyInfo(); var assemblyInfo = new AssemblyInfo();
RegisterSingleInstance(assemblyInfo); serviceCollection.AddSingleton<IAssemblyInfo>(assemblyInfo);
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory); LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory);
await LocalizationManager.LoadAll(); await LocalizationManager.LoadAll();
RegisterSingleInstance<ILocalizationManager>(LocalizationManager); serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
BlurayExaminer = new BdInfoExaminer(FileSystemManager); serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
RegisterSingleInstance(BlurayExaminer);
RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory()); serviceCollection.AddSingleton<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
RegisterSingleInstance(UserDataManager); serviceCollection.AddSingleton(UserDataManager);
UserRepository = GetUserRepository(); UserRepository = GetUserRepository();
// This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it // 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); var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LoggerFactory, JsonSerializer, ApplicationPaths, FileSystemManager);
DisplayPreferencesRepository = displayPreferencesRepo; serviceCollection.AddSingleton<IDisplayPreferencesRepository>(displayPreferencesRepo);
RegisterSingleInstance(DisplayPreferencesRepository);
ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo); ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LoggerFactory, assemblyInfo);
RegisterSingleInstance<IItemRepository>(ItemRepository); serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
AuthenticationRepository = GetAuthenticationRepository(); AuthenticationRepository = GetAuthenticationRepository();
RegisterSingleInstance(AuthenticationRepository); serviceCollection.AddSingleton(AuthenticationRepository);
UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager, CryptographyProvider); 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); 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 // TODO wtaylor: investigate use of second music manager
var musicManager = new MusicManager(LibraryManager); 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); 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); CertificateInfo = GetCertificateInfo(true);
Certificate = GetCertificate(CertificateInfo); Certificate = GetCertificate(CertificateInfo);
@ -848,81 +734,82 @@ namespace Emby.Server.Implementations
GetParseFn); GetParseFn);
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
RegisterSingleInstance(HttpServer); serviceCollection.AddSingleton(HttpServer);
ImageProcessor = GetImageProcessor(); ImageProcessor = GetImageProcessor();
RegisterSingleInstance(ImageProcessor); serviceCollection.AddSingleton(ImageProcessor);
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
RegisterSingleInstance(TVSeriesManager); serviceCollection.AddSingleton(TVSeriesManager);
var encryptionManager = new EncryptionManager(); var encryptionManager = new EncryptionManager();
RegisterSingleInstance<IEncryptionManager>(encryptionManager); serviceCollection.AddSingleton<IEncryptionManager>(encryptionManager);
DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager); 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); 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); 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); 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); 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); 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); 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); serviceCollection.AddSingleton<IDlnaManager>(
RegisterSingleInstance<IDlnaManager>(dlnaManager); new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo));
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
RegisterSingleInstance(CollectionManager); serviceCollection.AddSingleton(CollectionManager);
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager); 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); 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); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
RegisterSingleInstance(UserViewManager); serviceCollection.AddSingleton(UserViewManager);
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); 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); 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); EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
RegisterSingleInstance(EncodingManager); serviceCollection.AddSingleton(EncodingManager);
var activityLogRepo = GetActivityLogRepository(); var activityLogRepo = GetActivityLogRepository();
RegisterSingleInstance(activityLogRepo); serviceCollection.AddSingleton(activityLogRepo);
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
var authContext = new AuthorizationContext(AuthenticationRepository, UserManager); var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
RegisterSingleInstance<IAuthorizationContext>(authContext); serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager)); serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager); 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); 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(); displayPreferencesRepo.Initialize();
@ -935,6 +822,8 @@ namespace Emby.Server.Implementations
((UserDataManager)UserDataManager).Repository = userDataRepo; ((UserDataManager)UserDataManager).Repository = userDataRepo;
ItemRepository.Initialize(userDataRepo, UserManager); ItemRepository.Initialize(userDataRepo, UserManager);
((LibraryManager)LibraryManager).ItemRepository = ItemRepository; ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
_serviceProvider = serviceCollection.BuildServiceProvider();
} }
protected virtual IBrotliCompressor CreateBrotliCompressor() protected virtual IBrotliCompressor CreateBrotliCompressor()
@ -1066,7 +955,7 @@ namespace Emby.Server.Implementations
/// Registers the media encoder. /// Registers the media encoder.
/// </summary> /// </summary>
/// <returns>Task.</returns> /// <returns>Task.</returns>
private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) private void RegisterMediaEncoder(IServiceCollection serviceCollection)
{ {
string encoderPath = null; string encoderPath = null;
string probePath = null; string probePath = null;
@ -1098,7 +987,7 @@ namespace Emby.Server.Implementations
5000); 5000);
MediaEncoder = mediaEncoder; MediaEncoder = mediaEncoder;
RegisterSingleInstance(MediaEncoder); serviceCollection.AddSingleton(MediaEncoder);
} }
/// <summary> /// <summary>
@ -1174,7 +1063,10 @@ namespace Emby.Server.Implementations
} }
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>()); 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>()); HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>());
@ -1208,19 +1100,15 @@ namespace Emby.Server.Implementations
IsoManager.AddParts(GetExports<IIsoMounter>()); 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 try
{ {
var assemblyPlugin = plugin as IPluginAssembly; if (plugin is IPluginAssembly assemblyPlugin)
if (assemblyPlugin != null)
{ {
var assembly = plugin.GetType().Assembly; var assembly = plugin.GetType().Assembly;
var assemblyName = assembly.GetName(); var assemblyName = assembly.GetName();
var assemblyFilePath = assembly.Location;
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
@ -1264,78 +1152,15 @@ namespace Emby.Server.Implementations
{ {
Logger.LogInformation("Loading assemblies"); Logger.LogInformation("Loading assemblies");
var assemblyInfos = GetComposablePartAssemblies(); AllConcreteTypes = GetComposablePartAssemblies()
.SelectMany(x => x.ExportedTypes)
foreach (var assemblyInfo in assemblyInfos) .Where(type =>
{
var assembly = assemblyInfo.Item1;
var path = assemblyInfo.Item2;
if (path == null)
{ {
Logger.LogInformation("Loading {assemblyName}", assembly.FullName); return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType;
}
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;
}) })
.ToArray(); .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; } private CertificateInfo CertificateInfo { get; set; }
protected X509Certificate Certificate { get; private set; } protected X509Certificate Certificate { get; private set; }
@ -1546,151 +1371,64 @@ namespace Emby.Server.Implementations
/// Gets the composable part assemblies. /// Gets the composable part assemblies.
/// </summary> /// </summary>
/// <returns>IEnumerable{Assembly}.</returns> /// <returns>IEnumerable{Assembly}.</returns>
protected List<Tuple<Assembly, string>> GetComposablePartAssemblies() protected IEnumerable<Assembly> GetComposablePartAssemblies()
{ {
var list = GetPluginAssemblies(ApplicationPaths.PluginsPath); if (Directory.Exists(ApplicationPaths.PluginsPath))
{
// Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly))
// This will prevent the .dll file from getting locked, and allow us to replace it when needed {
Logger.LogInformation("Loading assembly {Path}", file);
yield return Assembly.LoadFrom(file);
}
}
// Include composable parts in the Api assembly // Include composable parts in the Api assembly
list.Add(GetAssembly(typeof(ApiEntryPoint))); yield return typeof(ApiEntryPoint).Assembly;
// Include composable parts in the Dashboard 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 // Include composable parts in the Model assembly
list.Add(GetAssembly(typeof(SystemInfo))); yield return typeof(SystemInfo).Assembly;
// Include composable parts in the Common 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 // Include composable parts in the Controller assembly
list.Add(GetAssembly(typeof(IServerApplicationHost))); yield return typeof(IServerApplicationHost).Assembly;
// Include composable parts in the Providers 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 // Include composable parts in the Photos assembly
list.Add(GetAssembly(typeof(PhotoProvider))); yield return typeof(PhotoProvider).Assembly;
// Emby.Server implementations // Emby.Server implementations
list.Add(GetAssembly(typeof(InstallationManager))); yield return typeof(InstallationManager).Assembly;
// MediaEncoding // MediaEncoding
list.Add(GetAssembly(typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder))); yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
// Dlna // Dlna
list.Add(GetAssembly(typeof(DlnaEntryPoint))); yield return typeof(DlnaEntryPoint).Assembly;
// Local metadata // Local metadata
list.Add(GetAssembly(typeof(BoxSetXmlSaver))); yield return typeof(BoxSetXmlSaver).Assembly;
// Notifications // Notifications
list.Add(GetAssembly(typeof(NotificationManager))); yield return typeof(NotificationManager).Assembly;
// Xbmc // Xbmc
list.Add(GetAssembly(typeof(ArtistNfoProvider))); yield return typeof(ArtistNfoProvider).Assembly;
list.AddRange(GetAssembliesWithPartsInternal().Select(i => new Tuple<Assembly, string>(i, null))); foreach (var i in GetAssembliesWithPartsInternal())
{
return list.ToList(); yield return i;
}
} }
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal(); 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> /// <summary>
/// Gets the system status. /// Gets the system status.
/// </summary> /// </summary>
@ -1718,7 +1456,7 @@ namespace Emby.Server.Implementations
SupportsHttps = SupportsHttps, SupportsHttps = SupportsHttps,
HttpsPortNumber = HttpsPort, HttpsPortNumber = HttpsPort,
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
OperatingSystemDisplayName = OperatingSystemDisplayName, OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName,
CanSelfRestart = CanSelfRestart, CanSelfRestart = CanSelfRestart,
CanSelfUpdate = CanSelfUpdate, CanSelfUpdate = CanSelfUpdate,
CanLaunchWebBrowser = CanLaunchWebBrowser, CanLaunchWebBrowser = CanLaunchWebBrowser,
@ -1788,7 +1526,7 @@ namespace Emby.Server.Implementations
public async Task<string> GetWanApiUrl(CancellationToken cancellationToken) public async Task<string> GetWanApiUrl(CancellationToken cancellationToken)
{ {
var url = "http://ipv4.icanhazip.com"; const string url = "http://ipv4.icanhazip.com";
try try
{ {
using (var response = await HttpClient.Get(new HttpRequestOptions using (var response = await HttpClient.Get(new HttpRequestOptions

View file

@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels namespace Emby.Server.Implementations.Channels
{ {
class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{ {
private readonly IChannelManager _channelManager; private readonly IChannelManager _channelManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;

View file

@ -22,9 +22,10 @@
</ItemGroup> </ItemGroup>
<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="ServiceStack.Text.Core" Version="5.4.0" />
<PackageReference Include="sharpcompress" Version="0.22.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="SQLitePCL.pretty.netstandard" Version="1.0.0" />
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" /> <PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
</ItemGroup> </ItemGroup>

View file

@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints namespace Emby.Server.Implementations.EntryPoints
{ {
class UserDataChangeNotifier : IServerEntryPoint public class UserDataChangeNotifier : IServerEntryPoint
{ {
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly ILogger _logger; private readonly ILogger _logger;

View file

@ -9,7 +9,7 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers namespace Emby.Server.Implementations.Library.Resolvers
{ {
class SpecialFolderResolver : FolderResolver<Folder> public class SpecialFolderResolver : FolderResolver<Folder>
{ {
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;

View file

@ -157,56 +157,56 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null; var nameInExtInf = nameParts.Length > 1 ? nameParts.Last().Trim() : null;
string numberString = null; string numberString = null;
string attributeValue;
double doubleValue;
// Check for channel number with the format from SatIp if (attributes.TryGetValue("tvg-chno", out attributeValue))
// #EXTINF:0,84. VOX Schweiz
// #EXTINF:0,84.0 - VOX Schweiz
if (!string.IsNullOrWhiteSpace(nameInExtInf))
{ {
var numberIndex = nameInExtInf.IndexOf(' '); if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
if (numberIndex > 0)
{ {
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); numberString = attributeValue;
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
{
numberString = numberPart;
}
} }
} }
if (!string.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
if (!IsValidChannelNumber(numberString)) 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)) if (String.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
if (!IsValidChannelNumber(numberString))
{
if (attributes.TryGetValue("channel-id", out string value))
{ {
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)) if (!IsValidChannelNumber(numberString))
@ -214,7 +214,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
numberString = null; numberString = null;
} }
if (string.IsNullOrWhiteSpace(numberString)) if (!string.IsNullOrWhiteSpace(numberString))
{
numberString = numberString.Trim();
}
else
{ {
if (string.IsNullOrWhiteSpace(mediaUrl)) if (string.IsNullOrWhiteSpace(mediaUrl))
{ {

View file

@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
//OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile; //OpenedMediaSource.Path = tempFile;

View file

@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <summary> /// <summary>
/// Class ChapterImagesTask /// Class ChapterImagesTask
/// </summary> /// </summary>
class ChapterImagesTask : IScheduledTask public class ChapterImagesTask : IScheduledTask
{ {
/// <summary> /// <summary>
/// The _logger /// The _logger

View file

@ -15,21 +15,17 @@ namespace Emby.Server.Implementations
/// </summary> /// </summary>
public ServerApplicationPaths( public ServerApplicationPaths(
string programDataPath, string programDataPath,
string appFolderPath, string logDirectoryPath,
string applicationResourcesPath, string configurationDirectoryPath,
string logDirectoryPath = null, string cacheDirectoryPath)
string configurationDirectoryPath = null,
string cacheDirectoryPath = null)
: base(programDataPath, : base(programDataPath,
appFolderPath,
logDirectoryPath, logDirectoryPath,
configurationDirectoryPath, configurationDirectoryPath,
cacheDirectoryPath) cacheDirectoryPath)
{ {
ApplicationResourcesPath = applicationResourcesPath;
} }
public string ApplicationResourcesPath { get; private set; } public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
/// <summary> /// <summary>
/// Gets the path to the base root media directory /// Gets the path to the base root media directory
@ -148,7 +144,6 @@ namespace Emby.Server.Implementations
set => _internalMetadataPath = value; set => _internalMetadataPath = value;
} }
private const string _virtualInternalMetadataPath = "%MetadataPath%"; public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
public string VirtualInternalMetadataPath => _virtualInternalMetadataPath;
} }
} }

View file

@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting namespace Emby.Server.Implementations.Sorting
{ {
class AiredEpisodeOrderComparer : IBaseItemComparer public class AiredEpisodeOrderComparer : IBaseItemComparer
{ {
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

View file

@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting namespace Emby.Server.Implementations.Sorting
{ {
class SeriesSortNameComparer : IBaseItemComparer public class SeriesSortNameComparer : IBaseItemComparer
{ {
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

View file

@ -282,7 +282,7 @@ namespace Jellyfin.Drawing.Skia
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack); var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
// decode // decode
var _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels()); _ = codec.GetPixels(bitmap.Info, bitmap.GetPixels());
origin = codec.EncodedOrigin; origin = codec.EncodedOrigin;

View file

@ -18,15 +18,17 @@ namespace Jellyfin.Server
public override bool CanSelfRestart => StartupOptions.RestartPath != null; public override bool CanSelfRestart => StartupOptions.RestartPath != null;
protected override bool SupportsDualModeSockets => true;
protected override void RestartInternal() => Program.Restart(); protected override void RestartInternal() => Program.Restart();
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal() protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
=> new[] { typeof(CoreAppHost).Assembly }; {
yield return typeof(CoreAppHost).Assembly;
}
protected override void ShutdownInternal() => Program.Shutdown(); protected override void ShutdownInternal() => Program.Shutdown();
protected override bool SupportsDualModeSockets => true;
protected override IHttpListener CreateHttpListener() protected override IHttpListener CreateHttpListener()
=> new WebSocketSharpListener( => new WebSocketSharpListener(
Logger, Logger,
@ -37,7 +39,6 @@ namespace Jellyfin.Server
CryptographyProvider, CryptographyProvider,
SupportsDualModeSockets, SupportsDualModeSockets,
FileSystemManager, FileSystemManager,
EnvironmentInfo EnvironmentInfo);
);
} }
} }

View file

@ -5,11 +5,14 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- We need C# 7.1 for async main--> <!-- We need C# 7.1 for async main-->
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<!-- Disable documentation warnings (for now) -->
<NoWarn>SA1600;CS1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -20,6 +23,10 @@
<EmbeddedResource Include="Resources/Configuration/*" /> <EmbeddedResource Include="Resources/Configuration/*" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code analysers--> <!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" /> <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />

View file

@ -21,6 +21,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Serilog; using Serilog;
using Serilog.AspNetCore; using Serilog.AspNetCore;
@ -56,13 +57,28 @@ namespace Jellyfin.Server
errs => Task.FromResult(0)).ConfigureAwait(false); 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) private static async Task StartApp(StartupOptions options)
{ {
ServerApplicationPaths appPaths = CreateApplicationPaths(options); ServerApplicationPaths appPaths = CreateApplicationPaths(options);
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath); Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
await CreateLogger(appPaths); await CreateLogger(appPaths).ConfigureAwait(false);
_logger = _loggerFactory.CreateLogger("Main"); _logger = _loggerFactory.CreateLogger("Main");
AppDomain.CurrentDomain.UnhandledException += (sender, e) AppDomain.CurrentDomain.UnhandledException += (sender, e)
@ -75,6 +91,7 @@ namespace Jellyfin.Server
{ {
return; // Already shutting down return; // Already shutting down
} }
e.Cancel = true; e.Cancel = true;
_logger.LogInformation("Ctrl+C, shutting down"); _logger.LogInformation("Ctrl+C, shutting down");
Environment.ExitCode = 128 + 2; Environment.ExitCode = 128 + 2;
@ -88,6 +105,7 @@ namespace Jellyfin.Server
{ {
return; // Already shutting down return; // Already shutting down
} }
_logger.LogInformation("Received a SIGTERM signal, shutting down"); _logger.LogInformation("Received a SIGTERM signal, shutting down");
Environment.ExitCode = 128 + 15; Environment.ExitCode = 128 + 15;
Shutdown(); Shutdown();
@ -101,7 +119,7 @@ namespace Jellyfin.Server
SQLitePCL.Batteries_V2.Init(); SQLitePCL.Batteries_V2.Init();
// Allow all https requests // 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); var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, null, appPaths.TempDirectory, true);
@ -114,18 +132,18 @@ namespace Jellyfin.Server
new NullImageEncoder(), new NullImageEncoder(),
new NetworkManager(_loggerFactory, environmentInfo))) new NetworkManager(_loggerFactory, environmentInfo)))
{ {
await appHost.Init(); await appHost.Init(new ServiceCollection()).ConfigureAwait(false);
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
await appHost.RunStartupTasks(); await appHost.RunStartupTasks().ConfigureAwait(false);
// TODO: read input for a stop command // TODO: read input for a stop command
try try
{ {
// Block main thread until shutdown // Block main thread until shutdown
await Task.Delay(-1, _tokenSource.Token); await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false);
} }
catch (TaskCanceledException) 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) private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
{ {
string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH"); // dataDir
if (string.IsNullOrEmpty(programDataPath)) // 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) dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
{
programDataPath = options.DataDir; if (string.IsNullOrEmpty(dataDir))
}
else
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
} }
else else
{ {
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored. // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); dataDir = 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)) // 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)) // configDir
{ // IF --configdir
Console.WriteLine("Cannot continue without path to program data folder (try -programdata)"); // ELSE IF $JELLYFIN_CONFIG_DIR
Environment.Exit(1); // ELSE IF --datadir, use <datadir>/config (assume portable run)
} // ELSE IF <datadir>/config exists, use that
else // ELSE IF windows, use <datadir>/config
{ // ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
Directory.CreateDirectory(programDataPath); // ELSE $HOME/.config/jellyfin
} var configDir = options.ConfigDir;
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir)) if (string.IsNullOrEmpty(configDir))
{ {
if (options.ConfigDir != null) configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{ {
configDir = options.ConfigDir; if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
} {
else // Hang config folder off already set dataDir
{ configDir = Path.Combine(dataDir, "config");
// Let BaseApplicationPaths set up the default value }
configDir = null; 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) // cacheDir
{ // IF --cachedir
Directory.CreateDirectory(configDir); // 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 (string.IsNullOrEmpty(cacheDir))
{ {
if (options.CacheDir != null) cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
if (string.IsNullOrEmpty(cacheDir))
{ {
cacheDir = options.CacheDir; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
}
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))
{ {
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) // logDir
{ // IF --logdir
Directory.CreateDirectory(cacheDir); // 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 (string.IsNullOrEmpty(logDir))
{ {
if (options.LogDir != null) logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
if (string.IsNullOrEmpty(logDir))
{ {
logDir = options.LogDir; // Hang log folder off already set dataDir
} logDir = Path.Combine(dataDir, "log");
else
{
// Let BaseApplicationPaths set up the default value
logDir = null;
} }
} }
if (logDir != null) // Ensure the main folders exist before we continue
try
{ {
Directory.CreateDirectory(dataDir);
Directory.CreateDirectory(logDir); 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(dataDir, logDir, configDir, cacheDir);
return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir);
} }
private static async Task CreateLogger(IApplicationPaths appPaths) private static async Task CreateLogger(IApplicationPaths appPaths)
@ -263,6 +325,7 @@ namespace Jellyfin.Server
await rscstr.CopyToAsync(fstr).ConfigureAwait(false); await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
} }
} }
var configuration = new ConfigurationBuilder() var configuration = new ConfigurationBuilder()
.SetBasePath(appPaths.ConfigurationDirectoryPath) .SetBasePath(appPaths.ConfigurationDirectoryPath)
.AddJsonFile("logging.json") .AddJsonFile("logging.json")
@ -290,7 +353,7 @@ namespace Jellyfin.Server
} }
} }
public static IImageEncoder GetImageEncoder( private static IImageEncoder GetImageEncoder(
IFileSystem fileSystem, IFileSystem fileSystem,
IApplicationPaths appPaths, IApplicationPaths appPaths,
ILocalizationManager localizationManager) ILocalizationManager localizationManager)
@ -331,26 +394,12 @@ namespace Jellyfin.Server
{ {
return MediaBrowser.Model.System.OperatingSystem.BSD; return MediaBrowser.Model.System.OperatingSystem.BSD;
} }
throw new Exception($"Can't resolve OS with description: '{osDescription}'"); 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) private static void StartNewInstance(StartupOptions options)
{ {
_logger.LogInformation("Starting new instance"); _logger.LogInformation("Starting new instance");

View file

@ -13,7 +13,7 @@ namespace Jellyfin.Server.SocketSharp
{ {
internal static string GetParameter(string header, string attr) internal static string GetParameter(string header, string attr)
{ {
int ap = header.IndexOf(attr); int ap = header.IndexOf(attr, StringComparison.Ordinal);
if (ap == -1) if (ap == -1)
{ {
return null; return null;
@ -82,9 +82,7 @@ namespace Jellyfin.Server.SocketSharp
} }
else else
{ {
//
// We use a substream, as in 2.x we will support large uploads streamed to disk, // 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); var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
files[e.Name] = sub; files[e.Name] = sub;
} }
@ -127,8 +125,12 @@ namespace Jellyfin.Server.SocketSharp
public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"]; public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
protected bool validate_cookies, validate_query_string, validate_form; protected bool validate_cookies { get; set; }
protected bool checked_cookies, checked_query_string, checked_form; 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) private static void ThrowValidationException(string name, string key, string value)
{ {
@ -138,8 +140,12 @@ namespace Jellyfin.Server.SocketSharp
v = v.Substring(0, 16) + "...\""; v = v.Substring(0, 16) + "...\"";
} }
string msg = string.Format("A potentially dangerous Request.{0} value was " + string msg = string.Format(
"detected from the client ({1}={2}).", name, key, v); CultureInfo.InvariantCulture,
"A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
name,
key,
v);
throw new Exception(msg); throw new Exception(msg);
} }
@ -179,6 +185,7 @@ namespace Jellyfin.Server.SocketSharp
for (int idx = 1; idx < len; idx++) for (int idx = 1; idx < len; idx++)
{ {
char next = val[idx]; char next = val[idx];
// See http://secunia.com/advisories/14325 // See http://secunia.com/advisories/14325
if (current == '<' || current == '\xff1c') if (current == '<' || current == '\xff1c')
{ {
@ -256,6 +263,7 @@ namespace Jellyfin.Server.SocketSharp
value.Append((char)c); value.Append((char)c);
} }
} }
if (c == -1) if (c == -1)
{ {
AddRawKeyValue(form, key, value); AddRawKeyValue(form, key, value);
@ -271,6 +279,7 @@ namespace Jellyfin.Server.SocketSharp
key.Append((char)c); key.Append((char)c);
} }
} }
if (c == -1) if (c == -1)
{ {
AddRawKeyValue(form, key, value); AddRawKeyValue(form, key, value);
@ -308,6 +317,7 @@ namespace Jellyfin.Server.SocketSharp
result.Append(key); result.Append(key);
result.Append('='); result.Append('=');
} }
result.Append(pair.Value); result.Append(pair.Value);
} }
@ -429,13 +439,13 @@ namespace Jellyfin.Server.SocketSharp
real = position + d; real = position + d;
break; break;
default: default:
throw new ArgumentException(nameof(origin)); throw new ArgumentException("Unknown SeekOrigin value", nameof(origin));
} }
long virt = real - offset; long virt = real - offset;
if (virt < 0 || virt > Length) if (virt < 0 || virt > Length)
{ {
throw new ArgumentException(); throw new ArgumentException("Invalid position", nameof(d));
} }
position = s.Seek(real, SeekOrigin.Begin); position = s.Seek(real, SeekOrigin.Begin);
@ -491,11 +501,6 @@ namespace Jellyfin.Server.SocketSharp
public Stream InputStream => stream; public Stream InputStream => stream;
} }
private class Helpers
{
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
}
internal static class StrUtils internal static class StrUtils
{ {
public static bool StartsWith(string str1, string str2, bool ignore_case) public static bool StartsWith(string str1, string str2, bool ignore_case)
@ -533,12 +538,17 @@ namespace Jellyfin.Server.SocketSharp
public class Element public class Element
{ {
public string ContentType; public string ContentType { get; set; }
public string Name;
public string Filename; public string Name { get; set; }
public Encoding Encoding;
public long Start; public string Filename { get; set; }
public long Length;
public Encoding Encoding { get; set; }
public long Start { get; set; }
public long Length { get; set; }
public override string ToString() public override string ToString()
{ {
@ -547,15 +557,23 @@ namespace Jellyfin.Server.SocketSharp
} }
} }
private Stream data; private const byte LF = (byte)'\n';
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', 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 // See RFC 2046
// In the case of multipart entities, in which one or more different // 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) public HttpMultipart(Stream data, string b, Encoding encoding)
{ {
this.data = data; 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 = b;
boundary_bytes = encoding.GetBytes(b); boundaryBytes = encoding.GetBytes(b);
buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--' buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
this.encoding = encoding; this.encoding = encoding;
sb = new StringBuilder(); 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() private string ReadLine()
{ {
// CRLF or LF are ok as line endings. // CRLF or LF are ok as line endings.
@ -600,6 +648,7 @@ namespace Jellyfin.Server.SocketSharp
{ {
break; break;
} }
got_cr = b == CR; got_cr = b == CR;
sb.Append((char)b); sb.Append((char)b);
} }
@ -769,7 +818,7 @@ namespace Jellyfin.Server.SocketSharp
return -1; return -1;
} }
if (!CompareBytes(boundary_bytes, buffer)) if (!CompareBytes(boundaryBytes, buffer))
{ {
state = 0; state = 0;
data.Position = retval + 2; data.Position = retval + 2;
@ -785,7 +834,7 @@ namespace Jellyfin.Server.SocketSharp
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-') if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
{ {
at_eof = true; atEof = true;
} }
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF) else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
{ {
@ -800,6 +849,7 @@ namespace Jellyfin.Server.SocketSharp
c = data.ReadByte(); c = data.ReadByte();
continue; continue;
} }
data.Position = retval + 2; data.Position = retval + 2;
if (got_cr) if (got_cr)
{ {
@ -818,42 +868,6 @@ namespace Jellyfin.Server.SocketSharp
return retval; 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) private static string StripPath(string path)
{ {
if (path == null || path.Length == 0) if (path == null || path.Length == 0)

View file

@ -24,6 +24,7 @@ namespace Jellyfin.Server.SocketSharp
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(); private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed = false;
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
{ {
@ -40,9 +41,9 @@ namespace Jellyfin.Server.SocketSharp
_logger = logger; _logger = logger;
WebSocket = socket; WebSocket = socket;
socket.OnMessage += socket_OnMessage; socket.OnMessage += OnSocketMessage;
socket.OnClose += socket_OnClose; socket.OnClose += OnSocketClose;
socket.OnError += socket_OnError; socket.OnError += OnSocketError;
WebSocket.ConnectAsServer(); WebSocket.ConnectAsServer();
} }
@ -52,29 +53,22 @@ namespace Jellyfin.Server.SocketSharp
return _taskCompletionSource.Task; 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); _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); _taskCompletionSource.TrySetResult(true);
Closed?.Invoke(this, EventArgs.Empty); 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) if (OnReceiveBytes != null)
{ {
OnReceiveBytes(e.RawData); OnReceiveBytes(e.RawData);
@ -117,6 +111,7 @@ namespace Jellyfin.Server.SocketSharp
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this);
} }
/// <summary> /// <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> /// <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) protected virtual void Dispose(bool dispose)
{ {
if (_disposed)
{
return;
}
if (dispose) if (dispose)
{ {
WebSocket.OnMessage -= socket_OnMessage; WebSocket.OnMessage -= OnSocketMessage;
WebSocket.OnClose -= socket_OnClose; WebSocket.OnClose -= OnSocketClose;
WebSocket.OnError -= socket_OnError; WebSocket.OnError -= OnSocketError;
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
WebSocket.Close(); WebSocket.Close();
} }
_disposed = true;
} }
/// <summary> /// <summary>
@ -142,11 +144,5 @@ namespace Jellyfin.Server.SocketSharp
/// </summary> /// </summary>
/// <value>The receive action.</value> /// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; } 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; }
} }
} }

View file

@ -34,9 +34,16 @@ namespace Jellyfin.Server.SocketSharp
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
private CancellationToken _disposeCancellationToken; private CancellationToken _disposeCancellationToken;
public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IStreamHelper streamHelper, public WebSocketSharpListener(
INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, ILogger logger,
bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment) X509Certificate certificate,
IStreamHelper streamHelper,
INetworkManager networkManager,
ISocketFactory socketFactory,
ICryptoProvider cryptoProvider,
bool enableDualMode,
IFileSystem fileSystem,
IEnvironmentInfo environment)
{ {
_logger = logger; _logger = logger;
_certificate = certificate; _certificate = certificate;
@ -61,7 +68,9 @@ namespace Jellyfin.Server.SocketSharp
public void Start(IEnumerable<string> urlPrefixes) public void Start(IEnumerable<string> urlPrefixes)
{ {
if (_listener == null) if (_listener == null)
{
_listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment); _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment);
}
_listener.EnableDualMode = _enableDualMode; _listener.EnableDualMode = _enableDualMode;
@ -83,15 +92,18 @@ namespace Jellyfin.Server.SocketSharp
private void ProcessContext(HttpListenerContext context) 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) private static void LogRequest(ILogger logger, HttpListenerRequest request)
{ {
var url = request.Url.ToString(); var url = request.Url.ToString();
logger.LogInformation("{0} {1}. UserAgent: {2}", logger.LogInformation(
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty); "{0} {1}. UserAgent: {2}",
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod,
url,
request.UserAgent ?? string.Empty);
} }
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken) private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
@ -201,7 +213,7 @@ namespace Jellyfin.Server.SocketSharp
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
//TODO Investigate and properly fix. // TODO: Investigate and properly fix.
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -223,38 +235,39 @@ namespace Jellyfin.Server.SocketSharp
public Task Stop() public Task Stop()
{ {
_disposeCancellationTokenSource.Cancel(); _disposeCancellationTokenSource.Cancel();
_listener?.Close();
if (_listener != null)
{
_listener.Close();
}
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary>
/// Releases the unmanaged resources and disposes of the managed resources used.
/// </summary>
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this);
} }
private bool _disposed; 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) protected virtual void Dispose(bool disposing)
{ {
if (_disposed) return; if (_disposed)
lock (_disposeLock)
{ {
if (_disposed) return; return;
if (disposing)
{
Stop();
}
//release unmanaged resources here...
_disposed = true;
} }
if (disposing)
{
Stop().GetAwaiter().GetResult();
}
_disposed = true;
} }
} }
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer;
@ -24,7 +25,7 @@ namespace Jellyfin.Server.SocketSharp
this.request = httpContext.Request; this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
//HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
} }
public HttpListenerRequest HttpRequest => request; public HttpListenerRequest HttpRequest => request;
@ -45,9 +46,11 @@ namespace Jellyfin.Server.SocketSharp
public string UserHostAddress => request.UserHostAddress; 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"]; 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) switch (crlf)
{ {
case 0: case 0:
{
if (c == '\r') if (c == '\r')
{ {
crlf = 1; crlf = 1;
@ -97,29 +101,39 @@ namespace Jellyfin.Server.SocketSharp
{ {
throw new ArgumentException("net_WebHeaderInvalidControlChars"); throw new ArgumentException("net_WebHeaderInvalidControlChars");
} }
break; break;
}
case 1: case 1:
{
if (c == '\n') if (c == '\n')
{ {
crlf = 2; crlf = 2;
break; break;
} }
throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
case 2: case 2:
{
if (c == ' ' || c == '\t') if (c == ' ' || c == '\t')
{ {
crlf = 0; crlf = 0;
break; break;
} }
throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
} }
} }
if (crlf != 0) if (crlf != 0)
{ {
throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
} }
return name; return name;
} }
@ -132,6 +146,7 @@ namespace Jellyfin.Server.SocketSharp
return true; return true;
} }
} }
return false; return false;
} }
@ -337,6 +352,7 @@ namespace Jellyfin.Server.SocketSharp
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
this.pathInfo = NormalizePathInfo(pathInfo, mode); this.pathInfo = NormalizePathInfo(pathInfo, mode);
} }
return this.pathInfo; return this.pathInfo;
} }
} }
@ -438,7 +454,7 @@ namespace Jellyfin.Server.SocketSharp
public string ContentType => request.ContentType; public string ContentType => request.ContentType;
public Encoding contentEncoding; private Encoding contentEncoding;
public Encoding ContentEncoding public Encoding ContentEncoding
{ {
get => contentEncoding ?? request.ContentEncoding; get => contentEncoding ?? request.ContentEncoding;
@ -496,6 +512,7 @@ namespace Jellyfin.Server.SocketSharp
i++; i++;
} }
} }
return httpFiles; return httpFiles;
} }
} }

View file

@ -13,12 +13,12 @@ using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
using IRequest = MediaBrowser.Model.Services.IRequest; using IRequest = MediaBrowser.Model.Services.IRequest;
namespace Jellyfin.Server.SocketSharp namespace Jellyfin.Server.SocketSharp
{ {
public class WebSocketSharpResponse : IHttpResponse public class WebSocketSharpResponse : IHttpResponse
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly HttpListenerResponse _response; private readonly HttpListenerResponse _response;
public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request) public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request)
@ -30,7 +30,9 @@ namespace Jellyfin.Server.SocketSharp
} }
public IRequest Request { get; private set; } public IRequest Request { get; private set; }
public Dictionary<string, object> Items { get; private set; } public Dictionary<string, object> Items { get; private set; }
public object OriginalResponse => _response; public object OriginalResponse => _response;
public int StatusCode public int StatusCode
@ -51,7 +53,7 @@ namespace Jellyfin.Server.SocketSharp
set => _response.ContentType = value; set => _response.ContentType = value;
} }
//public ICookies Cookies { get; set; } public QueryParamCollection Headers => _response.Headers;
public void AddHeader(string name, string value) public void AddHeader(string name, string value)
{ {
@ -64,8 +66,6 @@ namespace Jellyfin.Server.SocketSharp
_response.AddHeader(name, value); _response.AddHeader(name, value);
} }
public QueryParamCollection Headers => _response.Headers;
public string GetHeader(string name) public string GetHeader(string name)
{ {
return _response.Headers[name]; return _response.Headers[name];
@ -114,9 +114,9 @@ namespace Jellyfin.Server.SocketSharp
public void SetContentLength(long contentLength) public void SetContentLength(long contentLength)
{ {
//you can happily set the Content-Length header in Asp.Net // 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. // 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 // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
_response.ContentLength64 = contentLength; _response.ContentLength64 = contentLength;
} }
@ -147,15 +147,12 @@ namespace Jellyfin.Server.SocketSharp
{ {
sb.Append($";domain={cookie.Domain}"); sb.Append($";domain={cookie.Domain}");
} }
//else if (restrictAllCookiesToDomain != null)
//{
// sb.Append($";domain={restrictAllCookiesToDomain}");
//}
if (cookie.Secure) if (cookie.Secure)
{ {
sb.Append(";Secure"); sb.Append(";Secure");
} }
if (cookie.HttpOnly) if (cookie.HttpOnly)
{ {
sb.Append(";HttpOnly"); sb.Append(";HttpOnly");
@ -164,7 +161,6 @@ namespace Jellyfin.Server.SocketSharp
return sb.ToString(); return sb.ToString();
} }
public bool SendChunked public bool SendChunked
{ {
get => _response.SendChunked; get => _response.SendChunked;

View file

@ -11,7 +11,7 @@ namespace MediaBrowser.Api.Session
/// <summary> /// <summary>
/// Class SessionInfoWebSocketListener /// Class SessionInfoWebSocketListener
/// </summary> /// </summary>
class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState> public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>
{ {
/// <summary> /// <summary>
/// Gets the name. /// Gets the name.

View file

@ -11,7 +11,7 @@ namespace MediaBrowser.Api.System
/// <summary> /// <summary>
/// Class SessionInfoWebSocketListener /// Class SessionInfoWebSocketListener
/// </summary> /// </summary>
class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState> public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState>
{ {
/// <summary> /// <summary>
/// Gets the name. /// Gets the name.

View file

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Updates; using MediaBrowser.Model.Updates;
using Microsoft.Extensions.DependencyInjection;
namespace MediaBrowser.Common namespace MediaBrowser.Common
{ {
@ -13,12 +14,6 @@ namespace MediaBrowser.Common
/// </summary> /// </summary>
public interface IApplicationHost 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> /// <summary>
/// Gets the name. /// Gets the name.
/// </summary> /// </summary>
@ -104,13 +99,6 @@ namespace MediaBrowser.Common
/// <returns>``0.</returns> /// <returns>``0.</returns>
T Resolve<T>(); T Resolve<T>();
/// <summary>
/// Resolves this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
T TryResolve<T>();
/// <summary> /// <summary>
/// Shuts down. /// Shuts down.
/// </summary> /// </summary>
@ -131,7 +119,7 @@ namespace MediaBrowser.Common
/// <summary> /// <summary>
/// Inits this instance. /// Inits this instance.
/// </summary> /// </summary>
Task Init(); Task Init(IServiceCollection serviceCollection);
/// <summary> /// <summary>
/// Creates the instance. /// Creates the instance.

View file

@ -11,6 +11,10 @@
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\SharedVersion.cs" /> <Compile Include="..\SharedVersion.cs" />
</ItemGroup> </ItemGroup>

View file

@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Providers namespace MediaBrowser.LocalMetadata.Providers
{ {
class PlaylistXmlProvider : BaseXmlProvider<Playlist> public class PlaylistXmlProvider : BaseXmlProvider<Playlist>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;

View file

@ -24,12 +24,12 @@ namespace MediaBrowser.Model.System
/// Gets or sets the server version. /// Gets or sets the server version.
/// </summary> /// </summary>
/// <value>The version.</value> /// <value>The version.</value>
public string Version { get; set; } public string Version { get; set; }
/// <summary> /// <summary>
/// Gets or sets the operating sytem. /// Gets or sets the operating system.
/// </summary> /// </summary>
/// <value>The operating sytem.</value> /// <value>The operating system.</value>
public string OperatingSystem { get; set; } public string OperatingSystem { get; set; }
/// <summary> /// <summary>

View file

@ -1,3 +1,4 @@
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MediaBrowser.Model.Updates; using MediaBrowser.Model.Updates;
@ -136,7 +137,7 @@ namespace MediaBrowser.Model.System
/// </summary> /// </summary>
public SystemInfo() public SystemInfo()
{ {
CompletedInstallations = new InstallationInfo[] { }; CompletedInstallations = Array.Empty<InstallationInfo>();
} }
} }
} }

View file

@ -14,7 +14,7 @@ using MediaBrowser.Providers.Movies;
namespace MediaBrowser.Providers.BoxSets namespace MediaBrowser.Providers.BoxSets
{ {
class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder public class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;

View file

@ -16,7 +16,7 @@ using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Movies namespace MediaBrowser.Providers.Movies
{ {
class MovieDbImageProvider : IRemoteImageProvider, IHasOrder public class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
{ {
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;

View file

@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Music 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) protected override void MergeData(MetadataResult<MusicVideo> source, MetadataResult<MusicVideo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{ {

View file

@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Photos 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) protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{ {

View file

@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Photos 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) protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{ {

View file

@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Playlists namespace MediaBrowser.Providers.Playlists
{ {
class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo> public class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo>
{ {
protected override IList<BaseItem> GetChildrenForMetadataUpdates(Playlist item) protected override IList<BaseItem> GetChildrenForMetadataUpdates(Playlist item)
{ {

View file

@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV.Omdb namespace MediaBrowser.Providers.TV.Omdb
{ {
class OmdbEpisodeProvider : public class OmdbEpisodeProvider :
IRemoteMetadataProvider<Episode, EpisodeInfo>, IRemoteMetadataProvider<Episode, EpisodeInfo>,
IHasOrder IHasOrder
{ {

View file

@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV.TheMovieDb namespace MediaBrowser.Providers.TV.TheMovieDb
{ {
class MovieDbEpisodeProvider : public class MovieDbEpisodeProvider :
MovieDbProviderBase, MovieDbProviderBase,
IRemoteMetadataProvider<Episode, EpisodeInfo>, IRemoteMetadataProvider<Episode, EpisodeInfo>,
IHasOrder IHasOrder

@ -1 +1 @@
Subproject commit 094c1deae91c51b8bbf8ebb16a55758af110f04d Subproject commit c7ce1ac8eccd50f1bd759b30fbe60ea797ffe86e

View file

@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Parsers namespace MediaBrowser.XbmcMetadata.Parsers
{ {
class MovieNfoParser : BaseNfoParser<Video> public class MovieNfoParser : BaseNfoParser<Video>
{ {
protected override bool SupportsUrlAfterClosingXmlTag => true; protected override bool SupportsUrlAfterClosingXmlTag => true;

View file

@ -3,11 +3,19 @@
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers"> <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<!-- disable warning SA1101: Prefix local calls with 'this.' --> <!-- disable warning SA1101: Prefix local calls with 'this.' -->
<Rule Id="SA1101" Action="None" /> <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 --> <!-- disable warning SA1200: 'using' directive must appear within a namespace declaration -->
<Rule Id="SA1200" Action="None" /> <Rule Id="SA1200" Action="None" />
<!-- disable warning SA1309: Fields must not begin with an underscore --> <!-- disable warning SA1309: Fields must not begin with an underscore -->
<Rule Id="SA1309" Action="None" /> <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 --> <!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
<Rule Id="SA1633" Action="None" /> <Rule Id="SA1633" Action="None" />
</Rules> </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> </RuleSet>