diff options
| author | Bond-009 <bond.009@outlook.com> | 2019-12-06 12:06:13 +0100 |
|---|---|---|
| committer | Bond-009 <bond.009@outlook.com> | 2019-12-06 12:06:13 +0100 |
| commit | a2c35e6dba02f068a3f06e5a4e4964e6539069d1 (patch) | |
| tree | e75984ab85fedceaf96150ad9d5241cf88230a60 /Emby.Server.Implementations | |
| parent | 94edb5b9f98cf3b06144255eccc988712332f0a8 (diff) | |
| parent | 935525e77a18061195dea786be71d38fffe82a10 (diff) | |
Merge remote-tracking branch 'upstream/master' into random
Diffstat (limited to 'Emby.Server.Implementations')
86 files changed, 1064 insertions, 1355 deletions
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 1514402d6..efaaa116c 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -616,8 +616,8 @@ namespace Emby.Server.Implementations.Activity /// <summary> /// Constructs a string description of a time-span value. /// </summary> - /// <param name="value">The value of this item</param> - /// <param name="description">The name of this item (singular form)</param> + /// <param name="value">The value of this item.</param> + /// <param name="description">The name of this item (singular form).</param> private static string CreateValueString(int value, string description) { return string.Format( diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 4832c19c4..2a5d56c60 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Threading; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; @@ -16,7 +15,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.AppBase { /// <summary> - /// Class BaseConfigurationManager + /// Class BaseConfigurationManager. /// </summary> public abstract class BaseConfigurationManager : IConfigurationManager { @@ -35,7 +34,7 @@ namespace Emby.Server.Implementations.AppBase /// <summary> /// The _configuration sync lock. /// </summary> - private object _configurationSyncLock = new object(); + private readonly object _configurationSyncLock = new object(); /// <summary> /// The _configuration. @@ -48,7 +47,7 @@ namespace Emby.Server.Implementations.AppBase /// <param name="applicationPaths">The application paths.</param> /// <param name="loggerFactory">The logger factory.</param> /// <param name="xmlSerializer">The XML serializer.</param> - /// <param name="fileSystem">The file system</param> + /// <param name="fileSystem">The file system.</param> protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem) { CommonApplicationPaths = applicationPaths; @@ -85,6 +84,7 @@ namespace Emby.Server.Implementations.AppBase /// </summary> /// <value>The logger.</value> protected ILogger Logger { get; private set; } + /// <summary> /// Gets the XML serializer. /// </summary> @@ -92,23 +92,39 @@ namespace Emby.Server.Implementations.AppBase protected IXmlSerializer XmlSerializer { get; private set; } /// <summary> - /// Gets or sets the application paths. + /// Gets the application paths. /// </summary> /// <value>The application paths.</value> public IApplicationPaths CommonApplicationPaths { get; private set; } /// <summary> - /// Gets the system configuration + /// Gets or sets the system configuration. /// </summary> /// <value>The configuration.</value> public BaseApplicationConfiguration CommonConfiguration { get { - // Lazy load - LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer)); - return _configuration; + if (_configurationLoaded) + { + return _configuration; + } + + lock (_configurationSyncLock) + { + if (_configurationLoaded) + { + return _configuration; + } + + _configuration = (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer); + + _configurationLoaded = true; + + return _configuration; + } } + protected set { _configuration = value; @@ -158,7 +174,7 @@ namespace Emby.Server.Implementations.AppBase /// Replaces the configuration. /// </summary> /// <param name="newConfiguration">The new configuration.</param> - /// <exception cref="ArgumentNullException">newConfiguration</exception> + /// <exception cref="ArgumentNullException"><c>newConfiguration</c> is <c>null</c>.</exception> public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration) { if (newConfiguration == null) @@ -201,7 +217,7 @@ namespace Emby.Server.Implementations.AppBase cachePath = CommonConfiguration.CachePath; } - Logger.LogInformation("Setting cache path to " + cachePath); + Logger.LogInformation("Setting cache path: {Path}", cachePath); ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath; } @@ -209,7 +225,7 @@ namespace Emby.Server.Implementations.AppBase /// Replaces the cache path. /// </summary> /// <param name="newConfig">The new configuration.</param> - /// <exception cref="DirectoryNotFoundException"></exception> + /// <exception cref="DirectoryNotFoundException">The new cache path doesn't exist.</exception> private void ValidateCachePath(BaseApplicationConfiguration newConfig) { var newPath = newConfig.CachePath; @@ -220,7 +236,7 @@ namespace Emby.Server.Implementations.AppBase // Validate if (!Directory.Exists(newPath)) { - throw new FileNotFoundException( + throw new DirectoryNotFoundException( string.Format( CultureInfo.InvariantCulture, "{0} does not exist.", @@ -299,8 +315,7 @@ namespace Emby.Server.Implementations.AppBase throw new ArgumentException("Expected configuration type is " + configurationType.Name); } - var validatingStore = configurationStore as IValidatingConfiguration; - if (validatingStore != null) + if (configurationStore is IValidatingConfiguration validatingStore) { var currentConfiguration = GetConfiguration(key); diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 90b97061f..854d7b4cb 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -6,13 +6,13 @@ using MediaBrowser.Model.Serialization; namespace Emby.Server.Implementations.AppBase { /// <summary> - /// Class ConfigurationHelper + /// Class ConfigurationHelper. /// </summary> public static class ConfigurationHelper { /// <summary> /// Reads an xml configuration file from the file system - /// It will immediately re-serialize and save if new serialization data is available due to property changes + /// It will immediately re-serialize and save if new serialization data is available due to property changes. /// </summary> /// <param name="type">The type.</param> /// <param name="path">The path.</param> diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 04904fc4a..8c625539a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -88,7 +88,6 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; @@ -110,9 +109,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; -using ServiceStack; +using Microsoft.OpenApi.Models; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations @@ -232,7 +230,25 @@ namespace Emby.Server.Implementations } } - protected IServiceProvider _serviceProvider; + /// <summary> + /// Gets or sets the service provider. + /// </summary> + public IServiceProvider ServiceProvider { get; set; } + + /// <summary> + /// Gets the http port for the webhost. + /// </summary> + public int HttpPort { get; private set; } + + /// <summary> + /// Gets the https port for the webhost. + /// </summary> + public int HttpsPort { get; private set; } + + /// <summary> + /// Gets the content root for the webhost. + /// </summary> + public string ContentRoot { get; private set; } /// <summary> /// Gets the server configuration manager. @@ -321,7 +337,7 @@ namespace Emby.Server.Implementations private readonly IConfiguration _configuration; /// <summary> - /// Gets or sets the installation manager. + /// Gets the installation manager. /// </summary> /// <value>The installation manager.</value> protected IInstallationManager InstallationManager { get; private set; } @@ -362,7 +378,7 @@ namespace Emby.Server.Implementations { _configuration = configuration; - XmlSerializer = new MyXmlSerializer(fileSystem, loggerFactory); + XmlSerializer = new MyXmlSerializer(); NetworkManager = networkManager; networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets; @@ -410,13 +426,17 @@ namespace Emby.Server.Implementations _validAddressResults.Clear(); } - public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); + /// <inheritdoc /> + public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version; + + /// <inheritdoc /> + public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); /// <summary> /// Gets the current application user agent. /// </summary> /// <value>The application user agent.</value> - public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion; + public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString; /// <summary> /// Gets the email address for use within a comment section of a user agent field. @@ -452,20 +472,20 @@ namespace Emby.Server.Implementations public string Name => ApplicationProductName; /// <summary> - /// Creates an instance of type and resolves all constructor dependencies + /// Creates an instance of type and resolves all constructor dependencies. /// </summary> /// <param name="type">The type.</param> /// <returns>System.Object.</returns> public object CreateInstance(Type type) - => ActivatorUtilities.CreateInstance(_serviceProvider, type); + => ActivatorUtilities.CreateInstance(ServiceProvider, type); /// <summary> - /// Creates an instance of type and resolves all constructor dependencies + /// Creates an instance of type and resolves all constructor dependencies. /// </summary> /// /// <typeparam name="T">The type.</typeparam> /// <returns>T.</returns> public T CreateInstance<T>() - => ActivatorUtilities.CreateInstance<T>(_serviceProvider); + => ActivatorUtilities.CreateInstance<T>(ServiceProvider); /// <summary> /// Creates the instance safe. @@ -477,7 +497,7 @@ namespace Emby.Server.Implementations try { Logger.LogDebug("Creating instance of {Type}", type); - return ActivatorUtilities.CreateInstance(_serviceProvider, type); + return ActivatorUtilities.CreateInstance(ServiceProvider, type); } catch (Exception ex) { @@ -491,12 +511,12 @@ namespace Emby.Server.Implementations /// </summary> /// <typeparam name="T">The type</typeparam> /// <returns>``0.</returns> - public T Resolve<T>() => _serviceProvider.GetService<T>(); + public T Resolve<T>() => ServiceProvider.GetService<T>(); /// <summary> /// Gets the export types. /// </summary> - /// <typeparam name="T">The type</typeparam> + /// <typeparam name="T">The type.</typeparam> /// <returns>IEnumerable{Type}.</returns> public IEnumerable<Type> GetExportTypes<T>() { @@ -508,11 +528,12 @@ namespace Emby.Server.Implementations /// <inheritdoc /> public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true) { + // Convert to list so this isn't executed for each iteration var parts = GetExportTypes<T>() .Select(CreateInstanceSafe) .Where(i => i != null) .Cast<T>() - .ToList(); // Convert to list so this isn't executed for each iteration + .ToList(); if (manageLifetime) { @@ -607,77 +628,14 @@ namespace Emby.Server.Implementations await RegisterResources(serviceCollection).ConfigureAwait(false); - FindParts(); - - string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; - if (string.IsNullOrEmpty(contentRoot)) + ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; + if (string.IsNullOrEmpty(ContentRoot)) { - contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; - } - - var host = new WebHostBuilder() - .UseKestrel(options => - { - var addresses = ServerConfigurationManager - .Configuration - .LocalNetworkAddresses - .Select(NormalizeConfiguredLocalAddress) - .Where(i => i != null) - .ToList(); - if (addresses.Any()) - { - foreach (var address in addresses) - { - Logger.LogInformation("Kestrel listening on {ipaddr}", address); - options.Listen(address, HttpPort); - - if (EnableHttps && Certificate != null) - { - options.Listen(address, HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - } - else - { - Logger.LogInformation("Kestrel listening on all interfaces"); - options.ListenAnyIP(HttpPort); - - if (EnableHttps && Certificate != null) - { - options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - }) - .UseContentRoot(contentRoot) - .ConfigureServices(services => - { - services.AddResponseCompression(); - services.AddHttpContextAccessor(); - }) - .Configure(app => - { - app.UseWebSockets(); - - app.UseResponseCompression(); - - // TODO app.UseMiddleware<WebSocketMiddleware>(); - app.Use(ExecuteWebsocketHandlerAsync); - app.Use(ExecuteHttpHandlerAsync); - }) - .Build(); - - try - { - await host.StartAsync().ConfigureAwait(false); - } - catch - { - Logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); - throw; + ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; } } - private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next) + public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next) { if (!context.WebSockets.IsWebSocketRequest) { @@ -688,7 +646,7 @@ namespace Emby.Server.Implementations await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } - private async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next) + public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next) { if (context.WebSockets.IsWebSocketRequest) { @@ -749,7 +707,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper)); - serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider)); + var cryptoProvider = new CryptographyProvider(); + serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider); SocketFactory = new SocketFactory(); serviceCollection.AddSingleton(SocketFactory); @@ -788,7 +747,17 @@ namespace Emby.Server.Implementations _userRepository = GetUserRepository(); - UserManager = new UserManager(LoggerFactory.CreateLogger<UserManager>(), _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager); + UserManager = new UserManager( + LoggerFactory.CreateLogger<UserManager>(), + _userRepository, + XmlSerializer, + NetworkManager, + () => ImageProcessor, + () => DtoService, + this, + JsonSerializer, + FileSystemManager, + cryptoProvider); serviceCollection.AddSingleton(UserManager); @@ -866,8 +835,7 @@ namespace Emby.Server.Implementations NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); serviceCollection.AddSingleton(NotificationManager); - serviceCollection.AddSingleton<IDeviceDiscovery>( - new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); + serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager)); ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); @@ -896,7 +864,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<IAuthorizationContext>(authContext); serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager)); - AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager); + AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager); serviceCollection.AddSingleton(AuthService); SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); @@ -906,7 +874,7 @@ namespace Emby.Server.Implementations _displayPreferencesRepository.Initialize(); - var userDataRepo = new SqliteUserDataRepository(LoggerFactory, ApplicationPaths); + var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger<SqliteUserDataRepository>(), ApplicationPaths); SetStaticProperties(); @@ -915,8 +883,6 @@ namespace Emby.Server.Implementations ((UserDataManager)UserDataManager).Repository = userDataRepo; ItemRepository.Initialize(userDataRepo, UserManager); ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; - - _serviceProvider = serviceCollection.BuildServiceProvider(); } public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) @@ -1007,7 +973,7 @@ namespace Emby.Server.Implementations } /// <summary> - /// Dirty hacks + /// Dirty hacks. /// </summary> private void SetStaticProperties() { @@ -1073,9 +1039,9 @@ namespace Emby.Server.Implementations /// <summary> /// Finds the parts. /// </summary> - protected void FindParts() + public void FindParts() { - InstallationManager = _serviceProvider.GetService<IInstallationManager>(); + InstallationManager = ServiceProvider.GetService<IInstallationManager>(); InstallationManager.PluginInstalled += PluginInstalled; if (!ServerConfigurationManager.Configuration.IsPortAuthorized) @@ -1204,7 +1170,7 @@ namespace Emby.Server.Implementations private CertificateInfo CertificateInfo { get; set; } - protected X509Certificate2 Certificate { get; private set; } + public X509Certificate2 Certificate { get; private set; } private IEnumerable<string> GetUrlPrefixes() { @@ -1415,17 +1381,18 @@ namespace Emby.Server.Implementations /// <summary> /// Gets the system status. /// </summary> - /// <param name="cancellationToken">The cancellation token</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns>SystemInfo.</returns> public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + var transcodingTempPath = ConfigurationManager.GetTranscodePath(); return new SystemInfo { HasPendingRestart = HasPendingRestart, IsShuttingDown = IsShuttingDown, - Version = ApplicationVersion, + Version = ApplicationVersionString, WebSocketPortNumber = HttpPort, CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(), Id = SystemId, @@ -1443,7 +1410,7 @@ namespace Emby.Server.Implementations CanSelfRestart = CanSelfRestart, CanLaunchWebBrowser = CanLaunchWebBrowser, HasUpdateAvailable = HasUpdateAvailable, - TranscodingTempPath = ApplicationPaths.TranscodingTempPath, + TranscodingTempPath = transcodingTempPath, ServerName = FriendlyName, LocalAddress = localAddress, SupportsLibraryMonitor = true, @@ -1465,7 +1432,7 @@ namespace Emby.Server.Implementations return new PublicSystemInfo { - Version = ApplicationVersion, + Version = ApplicationVersionString, ProductName = ApplicationProductName, Id = SystemId, OperatingSystem = OperatingSystem.Id.ToString(), @@ -1588,7 +1555,7 @@ namespace Emby.Server.Implementations return resultList; } - private IPAddress NormalizeConfiguredLocalAddress(string address) + public IPAddress NormalizeConfiguredLocalAddress(string address) { var index = address.Trim('/').IndexOf('/'); @@ -1664,10 +1631,6 @@ namespace Emby.Server.Implementations ? Environment.MachineName : ServerConfigurationManager.Configuration.ServerName; - public int HttpPort { get; private set; } - - public int HttpsPort { get; private set; } - /// <summary> /// Shuts down. /// </summary> @@ -1730,7 +1693,7 @@ namespace Emby.Server.Implementations /// dns is prefixed with a valid Uri prefix. /// </summary> /// <param name="externalDns">The external dns prefix to get the hostname of.</param> - /// <returns>The hostname in <paramref name="externalDns"/></returns> + /// <returns>The hostname in <paramref name="externalDns"/>.</returns> private static string GetHostnameFromExternalDns(string externalDns) { if (string.IsNullOrEmpty(externalDns)) @@ -1844,6 +1807,7 @@ namespace Emby.Server.Implementations internal class CertificateInfo { public string Path { get; set; } + public string Password { get; set; } } } diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 54a027dac..8e955f1b0 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -470,10 +470,10 @@ namespace Emby.Server.Implementations.Channels _libraryManager.CreateItem(item, null); } - await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = !isNew && forceUpdate - }, cancellationToken); + }, cancellationToken).ConfigureAwait(false); return item; } @@ -636,7 +636,7 @@ namespace Emby.Server.Implementations.Channels private async Task RefreshLatestChannelItems(IChannel channel, CancellationToken cancellationToken) { - var internalChannel = await GetChannel(channel, cancellationToken); + var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false); var query = new InternalItemsQuery(); query.Parent = internalChannel; @@ -1156,7 +1156,7 @@ namespace Emby.Server.Implementations.Channels if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime)) { - _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal); + _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal); } return item; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index bb5057b1c..c5a77ce5b 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections // This could cause it to get re-resolved as a plain folder var folderName = _fileSystem.GetValidFilename(name) + " [boxset]"; - var parentFolder = GetCollectionsFolder(true).Result; + var parentFolder = GetCollectionsFolder(true).GetAwaiter().GetResult(); if (parentFolder == null) { @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Collections if (options.ItemIdList.Length > 0) { - AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { // The initial adding of items is going to create a local metadata file // This will cause internet metadata to be skipped as a result @@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Collections } else { - _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.High); + _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); } CollectionCreated?.Invoke(this, new CollectionCreatedEventArgs @@ -178,12 +178,12 @@ namespace Emby.Server.Implementations.Collections public void AddToCollection(Guid collectionId, IEnumerable<string> ids) { - AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))); + AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem))); } public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids) { - AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))); + AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem))); } private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions) @@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.Collections } collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, RefreshPriority.High); diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index c7f92b80b..2291345be 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using Emby.Server.Implementations.AppBase; using MediaBrowser.Common.Configuration; @@ -14,7 +13,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Configuration { /// <summary> - /// Class ServerConfigurationManager + /// Class ServerConfigurationManager. /// </summary> public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager { @@ -62,13 +61,6 @@ namespace Emby.Server.Implementations.Configuration base.OnConfigurationUpdated(); } - public override void AddParts(IEnumerable<IConfigurationFactory> factories) - { - base.AddParts(factories); - - UpdateTranscodePath(); - } - /// <summary> /// Updates the metadata path. /// </summary> @@ -85,28 +77,6 @@ namespace Emby.Server.Implementations.Configuration } /// <summary> - /// Updates the transcoding temporary path. - /// </summary> - private void UpdateTranscodePath() - { - var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding"); - - ((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ? - null : - Path.Combine(encodingConfig.TranscodingTempPath, "transcodes"); - } - - protected override void OnNamedConfigurationUpdated(string key, object configuration) - { - base.OnNamedConfigurationUpdated(key, configuration); - - if (string.Equals(key, "encoding", StringComparison.OrdinalIgnoreCase)) - { - UpdateTranscodePath(); - } - } - - /// <summary> /// Replaces the configuration. /// </summary> /// <param name="newConfiguration">The new configuration.</param> @@ -123,7 +93,6 @@ namespace Emby.Server.Implementations.Configuration base.ReplaceConfiguration(newConfiguration); } - /// <summary> /// Validates the SSL certificate. /// </summary> diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 23b77e268..fec7d161e 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -30,6 +30,9 @@ namespace Emby.Server.Implementations.Cryptography private bool _disposed = false; + /// <summary> + /// Initializes a new instance of the <see cref="CryptographyProvider"/> class. + /// </summary> public CryptographyProvider() { // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto @@ -59,12 +62,6 @@ namespace Emby.Server.Implementations.Cryptography throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}"); } - public byte[] ComputeHash(string hashMethod, byte[] bytes) - => ComputeHash(hashMethod, bytes, Array.Empty<byte>()); - - public byte[] ComputeHashWithDefaultMethod(byte[] bytes) - => ComputeHash(DefaultHashMethod, bytes); - public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) { if (hashMethod == DefaultHashMethod) @@ -90,7 +87,6 @@ namespace Emby.Server.Implementations.Cryptography } throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); - } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 2cd4d65b3..2f6c1288d 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -110,8 +110,8 @@ namespace Emby.Server.Implementations.Data using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)")) { - statement.TryBind("@id", displayPreferences.Id.ToGuidBlob()); - statement.TryBind("@userId", userId.ToGuidBlob()); + statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray()); + statement.TryBind("@userId", userId.ToByteArray()); statement.TryBind("@client", client); statement.TryBind("@data", serialized); @@ -170,8 +170,8 @@ namespace Emby.Server.Implementations.Data { using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client")) { - statement.TryBind("@id", guidId.ToGuidBlob()); - statement.TryBind("@userId", userId.ToGuidBlob()); + statement.TryBind("@id", guidId.ToByteArray()); + statement.TryBind("@userId", userId.ToByteArray()); statement.TryBind("@client", client); foreach (var row in statement.ExecuteQuery()) @@ -200,7 +200,7 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection(true)) using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId")) { - statement.TryBind("@userId", userId.ToGuidBlob()); + statement.TryBind("@userId", userId.ToByteArray()); foreach (var row in statement.ExecuteQuery()) { diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index e7c394b54..c76ae0cac 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -9,6 +9,47 @@ namespace Emby.Server.Implementations.Data { public static class SqliteExtensions { + private const string DatetimeFormatUtc = "yyyy-MM-dd HH:mm:ss.FFFFFFFK"; + private const string DatetimeFormatLocal = "yyyy-MM-dd HH:mm:ss.FFFFFFF"; + + /// <summary> + /// An array of ISO-8601 DateTime formats that we support parsing. + /// </summary> + private static readonly string[] _datetimeFormats = new string[] + { + "THHmmssK", + "THHmmK", + "HH:mm:ss.FFFFFFFK", + "HH:mm:ssK", + "HH:mmK", + DatetimeFormatUtc, + "yyyy-MM-dd HH:mm:ssK", + "yyyy-MM-dd HH:mmK", + "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", + "yyyy-MM-ddTHH:mmK", + "yyyy-MM-ddTHH:mm:ssK", + "yyyyMMddHHmmssK", + "yyyyMMddHHmmK", + "yyyyMMddTHHmmssFFFFFFFK", + "THHmmss", + "THHmm", + "HH:mm:ss.FFFFFFF", + "HH:mm:ss", + "HH:mm", + DatetimeFormatLocal, + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm", + "yyyy-MM-ddTHH:mm:ss.FFFFFFF", + "yyyy-MM-ddTHH:mm", + "yyyy-MM-ddTHH:mm:ss", + "yyyyMMddHHmmss", + "yyyyMMddHHmm", + "yyyyMMddTHHmmssFFFFFFF", + "yyyy-MM-dd", + "yyyyMMdd", + "yy-MM-dd" + }; + public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries) { if (queries == null) @@ -22,16 +63,6 @@ namespace Emby.Server.Implementations.Data }); } - public static byte[] ToGuidBlob(this string str) - { - return ToGuidBlob(new Guid(str)); - } - - public static byte[] ToGuidBlob(this Guid guid) - { - return guid.ToByteArray(); - } - public static Guid ReadGuidFromBlob(this IResultSetValue result) { return new Guid(result.ToBlob()); @@ -50,58 +81,16 @@ namespace Emby.Server.Implementations.Data CultureInfo.InvariantCulture); } - private static string GetDateTimeKindFormat( - DateTimeKind kind) - { - return (kind == DateTimeKind.Utc) ? _datetimeFormatUtc : _datetimeFormatLocal; - } - - /// <summary> - /// An array of ISO-8601 DateTime formats that we support parsing. - /// </summary> - private static string[] _datetimeFormats = new string[] { - "THHmmssK", - "THHmmK", - "HH:mm:ss.FFFFFFFK", - "HH:mm:ssK", - "HH:mmK", - "yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (5). */ - "yyyy-MM-dd HH:mm:ssK", - "yyyy-MM-dd HH:mmK", - "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", - "yyyy-MM-ddTHH:mmK", - "yyyy-MM-ddTHH:mm:ssK", - "yyyyMMddHHmmssK", - "yyyyMMddHHmmK", - "yyyyMMddTHHmmssFFFFFFFK", - "THHmmss", - "THHmm", - "HH:mm:ss.FFFFFFF", - "HH:mm:ss", - "HH:mm", - "yyyy-MM-dd HH:mm:ss.FFFFFFF", /* NOTE: Non-UTC default (19). */ - "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mm", - "yyyy-MM-ddTHH:mm:ss.FFFFFFF", - "yyyy-MM-ddTHH:mm", - "yyyy-MM-ddTHH:mm:ss", - "yyyyMMddHHmmss", - "yyyyMMddHHmm", - "yyyyMMddTHHmmssFFFFFFF", - "yyyy-MM-dd", - "yyyyMMdd", - "yy-MM-dd" - }; - - private static string _datetimeFormatUtc = _datetimeFormats[5]; - private static string _datetimeFormatLocal = _datetimeFormats[19]; + private static string GetDateTimeKindFormat(DateTimeKind kind) + => (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal; public static DateTime ReadDateTime(this IResultSetValue result) { var dateText = result.ToString(); return DateTime.ParseExact( - dateText, _datetimeFormats, + dateText, + _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime(); } @@ -139,7 +128,10 @@ namespace Emby.Server.Implementations.Data public static void Attach(SQLiteDatabaseConnection db, string path, string alias) { - var commandText = string.Format("attach @path as {0};", alias); + var commandText = string.Format( + CultureInfo.InvariantCulture, + "attach @path as {0};", + alias); using (var statement = db.PrepareStatement(commandText)) { @@ -186,10 +178,7 @@ namespace Emby.Server.Implementations.Data private static void CheckName(string name) { #if DEBUG - //if (!name.IndexOf("@", StringComparison.OrdinalIgnoreCase) != 0) - { - throw new Exception("Invalid param name: " + name); - } + throw new ArgumentException("Invalid param name: " + name, nameof(name)); #endif } @@ -264,7 +253,7 @@ namespace Emby.Server.Implementations.Data { if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam)) { - bindParam.Bind(value.ToGuidBlob()); + bindParam.Bind(value.ToByteArray()); } else { @@ -392,8 +381,7 @@ namespace Emby.Server.Implementations.Data } } - public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery( - this IStatement This) + public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This) { while (This.MoveNext()) { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index b372ab55c..69cfcb67b 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -27,7 +27,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; @@ -548,7 +547,7 @@ namespace Emby.Server.Implementations.Data { using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) { - saveImagesStatement.TryBind("@Id", item.Id.ToGuidBlob()); + saveImagesStatement.TryBind("@Id", item.Id.ToByteArray()); saveImagesStatement.TryBind("@Images", SerializeImages(item)); saveImagesStatement.MoveNext(); @@ -658,12 +657,14 @@ namespace Emby.Server.Implementations.Data private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement) { + Type type = item.GetType(); + saveItemStatement.TryBind("@guid", item.Id); - saveItemStatement.TryBind("@type", item.GetType().FullName); + saveItemStatement.TryBind("@type", type.FullName); - if (TypeRequiresDeserialization(item.GetType())) + if (TypeRequiresDeserialization(type)) { - saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, _jsonOptions)); + saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, type, _jsonOptions)); } else { @@ -1177,7 +1178,7 @@ namespace Emby.Server.Implementations.Data { if (id == Guid.Empty) { - throw new ArgumentException(nameof(id), "Guid can't be empty"); + throw new ArgumentException("Guid can't be empty", nameof(id)); } CheckDisposed(); @@ -1988,7 +1989,7 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(chapters)); } - var idBlob = id.ToGuidBlob(); + var idBlob = id.ToByteArray(); using (var connection = GetConnection()) { @@ -3760,7 +3761,7 @@ namespace Emby.Server.Implementations.Data if (statement != null) { - statement.TryBind(paramName, personId.ToGuidBlob()); + statement.TryBind(paramName, personId.ToByteArray()); } index++; } @@ -3971,7 +3972,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToGuidBlob()); + statement.TryBind(paramName, artistId.ToByteArray()); } index++; } @@ -3990,7 +3991,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToGuidBlob()); + statement.TryBind(paramName, artistId.ToByteArray()); } index++; } @@ -4009,7 +4010,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToGuidBlob()); + statement.TryBind(paramName, artistId.ToByteArray()); } index++; } @@ -4028,7 +4029,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")"); if (statement != null) { - statement.TryBind(paramName, albumId.ToGuidBlob()); + statement.TryBind(paramName, albumId.ToByteArray()); } index++; } @@ -4047,7 +4048,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToGuidBlob()); + statement.TryBind(paramName, artistId.ToByteArray()); } index++; } @@ -4066,7 +4067,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))"); if (statement != null) { - statement.TryBind(paramName, genreId.ToGuidBlob()); + statement.TryBind(paramName, genreId.ToByteArray()); } index++; } @@ -4137,7 +4138,7 @@ namespace Emby.Server.Implementations.Data if (statement != null) { - statement.TryBind(paramName, studioId.ToGuidBlob()); + statement.TryBind(paramName, studioId.ToByteArray()); } index++; } @@ -4913,7 +4914,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { connection.RunInTransaction(db => { - var idBlob = id.ToGuidBlob(); + var idBlob = id.ToByteArray(); // Delete people ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob); @@ -5032,7 +5033,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type whereClauses.Add("ItemId=@ItemId"); if (statement != null) { - statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); + statement.TryBind("@ItemId", query.ItemId.ToByteArray()); } } if (!query.AppearsInItemId.Equals(Guid.Empty)) @@ -5040,7 +5041,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)"); if (statement != null) { - statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidBlob()); + statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray()); } } var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList(); @@ -5109,7 +5110,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); - var itemIdBlob = itemId.ToGuidBlob(); + var itemIdBlob = itemId.ToByteArray(); // First delete deleteAncestorsStatement.Reset(); @@ -5143,7 +5144,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var ancestorId = ancestorIds[i]; - statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob()); + statement.TryBind("@AncestorId" + index, ancestorId.ToByteArray()); statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture)); } @@ -5608,7 +5609,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); - var guidBlob = itemId.ToGuidBlob(); + var guidBlob = itemId.ToByteArray(); // First delete db.Execute("delete from ItemValues where ItemId=@Id", guidBlob); @@ -5632,10 +5633,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { if (isSubsequentRow) { - insertText.Append(","); + insertText.Append(','); } - insertText.AppendFormat("(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})", i.ToString(CultureInfo.InvariantCulture)); + insertText.AppendFormat( + CultureInfo.InvariantCulture, + "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})", + i); isSubsequentRow = true; } @@ -5688,7 +5692,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { connection.RunInTransaction(db => { - var itemIdBlob = itemId.ToGuidBlob(); + var itemIdBlob = itemId.ToByteArray(); // First delete chapters db.Execute("delete from People where ItemId=@ItemId", itemIdBlob); @@ -5807,7 +5811,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type using (var statement = PrepareStatement(connection, cmdText)) { - statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); + statement.TryBind("@ItemId", query.ItemId.ToByteArray()); if (query.Type.HasValue) { @@ -5849,7 +5853,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { connection.RunInTransaction(db => { - var itemIdBlob = id.ToGuidBlob(); + var itemIdBlob = id.ToByteArray(); // First delete chapters db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 9d4855bcf..26ac17bdc 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; @@ -15,23 +14,19 @@ namespace Emby.Server.Implementations.Data public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository { public SqliteUserDataRepository( - ILoggerFactory loggerFactory, + ILogger<SqliteUserDataRepository> logger, IApplicationPaths appPaths) - : base(loggerFactory.CreateLogger(nameof(SqliteUserDataRepository))) + : base(logger) { DbFilePath = Path.Combine(appPaths.DataPath, "library.db"); } - /// <summary> - /// Gets the name of the repository - /// </summary> - /// <value>The name.</value> + /// <inheritdoc /> public string Name => "SQLite"; /// <summary> - /// Opens the connection to the database + /// Opens the connection to the database. /// </summary> - /// <returns>Task.</returns> public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection) { WriteLock.Dispose(); @@ -97,7 +92,7 @@ namespace Emby.Server.Implementations.Data continue; } - statement.TryBind("@UserId", user.Id.ToGuidBlob()); + statement.TryBind("@UserId", user.Id.ToByteArray()); statement.TryBind("@InternalUserId", user.InternalId); statement.MoveNext(); diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 80fe278f8..26798993b 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) { - statement.TryBind("@guid", user.Id.ToGuidBlob()); + statement.TryBind("@guid", user.Id.ToByteArray()); statement.TryBind("@data", serialized); statement.MoveNext(); diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index d1704b373..36d441851 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -130,7 +130,6 @@ namespace Emby.Server.Implementations.Devices var session = _authRepo.Get(new AuthenticationInfoQuery { DeviceId = id - }).Items.FirstOrDefault(); var device = session == null ? null : ToDeviceInfo(session); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index ea4444268..fde4d7059 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -3,6 +3,7 @@ <ItemGroup> <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" /> <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" /> + <ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> @@ -10,7 +11,6 @@ <ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" /> <ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" /> <ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" /> - <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" /> <ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" /> <ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" /> <ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" /> @@ -29,12 +29,15 @@ <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" /> - <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" /> - <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" /> - <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" /> - <PackageReference Include="ServiceStack.Text.Core" Version="5.6.0" /> + <PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.1" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" /> + <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" /> + <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" /> + <PackageReference Include="Mono.Nat" Version="2.0.0" /> + <PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" /> <PackageReference Include="sharpcompress" Version="0.24.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" /> + <PackageReference Include="System.Interactive.Async" Version="4.0.0" /> </ItemGroup> <ItemGroup> @@ -47,16 +50,12 @@ <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup> - <PropertyGroup> - <!-- We need at least C# 7.3 to compare tuples--> - <LangVersion>latest</LangVersion> - </PropertyGroup> - <!-- Code analysers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" /> </ItemGroup> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index f26a70586..a2619367d 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Plugins; @@ -15,209 +14,134 @@ using Mono.Nat; namespace Emby.Server.Implementations.EntryPoints { + /// <summary> + /// Server entrypoint handling external port forwarding. + /// </summary> public class ExternalPortForwarding : IServerEntryPoint { private readonly IServerApplicationHost _appHost; private readonly ILogger _logger; - private readonly IHttpClient _httpClient; private readonly IServerConfigurationManager _config; private readonly IDeviceDiscovery _deviceDiscovery; + private readonly object _createdRulesLock = new object(); + private List<IPEndPoint> _createdRules = new List<IPEndPoint>(); private Timer _timer; + private string _lastConfigIdentifier; - private NatManager _natManager; + private bool _disposed = false; - public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient) + /// <summary> + /// Initializes a new instance of the <see cref="ExternalPortForwarding"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="appHost">The application host.</param> + /// <param name="config">The configuration manager.</param> + /// <param name="deviceDiscovery">The device discovery.</param> + public ExternalPortForwarding( + ILogger<ExternalPortForwarding> logger, + IServerApplicationHost appHost, + IServerConfigurationManager config, + IDeviceDiscovery deviceDiscovery) { - _logger = loggerFactory.CreateLogger("PortMapper"); + _logger = logger; _appHost = appHost; _config = config; _deviceDiscovery = deviceDiscovery; - _httpClient = httpClient; - _config.ConfigurationUpdated += _config_ConfigurationUpdated1; - } - - private void _config_ConfigurationUpdated1(object sender, EventArgs e) - { - _config_ConfigurationUpdated(sender, e); } - private string _lastConfigIdentifier; private string GetConfigIdentifier() { - var values = new List<string>(); + const char Separator = '|'; var config = _config.Configuration; - values.Add(config.EnableUPnP.ToString()); - values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture)); - values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture)); - values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture)); - values.Add(_appHost.EnableHttps.ToString()); - values.Add((config.EnableRemoteAccess).ToString()); - - return string.Join("|", values.ToArray()); + return new StringBuilder(32) + .Append(config.EnableUPnP).Append(Separator) + .Append(config.PublicPort).Append(Separator) + .Append(_appHost.HttpPort).Append(Separator) + .Append(_appHost.HttpsPort).Append(Separator) + .Append(_appHost.EnableHttps).Append(Separator) + .Append(config.EnableRemoteAccess).Append(Separator) + .ToString(); } - private async void _config_ConfigurationUpdated(object sender, EventArgs e) + private void OnConfigurationUpdated(object sender, EventArgs e) { if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase)) { - DisposeNat(); - - await RunAsync(); + Stop(); + Start(); } } + /// <inheritdoc /> public Task RunAsync() { - if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess) - { - Start(); - } + Start(); - _config.ConfigurationUpdated -= _config_ConfigurationUpdated; - _config.ConfigurationUpdated += _config_ConfigurationUpdated; + _config.ConfigurationUpdated += OnConfigurationUpdated; return Task.CompletedTask; } private void Start() { - _logger.LogDebug("Starting NAT discovery"); - if (_natManager == null) + if (!_config.Configuration.EnableUPnP || !_config.Configuration.EnableRemoteAccess) { - _natManager = new NatManager(_logger, _httpClient); - _natManager.DeviceFound += NatUtility_DeviceFound; - _natManager.StartDiscovery(); + return; } + _logger.LogDebug("Starting NAT discovery"); + + NatUtility.DeviceFound += OnNatUtilityDeviceFound; + NatUtility.StartDiscovery(); + _timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); - _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; + _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered; _lastConfigIdentifier = GetConfigIdentifier(); } - private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e) + private void Stop() { - if (_disposed) - { - return; - } - - var info = e.Argument; - - if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty; - - if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty; - - // Filter device type - if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && - nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && - usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && - nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1) - { - return; - } - - var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn; - - if (info.Location == null) - { - return; - } - - lock (_usnsHandled) - { - if (_usnsHandled.Contains(identifier)) - { - return; - } - _usnsHandled.Add(identifier); - } - - _logger.LogDebug("Found NAT device: " + identifier); - - if (IPAddress.TryParse(info.Location.Host, out var address)) - { - // The Handle method doesn't need the port - var endpoint = new IPEndPoint(address, info.Location.Port); - - IPAddress localAddress = null; + _logger.LogDebug("Stopping NAT discovery"); - try - { - var localAddressString = await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false); + NatUtility.StopDiscovery(); + NatUtility.DeviceFound -= OnNatUtilityDeviceFound; - if (Uri.TryCreate(localAddressString, UriKind.Absolute, out var uri)) - { - localAddressString = uri.Host; + _timer?.Dispose(); - if (!IPAddress.TryParse(localAddressString, out localAddress)) - { - return; - } - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error"); - return; - } - - if (_disposed) - { - return; - } - - // This should never happen, but the Handle method will throw ArgumentNullException if it does - if (localAddress == null) - { - return; - } - - var natManager = _natManager; - if (natManager != null) - { - await natManager.Handle(localAddress, info, endpoint, NatProtocol.Upnp).ConfigureAwait(false); - } - } + _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered; } private void ClearCreatedRules(object state) { - lock (_createdRules) + lock (_createdRulesLock) { _createdRules.Clear(); } - lock (_usnsHandled) - { - _usnsHandled.Clear(); - } } - void NatUtility_DeviceFound(object sender, DeviceEventArgs e) + private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e) { - if (_disposed) - { - return; - } + NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp); + } + private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e) + { try { var device = e.Device; CreateRules(device); } - catch + catch (Exception ex) { - // Commenting out because users are reporting problems out of our control - //_logger.LogError(ex, "Error creating port forwarding rules"); + _logger.LogError(ex, "Error creating port forwarding rules"); } } - private List<string> _createdRules = new List<string>(); - private List<string> _usnsHandled = new List<string>(); private async void CreateRules(INatDevice device) { if (_disposed) @@ -227,15 +151,13 @@ namespace Emby.Server.Implementations.EntryPoints // On some systems the device discovered event seems to fire repeatedly // This check will help ensure we're not trying to port map the same device over and over - var address = device.LocalAddress; - - var addressString = address.ToString(); + var address = device.DeviceEndpoint; - lock (_createdRules) + lock (_createdRulesLock) { - if (!_createdRules.Contains(addressString)) + if (!_createdRules.Contains(address)) { - _createdRules.Add(addressString); + _createdRules.Add(address); } else { @@ -263,54 +185,43 @@ namespace Emby.Server.Implementations.EntryPoints } } - private Task CreatePortMap(INatDevice device, int privatePort, int publicPort) + private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort) { - _logger.LogDebug("Creating port map on local port {0} to public port {1} with device {2}", privatePort, publicPort, device.LocalAddress.ToString()); - - return device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort) - { - Description = _appHost.Name - }); + _logger.LogDebug( + "Creating port map on local port {0} to public port {1} with device {2}", + privatePort, + publicPort, + device.DeviceEndpoint); + + return device.CreatePortMapAsync( + new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name)); } - private bool _disposed = false; + /// <inheritdoc /> public void Dispose() { - _disposed = true; - DisposeNat(); + Dispose(true); + GC.SuppressFinalize(this); } - private void DisposeNat() + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) { - _logger.LogDebug("Stopping NAT discovery"); - - if (_timer != null) + if (_disposed) { - _timer.Dispose(); - _timer = null; + return; } - _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; + _config.ConfigurationUpdated -= OnConfigurationUpdated; - var natManager = _natManager; + Stop(); - if (natManager != null) - { - _natManager = null; + _timer = null; - using (natManager) - { - try - { - natManager.StopDiscovery(); - natManager.DeviceFound -= NatUtility_DeviceFound; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error stopping NAT Discovery"); - } - } - } + _disposed = true; } } } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 9c0db2cf5..24906220d 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints public class LibraryChangedNotifier : IServerEntryPoint { /// <summary> - /// The _library manager + /// The library manager. /// </summary> private readonly ILibraryManager _libraryManager; @@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ILogger _logger; /// <summary> - /// The _library changed sync lock + /// The library changed sync lock. /// </summary> private readonly object _libraryChangedSyncLock = new object(); @@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.EntryPoints private Timer LibraryUpdateTimer { get; set; } /// <summary> - /// The library update duration + /// The library update duration. /// </summary> private const int LibraryUpdateDuration = 30000; @@ -188,8 +188,11 @@ namespace Emby.Server.Implementations.EntryPoints { if (LibraryUpdateTimer == null) { - LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, - Timeout.Infinite); + LibraryUpdateTimer = new Timer( + LibraryUpdateTimerCallback, + null, + LibraryUpdateDuration, + Timeout.Infinite); } else { @@ -452,7 +455,7 @@ namespace Emby.Server.Implementations.EntryPoints return new[] { item }; } - return new T[] { }; + return Array.Empty<T>(); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs index b2328121e..3a7516dca 100644 --- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.EntryPoints { cancellationToken.ThrowIfCancellationRequested(); - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); + await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false); } } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 141e72958..3ff8d9968 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.EntryPoints { try { - await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None); + await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None).ConfigureAwait(false); } catch (Exception) { diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 0e6083773..2da0191dd 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -325,7 +325,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (options.LogErrorResponseBody) { - var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + string msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); _logger.LogError("HTTP request failed with message: {Message}", msg); } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e4f98acb9..6dd016f8a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -7,7 +7,6 @@ using System.Net.Sockets; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.Configuration; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Services; using MediaBrowser.Common.Extensions; @@ -16,11 +15,9 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -166,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, - QueryString = e.QueryString ?? new QueryCollection() + QueryString = e.QueryString }; connection.Closed += OnConnectionClosed; @@ -539,6 +536,11 @@ namespace Emby.Server.Implementations.HttpServer } finally { + if (httpRes.StatusCode >= 500) + { + _logger.LogDebug("Sending HTTP Response 500 in response to {Url}", urlToLog); + } + stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 0b2924a3b..b5cfb6b09 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -460,7 +460,7 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrEmpty(path)) { - throw new ArgumentNullException(nameof(path)); + throw new ArgumentException("Path can't be empty.", nameof(options)); } if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index e27f794ba..320136d11 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -48,12 +48,14 @@ namespace Emby.Server.Implementations.HttpServer public IDictionary<string, string> Headers => _options; /// <summary> - /// Initializes a new instance of the <see cref="StreamWriter" /> class. + /// Initializes a new instance of the <see cref="RangeRequestWriter" /> class. /// </summary> /// <param name="rangeHeader">The range header.</param> + /// <param name="contentLength">The content length.</param> /// <param name="source">The source.</param> /// <param name="contentType">Type of the content.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> + /// <param name="logger">The logger instance.</param> public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger) { if (string.IsNullOrEmpty(contentType)) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 93a61fe67..594f46498 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -7,22 +8,27 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { + private readonly ILogger<AuthService> _logger; private readonly IAuthorizationContext _authorizationContext; private readonly ISessionManager _sessionManager; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; public AuthService( + ILogger<AuthService> logger, IAuthorizationContext authorizationContext, IServerConfigurationManager config, ISessionManager sessionManager, INetworkManager networkManager) { + _logger = logger; _authorizationContext = authorizationContext; _config = config; _sessionManager = sessionManager; @@ -34,7 +40,14 @@ namespace Emby.Server.Implementations.HttpServer.Security ValidateUser(request, authAttribtues); } - private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) + public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) + { + var req = new WebSocketSharpRequest(request, null, request.Path, _logger); + var user = ValidateUser(req, authAttributes); + return user; + } + + private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service var auth = _authorizationContext.GetAuthorizationInfo(request); @@ -81,6 +94,8 @@ namespace Emby.Server.Implementations.HttpServer.Security request.RemoteIp, user); } + + return user; } private void ValidateUserAccess( diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index c95b00ede..c043568d5 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -2,11 +2,11 @@ using System; using System.Linq; using System.Text; using System.Threading.Tasks; +using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Cryptography; -using static MediaBrowser.Common.HexHelper; namespace Emby.Server.Implementations.Library { @@ -59,7 +59,10 @@ namespace Emby.Server.Implementations.Library if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id) { - byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.Salt); + byte[] calculatedHash = _cryptographyProvider.ComputeHash( + readyHash.Id, + passwordbytes, + readyHash.Salt); if (calculatedHash.SequenceEqual(readyHash.Hash)) { @@ -122,7 +125,7 @@ namespace Emby.Server.Implementations.Library { return string.IsNullOrEmpty(user.EasyPassword) ? null - : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash); + : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); } /// <summary> diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs index 6956369dc..7913df5e4 100644 --- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs +++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs @@ -1,7 +1,6 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Net; namespace Emby.Server.Implementations.Library { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 9a2a6dc5f..826cdb9c6 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -519,7 +519,7 @@ namespace Emby.Server.Implementations.Library } public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null) - => ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent); + => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent); private BaseItem ResolvePath( FileSystemMetadata fileInfo, @@ -1045,7 +1045,7 @@ namespace Emby.Server.Implementations.Library await RootFolder.ValidateChildren( new SimpleProgress<double>(), cancellationToken, - new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), + new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: false).ConfigureAwait(false); await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); @@ -1053,7 +1053,7 @@ namespace Emby.Server.Implementations.Library await GetUserRootFolder().ValidateChildren( new SimpleProgress<double>(), cancellationToken, - new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), + new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: false).ConfigureAwait(false); // Quickly scan CollectionFolders for changes @@ -1074,7 +1074,7 @@ namespace Emby.Server.Implementations.Library innerProgress.RegisterAction(pct => progress.Report(pct * .96)); // Now validate the entire media library - await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: true).ConfigureAwait(false); + await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false); progress.Report(96); @@ -1899,7 +1899,7 @@ namespace Emby.Server.Implementations.Library /// <param name="cancellationToken">The cancellation token.</param> public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - UpdateItems(new [] { item }, parent, updateReason, cancellationToken); + UpdateItems(new[] { item }, parent, updateReason, cancellationToken); } /// <summary> @@ -2135,7 +2135,7 @@ namespace Emby.Server.Implementations.Library if (refresh) { item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None); - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal); + _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal); } return item; @@ -2175,7 +2175,6 @@ namespace Emby.Server.Implementations.Library DisplayParentId = parentId }; - CreateItem(item, null); isNew = true; @@ -2193,11 +2192,10 @@ namespace Emby.Server.Implementations.Library { _providerManagerFactory().QueueRefresh( item.Id, - new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { // Need to force save to increment DateLastSaved ForceSave = true - }, RefreshPriority.Normal); } @@ -2261,7 +2259,7 @@ namespace Emby.Server.Implementations.Library { _providerManagerFactory().QueueRefresh( item.Id, - new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { // Need to force save to increment DateLastSaved ForceSave = true @@ -2338,7 +2336,7 @@ namespace Emby.Server.Implementations.Library { _providerManagerFactory().QueueRefresh( item.Id, - new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { // Need to force save to increment DateLastSaved ForceSave = true @@ -2487,6 +2485,15 @@ namespace Emby.Server.Implementations.Library { episode.ParentIndexNumber = season.IndexNumber; } + else + { + /* + Anime series don't generally have a season in their file name, however, + tvdb needs a season to correctly get the metadata. + Hence, a null season needs to be filled with something. */ + //FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified + episode.ParentIndexNumber = 1; + } if (episode.ParentIndexNumber.HasValue) { diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index d83e1fc02..7a26e0c37 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -134,12 +134,13 @@ namespace Emby.Server.Implementations.Library if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video)) { - await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - EnableRemoteContentProbe = true, - MetadataRefreshMode = MediaBrowser.Controller.Providers.MetadataRefreshMode.FullRefresh - - }, cancellationToken).ConfigureAwait(false); + await item.RefreshMetadata( + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + EnableRemoteContentProbe = true, + MetadataRefreshMode = MetadataRefreshMode.FullRefresh + }, + cancellationToken).ConfigureAwait(false); mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index c10b77a24..c749aec42 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Library if (string.IsNullOrEmpty(searchTerm)) { - throw new ArgumentNullException(nameof(searchTerm)); + throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm)); } searchTerm = searchTerm.Trim().RemoveDiacritics(); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 52b2f56ff..2b22129f3 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -24,6 +24,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; @@ -31,7 +32,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; -using static MediaBrowser.Common.HexHelper; namespace Emby.Server.Implementations.Library { @@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.Library private readonly Func<IDtoService> _dtoServiceFactory; private readonly IServerApplicationHost _appHost; private readonly IFileSystem _fileSystem; + private readonly ICryptoProvider _cryptoProvider; private ConcurrentDictionary<Guid, User> _users; @@ -80,7 +81,8 @@ namespace Emby.Server.Implementations.Library Func<IDtoService> dtoServiceFactory, IServerApplicationHost appHost, IJsonSerializer jsonSerializer, - IFileSystem fileSystem) + IFileSystem fileSystem, + ICryptoProvider cryptoProvider) { _logger = logger; _userRepository = userRepository; @@ -91,6 +93,7 @@ namespace Emby.Server.Implementations.Library _appHost = appHost; _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; + _cryptoProvider = cryptoProvider; _users = null; } @@ -179,12 +182,7 @@ namespace Emby.Server.Implementations.Library _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); } - /// <summary> - /// Gets a User by Id. - /// </summary> - /// <param name="id">The id.</param> - /// <returns>User.</returns> - /// <exception cref="ArgumentException"></exception> + /// <inheritdoc /> public User GetUserById(Guid id) { if (id == Guid.Empty) @@ -196,11 +194,7 @@ namespace Emby.Server.Implementations.Library return user; } - /// <summary> - /// Gets the user by identifier. - /// </summary> - /// <param name="id">The identifier.</param> - /// <returns>User.</returns> + /// <inheritdoc /> public User GetUserById(string id) => GetUserById(new Guid(id)); @@ -428,7 +422,6 @@ namespace Emby.Server.Implementations.Library { try { - var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) : await provider.Authenticate(username, password).ConfigureAwait(false); @@ -475,24 +468,21 @@ namespace Emby.Server.Implementations.Library if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) - && user.Configuration.EnableLocalPassword) + && user.Configuration.EnableLocalPassword + && !string.IsNullOrEmpty(user.EasyPassword)) { - success = string.Equals( - GetLocalPasswordHash(user), - _defaultAuthenticationProvider.GetHashedString(user, password), - StringComparison.OrdinalIgnoreCase); + // Check easy password + var passwordHash = PasswordHash.Parse(user.EasyPassword); + var hash = _cryptoProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(password), + passwordHash.Salt); + success = passwordHash.Hash.SequenceEqual(hash); } return (authenticationProvider, username, success); } - private string GetLocalPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? null - : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash); - } - private void ResetInvalidLoginAttemptCount(User user) { user.Policy.InvalidLoginAttemptCount = 0; @@ -538,6 +528,8 @@ namespace Emby.Server.Implementations.Library defaultName = "MyJellyfinUser"; } + _logger.LogWarning("No users, creating one with username {UserName}", defaultName); + var name = MakeValidUsername(defaultName); var user = InstantiateNewUser(name); @@ -601,7 +593,7 @@ namespace Emby.Server.Implementations.Library catch (Exception ex) { // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions - _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); + _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {User}", user.Name); } } @@ -625,7 +617,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); + _logger.LogError(ex, "Error getting {ImageType} image info for {ImagePath}", image.Type, image.Path); return null; } } @@ -639,7 +631,7 @@ namespace Emby.Server.Implementations.Library { foreach (var user in Users) { - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); + await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false); } } diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 1b0813280..a9b39c064 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -42,6 +42,11 @@ namespace Emby.Server.Implementations.Library { var user = _userManager.GetUserById(query.UserId); + if (user == null) + { + throw new ArgumentException("User Id specified in the query does not exist.", nameof(query)); + } + var folders = _libraryManager.GetUserRootFolder() .GetChildren(user, true) .OfType<Folder>() @@ -54,7 +59,7 @@ namespace Emby.Server.Implementations.Library foreach (var folder in folders) { var collectionFolder = folder as ICollectionFolder; - var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType; + var folderViewType = collectionFolder?.CollectionType; if (UserView.IsUserSpecific(folder)) { @@ -130,16 +135,11 @@ namespace Emby.Server.Implementations.Library { var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture)); - if (index == -1) + if (index == -1 + && i is UserView view + && view.DisplayParentId != Guid.Empty) { - var view = i as UserView; - if (view != null) - { - if (!view.DisplayParentId.Equals(Guid.Empty)) - { - index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture)); - } - } + index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture)); } return index == -1 ? int.MaxValue : index; diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index b584cc649..d06cda177 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -28,10 +28,11 @@ namespace Emby.Server.Implementations.Library.Validators private readonly IItemRepository _itemRepo; /// <summary> - /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// Initializes a new instance of the <see cref="ArtistsValidator" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs index 056807300..3bc5c2fb2 100644 --- a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs @@ -10,17 +10,18 @@ namespace Emby.Server.Implementations.Library.Validators public class GenresPostScanTask : ILibraryPostScanTask { /// <summary> - /// The _library manager + /// The _library manager. /// </summary> private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// <summary> - /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// Initializes a new instance of the <see cref="GenresPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs index d7ab92d30..9ac4bf761 100644 --- a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs @@ -20,10 +20,11 @@ namespace Emby.Server.Implementations.Library.Validators private readonly IItemRepository _itemRepo; /// <summary> - /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> /// <param name="logger">The logger.</param> + /// <param name="itemRepo">The item repository.</param> public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index d00c6cde1..137a010ec 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -11,16 +11,17 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library.Validators { /// <summary> - /// Class PeopleValidator + /// Class PeopleValidator. /// </summary> public class PeopleValidator { /// <summary> - /// The _library manager + /// The _library manager. /// </summary> private readonly ILibraryManager _libraryManager; + /// <summary> - /// The _logger + /// The _logger. /// </summary> private readonly ILogger _logger; @@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators { var item = _libraryManager.GetPerson(person); - var options = new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ImageRefreshMode = MetadataRefreshMode.ValidationOnly, MetadataRefreshMode = MetadataRefreshMode.ValidationOnly @@ -96,12 +97,19 @@ namespace Emby.Server.Implementations.Library.Validators foreach (var item in deadEntities) { - _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); - - _libraryManager.DeleteItem(item, new DeleteOptions - { - DeleteFileLocation = false - }, false); + _logger.LogInformation( + "Deleting dead {2} {0} {1}.", + item.Id.ToString("N", CultureInfo.InvariantCulture), + item.Name, + item.GetType().Name); + + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false + }, + false); } progress.Report(100); diff --git a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs index 4aa5c7e72..2efae0fe4 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -21,9 +21,11 @@ namespace Emby.Server.Implementations.Library.Validators private readonly IItemRepository _itemRepo; /// <summary> - /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="itemRepo">Th item repository.</param> public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7b0cd4022..3b6bfce6a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (requiresRefresh) { - await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None); + await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None).ConfigureAwait(false); } } @@ -1489,16 +1489,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { _logger.LogInformation("Refreshing recording parent {path}", item.Path); - _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - RefreshPaths = new string[] + _providerManager.QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { - path, - Path.GetDirectoryName(path), - Path.GetDirectoryName(Path.GetDirectoryName(path)) - } - - }, RefreshPriority.High); + RefreshPaths = new string[] + { + path, + Path.GetDirectoryName(path), + Path.GetDirectoryName(Path.GetDirectoryName(path)) + } + }, + RefreshPriority.High); } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 9a4c91d0b..838ac97d7 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -501,7 +501,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings public async Task<List<NameIdPair>> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken) { - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); var lineups = new List<NameIdPair>(); @@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken) { - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(token)) { @@ -738,7 +738,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings httpOptions.RequestHeaders["token"] = token; - using (await _httpClient.SendAsync(httpOptions, "PUT")) + using (await _httpClient.SendAsync(httpOptions, "PUT").ConfigureAwait(false)) { } } @@ -750,7 +750,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new ArgumentException("Listings Id required"); } - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(token)) { @@ -833,7 +833,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new Exception("ListingsId required"); } - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(token)) { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index c2350684b..2ecf4e184 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1226,12 +1226,13 @@ namespace Emby.Server.Implementations.LiveTv currentChannel.AddTag("Kids"); } - //currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken); - await currentChannel.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - ForceSave = true - - }, cancellationToken).ConfigureAwait(false); + currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken); + await currentChannel.RefreshMetadata( + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ForceSave = true + }, + cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -1245,7 +1246,7 @@ namespace Emby.Server.Implementations.LiveTv numComplete++; double percent = numComplete / (double)allChannelsList.Count; - progress.Report(85 * percent + 15); + progress.Report((85 * percent) + 15); } progress.Report(100); @@ -1278,12 +1279,14 @@ namespace Emby.Server.Implementations.LiveTv if (item != null) { - _libraryManager.DeleteItem(item, new DeleteOptions - { - DeleteFileLocation = false, - DeleteFromExternalProvider = false - - }, false); + _libraryManager.DeleteItem( + item, + new DeleteOptions + { + DeleteFileLocation = false, + DeleteFromExternalProvider = false + }, + false); } } @@ -2301,8 +2304,10 @@ namespace Emby.Server.Implementations.LiveTv if (provider == null) { throw new ResourceNotFoundException( - string.Format("Couldn't find provider of type: '{0}'", info.Type) - ); + string.Format( + CultureInfo.InvariantCulture, + "Couldn't find provider of type: '{0}'", + info.Type)); } await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index 542951de4..1056a33b9 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -38,8 +38,8 @@ namespace Emby.Server.Implementations.LiveTv /// <returns>IEnumerable{BaseTaskTrigger}.</returns> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] { - + return new[] + { // Every so often new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} }; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index da98f3e58..06f27fa3e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Url = string.Format("{0}/tuners.html", GetApiUrl(info)), CancellationToken = cancellationToken, BufferContent = false - }, HttpMethod.Get)) + }, HttpMethod.Get).ConfigureAwait(false)) using (var stream = response.Content) using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8)) { @@ -259,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun for (int i = 0; i < model.TunerCount; ++i) { var name = string.Format("Tuner {0}", i + 1); - var currentChannel = "none"; /// @todo Get current channel and map back to Station Id + var currentChannel = "none"; // @todo Get current channel and map back to Station Id var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false); var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv; tuners.Add(new LiveTvTunerInfo @@ -298,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken) { // TODO Need faster way to determine UDP vs HTTP - var channels = await GetChannels(info, true, cancellationToken); + var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false); var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo; @@ -582,11 +582,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun modelInfo.TunerCount, FileSystem, Logger, - Config.ApplicationPaths, + Config, _appHost, _networkManager, _streamHelper); - } var enableHttpStream = true; @@ -611,7 +610,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun FileSystem, _httpClient, Logger, - Config.ApplicationPaths, + Config, _appHost, _streamHelper); } @@ -624,7 +623,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun modelInfo.TunerCount, FileSystem, Logger, - Config.ApplicationPaths, + Config, _appHost, _networkManager, _streamHelper); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index eafa86d54..649becbd3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -6,6 +6,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; @@ -33,11 +34,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int numTuners, IFileSystem fileSystem, ILogger logger, - IServerApplicationPaths appPaths, + IConfigurationManager configurationManager, IServerApplicationHost appHost, INetworkManager networkManager, IStreamHelper streamHelper) - : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper) + : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper) { _appHost = appHost; _networkManager = networkManager; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index d12c96392..1d55e7992 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -5,8 +5,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller; using MediaBrowser.Controller.Library; +using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; @@ -16,8 +16,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { public class LiveStream : ILiveStream { + private readonly IConfigurationManager _configurationManager; + protected readonly IFileSystem FileSystem; - protected readonly IServerApplicationPaths AppPaths; + protected readonly IStreamHelper StreamHelper; protected string TempFilePath; @@ -29,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, - IServerApplicationPaths appPaths, + IConfigurationManager configurationManager, IStreamHelper streamHelper) { OriginalMediaSource = mediaSource; @@ -44,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts TunerHostId = tuner.Id; } - AppPaths = appPaths; + _configurationManager = configurationManager; StreamHelper = streamHelper; ConsumerCount = 1; @@ -68,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected void SetTempFilePath(string extension) { - TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension); + TempFilePath = Path.Combine(_configurationManager.GetTranscodePath(), UniqueId + "." + extension); } public virtual Task Open(CancellationToken openCancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index a02a9ade4..df054f1eb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -114,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { - return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper); + return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config, _appHost, _streamHelper); } } - return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths, _streamHelper); + return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper); } public async Task Validate(TunerHostInfo info) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index c6e894560..758495362 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; @@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, - IServerApplicationPaths appPaths, + IConfigurationManager configurationManager, IServerApplicationHost appHost, IStreamHelper streamHelper) - : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper) + : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper) { _httpClient = httpClient; _appHost = appHost; diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json new file mode 100644 index 000000000..dcec26801 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -0,0 +1,96 @@ +{ + "Artists": "Kunstenare", + "Channels": "Kanale", + "Folders": "Fouers", + "Favorites": "Gunstelinge", + "HeaderFavoriteShows": "Gunsteling Vertonings", + "ValueSpecialEpisodeName": "Spesiaal - {0}", + "HeaderAlbumArtists": "Album Kunstenaars", + "Books": "Boeke", + "HeaderNextUp": "Volgende", + "Movies": "Rolprente", + "Shows": "Program", + "HeaderContinueWatching": "Hou Aan Kyk", + "HeaderFavoriteEpisodes": "Gunsteling Episodes", + "Photos": "Fotos", + "Playlists": "Speellysse", + "HeaderFavoriteArtists": "Gunsteling Kunstenaars", + "HeaderFavoriteAlbums": "Gunsteling Albums", + "Sync": "Sinkroniseer", + "HeaderFavoriteSongs": "Gunsteling Liedjies", + "Songs": "Liedjies", + "DeviceOnlineWithName": "{0} is verbind", + "DeviceOfflineWithName": "{0} het afgesluit", + "Collections": "Versamelings", + "Inherit": "Ontvang", + "HeaderLiveTV": "Live TV", + "Application": "Program", + "AppDeviceValues": "App: {0}, Toestel: {1}", + "VersionNumber": "Weergawe {0}", + "ValueHasBeenAddedToLibrary": "{0} is by jou media biblioteek bygevoeg", + "UserStoppedPlayingItemWithValues": "{0} het klaar {1} op {2} gespeel", + "UserStartedPlayingItemWithValues": "{0} is besig om {1} op {2} te speel", + "UserPolicyUpdatedWithName": "Gebruiker beleid is verander vir {0}", + "UserPasswordChangedWithName": "Gebruiker {0} se wagwoord is verander", + "UserOnlineFromDevice": "{0} is aanlyn van {1}", + "UserOfflineFromDevice": "{0} is ontkoppel van {1}", + "UserLockedOutWithName": "Gebruiker {0} is uitgesluit", + "UserDownloadingItemWithValues": "{0} is besig om {1} af te laai", + "UserDeletedWithName": "Gebruiker {0} is verwyder", + "UserCreatedWithName": "Gebruiker {0} is geskep", + "User": "Gebruiker", + "TvShows": "TV Programme", + "System": "Stelsel", + "SubtitlesDownloadedForItem": "Ondertitels afgelaai vir {0}", + "SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}", + "StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.", + "ServerNameNeedsToBeRestarted": "{0} moet herbegin word", + "ScheduledTaskStartedWithName": "{0} het begin", + "ScheduledTaskFailedWithName": "{0} het misluk", + "ProviderValue": "Voorsiener: {0}", + "PluginUpdatedWithName": "{0} was opgedateer", + "PluginUninstalledWithName": "{0} was verwyder", + "PluginInstalledWithName": "{0} is geïnstalleer", + "Plugin": "Inprop module", + "NotificationOptionVideoPlaybackStopped": "Video terugspeel het gestop", + "NotificationOptionVideoPlayback": "Video terugspeel het begin", + "NotificationOptionUserLockedOut": "Gebruiker uitgeslyt", + "NotificationOptionTaskFailed": "Geskeduleerde taak het misluk", + "NotificationOptionServerRestartRequired": "Bediener herbegin nodig", + "NotificationOptionPluginUpdateInstalled": "Nuwe inprop module geïnstalleer", + "NotificationOptionPluginUninstalled": "Inprop module verwyder", + "NotificationOptionPluginInstalled": "Inprop module geïnstalleer", + "NotificationOptionPluginError": "Inprop module het misluk", + "NotificationOptionNewLibraryContent": "Nuwe inhoud bygevoeg", + "NotificationOptionInstallationFailed": "Installering het misluk", + "NotificationOptionCameraImageUploaded": "Kamera foto is opgelaai", + "NotificationOptionAudioPlaybackStopped": "Oudio terugspeel het gestop", + "NotificationOptionAudioPlayback": "Oudio terugspeel het begin", + "NotificationOptionApplicationUpdateInstalled": "Nuwe program weergawe geïnstalleer", + "NotificationOptionApplicationUpdateAvailable": "Nuwe program weergawe beskikbaar", + "NewVersionIsAvailable": "'n Nuwe Jellyfin Bedienaar weergawe kan afgelaai word.", + "NameSeasonUnknown": "Seisoen Onbekend", + "NameSeasonNumber": "Seisoen {0}", + "NameInstallFailed": "{0} installering het misluk", + "MusicVideos": "Musiek videos", + "Music": "Musiek", + "MixedContent": "Gemengde inhoud", + "MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer", + "MessageNamedServerConfigurationUpdatedWithValue": "Bediener konfigurasie seksie {0} is opgedateer", + "MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}", + "MessageApplicationUpdated": "Jellyfin Bediener is opgedateer", + "Latest": "Nuutste", + "LabelRunningTimeValue": "Lopende tyd: {0}", + "LabelIpAddressValue": "IP adres: {0}", + "ItemRemovedWithName": "{0} is uit versameling verwyder", + "ItemAddedWithName": "{0} is in die versameling", + "HomeVideos": "Tuis opnames", + "HeaderRecordingGroups": "Groep Opnames", + "HeaderCameraUploads": "Kamera Oplaai", + "Genres": "Genres", + "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}", + "ChapterNameValue": "Hoofstuk", + "CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}", + "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer", + "Albums": "Albums" +} diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index a71dc9346..bfe32a6c2 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -1,22 +1,22 @@ { "Albums": "Албуми", - "AppDeviceValues": "Програма: {0}, Устройство: {1}", + "AppDeviceValues": "Програма: {0}, устройство: {1}", "Application": "Програма", "Artists": "Изпълнители", "AuthenticationSucceededWithUserName": "{0} се удостовери успешно", "Books": "Книги", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "", "Channels": "Канали", "ChapterNameValue": "Глава {0}", "Collections": "Колекции", "DeviceOfflineWithName": "{0} се разкачи", "DeviceOnlineWithName": "{0} е свързан", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", + "FailedLoginAttemptWithUserName": "", "Favorites": "Любими", "Folders": "Папки", "Genres": "Жанрове", "HeaderAlbumArtists": "Изпълнители на албуми", - "HeaderCameraUploads": "Camera Uploads", + "HeaderCameraUploads": "", "HeaderContinueWatching": "Продължаване на гледането", "HeaderFavoriteAlbums": "Любими албуми", "HeaderFavoriteArtists": "Любими изпълнители", @@ -25,26 +25,26 @@ "HeaderFavoriteSongs": "Любими песни", "HeaderLiveTV": "Телевизия на живо", "HeaderNextUp": "Следва", - "HeaderRecordingGroups": "Recording Groups", + "HeaderRecordingGroups": "", "HomeVideos": "Домашни клипове", "Inherit": "Наследяване", "ItemAddedWithName": "{0} е добавено към библиотеката", "ItemRemovedWithName": "{0} е премахнато от библиотеката", "LabelIpAddressValue": "ИП адрес: {0}", - "LabelRunningTimeValue": "Running time: {0}", + "LabelRunningTimeValue": "", "Latest": "Последни", "MessageApplicationUpdated": "Сървърът е обновен", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", + "MessageApplicationUpdatedTo": "", + "MessageNamedServerConfigurationUpdatedWithValue": "", + "MessageServerConfigurationUpdated": "", "MixedContent": "Смесено съдържание", "Movies": "Филми", "Music": "Музика", "MusicVideos": "Музикални клипове", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "", "NameSeasonNumber": "Сезон {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NameSeasonUnknown": "Неразпознат сезон", + "NewVersionIsAvailable": "", "NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата", "NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано", "NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна", @@ -58,7 +58,7 @@ "NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано", "NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра", "NotificationOptionTaskFailed": "Грешка в планирана задача", - "NotificationOptionUserLockedOut": "User locked out", + "NotificationOptionUserLockedOut": "", "NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна", "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно", "Photos": "Снимки", @@ -70,12 +70,12 @@ "ProviderValue": "Доставчик: {0}", "ScheduledTaskFailedWithName": "{0} се провали", "ScheduledTaskStartedWithName": "{0} започна", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "", "Shows": "Сериали", "Songs": "Песни", "StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.", "SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "", "SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}", "Sync": "Синхронизиране", "System": "Система", @@ -83,15 +83,15 @@ "User": "Потребител", "UserCreatedWithName": "Потребителят {0} е създаден", "UserDeletedWithName": "Потребителят {0} е изтрит", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", + "UserDownloadingItemWithValues": "", + "UserLockedOutWithName": "", "UserOfflineFromDevice": "{0} се разкачи от {1}", "UserOnlineFromDevice": "{0} е на линия от {1}", "UserPasswordChangedWithName": "Паролата на потребителя {0} е променена", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", + "UserPolicyUpdatedWithName": "", "UserStartedPlayingItemWithValues": "{0} пусна {1}", "UserStoppedPlayingItemWithValues": "{0} спря {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "", "ValueSpecialEpisodeName": "Специални - {0}", "VersionNumber": "Версия {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index c19148921..86fbac380 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -23,7 +23,7 @@ "HeaderFavoriteEpisodes": "Oblíbené epizody", "HeaderFavoriteShows": "Oblíbené seriály", "HeaderFavoriteSongs": "Oblíbená hudba", - "HeaderLiveTV": "Live TV", + "HeaderLiveTV": "Živá TV", "HeaderNextUp": "Nadcházející", "HeaderRecordingGroups": "Skupiny nahrávek", "HomeVideos": "Domáci videa", diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 0db201769..019736c47 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -3,14 +3,14 @@ "AppDeviceValues": "App: {0}, Gerät: {1}", "Application": "Anwendung", "Artists": "Interpreten", - "AuthenticationSucceededWithUserName": "{0} hat sich angemeldet", + "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet", "Books": "Bücher", "CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}", "Channels": "Kanäle", "ChapterNameValue": "Kapitel {0}", "Collections": "Sammlungen", "DeviceOfflineWithName": "{0} wurde getrennt", - "DeviceOnlineWithName": "{0} hat sich verbunden", + "DeviceOnlineWithName": "{0} ist verbunden", "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}", "Favorites": "Favoriten", "Folders": "Verzeichnisse", @@ -23,7 +23,7 @@ "HeaderFavoriteEpisodes": "Lieblingsepisoden", "HeaderFavoriteShows": "Lieblingsserien", "HeaderFavoriteSongs": "Lieblingslieder", - "HeaderLiveTV": "Live-TV", + "HeaderLiveTV": "Live TV", "HeaderNextUp": "Als Nächstes", "HeaderRecordingGroups": "Aufnahme-Gruppen", "HomeVideos": "Heimvideos", @@ -35,7 +35,7 @@ "Latest": "Neueste", "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert", "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert", - "MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert", + "MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert", "MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert", "MixedContent": "Gemischte Inhalte", "Movies": "Filme", diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 6bfedb712..9805992be 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -5,7 +5,7 @@ "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès", "Books": "Livres", - "CameraImageUploadedFrom": "Une image de caméra a été chargée depuis {0}", + "CameraImageUploadedFrom": "Une nouvelle image de caméra a été chargée depuis {0}", "Channels": "Chaînes", "ChapterNameValue": "Chapitre {0}", "Collections": "Collections", @@ -17,7 +17,7 @@ "Genres": "Genres", "HeaderAlbumArtists": "Artistes de l'album", "HeaderCameraUploads": "Photos transférées", - "HeaderContinueWatching": "Reprendre", + "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes favoris", "HeaderFavoriteEpisodes": "Épisodes favoris", @@ -34,14 +34,14 @@ "LabelRunningTimeValue": "Durée : {0}", "Latest": "Derniers", "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour", - "MessageApplicationUpdatedTo": "Jellyfin Serveur a été mis à jour en version {0}", + "MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers {0}", "MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour", "MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour", "MixedContent": "Contenu mixte", "Movies": "Films", "Music": "Musique", "MusicVideos": "Vidéos musicales", - "NameInstallFailed": "{0} échec d'installation", + "NameInstallFailed": "{0} échec de l'installation", "NameSeasonNumber": "Saison {0}", "NameSeasonUnknown": "Saison Inconnue", "NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "Lecture audio démarrée", "NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée", "NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée", - "NotificationOptionInstallationFailed": "Échec d'installation", + "NotificationOptionInstallationFailed": "Échec de l'installation", "NotificationOptionNewLibraryContent": "Nouveau contenu ajouté", "NotificationOptionPluginError": "Erreur d'extension", "NotificationOptionPluginInstalled": "Extension installée", @@ -91,7 +91,7 @@ "UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}", "UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}", "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}", - "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre librairie", + "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque", "ValueSpecialEpisodeName": "Spécial - {0}", "VersionNumber": "Version {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 0ed998c4b..b08c8966e 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -1,41 +1,41 @@ { "Albums": "אלבומים", - "AppDeviceValues": "App: {0}, Device: {1}", + "AppDeviceValues": "יישום: {0}, מכשיר: {1}", "Application": "אפליקציה", "Artists": "אמנים", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", + "AuthenticationSucceededWithUserName": "{0} זוהה בהצלחה", "Books": "ספרים", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", + "CameraImageUploadedFrom": "תמונה חדשה הועלתה מ{0}", + "Channels": "ערוצים", + "ChapterNameValue": "פרק {0}", + "Collections": "קולקציות", + "DeviceOfflineWithName": "{0} התנתק", + "DeviceOnlineWithName": "{0} מחובר", + "FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי מ{0}", + "Favorites": "אהובים", + "Folders": "תיקיות", "Genres": "ז'אנרים", - "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "המשך בצפייה", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favorite Shows", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", + "HeaderAlbumArtists": "אמני האלבום", + "HeaderCameraUploads": "העלאות ממצלמה", + "HeaderContinueWatching": "המשך לצפות", + "HeaderFavoriteAlbums": "אלבומים שאהבתי", + "HeaderFavoriteArtists": "אמנים שאהבתי", + "HeaderFavoriteEpisodes": "פרקים אהובים", + "HeaderFavoriteShows": "תוכניות אהובות", + "HeaderFavoriteSongs": "שירים שאהבתי", + "HeaderLiveTV": "טלוויזיה בשידור חי", + "HeaderNextUp": "הבא", "HeaderRecordingGroups": "קבוצות הקלטה", - "HomeVideos": "Home videos", - "Inherit": "Inherit", + "HomeVideos": "סרטונים בייתים", + "Inherit": "הורש", "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", + "ItemRemovedWithName": "{0} נמחק מהספרייה", + "LabelIpAddressValue": "Ip כתובת: {0}", + "LabelRunningTimeValue": "משך צפייה: {0}", "Latest": "אחרון", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", + "MessageApplicationUpdated": "שרת הJellyfin עודכן", + "MessageApplicationUpdatedTo": "שרת הJellyfin עודכן לגרסא {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "הגדרת השרת {0} שונתה", "MessageServerConfigurationUpdated": "Server configuration has been updated", "MixedContent": "תוכן מעורב", "Movies": "סרטים", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", + "NotificationOptionInstallationFailed": "התקנה נכשלה", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", "NotificationOptionPluginInstalled": "Plugin installed", diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index a2fc126a2..3a6852321 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -5,7 +5,7 @@ "Artists": "Előadók", "AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva", "Books": "Könyvek", - "CameraImageUploadedFrom": "Új kamerakép került feltöltésre {0}", + "CameraImageUploadedFrom": "Új kamerakép került feltöltésre innen: {0}", "Channels": "Csatornák", "ChapterNameValue": "Jelenet {0}", "Collections": "Gyűjtemények", @@ -15,14 +15,14 @@ "Favorites": "Kedvencek", "Folders": "Könyvtárak", "Genres": "Műfajok", - "HeaderAlbumArtists": "Album Előadók", + "HeaderAlbumArtists": "Album előadók", "HeaderCameraUploads": "Kamera feltöltések", "HeaderContinueWatching": "Folyamatban lévő filmek", - "HeaderFavoriteAlbums": "Kedvenc Albumok", - "HeaderFavoriteArtists": "Kedvenc Előadók", - "HeaderFavoriteEpisodes": "Kedvenc Epizódok", - "HeaderFavoriteShows": "Kedvenc Sorozatok", - "HeaderFavoriteSongs": "Kedvenc Dalok", + "HeaderFavoriteAlbums": "Kedvenc albumok", + "HeaderFavoriteArtists": "Kedvenc előadók", + "HeaderFavoriteEpisodes": "Kedvenc epizódok", + "HeaderFavoriteShows": "Kedvenc sorozatok", + "HeaderFavoriteSongs": "Kedvenc dalok", "HeaderLiveTV": "Élő TV", "HeaderNextUp": "Következik", "HeaderRecordingGroups": "Felvételi csoportok", @@ -34,21 +34,21 @@ "LabelRunningTimeValue": "Futási idő: {0}", "Latest": "Legújabb", "MessageApplicationUpdated": "Jellyfin Szerver frissítve", - "MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész {0} frissítve", + "MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre: {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész frissítve: {0}", "MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve", "MixedContent": "Vegyes tartalom", "Movies": "Filmek", "Music": "Zene", - "MusicVideos": "Zenei Videók", + "MusicVideos": "Zenei videók", "NameInstallFailed": "{0} sikertelen telepítés", "NameSeasonNumber": "Évad {0}", "NameSeasonUnknown": "Ismeretlen évad", "NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.", - "NotificationOptionApplicationUpdateAvailable": "Új programfrissítés érhető el", - "NotificationOptionApplicationUpdateInstalled": "Programfrissítés telepítve", + "NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz", + "NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve", "NotificationOptionAudioPlayback": "Audió lejátszás elkezdve", - "NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve", + "NotificationOptionAudioPlaybackStopped": "Audió lejátszás leállítva", "NotificationOptionCameraImageUploaded": "Kamera kép feltöltve", "NotificationOptionInstallationFailed": "Telepítési hiba", "NotificationOptionNewLibraryContent": "Új tartalom hozzáadva", @@ -60,15 +60,15 @@ "NotificationOptionTaskFailed": "Ütemezett feladat hiba", "NotificationOptionUserLockedOut": "Felhasználó tiltva", "NotificationOptionVideoPlayback": "Videó lejátszás elkezdve", - "NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve", + "NotificationOptionVideoPlaybackStopped": "Videó lejátszás leállítva", "Photos": "Fényképek", "Playlists": "Lejátszási listák", "Plugin": "Bővítmény", "PluginInstalledWithName": "{0} telepítve", "PluginUninstalledWithName": "{0} eltávolítva", "PluginUpdatedWithName": "{0} frissítve", - "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} hiba", + "ProviderValue": "Szolgáltató: {0}", + "ScheduledTaskFailedWithName": "{0} sikertelen", "ScheduledTaskStartedWithName": "{0} elkezdve", "ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani", "Shows": "Műsorok", @@ -76,10 +76,10 @@ "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}", - "SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz {0}", + "SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz: {0}", "Sync": "Szinkronizál", "System": "Rendszer", - "TvShows": "TV Műsorok", + "TvShows": "TV műsorok", "User": "Felhasználó", "UserCreatedWithName": "{0} felhasználó létrehozva", "UserDeletedWithName": "{0} felhasználó törölve", @@ -88,7 +88,7 @@ "UserOfflineFromDevice": "{0} kijelentkezett innen: {1}", "UserOnlineFromDevice": "{0} online itt: {1}", "UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}", - "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett {0}", + "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}", "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}", "UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt: {2}", "ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz", diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 357883cd3..8f91effb9 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -9,13 +9,13 @@ "Channels": "Canali", "ChapterNameValue": "Capitolo {0}", "Collections": "Collezioni", - "DeviceOfflineWithName": "{0} è stato disconnesso", + "DeviceOfflineWithName": "{0} ha disconnesso", "DeviceOnlineWithName": "{0} è connesso", "FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}", "Favorites": "Preferiti", "Folders": "Cartelle", "Genres": "Generi", - "HeaderAlbumArtists": "Artisti Album", + "HeaderAlbumArtists": "Artisti dell' Album", "HeaderCameraUploads": "Caricamenti Fotocamera", "HeaderContinueWatching": "Continua a guardare", "HeaderFavoriteAlbums": "Album preferiti", @@ -32,7 +32,7 @@ "ItemRemovedWithName": "{0} è stato rimosso dalla libreria", "LabelIpAddressValue": "Indirizzo IP: {0}", "LabelRunningTimeValue": "Durata: {0}", - "Latest": "Più recenti", + "Latest": "Novità", "MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato", "MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}", "MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata", @@ -43,7 +43,7 @@ "MusicVideos": "Video musicali", "NameInstallFailed": "{0} installazione fallita", "NameSeasonNumber": "Stagione {0}", - "NameSeasonUnknown": "Stagione sconosciuto", + "NameSeasonUnknown": "Stagione sconosciuta", "NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.", "NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile", "NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato", @@ -88,9 +88,9 @@ "UserOfflineFromDevice": "{0} è stato disconnesso da {1}", "UserOnlineFromDevice": "{0} è online da {1}", "UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}", - "UserPolicyUpdatedWithName": "La politica dell'utente è stata aggiornata per {0}", - "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1}", - "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1}", + "UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}", + "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}", + "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}", "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale", "ValueSpecialEpisodeName": "Speciale - {0}", "VersionNumber": "Versione {0}" diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 24af1839f..637e514ed 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -1,7 +1,7 @@ { "Albums": "Albums", "AppDeviceValues": "App: {0}, Apparaat: {1}", - "Application": "Toepassing", + "Application": "Applicatie", "Artists": "Artiesten", "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", "Books": "Boeken", @@ -30,7 +30,7 @@ "Inherit": "Overerven", "ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek", "ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek", - "LabelIpAddressValue": "IP adres: {0}", + "LabelIpAddressValue": "IP-adres: {0}", "LabelRunningTimeValue": "Looptijd: {0}", "Latest": "Nieuwste", "MessageApplicationUpdated": "Jellyfin Server is bijgewerkt", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "Muziek gestart", "NotificationOptionAudioPlaybackStopped": "Muziek gestopt", "NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload", - "NotificationOptionInstallationFailed": "Installatie mislukt", + "NotificationOptionInstallationFailed": "Installatie mislukking", "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd", "NotificationOptionPluginError": "Plug-in fout", "NotificationOptionPluginInstalled": "Plug-in geïnstalleerd", diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index 6eade7942..75eecbe3e 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -5,7 +5,7 @@ "Artists": "Umelci", "AuthenticationSucceededWithUserName": "{0} úspešne overený", "Books": "Knihy", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Z {0} bola nahraná nová fotografia", "Channels": "Kanály", "ChapterNameValue": "Kapitola {0}", "Collections": "Zbierky", @@ -15,9 +15,9 @@ "Favorites": "Obľúbené", "Folders": "Priečinky", "Genres": "Žánre", - "HeaderAlbumArtists": "Album Artists", + "HeaderAlbumArtists": "Albumy umelcov", "HeaderCameraUploads": "Nahrané fotografie", - "HeaderContinueWatching": "Pokračujte v pozeraní", + "HeaderContinueWatching": "Pokračovať v pozeraní", "HeaderFavoriteAlbums": "Obľúbené albumy", "HeaderFavoriteArtists": "Obľúbení umelci", "HeaderFavoriteEpisodes": "Obľúbené epizódy", diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 3cc95e46e..eb1c2623f 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -5,93 +5,93 @@ "Artists": "Sanatçılar", "AuthenticationSucceededWithUserName": "{0} kimlik başarıyla doğrulandı", "Books": "Kitaplar", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "{0} 'den yeni bir kamera resmi yüklendi", "Channels": "Kanallar", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", - "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", + "ChapterNameValue": "Bölüm {0}", + "Collections": "Koleksiyonlar", + "DeviceOfflineWithName": "{0} bağlantısı kesildi", + "DeviceOnlineWithName": "{0} bağlı", + "FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu", + "Favorites": "Favoriler", + "Folders": "Klasörler", + "Genres": "Türler", + "HeaderAlbumArtists": "Albüm Sanatçıları", + "HeaderCameraUploads": "Kamera Yüklemeleri", "HeaderContinueWatching": "İzlemeye Devam Et", "HeaderFavoriteAlbums": "Favori Albümler", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favori Showlar", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip adresi: {0}", + "HeaderFavoriteArtists": "Favori Sanatçılar", + "HeaderFavoriteEpisodes": "Favori Bölümler", + "HeaderFavoriteShows": "Favori Diziler", + "HeaderFavoriteSongs": "Favori Şarkılar", + "HeaderLiveTV": "Canlı TV", + "HeaderNextUp": "Sonraki hafta", + "HeaderRecordingGroups": "Kayıt Grupları", + "HomeVideos": "Ev videoları", + "Inherit": "Devral", + "ItemAddedWithName": "{0} kütüphaneye eklendi", + "ItemRemovedWithName": "{0} kütüphaneden silindi", + "LabelIpAddressValue": "IP adresi: {0}", "LabelRunningTimeValue": "Çalışma süresi: {0}", - "Latest": "Latest", + "Latest": "En son", "MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", - "Movies": "Movies", + "MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} olarak güncellendi", + "MessageNamedServerConfigurationUpdatedWithValue": "Sunucu ayarları kısım {0} güncellendi", + "MessageServerConfigurationUpdated": "Sunucu ayarları güncellendi", + "MixedContent": "Karışık içerik", + "Movies": "Filmler", "Music": "Müzik", "MusicVideos": "Müzik videoları", - "NameInstallFailed": "{0} kurulum başarısız", + "NameInstallFailed": "{0} kurulumu başarısız", "NameSeasonNumber": "Sezon {0}", "NameSeasonUnknown": "Bilinmeyen Sezon", "NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Yükleme hatası", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", - "Playlists": "Playlists", - "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", - "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", - "Shows": "Shows", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut", + "NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi", + "NotificationOptionAudioPlayback": "Ses çalma başladı", + "NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu", + "NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi", + "NotificationOptionInstallationFailed": "Yükleme başarısız oldu", + "NotificationOptionNewLibraryContent": "Yeni içerik eklendi", + "NotificationOptionPluginError": "Eklenti hatası", + "NotificationOptionPluginInstalled": "Eklenti yüklendi", + "NotificationOptionPluginUninstalled": "Eklenti kaldırıldı", + "NotificationOptionPluginUpdateInstalled": "Eklenti güncellemesi yüklendi", + "NotificationOptionServerRestartRequired": "Sunucu yeniden başlatma gerekli", + "NotificationOptionTaskFailed": "Zamanlanmış görev hatası", + "NotificationOptionUserLockedOut": "Kullanıcı kitlendi", + "NotificationOptionVideoPlayback": "Video oynatma başladı", + "NotificationOptionVideoPlaybackStopped": "Video oynatma durduruldu", + "Photos": "Fotoğraflar", + "Playlists": "Çalma listeleri", + "Plugin": "Eklenti", + "PluginInstalledWithName": "{0} yüklendi", + "PluginUninstalledWithName": "{0} kaldırıldı", + "PluginUpdatedWithName": "{0} güncellendi", + "ProviderValue": "Sağlayıcı: {0}", + "ScheduledTaskFailedWithName": "{0} başarısız oldu", + "ScheduledTaskStartedWithName": "{0} başladı", + "ServerNameNeedsToBeRestarted": "{0} yeniden başlatılması gerekiyor", + "Shows": "Diziler", + "Songs": "Şarkılar", + "StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", - "System": "System", - "TvShows": "TV Shows", - "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", - "ValueSpecialEpisodeName": "Special - {0}", - "VersionNumber": "Version {0}" + "SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi", + "SubtitlesDownloadedForItem": "{0} için altyazılar indirildi", + "Sync": "Eşitle", + "System": "Sistem", + "TvShows": "Diziler", + "User": "Kullanıcı", + "UserCreatedWithName": "{0} kullanıcısı oluşturuldu", + "UserDeletedWithName": "Kullanıcı {0} silindi", + "UserDownloadingItemWithValues": "{0} indiriliyor {1}", + "UserLockedOutWithName": "Kullanıcı {0} kitlendi", + "UserOfflineFromDevice": "{0}, {1} ile bağlantısı kesildi", + "UserOnlineFromDevice": "{0}, {1} çevrimiçi", + "UserPasswordChangedWithName": "{0} kullanıcısı için şifre değiştirildi", + "UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi", + "UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor", + "UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi", + "ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi", + "ValueSpecialEpisodeName": "Özel - {0}", + "VersionNumber": "Versiyon {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index ba5e93982..87f8553ae 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -5,7 +5,7 @@ "Artists": "艺术家", "AuthenticationSucceededWithUserName": "{0} 认证成功", "Books": "书籍", - "CameraImageUploadedFrom": "已从 {0} 上传了一张新的相机图像", + "CameraImageUploadedFrom": "已从 {0} 上传了一张新的相片", "Channels": "频道", "ChapterNameValue": "章节 {0}", "Collections": "合集", diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 387fc2b92..33fcb2d37 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -2,7 +2,7 @@ "Albums": "Albums", "AppDeviceValues": "App: {0}, Device: {1}", "Application": "Application", - "Artists": "Artists", + "Artists": "藝人", "AuthenticationSucceededWithUserName": "{0} successfully authenticated", "Books": "Books", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index 293fc28a8..33bdbfb98 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -1,6 +1,6 @@ { "Albums": "專輯", - "AppDeviceValues": "應用: {0}, 裝置: {1}", + "AppDeviceValues": "軟體: {0}, 裝置: {1}", "Application": "應用程式", "Artists": "演出者", "AuthenticationSucceededWithUserName": "{0} 成功授權", diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 13cdc50ca..bda43e832 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -5,11 +5,9 @@ using System.Globalization; using System.IO; using System.Linq; using System.Reflection; -using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs index 268bf4042..fda32da5e 100644 --- a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs +++ b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs @@ -27,12 +27,12 @@ namespace Emby.Server.Implementations.Middleware var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); if (webSocketContext != null) { - await _webSocketManager.OnWebSocketConnected(webSocketContext); + await _webSocketManager.OnWebSocketConnected(webSocketContext).ConfigureAwait(false); } } else { - await _next.Invoke(httpContext); + await _next.Invoke(httpContext).ConfigureAwait(false); } } } diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 7d85a0666..1d8d3cf39 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -7,8 +7,6 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading.Tasks; using MediaBrowser.Common.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Networking @@ -20,6 +18,9 @@ namespace Emby.Server.Implementations.Networking private IPAddress[] _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); + private readonly object _subnetLookupLock = new object(); + private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal); + public NetworkManager(ILogger<NetworkManager> logger) { _logger = logger; @@ -28,10 +29,10 @@ namespace Emby.Server.Implementations.Networking NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; } - public Func<string[]> LocalSubnetsFn { get; set; } - public event EventHandler NetworkChanged; + public Func<string[]> LocalSubnetsFn { get; set; } + private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e) { _logger.LogDebug("NetworkAvailabilityChanged"); @@ -52,10 +53,7 @@ namespace Emby.Server.Implementations.Networking _macAddresses = null; } - if (NetworkChanged != null) - { - NetworkChanged(this, EventArgs.Empty); - } + NetworkChanged?.Invoke(this, EventArgs.Empty); } public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) @@ -179,10 +177,9 @@ namespace Emby.Server.Implementations.Networking return false; } - private Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal); private List<string> GetSubnets(string endpointFirstPart) { - lock (_subnetLookup) + lock (_subnetLookupLock) { if (_subnetLookup.TryGetValue(endpointFirstPart, out var subnets)) { @@ -200,7 +197,11 @@ namespace Emby.Server.Implementations.Networking int subnet_Test = 0; foreach (string part in unicastIPAddressInformation.IPv4Mask.ToString().Split('.')) { - if (part.Equals("0")) break; + if (part.Equals("0", StringComparison.Ordinal)) + { + break; + } + subnet_Test++; } @@ -255,10 +256,10 @@ namespace Emby.Server.Implementations.Networking return true; } - if (normalizedSubnet.IndexOf('/') != -1) + if (normalizedSubnet.Contains('/', StringComparison.Ordinal)) { - var ipnetwork = IPNetwork.Parse(normalizedSubnet); - if (ipnetwork.Contains(address)) + var ipNetwork = IPNetwork.Parse(normalizedSubnet); + if (ipNetwork.Contains(address)) { return true; } @@ -447,119 +448,11 @@ namespace Emby.Server.Implementations.Networking .Select(x => x.GetPhysicalAddress()) .Where(x => x != null && x != PhysicalAddress.None); - /// <summary> - /// Parses the specified endpointstring. - /// </summary> - /// <param name="endpointstring">The endpointstring.</param> - /// <returns>IPEndPoint.</returns> - public IPEndPoint Parse(string endpointstring) - { - return Parse(endpointstring, -1).Result; - } - - /// <summary> - /// Parses the specified endpointstring. - /// </summary> - /// <param name="endpointstring">The endpointstring.</param> - /// <param name="defaultport">The defaultport.</param> - /// <returns>IPEndPoint.</returns> - /// <exception cref="ArgumentException">Endpoint descriptor may not be empty.</exception> - /// <exception cref="FormatException"></exception> - private static async Task<IPEndPoint> Parse(string endpointstring, int defaultport) - { - if (string.IsNullOrEmpty(endpointstring) - || endpointstring.Trim().Length == 0) - { - throw new ArgumentException("Endpoint descriptor may not be empty."); - } - - if (defaultport != -1 && - (defaultport < IPEndPoint.MinPort - || defaultport > IPEndPoint.MaxPort)) - { - throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport)); - } - - string[] values = endpointstring.Split(new char[] { ':' }); - IPAddress ipaddy; - int port = -1; - - //check if we have an IPv6 or ports - if (values.Length <= 2) // ipv4 or hostname - { - port = values.Length == 1 ? defaultport : GetPort(values[1]); - - //try to use the address as IPv4, otherwise get hostname - if (!IPAddress.TryParse(values[0], out ipaddy)) - ipaddy = await GetIPfromHost(values[0]).ConfigureAwait(false); - } - else if (values.Length > 2) //ipv6 - { - //could [a:b:c]:d - if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]")) - { - string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray()); - ipaddy = IPAddress.Parse(ipaddressstring); - port = GetPort(values[values.Length - 1]); - } - else //[a:b:c] or a:b:c - { - ipaddy = IPAddress.Parse(endpointstring); - port = defaultport; - } - } - else - { - throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring)); - } - - if (port == -1) - throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring)); - - return new IPEndPoint(ipaddy, port); - } - - protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - /// <summary> - /// Gets the port. - /// </summary> - /// <param name="p">The p.</param> - /// <returns>System.Int32.</returns> - /// <exception cref="FormatException"></exception> - private static int GetPort(string p) - { - if (!int.TryParse(p, out var port) - || port < IPEndPoint.MinPort - || port > IPEndPoint.MaxPort) - { - throw new FormatException(string.Format("Invalid end point port '{0}'", p)); - } - - return port; - } - - /// <summary> - /// Gets the I pfrom host. - /// </summary> - /// <param name="p">The p.</param> - /// <returns>IPAddress.</returns> - /// <exception cref="ArgumentException"></exception> - private static async Task<IPAddress> GetIPfromHost(string p) - { - var hosts = await Dns.GetHostAddressesAsync(p).ConfigureAwait(false); - - if (hosts == null || hosts.Length == 0) - throw new ArgumentException(string.Format("Host not found: {0}", p)); - - return hosts[0]; - } - public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask) { - IPAddress network1 = GetNetworkAddress(address1, subnetMask); - IPAddress network2 = GetNetworkAddress(address2, subnetMask); - return network1.Equals(network2); + IPAddress network1 = GetNetworkAddress(address1, subnetMask); + IPAddress network2 = GetNetworkAddress(address2, subnetMask); + return network1.Equals(network2); } private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask) @@ -575,7 +468,7 @@ namespace Emby.Server.Implementations.Networking byte[] broadcastAddress = new byte[ipAdressBytes.Length]; for (int i = 0; i < broadcastAddress.Length; i++) { - broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i])); + broadcastAddress[i] = (byte)(ipAdressBytes[i] & subnetMaskBytes[i]); } return new IPAddress(broadcastAddress); @@ -615,24 +508,5 @@ namespace Emby.Server.Implementations.Networking return null; } - - /// <summary> - /// Gets the network shares. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>IEnumerable{NetworkShare}.</returns> - public virtual IEnumerable<NetworkShare> GetNetworkShares(string path) - { - return new List<NetworkShare>(); - } - - /// <summary> - /// Gets available devices within the domain - /// </summary> - /// <returns>PC's in the Domain</returns> - public virtual IEnumerable<FileSystemEntryInfo> GetNetworkDevices() - { - return new List<FileSystemEntryInfo>(); - } } } diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index f4decc856..cd9f7946e 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; namespace Emby.Server.Implementations.Playlists { @@ -24,13 +24,13 @@ namespace Emby.Server.Implementations.Playlists return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>(); } - [IgnoreDataMember] + [JsonIgnore] public override bool IsHidden => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; - [IgnoreDataMember] + [JsonIgnore] public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists; protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query) @@ -48,4 +48,3 @@ namespace Emby.Server.Implementations.Playlists } } } - diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 40b568b40..0f58e43ed 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -90,8 +90,7 @@ namespace Emby.Server.Implementations.Playlists } else { - var folder = item as Folder; - if (folder != null) + if (item is Folder folder) { options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) .Select(i => i.MediaType) @@ -140,7 +139,7 @@ namespace Emby.Server.Implementations.Playlists parentFolder.AddChild(playlist, CancellationToken.None); - await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) { ForceSave = true }, CancellationToken.None) + await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None) .ConfigureAwait(false); if (options.ItemIdList.Length > 0) @@ -201,7 +200,7 @@ namespace Emby.Server.Implementations.Playlists var list = new List<LinkedChild>(); - var items = (GetPlaylistItems(itemIds, playlist.MediaType, user, options)) + var items = GetPlaylistItems(itemIds, playlist.MediaType, user, options) .Where(i => i.SupportsAddingToPlaylist) .ToList(); @@ -221,18 +220,18 @@ namespace Emby.Server.Implementations.Playlists SavePlaylistFile(playlist); } - _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - ForceSave = true - - }, RefreshPriority.High); + _providerManager.QueueRefresh( + playlist.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ForceSave = true + }, + RefreshPriority.High); } public void RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds) { - var playlist = _libraryManager.GetItemById(playlistId) as Playlist; - - if (playlist == null) + if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist)) { throw new ArgumentException("No Playlist exists with the supplied Id"); } @@ -254,7 +253,7 @@ namespace Emby.Server.Implementations.Playlists SavePlaylistFile(playlist); } - _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true @@ -263,9 +262,7 @@ namespace Emby.Server.Implementations.Playlists public void MoveItem(string playlistId, string entryId, int newIndex) { - var playlist = _libraryManager.GetItemById(playlistId) as Playlist; - - if (playlist == null) + if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist)) { throw new ArgumentException("No Playlist exists with the supplied Id"); } diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 83226b07f..5b188d962 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public event EventHandler<GenericEventArgs<double>> TaskProgress; /// <summary> - /// Gets or sets the scheduled task. + /// Gets the scheduled task. /// </summary> /// <value>The scheduled task.</value> public IScheduledTask ScheduledTask { get; private set; } @@ -215,11 +215,12 @@ namespace Emby.Server.Implementations.ScheduledTasks public double? CurrentProgress { get; private set; } /// <summary> - /// The _triggers + /// The _triggers. /// </summary> private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers; + /// <summary> - /// Gets the triggers that define when the task will run + /// Gets the triggers that define when the task will run. /// </summary> /// <value>The triggers.</value> private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers @@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Gets the triggers that define when the task will run + /// Gets the triggers that define when the task will run. /// </summary> /// <value>The triggers.</value> /// <exception cref="ArgumentNullException">value</exception> diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 595c3037d..ecf58dbc0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -36,19 +36,19 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Gets or sets the json serializer. /// </summary> /// <value>The json serializer.</value> - private IJsonSerializer JsonSerializer { get; set; } + private readonly IJsonSerializer _jsonSerializer; /// <summary> /// Gets or sets the application paths. /// </summary> /// <value>The application paths.</value> - private IApplicationPaths ApplicationPaths { get; set; } + private readonly IApplicationPaths _applicationPaths; /// <summary> /// Gets the logger. /// </summary> /// <value>The logger.</value> - private ILogger Logger { get; set; } + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; /// <summary> @@ -57,19 +57,19 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <param name="applicationPaths">The application paths.</param> /// <param name="jsonSerializer">The json serializer.</param> /// <param name="loggerFactory">The logger factory.</param> - /// <exception cref="System.ArgumentException">kernel</exception> + /// <param name="fileSystem">The filesystem manager.</param> public TaskManager( IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory, IFileSystem fileSystem) { - ApplicationPaths = applicationPaths; - JsonSerializer = jsonSerializer; - Logger = loggerFactory.CreateLogger(nameof(TaskManager)); + _applicationPaths = applicationPaths; + _jsonSerializer = jsonSerializer; + _logger = loggerFactory.CreateLogger(nameof(TaskManager)); _fileSystem = fileSystem; - ScheduledTasks = new IScheduledTaskWorker[] { }; + ScheduledTasks = Array.Empty<IScheduledTaskWorker>(); } /// <summary> @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <typeparam name="T"></typeparam> /// <param name="options">Task options.</param> public void CancelIfRunningAndQueue<T>(TaskOptions options) - where T : IScheduledTask + where T : IScheduledTask { var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); ((ScheduledTaskWorker)task).CancelIfRunning(); @@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.ScheduledTasks if (scheduledTask == null) { - Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name); + _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name); } else { @@ -147,13 +147,13 @@ namespace Emby.Server.Implementations.ScheduledTasks if (scheduledTask == null) { - Logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name); + _logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name); } else { var type = scheduledTask.ScheduledTask.GetType(); - Logger.LogInformation("Queueing task {0}", type.Name); + _logger.LogInformation("Queueing task {0}", type.Name); lock (_taskQueue) { @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.ScheduledTasks if (scheduledTask == null) { - Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name); + _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name); } else { @@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { var type = task.ScheduledTask.GetType(); - Logger.LogInformation("Queueing task {0}", type.Name); + _logger.LogInformation("Queueing task {0}", type.Name); lock (_taskQueue) { @@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <param name="tasks">The tasks.</param> public void AddTasks(IEnumerable<IScheduledTask> tasks) { - var list = tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)); + var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger, _fileSystem)); ScheduledTasks = ScheduledTasks.Concat(list).ToArray(); } @@ -281,7 +281,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> private void ExecuteQueuedTasks() { - Logger.LogInformation("ExecuteQueuedTasks"); + _logger.LogInformation("ExecuteQueuedTasks"); // Execute queued tasks lock (_taskQueue) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 2f07ff15a..ecd526251 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -19,16 +19,17 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Class ChapterImagesTask + /// Class ChapterImagesTask. /// </summary> public class ChapterImagesTask : IScheduledTask { /// <summary> - /// The _logger + /// The _logger. /// </summary> private readonly ILogger _logger; + /// <summary> - /// The _library manager + /// The _library manager. /// </summary> private readonly ILibraryManager _libraryManager; @@ -53,12 +54,12 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// </summary> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] { - + return new[] + { new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerDaily, @@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.ScheduledTasks previouslyFailedImages = new List<string>(); } - var directoryService = new DirectoryService(_logger, _fileSystem); + var directoryService = new DirectoryService(_fileSystem); foreach (var video in videos) { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index c343a7d48..72b524df0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -15,24 +16,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// </summary> public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask { - /// <summary> - /// Gets or sets the application paths. - /// </summary> - /// <value>The application paths.</value> - private ServerApplicationPaths ApplicationPaths { get; set; } - private readonly ILogger _logger; - + private readonly IConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; /// <summary> /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class. /// </summary> - public DeleteTranscodeFileTask(ServerApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) + public DeleteTranscodeFileTask(ILogger logger, IFileSystem fileSystem, IConfigurationManager configurationManager) { - ApplicationPaths = appPaths; _logger = logger; _fileSystem = fileSystem; + _configurationManager = configurationManager; } /// <summary> @@ -52,14 +47,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks var minDateModified = DateTime.UtcNow.AddDays(-1); progress.Report(50); - try - { - DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress); - } - catch (DirectoryNotFoundException) - { - // No biggie here. Nothing to delete - } + DeleteTempFilesFromDirectory(cancellationToken, _configurationManager.GetTranscodePath(), minDateModified, progress); return Task.CompletedTask; } @@ -138,13 +126,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } - public string Name => "Transcoding temp cleanup"; + public string Name => "Transcode file cleanup"; - public string Description => "Deletes transcoding temp files older than 24 hours."; + public string Description => "Deletes transcode files more than 24 hours old."; public string Category => "Maintenance"; - public string Key => "DeleteTranscodingTempFiles"; + public string Key => "DeleteTranscodeFiles"; public bool IsHidden => false; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index 7afeba9dd..909fffb1f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -1,24 +1,23 @@ -using MediaBrowser.Common.Updates; -using MediaBrowser.Model.Net; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Progress; +using MediaBrowser.Common.Updates; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Plugin Update Task + /// Plugin Update Task. /// </summary> public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask { /// <summary> - /// The _logger + /// The _logger. /// </summary> private readonly ILogger _logger; @@ -31,7 +30,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// </summary> /// <returns>IEnumerable{BaseTaskTrigger}.</returns> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() @@ -44,16 +43,18 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Update installed plugins + /// Update installed plugins. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> + /// <returns><see cref="Task" />.</returns> public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { progress.Report(0); - var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(typeof(PluginUpdateTask).Assembly.GetName().Version, true, cancellationToken).ConfigureAwait(false)).ToList(); + var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken) + .ToListAsync(cancellationToken) + .ConfigureAwait(false); progress.Report(10); @@ -94,18 +95,25 @@ namespace Emby.Server.Implementations.ScheduledTasks progress.Report(100); } + /// <inheritdoc /> public string Name => "Check for plugin updates"; + /// <inheritdoc /> public string Description => "Downloads and installs updates for plugins that are configured to update automatically."; + /// <inheritdoc /> public string Category => "Application"; + /// <inheritdoc /> public string Key => "PluginUpdates"; + /// <inheritdoc /> public bool IsHidden => false; + /// <inheritdoc /> public bool IsEnabled => true; + /// <inheritdoc /> public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index ec9466c4a..ea278de0d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Threading; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -7,12 +6,12 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Represents a task trigger that fires everyday + /// Represents a task trigger that fires everyday. /// </summary> public class DailyTrigger : ITaskTrigger { /// <summary> - /// Get the time of day to trigger the task to run + /// Get the time of day to trigger the task to run. /// </summary> /// <value>The time of day.</value> public TimeSpan TimeOfDay { get; set; } diff --git a/Emby.Server.Implementations/Serialization/XmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs index 6400ec16e..296822981 100644 --- a/Emby.Server.Implementations/Serialization/XmlSerializer.cs +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.IO; using System.Xml; using System.Xml.Serialization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Serialization { @@ -14,35 +12,13 @@ namespace Emby.Server.Implementations.Serialization /// </summary> public class MyXmlSerializer : IXmlSerializer { - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - - public MyXmlSerializer( - IFileSystem fileSystem, - ILoggerFactory loggerFactory) - { - _fileSystem = fileSystem; - _logger = loggerFactory.CreateLogger("XmlSerializer"); - } - // Need to cache these // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html - private readonly Dictionary<string, XmlSerializer> _serializers = - new Dictionary<string, XmlSerializer>(); + private static readonly ConcurrentDictionary<string, XmlSerializer> _serializers = + new ConcurrentDictionary<string, XmlSerializer>(); - private XmlSerializer GetSerializer(Type type) - { - var key = type.FullName; - lock (_serializers) - { - if (!_serializers.TryGetValue(key, out var serializer)) - { - serializer = new XmlSerializer(type); - _serializers[key] = serializer; - } - return serializer; - } - } + private static XmlSerializer GetSerializer(Type type) + => _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type)); /// <summary> /// Serializes to writer. @@ -91,7 +67,6 @@ namespace Emby.Server.Implementations.Serialization /// <param name="file">The file.</param> public void SerializeToFile(object obj, string file) { - _logger.LogDebug("Serializing to file {0}", file); using (var stream = new FileStream(file, FileMode.Create)) { SerializeToStream(obj, stream); @@ -106,7 +81,6 @@ namespace Emby.Server.Implementations.Serialization /// <returns>System.Object.</returns> public object DeserializeFromFile(Type type, string file) { - _logger.LogDebug("Deserializing file {0}", file); using (var stream = File.OpenRead(file)) { return DeserializeFromStream(type, stream); diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 2f5a8af80..2f57c97a1 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using Emby.Server.Implementations.AppBase; using MediaBrowser.Controller; @@ -6,12 +5,10 @@ using MediaBrowser.Controller; namespace Emby.Server.Implementations { /// <summary> - /// Extends BaseApplicationPaths to add paths that are only applicable on the server + /// Extends BaseApplicationPaths to add paths that are only applicable on the server. /// </summary> public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths { - private string _defaultTranscodingTempPath; - private string _transcodingTempPath; private string _internalMetadataPath; /// <summary> @@ -23,7 +20,8 @@ namespace Emby.Server.Implementations string configurationDirectoryPath, string cacheDirectoryPath, string webDirectoryPath) - : base(programDataPath, + : base( + programDataPath, logDirectoryPath, configurationDirectoryPath, cacheDirectoryPath, @@ -31,8 +29,6 @@ namespace Emby.Server.Implementations { } - public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory; - /// <summary> /// Gets the path to the base root media directory. /// </summary> @@ -46,17 +42,12 @@ namespace Emby.Server.Implementations public string DefaultUserViewsPath => Path.Combine(RootFolderPath, "default"); /// <summary> - /// Gets the path to localization data. - /// </summary> - /// <value>The localization path.</value> - public string LocalizationPath => Path.Combine(ProgramDataPath, "localization"); - - /// <summary> /// Gets the path to the People directory. /// </summary> /// <value>The people path.</value> public string PeoplePath => Path.Combine(InternalMetadataPath, "People"); + /// <inheritdoc /> public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists"); /// <summary> @@ -107,46 +98,14 @@ namespace Emby.Server.Implementations /// <value>The user configuration directory path.</value> public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users"); - public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp")); - - public string TranscodingTempPath - { - get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath); - set => _transcodingTempPath = value; - } - - public string GetTranscodingTempPath() - { - var path = TranscodingTempPath; - - if (!string.Equals(path, DefaultTranscodingTempPath, StringComparison.OrdinalIgnoreCase)) - { - try - { - Directory.CreateDirectory(path); - - var testPath = Path.Combine(path, Guid.NewGuid().ToString()); - Directory.CreateDirectory(testPath); - Directory.Delete(testPath); - - return path; - } - catch - { - } - } - - path = DefaultTranscodingTempPath; - Directory.CreateDirectory(path); - return path; - } - + /// <inheritdoc /> public string InternalMetadataPath { get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata")); set => _internalMetadataPath = value; } + /// <inheritdoc /> public string VirtualInternalMetadataPath { get; } = "%MetadataPath%"; } } diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index ccb28e8df..27c4dcba0 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; +using System.Text.Json.Serialization; namespace Emby.Server.Implementations.Services { @@ -28,6 +30,13 @@ namespace Emby.Server.Implementations.Services private readonly bool[] isWildcard; private readonly int wildcardCount = 0; + internal static string[] IgnoreAttributesNamed = new[] + { + nameof(JsonIgnoreAttribute) + }; + + private static Type _excludeType = typeof(Stream); + public int VariableArgsCount { get; set; } /// <summary> @@ -37,8 +46,8 @@ namespace Emby.Server.Implementations.Services public int PathComponentsCount { get; set; } /// <summary> - /// The total number of segments after subparts have been exploded ('.') - /// e.g. /path/to/here.ext == 4 + /// Gets or sets the total number of segments after subparts have been exploded ('.') + /// e.g. /path/to/here.ext == 4. /// </summary> public int TotalComponentsCount { get; set; } @@ -190,21 +199,12 @@ namespace Emby.Server.Implementations.Services StringComparer.OrdinalIgnoreCase); } - internal static string[] IgnoreAttributesNamed = new[] - { - "IgnoreDataMemberAttribute", - "JsonIgnoreAttribute" - }; - - - private static Type excludeType = typeof(Stream); - internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type) { foreach (var prop in GetPublicProperties(type)) { if (prop.GetMethod == null - || excludeType == prop.PropertyType) + || _excludeType == prop.PropertyType) { continue; } @@ -280,7 +280,7 @@ namespace Emby.Server.Implementations.Services } /// <summary> - /// Provide for quick lookups based on hashes that can be determined from a request url + /// Provide for quick lookups based on hashes that can be determined from a request url. /// </summary> public string FirstMatchHashKey { get; private set; } @@ -437,9 +437,12 @@ namespace Emby.Server.Implementations.Services && requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount; if (!isValidWildCardPath) - throw new ArgumentException(string.Format( - "Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'", - pathInfo, this.restPath)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'", + pathInfo, + this.restPath)); } var requestKeyValuesMap = new Dictionary<string, string>(); diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index d22386436..c30f32af9 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Net; @@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.Services private SwaggerTag[] GetTags() { - return new SwaggerTag[] { }; + return Array.Empty<SwaggerTag>(); } private Dictionary<string, SwaggerDefinition> GetDefinitions() diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index 1104a7a85..dfb81816c 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.Session return SendMessage(command.Command.ToString(), messageId, args, cancellationToken); } - private string[] _supportedMessages = new string[] { }; + private string[] _supportedMessages = Array.Empty<string>(); public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) { if (!IsSessionActive) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 68cd3c0ba..b87ca3a11 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1386,27 +1386,28 @@ namespace Emby.Server.Implementations.Session if (user != null) { // TODO: Move this to userManager? - if (!string.IsNullOrEmpty(request.DeviceId)) + if (!string.IsNullOrEmpty(request.DeviceId) + && !_deviceManager.CanAccessDevice(user, request.DeviceId)) { - if (!_deviceManager.CanAccessDevice(user, request.DeviceId)) - { - throw new SecurityException("User is not allowed access from this device."); - } + throw new SecurityException("User is not allowed access from this device."); } } if (enforcePassword) { - var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.RemoteEndPoint, true).ConfigureAwait(false); - - if (result == null) - { - AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request)); + user = await _userManager.AuthenticateUser( + request.Username, + request.Password, + request.PasswordSha1, + request.RemoteEndPoint, + true).ConfigureAwait(false); + } - throw new SecurityException("Invalid user or password entered."); - } + if (user == null) + { + AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request)); - user = result; + throw new SecurityException("Invalid user or password entered."); } var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 63ec75762..930f2d35d 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -67,7 +66,7 @@ namespace Emby.Server.Implementations.Session { if (queryString == null) { - throw new ArgumentNullException(nameof(queryString)); + return null; } var token = queryString["api_key"]; @@ -75,6 +74,7 @@ namespace Emby.Server.Implementations.Session { return null; } + var deviceId = queryString["deviceId"]; return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); } diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 62b16ed8c..67521d6c6 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.SocketSharp } /// <summary> - /// Gets or sets the state. + /// Gets the state. /// </summary> /// <value>The state.</value> public WebSocketState State => _webSocket.State; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index e93bff124..ba5ba1904 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -81,8 +81,10 @@ namespace Emby.Server.Implementations.SocketSharp if (webSocketContext.State == WebSocketState.Open) { - await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, _disposeCancellationToken); + await webSocketContext.CloseAsync( + result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, + _disposeCancellationToken).ConfigureAwait(false); } socket.Dispose(); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 332ce3903..1781df8b5 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Net; -using System.Linq; +using System.Net.Mime; using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -13,11 +13,11 @@ using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; namespace Emby.Server.Implementations.SocketSharp { - public partial class WebSocketSharpRequest : IHttpRequest + public class WebSocketSharpRequest : IHttpRequest { - public const string FormUrlEncoded = "application/x-www-form-urlencoded"; - public const string MultiPartFormData = "multipart/form-data"; - public const string Soap11 = "text/xml; charset=utf-8"; + private const string FormUrlEncoded = "application/x-www-form-urlencoded"; + private const string MultiPartFormData = "multipart/form-data"; + private const string Soap11 = "text/xml; charset=utf-8"; private string _remoteIp; private Dictionary<string, object> _items; @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.SocketSharp get => _responseContentType ?? (_responseContentType = GetResponseContentType(Request)); - set => this._responseContentType = value; + set => _responseContentType = value; } public string PathInfo => Request.Path.Value; @@ -91,7 +91,6 @@ namespace Emby.Server.Implementations.SocketSharp public bool IsLocal => Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress); - public string HttpMethod => Request.Method; public string Verb => HttpMethod; @@ -124,24 +123,29 @@ namespace Emby.Server.Implementations.SocketSharp return specifiedContentType; } - const string serverDefaultContentType = "application/json"; + const string ServerDefaultContentType = MediaTypeNames.Application.Json; var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); string defaultContentType = null; if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) { - defaultContentType = serverDefaultContentType; + defaultContentType = ServerDefaultContentType; } var acceptsAnything = false; var hasDefaultContentType = defaultContentType != null; if (acceptContentTypes != null) { - foreach (var acceptsType in acceptContentTypes) + foreach (ReadOnlySpan<char> acceptsType in acceptContentTypes) { - // TODO: @bond move to Span when Span.Split lands - // https://github.com/dotnet/corefx/issues/26528 - var contentType = acceptsType?.Split(';')[0].Trim(); + ReadOnlySpan<char> contentType = acceptsType; + var index = contentType.IndexOf(';'); + if (index != -1) + { + contentType = contentType.Slice(0, index); + } + + contentType = contentType.Trim(); acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase); if (acceptsAnything) @@ -158,7 +162,7 @@ namespace Emby.Server.Implementations.SocketSharp } else { - return serverDefaultContentType; + return ServerDefaultContentType; } } } @@ -169,7 +173,7 @@ namespace Emby.Server.Implementations.SocketSharp } // We could also send a '406 Not Acceptable', but this is allowed also - return serverDefaultContentType; + return ServerDefaultContentType; } public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes) @@ -197,12 +201,12 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { - ReadOnlySpan<char> format = httpReq.Query["format"].ToString().AsSpan(); + ReadOnlySpan<char> format = httpReq.Query["format"].ToString(); if (format == null) { - const int formatMaxLength = 4; - ReadOnlySpan<char> pi = httpReq.Path.ToString().AsSpan(); - if (pi == null || pi.Length <= formatMaxLength) + const int FormatMaxLength = 4; + ReadOnlySpan<char> pi = httpReq.Path.ToString(); + if (pi == null || pi.Length <= FormatMaxLength) { return null; } @@ -213,18 +217,18 @@ namespace Emby.Server.Implementations.SocketSharp } format = LeftPart(pi, '/'); - if (format.Length > formatMaxLength) + if (format.Length > FormatMaxLength) { return null; } } format = LeftPart(format, '.'); - if (format.Contains("json".AsSpan(), StringComparison.OrdinalIgnoreCase)) + if (format.Contains("json", StringComparison.OrdinalIgnoreCase)) { return "application/json"; } - else if (format.Contains("xml".AsSpan(), StringComparison.OrdinalIgnoreCase)) + else if (format.Contains("xml", StringComparison.OrdinalIgnoreCase)) { return "application/xml"; } diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index 10fa4359a..4eb1549f5 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -19,10 +19,14 @@ namespace Emby.Server.Implementations.Sorting public int Compare(BaseItem x, BaseItem y) { if (x == null) + { throw new ArgumentNullException(nameof(x)); + } if (y == null) + { throw new ArgumentNullException(nameof(y)); + } return string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase); } diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index e8fa8edc8..7afbd9ff7 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -24,10 +24,14 @@ namespace Emby.Server.Implementations.Sorting public int Compare(BaseItem x, BaseItem y) { if (x == null) + { throw new ArgumentNullException(nameof(x)); + } if (y == null) + { throw new ArgumentNullException(nameof(y)); + } var levelX = string.IsNullOrEmpty(x.OfficialRating) ? 0 : _localization.GetRatingLevel(x.OfficialRating) ?? 0; var levelY = string.IsNullOrEmpty(y.OfficialRating) ? 0 : _localization.GetRatingLevel(y.OfficialRating) ?? 0; diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index 617ed55d5..c9ac765c1 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -17,10 +17,15 @@ namespace Emby.Server.Implementations.Sorting public int Compare(BaseItem x, BaseItem y) { if (x == null) + { throw new ArgumentNullException(nameof(x)); + } if (y == null) + { throw new ArgumentNullException(nameof(y)); + } + return AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty); } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 0c0c77cda..09a5a0dca 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -19,52 +19,18 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; using Microsoft.Extensions.Logging; -using static MediaBrowser.Common.HexHelper; namespace Emby.Server.Implementations.Updates { /// <summary> - /// Manages all install, uninstall and update operations (both plugins and system) + /// Manages all install, uninstall and update operations (both plugins and system). /// </summary> public class InstallationManager : IInstallationManager { - public event EventHandler<InstallationEventArgs> PackageInstalling; - public event EventHandler<InstallationEventArgs> PackageInstallationCompleted; - public event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed; - public event EventHandler<InstallationEventArgs> PackageInstallationCancelled; - - /// <summary> - /// The current installations - /// </summary> - private List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations { get; set; } - /// <summary> - /// The completed installations - /// </summary> - private ConcurrentBag<InstallationInfo> _completedInstallationsInternal; - - public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal; - - /// <summary> - /// Occurs when [plugin uninstalled]. - /// </summary> - public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled; - - /// <summary> - /// Occurs when [plugin updated]. - /// </summary> - public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated; - - /// <summary> - /// Occurs when [plugin updated]. - /// </summary> - public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled; - - /// <summary> - /// The _logger + /// The _logger. /// </summary> private readonly ILogger _logger; - private readonly IApplicationPaths _appPaths; private readonly IHttpClient _httpClient; private readonly IJsonSerializer _jsonSerializer; @@ -79,6 +45,18 @@ namespace Emby.Server.Implementations.Updates private readonly IZipClient _zipClient; + private readonly object _currentInstallationsLock = new object(); + + /// <summary> + /// The current installations. + /// </summary> + private readonly List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations; + + /// <summary> + /// The completed installations. + /// </summary> + private readonly ConcurrentBag<InstallationInfo> _completedInstallationsInternal; + public InstallationManager( ILogger<InstallationManager> logger, IApplicationHost appHost, @@ -107,26 +85,32 @@ namespace Emby.Server.Implementations.Updates _zipClient = zipClient; } - /// <summary> - /// Gets all available packages. - /// </summary> - /// <returns>Task{List{PackageInfo}}.</returns> - public async Task<List<PackageInfo>> GetAvailablePackages( - CancellationToken cancellationToken, - bool withRegistration = true, - string packageType = null, - Version applicationVersion = null) - { - var packages = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false); - return FilterPackages(packages, packageType, applicationVersion); - } + /// <inheritdoc /> + public event EventHandler<InstallationEventArgs> PackageInstalling; - /// <summary> - /// Gets all available packages. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{List{PackageInfo}}.</returns> - public async Task<List<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken) + /// <inheritdoc /> + public event EventHandler<InstallationEventArgs> PackageInstallationCompleted; + + /// <inheritdoc /> + public event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed; + + /// <inheritdoc /> + public event EventHandler<InstallationEventArgs> PackageInstallationCancelled; + + /// <inheritdoc /> + public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled; + + /// <inheritdoc /> + public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated; + + /// <inheritdoc /> + public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled; + + /// <inheritdoc /> + public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal; + + /// <inheritdoc /> + public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default) { using (var response = await _httpClient.SendAsync( new HttpRequestOptions @@ -134,178 +118,95 @@ namespace Emby.Server.Implementations.Updates Url = "https://repo.jellyfin.org/releases/plugin/manifest.json", CancellationToken = cancellationToken, CacheMode = CacheMode.Unconditional, - CacheLength = GetCacheLength() + CacheLength = TimeSpan.FromMinutes(3) }, HttpMethod.Get).ConfigureAwait(false)) using (Stream stream = response.Content) { - return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync<PackageInfo[]>(stream).ConfigureAwait(false)); + return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>( + stream).ConfigureAwait(false); } } - private static TimeSpan GetCacheLength() - { - return TimeSpan.FromMinutes(3); - } - - protected List<PackageInfo> FilterPackages(IEnumerable<PackageInfo> packages) + /// <inheritdoc /> + public IEnumerable<PackageInfo> FilterPackages( + IEnumerable<PackageInfo> availablePackages, + string name = null, + Guid guid = default) { - var list = new List<PackageInfo>(); - - foreach (var package in packages) + if (name != null) { - var versions = new List<PackageVersionInfo>(); - foreach (var version in package.versions) - { - if (string.IsNullOrEmpty(version.sourceUrl)) - { - continue; - } - - versions.Add(version); - } - - package.versions = versions - .OrderByDescending(x => x.Version) - .ToArray(); - - if (package.versions.Length == 0) - { - continue; - } - - list.Add(package); + availablePackages = availablePackages.Where(x => x.name.Equals(name, StringComparison.OrdinalIgnoreCase)); } - // Remove packages with no versions - return list; - } - - protected List<PackageInfo> FilterPackages(IEnumerable<PackageInfo> packages, string packageType, Version applicationVersion) - { - var packagesList = FilterPackages(packages); - - var returnList = new List<PackageInfo>(); - - var filterOnPackageType = !string.IsNullOrEmpty(packageType); - - foreach (var p in packagesList) + if (guid != Guid.Empty) { - if (filterOnPackageType && !string.Equals(p.type, packageType, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - // If an app version was supplied, filter the versions for each package to only include supported versions - if (applicationVersion != null) - { - p.versions = p.versions.Where(v => IsPackageVersionUpToDate(v, applicationVersion)).ToArray(); - } - - if (p.versions.Length == 0) - { - continue; - } - - returnList.Add(p); + var strGuid = guid.ToString("N", CultureInfo.InvariantCulture); + availablePackages = availablePackages.Where(x => x.guid.Equals(strGuid, StringComparison.OrdinalIgnoreCase)); } - return returnList; + return availablePackages; } - /// <summary> - /// Determines whether [is package version up to date] [the specified package version info]. - /// </summary> - /// <param name="packageVersionInfo">The package version info.</param> - /// <param name="currentServerVersion">The current server version.</param> - /// <returns><c>true</c> if [is package version up to date] [the specified package version info]; otherwise, <c>false</c>.</returns> - private static bool IsPackageVersionUpToDate(PackageVersionInfo packageVersionInfo, Version currentServerVersion) + /// <inheritdoc /> + public IEnumerable<PackageVersionInfo> GetCompatibleVersions( + IEnumerable<PackageVersionInfo> availableVersions, + Version minVersion = null, + PackageVersionClass classification = PackageVersionClass.Release) { - if (string.IsNullOrEmpty(packageVersionInfo.requiredVersionStr)) + var appVer = _applicationHost.ApplicationVersion; + availableVersions = availableVersions + .Where(x => x.classification == classification + && Version.Parse(x.requiredVersionStr) <= appVer); + + if (minVersion != null) { - return true; + availableVersions = availableVersions.Where(x => x.Version >= minVersion); } - return Version.TryParse(packageVersionInfo.requiredVersionStr, out var requiredVersion) && currentServerVersion >= requiredVersion; + return availableVersions.OrderByDescending(x => x.Version); } - /// <summary> - /// Gets the package. - /// </summary> - /// <param name="name">The name.</param> - /// <param name="guid">The assembly guid</param> - /// <param name="classification">The classification.</param> - /// <param name="version">The version.</param> - /// <returns>Task{PackageVersionInfo}.</returns> - public async Task<PackageVersionInfo> GetPackage(string name, string guid, PackageVersionClass classification, Version version) + /// <inheritdoc /> + public IEnumerable<PackageVersionInfo> GetCompatibleVersions( + IEnumerable<PackageInfo> availablePackages, + string name = null, + Guid guid = default, + Version minVersion = null, + PackageVersionClass classification = PackageVersionClass.Release) { - var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false); - - var package = packages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase)) - ?? packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); + var package = FilterPackages(availablePackages, name, guid).FirstOrDefault(); + // Package not found. if (package == null) { - return null; + return Enumerable.Empty<PackageVersionInfo>(); } - return package.versions.FirstOrDefault(v => v.Version == version && v.classification == classification); - } - - /// <summary> - /// Gets the latest compatible version. - /// </summary> - /// <param name="name">The name.</param> - /// <param name="guid">The assembly guid if this is a plug-in</param> - /// <param name="currentServerVersion">The current server version.</param> - /// <param name="classification">The classification.</param> - /// <returns>Task{PackageVersionInfo}.</returns> - public async Task<PackageVersionInfo> GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release) - { - var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false); - - return GetLatestCompatibleVersion(packages, name, guid, currentServerVersion, classification); + return GetCompatibleVersions( + package.versions, + minVersion, + classification); } - /// <summary> - /// Gets the latest compatible version. - /// </summary> - /// <param name="availablePackages">The available packages.</param> - /// <param name="name">The name.</param> - /// <param name="currentServerVersion">The current server version.</param> - /// <param name="classification">The classification.</param> - /// <returns>PackageVersionInfo.</returns> - public PackageVersionInfo GetLatestCompatibleVersion(IEnumerable<PackageInfo> availablePackages, string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release) - { - var package = availablePackages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase)) - ?? availablePackages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); - - return package?.versions - .OrderByDescending(x => x.Version) - .FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion)); - } - - /// <summary> - /// Gets the available plugin updates. - /// </summary> - /// <param name="applicationVersion">The current server version.</param> - /// <param name="withAutoUpdateEnabled">if set to <c>true</c> [with auto update enabled].</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{IEnumerable{PackageVersionInfo}}.</returns> - public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(Version applicationVersion, bool withAutoUpdateEnabled, CancellationToken cancellationToken) + /// <inheritdoc /> + public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(CancellationToken cancellationToken = default) { - var catalog = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false); + var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false); var systemUpdateLevel = _applicationHost.SystemUpdateLevel; // Figure out what needs to be installed - return _applicationHost.Plugins.Select(p => + foreach (var plugin in _applicationHost.Plugins) { - var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, systemUpdateLevel); - - return latestPluginInfo != null && latestPluginInfo.Version > p.Version ? latestPluginInfo : null; - }).Where(i => i != null) - .Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase))); + var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel); + var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version); + if (version != null + && !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase))) + { + yield return version; + } + } } /// <inheritdoc /> @@ -330,7 +231,7 @@ namespace Emby.Server.Implementations.Updates var tuple = (installationInfo, innerCancellationTokenSource); // Add it to the in-progress list - lock (_currentInstallations) + lock (_currentInstallationsLock) { _currentInstallations.Add(tuple); } @@ -349,7 +250,7 @@ namespace Emby.Server.Implementations.Updates { await InstallPackageInternal(package, linkedToken).ConfigureAwait(false); - lock (_currentInstallations) + lock (_currentInstallationsLock) { _currentInstallations.Remove(tuple); } @@ -360,7 +261,7 @@ namespace Emby.Server.Implementations.Updates } catch (OperationCanceledException) { - lock (_currentInstallations) + lock (_currentInstallationsLock) { _currentInstallations.Remove(tuple); } @@ -375,7 +276,7 @@ namespace Emby.Server.Implementations.Updates { _logger.LogError(ex, "Package installation failed"); - lock (_currentInstallations) + lock (_currentInstallationsLock) { _currentInstallations.Remove(tuple); } @@ -391,7 +292,7 @@ namespace Emby.Server.Implementations.Updates finally { // Dispose the progress object and remove the installation from the in-progress list - tuple.Item2.Dispose(); + tuple.innerCancellationTokenSource.Dispose(); } } @@ -439,7 +340,7 @@ namespace Emby.Server.Implementations.Updates // Always override the passed-in target (which is a file) and figure it out again string targetDir = Path.Combine(_appPaths.PluginsPath, package.name); -// CA5351: Do Not Use Broken Cryptographic Algorithms + // CA5351: Do Not Use Broken Cryptographic Algorithms #pragma warning disable CA5351 using (var res = await _httpClient.SendAsync( new HttpRequestOptions @@ -455,7 +356,7 @@ namespace Emby.Server.Implementations.Updates { cancellationToken.ThrowIfCancellationRequested(); - var hash = ToHexString(md5.ComputeHash(stream)); + var hash = Hex.Encode(md5.ComputeHash(stream)); if (!string.Equals(package.checksum, hash, StringComparison.OrdinalIgnoreCase)) { _logger.LogError( @@ -535,20 +436,21 @@ namespace Emby.Server.Implementations.Updates /// <inheritdoc/> public bool CancelInstallation(Guid id) { - lock (_currentInstallations) + lock (_currentInstallationsLock) { - var install = _currentInstallations.Find(x => x.Item1.Id == id); + var install = _currentInstallations.Find(x => x.info.Id == id); if (install == default((InstallationInfo, CancellationTokenSource))) { return false; } - install.Item2.Cancel(); + install.token.Cancel(); _currentInstallations.Remove(install); return true; } } + /// <inheritdoc /> public void Dispose() { Dispose(true); @@ -563,11 +465,11 @@ namespace Emby.Server.Implementations.Updates { if (dispose) { - lock (_currentInstallations) + lock (_currentInstallationsLock) { foreach (var tuple in _currentInstallations) { - tuple.Item2.Dispose(); + tuple.token.Dispose(); } _currentInstallations.Clear(); diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 04c73ecea..efd97e4ff 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -39,12 +39,12 @@ namespace Emby.Server.Implementations.WebSockets do { var buffer = WebSocket.CreateServerBuffer(BufferSize); - result = await webSocket.ReceiveAsync(buffer, cancellationToken); + result = await webSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); message.AddRange(buffer.Array.Take(result.Count)); if (result.EndOfMessage) { - await ProcessMessage(message.ToArray(), taskCompletionSource); + await ProcessMessage(message.ToArray(), taskCompletionSource).ConfigureAwait(false); message.Clear(); } } while (!taskCompletionSource.Task.IsCompleted && @@ -53,8 +53,10 @@ namespace Emby.Server.Implementations.WebSockets if (webSocket.State == WebSocketState.Open) { - await webSocket.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, cancellationToken); + await webSocket.CloseAsync( + result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, + cancellationToken).ConfigureAwait(false); } } |
