diff options
Diffstat (limited to 'Emby.Server.Implementations')
25 files changed, 155 insertions, 156 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 73919f306..c17d355e5 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -3,6 +3,7 @@ #pragma warning disable CS1591 using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -118,7 +119,7 @@ namespace Emby.Server.Implementations /// <summary> /// The disposable parts. /// </summary> - private readonly List<IDisposable> _disposableParts = new List<IDisposable>(); + private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new (); private readonly IFileSystem _fileSystemManager; private readonly IConfiguration _startupConfig; @@ -129,7 +130,6 @@ namespace Emby.Server.Implementations private List<Type> _creatingInstances; private IMediaEncoder _mediaEncoder; private ISessionManager _sessionManager; - private string[] _urlPrefixes; /// <summary> /// Gets or sets all concrete types. @@ -210,7 +210,7 @@ namespace Emby.Server.Implementations /// <summary> /// Gets the <see cref="INetworkManager"/> singleton instance. /// </summary> - public INetworkManager NetManager { get; internal set; } + public INetworkManager NetManager { get; private set; } /// <summary> /// Gets a value indicating whether this instance has changes that require the entire application to restart. @@ -232,16 +232,16 @@ namespace Emby.Server.Implementations protected ILoggerFactory LoggerFactory { get; } /// <summary> - /// Gets or sets the application paths. + /// Gets the application paths. /// </summary> /// <value>The application paths.</value> - protected IServerApplicationPaths ApplicationPaths { get; set; } + protected IServerApplicationPaths ApplicationPaths { get; } /// <summary> - /// Gets or sets the configuration manager. + /// Gets the configuration manager. /// </summary> /// <value>The configuration manager.</value> - public ServerConfigurationManager ConfigurationManager { get; set; } + public ServerConfigurationManager ConfigurationManager { get; } /// <summary> /// Gets or sets the service provider. @@ -345,22 +345,6 @@ namespace Emby.Server.Implementations } /// <summary> - /// 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); - - /// <summary> - /// 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); - - /// <summary> /// Creates the instance safe. /// </summary> /// <param name="type">The type.</param> @@ -369,7 +353,7 @@ namespace Emby.Server.Implementations { _creatingInstances ??= new List<Type>(); - if (_creatingInstances.IndexOf(type) != -1) + if (_creatingInstances.Contains(type)) { Logger.LogError("DI Loop detected in the attempted creation of {Type}", type.FullName); foreach (var entry in _creatingInstances) @@ -379,7 +363,7 @@ namespace Emby.Server.Implementations _pluginManager.FailPlugin(type.Assembly); - throw new ExternalException("DI Loop detected."); + throw new TypeLoadException("DI Loop detected"); } try @@ -412,8 +396,15 @@ namespace Emby.Server.Implementations public IEnumerable<Type> GetExportTypes<T>() { var currentType = typeof(T); - - return _allConcreteTypes.Where(i => currentType.IsAssignableFrom(i)); + var numberOfConcreteTypes = _allConcreteTypes.Length; + for (var i = 0; i < numberOfConcreteTypes; i++) + { + var type = _allConcreteTypes[i]; + if (currentType.IsAssignableFrom(type)) + { + yield return type; + } + } } /// <inheritdoc /> @@ -428,9 +419,9 @@ namespace Emby.Server.Implementations if (manageLifetime) { - lock (_disposableParts) + foreach (var part in parts.OfType<IDisposable>()) { - _disposableParts.AddRange(parts.OfType<IDisposable>()); + _disposableParts.TryAdd(part, byte.MinValue); } } @@ -449,9 +440,9 @@ namespace Emby.Server.Implementations if (manageLifetime) { - lock (_disposableParts) + foreach (var part in parts.OfType<IDisposable>()) { - _disposableParts.AddRange(parts.OfType<IDisposable>()); + _disposableParts.TryAdd(part, byte.MinValue); } } @@ -563,7 +554,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager); serviceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager); serviceCollection.AddSingleton<IApplicationHost>(this); - serviceCollection.AddSingleton<IPluginManager>(_pluginManager); + serviceCollection.AddSingleton(_pluginManager); serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths); serviceCollection.AddSingleton(_fileSystemManager); @@ -586,7 +577,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<IZipClient, ZipClient>(); serviceCollection.AddSingleton<IServerApplicationHost>(this); - serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths); + serviceCollection.AddSingleton(ApplicationPaths); serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>(); @@ -790,8 +781,6 @@ namespace Emby.Server.Implementations _pluginManager.CreatePlugins(); - _urlPrefixes = GetUrlPrefixes().ToArray(); - Resolve<ILibraryManager>().AddParts( GetExports<IResolverIgnoreRule>(), GetExports<IItemResolver>(), @@ -859,32 +848,12 @@ namespace Emby.Server.Implementations } } - private IEnumerable<string> GetUrlPrefixes() - { - var hosts = new[] { "+" }; - - return hosts.SelectMany(i => - { - var prefixes = new List<string> - { - "http://" + i + ":" + HttpPort + "/" - }; - - if (Certificate != null) - { - prefixes.Add("https://" + i + ":" + HttpsPort + "/"); - } - - return prefixes; - }); - } - /// <summary> /// Called when [configuration updated]. /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> - protected void OnConfigurationUpdated(object sender, EventArgs e) + private void OnConfigurationUpdated(object sender, EventArgs e) { var requiresRestart = false; var networkConfiguration = ConfigurationManager.GetNetworkConfiguration(); @@ -893,8 +862,8 @@ namespace Emby.Server.Implementations if (HttpPort != 0 && HttpsPort != 0) { // Need to restart if ports have changed - if (networkConfiguration.HttpServerPortNumber != HttpPort || - networkConfiguration.HttpsPortNumber != HttpsPort) + if (networkConfiguration.HttpServerPortNumber != HttpPort + || networkConfiguration.HttpsPortNumber != HttpsPort) { if (ConfigurationManager.Configuration.IsPortAuthorized) { @@ -906,11 +875,6 @@ namespace Emby.Server.Implementations } } - if (!_urlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) - { - requiresRestart = true; - } - if (ValidateSslCertificate(networkConfiguration)) { requiresRestart = true; @@ -952,7 +916,7 @@ namespace Emby.Server.Implementations } /// <summary> - /// Notifies that the kernel that a change has been made that requires a restart. + /// Notifies the kernel that a change has been made that requires a restart. /// </summary> public void NotifyPendingRestart() { @@ -1093,11 +1057,6 @@ namespace Emby.Server.Implementations }; } - public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo() - => NetManager.GetMacAddresses() - .Select(i => new WakeOnLanInfo(i)) - .ToList(); - public PublicSystemInfo GetPublicSystemInfo(HttpRequest request) { return new PublicSystemInfo @@ -1113,7 +1072,7 @@ namespace Emby.Server.Implementations } /// <inheritdoc/> - public string GetSmartApiUrl(IPAddress remoteAddr, int? port = null) + public string GetSmartApiUrl(IPAddress remoteAddr) { // Published server ends with a / if (!string.IsNullOrEmpty(PublishedServerUrl)) @@ -1122,12 +1081,12 @@ namespace Emby.Server.Implementations return PublishedServerUrl.Trim('/'); } - string smart = NetManager.GetBindInterface(remoteAddr, out port); + string smart = NetManager.GetBindInterface(remoteAddr, out var port); return GetLocalApiUrl(smart.Trim('/'), null, port); } /// <inheritdoc/> - public string GetSmartApiUrl(HttpRequest request, int? port = null) + public string GetSmartApiUrl(HttpRequest request) { // Return the host in the HTTP request as the API url if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest) @@ -1148,12 +1107,12 @@ namespace Emby.Server.Implementations return PublishedServerUrl.Trim('/'); } - string smart = NetManager.GetBindInterface(request, out port); + string smart = NetManager.GetBindInterface(request, out var port); return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port); } /// <inheritdoc/> - public string GetSmartApiUrl(string hostname, int? port = null) + public string GetSmartApiUrl(string hostname) { // Published server ends with a / if (!string.IsNullOrEmpty(PublishedServerUrl)) @@ -1162,7 +1121,7 @@ namespace Emby.Server.Implementations return PublishedServerUrl.Trim('/'); } - string smart = NetManager.GetBindInterface(hostname, out port); + string smart = NetManager.GetBindInterface(hostname, out var port); return GetLocalApiUrl(smart.Trim('/'), null, port); } @@ -1258,12 +1217,15 @@ namespace Emby.Server.Implementations Logger.LogInformation("Disposing {Type}", type.Name); - var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList(); - _disposableParts.Clear(); - - foreach (var part in parts) + foreach (var (part, _) in _disposableParts) { - Logger.LogInformation("Disposing {Type}", part.GetType().Name); + var partType = part.GetType(); + if (partType == type) + { + continue; + } + + Logger.LogInformation("Disposing {Type}", partType.Name); try { @@ -1271,9 +1233,11 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name); + Logger.LogError(ex, "Error disposing {Type}", partType.Name); } } + + _disposableParts.Clear(); } _disposed = true; diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 673810c49..e9c005cea 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Security.Cryptography; +using System.Text; using MediaBrowser.Common.Extensions; using MediaBrowser.Model.Cryptography; -using static MediaBrowser.Common.Cryptography.Constants; +using static MediaBrowser.Model.Cryptography.Constants; namespace Emby.Server.Implementations.Cryptography { @@ -12,10 +14,7 @@ namespace Emby.Server.Implementations.Cryptography /// </summary> public class CryptographyProvider : ICryptoProvider { - // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto - // Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1 - // there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one - // Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1 + // TODO: remove when not needed for backwards compat private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>() { "MD5", @@ -35,60 +34,81 @@ namespace Emby.Server.Implementations.Cryptography }; /// <inheritdoc /> - public string DefaultHashMethod => "PBKDF2"; + public string DefaultHashMethod => "PBKDF2-SHA512"; /// <inheritdoc /> - public IEnumerable<string> GetSupportedHashMethods() - => _supportedHashMethods; - - private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) + public PasswordHash CreatePasswordHash(ReadOnlySpan<char> password) { - // downgrading for now as we need this library to be dotnetstandard compliant - // with this downgrade we'll add a check to make sure we're on the downgrade method at the moment - if (method != DefaultHashMethod) - { - throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}"); - } - - using var r = new Rfc2898DeriveBytes(bytes, salt, iterations); - return r.GetBytes(32); + byte[] salt = GenerateSalt(); + return new PasswordHash( + DefaultHashMethod, + Rfc2898DeriveBytes.Pbkdf2( + password, + salt, + DefaultIterations, + HashAlgorithmName.SHA512, + DefaultOutputLength), + salt, + new Dictionary<string, string> + { + { "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) } + }); } /// <inheritdoc /> - public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) + public bool Verify(PasswordHash hash, ReadOnlySpan<char> password) { - if (hashMethod == DefaultHashMethod) + if (string.Equals(hash.Id, "PBKDF2", StringComparison.Ordinal)) { - return PBKDF2(hashMethod, bytes, salt, DefaultIterations); + return hash.Hash.SequenceEqual( + Rfc2898DeriveBytes.Pbkdf2( + password, + hash.Salt, + int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture), + HashAlgorithmName.SHA1, + 32)); } - if (!_supportedHashMethods.Contains(hashMethod)) + if (string.Equals(hash.Id, "PBKDF2-SHA512", StringComparison.Ordinal)) { - throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); + return hash.Hash.SequenceEqual( + Rfc2898DeriveBytes.Pbkdf2( + password, + hash.Salt, + int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture), + HashAlgorithmName.SHA512, + DefaultOutputLength)); } - using var h = HashAlgorithm.Create(hashMethod) ?? throw new ResourceNotFoundException($"Unknown hash method: {hashMethod}."); - if (salt.Length == 0) + if (!_supportedHashMethods.Contains(hash.Id)) { - return h.ComputeHash(bytes); + throw new CryptographicException($"Requested hash method is not supported: {hash.Id}"); } - byte[] salted = new byte[bytes.Length + salt.Length]; + using var h = HashAlgorithm.Create(hash.Id) ?? throw new ResourceNotFoundException($"Unknown hash method: {hash.Id}."); + var bytes = Encoding.UTF8.GetBytes(password.ToArray()); + if (hash.Salt.Length == 0) + { + return hash.Hash.SequenceEqual(h.ComputeHash(bytes)); + } + + byte[] salted = new byte[bytes.Length + hash.Salt.Length]; Array.Copy(bytes, salted, bytes.Length); - Array.Copy(salt, 0, salted, bytes.Length, salt.Length); - return h.ComputeHash(salted); + hash.Salt.CopyTo(salted.AsSpan(bytes.Length)); + return hash.Hash.SequenceEqual(h.ComputeHash(salted)); } /// <inheritdoc /> - public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) - => PBKDF2(DefaultHashMethod, bytes, salt, DefaultIterations); - - /// <inheritdoc /> public byte[] GenerateSalt() => GenerateSalt(DefaultSaltLength); /// <inheritdoc /> public byte[] GenerateSalt(int length) - => RandomNumberGenerator.GetBytes(length); + { + var salt = new byte[length]; + using var rng = RandomNumberGenerator.Create(); + rng.GetNonZeroBytes(salt); + return salt; + } } } diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 44dad5b17..11e33278d 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -7,7 +7,7 @@ using SQLitePCL.pretty; namespace Emby.Server.Implementations.Data { - public class ManagedConnection : IDisposable + public sealed class ManagedConnection : IDisposable { private readonly SemaphoreSlim _writeLock; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index e2ad07177..e7103ec95 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (!auth.HasToken) { - throw new AuthenticationException("Request does not contain a token."); + return auth; } if (!auth.IsAuthenticated) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index f86bfd755..e99876dce 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -35,7 +35,12 @@ namespace Emby.Server.Implementations.HttpServer /// <inheritdoc /> public async Task WebSocketRequestHandler(HttpContext context) { - _ = await _authService.Authenticate(context.Request).ConfigureAwait(false); + var authorizationInfo = await _authService.Authenticate(context.Request).ConfigureAwait(false); + if (!authorizationInfo.IsAuthenticated) + { + throw new SecurityException("Token is required"); + } + try { _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 47a83d77c..e62361c1e 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -14,7 +12,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.IO { - public class FileRefresher : IDisposable + public sealed class FileRefresher : IDisposable { private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; @@ -22,7 +20,7 @@ namespace Emby.Server.Implementations.IO private readonly List<string> _affectedPaths = new List<string>(); private readonly object _timerLock = new object(); - private Timer _timer; + private Timer? _timer; private bool _disposed; public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger) @@ -36,7 +34,7 @@ namespace Emby.Server.Implementations.IO AddPath(path); } - public event EventHandler<EventArgs> Completed; + public event EventHandler<EventArgs>? Completed; public string Path { get; private set; } @@ -111,7 +109,7 @@ namespace Emby.Server.Implementations.IO RestartTimer(); } - private void OnTimerCallback(object state) + private void OnTimerCallback(object? state) { List<string> paths; @@ -127,7 +125,7 @@ namespace Emby.Server.Implementations.IO try { - ProcessPathChanges(paths.ToList()); + ProcessPathChanges(paths); } catch (Exception ex) { @@ -137,12 +135,12 @@ namespace Emby.Server.Implementations.IO private void ProcessPathChanges(List<string> paths) { - var itemsToRefresh = paths + IEnumerable<BaseItem> itemsToRefresh = paths .Distinct(StringComparer.OrdinalIgnoreCase) .Select(GetAffectedBaseItem) .Where(item => item != null) - .GroupBy(x => x.Id) - .Select(x => x.First()); + .GroupBy(x => x!.Id) // Removed null values in the previous .Where() + .Select(x => x.First())!; foreach (var item in itemsToRefresh) { @@ -176,15 +174,15 @@ namespace Emby.Server.Implementations.IO /// </summary> /// <param name="path">The path.</param> /// <returns>BaseItem.</returns> - private BaseItem GetAffectedBaseItem(string path) + private BaseItem? GetAffectedBaseItem(string path) { - BaseItem item = null; + BaseItem? item = null; while (item == null && !string.IsNullOrEmpty(path)) { item = _libraryManager.FindByPath(path, null); - path = System.IO.Path.GetDirectoryName(path); + path = System.IO.Path.GetDirectoryName(path) ?? string.Empty; } if (item != null) diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index b525f5a2f..9fcc7fe59 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -449,12 +449,12 @@ namespace Emby.Server.Implementations.IO } var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger); - newRefresher.Completed += NewRefresher_Completed; + newRefresher.Completed += OnNewRefresherCompleted; _activeRefreshers.Add(newRefresher); } } - private void NewRefresher_Completed(object sender, EventArgs e) + private void OnNewRefresherCompleted(object sender, EventArgs e) { var refresher = (FileRefresher)sender; DisposeRefresher(refresher); @@ -481,6 +481,7 @@ namespace Emby.Server.Implementations.IO { lock (_activeRefreshers) { + refresher.Completed -= OnNewRefresherCompleted; refresher.Dispose(); _activeRefreshers.Remove(refresher); } @@ -492,6 +493,7 @@ namespace Emby.Server.Implementations.IO { foreach (var refresher in _activeRefreshers.ToList()) { + refresher.Completed -= OnNewRefresherCompleted; refresher.Dispose(); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 5726d7158..a88a1fe84 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV CultureInfo.InvariantCulture, "-i \"{0}\" {2} -map_metadata -1 -threads {6} {3}{4}{5} -y \"{1}\"", inputTempFile, - targetFile.Replace("\"", "\\\""), // Escape quotes in filename + targetFile.Replace("\"", "\\\"", StringComparison.Ordinal), // Escape quotes in filename videoArgs, GetAudioArgs(mediaSource), subtitleArgs, diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1f963e4a2..615539db3 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -11,6 +11,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; +using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Threading; @@ -648,7 +649,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken cancellationToken) { using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token"); - var hashedPasswordBytes = _cryptoProvider.ComputeHash("SHA1", Encoding.ASCII.GetBytes(password), Array.Empty<byte>()); + var hashedPasswordBytes = SHA1.HashData(Encoding.ASCII.GetBytes(password)); // TODO: remove ToLower when Convert.ToHexString supports lowercase // Schedules Direct requires the hex to be lowercase string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant(); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index f9d151a81..9ab4cc628 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken) { using var client = new TcpClient(); - client.Connect(remoteIp, HdHomeRunPort); + await client.ConnectAsync(remoteIp, HdHomeRunPort).ConfigureAwait(false); using var stream = client.GetStream(); return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 506ef5548..708ff52d7 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -283,7 +283,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts // #EXTINF:0,84.0 - VOX Schweiz if (!string.IsNullOrWhiteSpace(nameInExtInf)) { - var numberIndex = nameInExtInf.IndexOf(' '); + var numberIndex = nameInExtInf.IndexOf(' ', StringComparison.Ordinal); if (numberIndex > 0) { var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); diff --git a/Emby.Server.Implementations/Localization/Core/as.json b/Emby.Server.Implementations/Localization/Core/as.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/as.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/be.json b/Emby.Server.Implementations/Localization/Core/be.json new file mode 100644 index 000000000..56c4e7d39 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/be.json @@ -0,0 +1,4 @@ +{ + "Sync": "Сінхранізацыя", + "Playlists": "Плэйліст" +} diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index db3c13d80..2dee5e327 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -25,7 +25,7 @@ "HeaderLiveTV": "TV en Directe", "HeaderNextUp": "A continuació", "HeaderRecordingGroups": "Grups d'Enregistrament", - "HomeVideos": "Vídeos domèstics", + "HomeVideos": "Vídeos Domèstics", "Inherit": "Hereta", "ItemAddedWithName": "{0} ha estat afegit a la biblioteca", "ItemRemovedWithName": "{0} ha estat eliminat de la biblioteca", @@ -39,7 +39,7 @@ "MixedContent": "Contingut barrejat", "Movies": "Pel·lícules", "Music": "Música", - "MusicVideos": "Vídeos musicals", + "MusicVideos": "Vídeos Musicals", "NameInstallFailed": "Instalació de {0} fallida", "NameSeasonNumber": "Temporada {0}", "NameSeasonUnknown": "Temporada Desconeguda", diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json index e5405e515..626d76d6b 100644 --- a/Emby.Server.Implementations/Localization/Core/et.json +++ b/Emby.Server.Implementations/Localization/Core/et.json @@ -114,5 +114,7 @@ "Artists": "Esitajad", "Application": "Rakendus", "AppDeviceValues": "Rakendus: {0}, seade: {1}", - "Albums": "Albumid" + "Albums": "Albumid", + "UserOfflineFromDevice": "{0} katkestas ühenduse {1}-ga", + "SubtitleDownloadFailureFromForItem": "Subtiitrite allalaadimine {0} > {1} nurjus" } diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json index ba3513870..37d59abd9 100644 --- a/Emby.Server.Implementations/Localization/Core/id.json +++ b/Emby.Server.Implementations/Localization/Core/id.json @@ -7,10 +7,10 @@ "MessageApplicationUpdated": "Jellyfin Server sudah diperbarui", "Latest": "Terbaru", "LabelIpAddressValue": "Alamat IP: {0}", - "ItemRemovedWithName": "{0} sudah dikeluarkan dari pustaka", + "ItemRemovedWithName": "{0} sudah dihapus dari pustaka", "ItemAddedWithName": "{0} telah dimasukkan ke dalam pustaka", - "Inherit": "Warisan", - "HomeVideos": "Video Rumah", + "Inherit": "Warisi", + "HomeVideos": "Video Rumahan", "HeaderRecordingGroups": "Grup Rekaman", "HeaderNextUp": "Selanjutnya", "HeaderLiveTV": "TV Live", @@ -73,7 +73,7 @@ "NotificationOptionCameraImageUploaded": "Gambar kamera terunggah", "NotificationOptionApplicationUpdateInstalled": "Pembaruan aplikasi terpasang", "NotificationOptionApplicationUpdateAvailable": "Pembaruan aplikasi tersedia", - "NewVersionIsAvailable": "Versi baru dari Jellyfin Server tersedia untuk diunduh.", + "NewVersionIsAvailable": "Versi baru dari Jellyfin Server sudah tersedia untuk diunduh.", "NameSeasonUnknown": "Musim tak diketahui", "NameSeasonNumber": "Musim {0}", "NameInstallFailed": "{0} penginstalan gagal", @@ -117,5 +117,7 @@ "TaskCleanActivityLog": "Bersihkan Log Aktivitas", "Undefined": "Tidak terdefinisi", "Forced": "Dipaksa", - "Default": "Bawaan" + "Default": "Bawaan", + "TaskOptimizeDatabaseDescription": "Rapihkan basis data dan membersihkan ruang kosong. Menjalankan tugas ini setelah memindai pustaka atau melakukan perubahan lain yang menyiratkan modifikasi basis data dapat meningkatkan kinerja.", + "TaskOptimizeDatabase": "Optimalkan basis data" } diff --git a/Emby.Server.Implementations/Localization/Core/te.json b/Emby.Server.Implementations/Localization/Core/te.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/te.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/zu.json b/Emby.Server.Implementations/Localization/Core/zu.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/zu.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index b07798fa4..9481e26f7 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -527,7 +527,7 @@ namespace Emby.Server.Implementations.Playlists var relativeUri = folderUri.MakeRelativeUri(fileAbsoluteUri); string relativePath = Uri.UnescapeDataString(relativeUri.ToString()); - if (fileAbsoluteUri.Scheme.Equals("file", StringComparison.CurrentCultureIgnoreCase)) + if (fileAbsoluteUri.Scheme.Equals("file", StringComparison.OrdinalIgnoreCase)) { relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index bd1966623..67a9fbd3b 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.Sorting /// <returns>System.Int32.</returns> public int Compare(BaseItem? x, BaseItem? y) { - return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); + return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase); } /// <summary> diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs index fe7dc84cb..4d09dda84 100644 --- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Sorting /// <returns>System.Int32.</returns> public int Compare(BaseItem? x, BaseItem? y) { - return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); + return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -35,9 +35,7 @@ namespace Emby.Server.Implementations.Sorting /// <returns>System.String.</returns> private static string? GetValue(BaseItem? x) { - var audio = x as Audio; - - return audio == null ? string.Empty : audio.Album; + return x is Audio audio ? audio.Album : string.Empty; } } } diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs index 7b7ba5753..a8bb55e2b 100644 --- a/Emby.Server.Implementations/Sorting/ArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Sorting /// <inheritdoc /> public int Compare(BaseItem? x, BaseItem? y) { - return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); + return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase); } /// <summary> diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index 8f87717f4..c2875eeb9 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Sorting throw new ArgumentNullException(nameof(y)); } - return string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase); + return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); } } } diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 4123a59f8..0bd9600b9 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Sorting /// <returns>System.Int32.</returns> public int Compare(BaseItem x, BaseItem y) { - return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); + return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase); } private static string GetValue(BaseItem item) diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs index fb97a0349..79be9a89a 100644 --- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Sorting throw new ArgumentNullException(nameof(y)); } - return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase); + return string.Compare(x.SortName, y.SortName, StringComparison.OrdinalIgnoreCase); } } } |
