diff options
Diffstat (limited to 'Emby.Server.Implementations')
13 files changed, 211 insertions, 339 deletions
diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index f726dae2e..23b77e268 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; using System.Security.Cryptography; -using System.Text; using MediaBrowser.Model.Cryptography; +using static MediaBrowser.Common.Cryptography.Constants; namespace Emby.Server.Implementations.Cryptography { @@ -30,8 +28,6 @@ namespace Emby.Server.Implementations.Cryptography private RandomNumberGenerator _randomNumberGenerator; - private const int _defaultIterations = 1000; - private bool _disposed = false; public CryptographyProvider() @@ -45,44 +41,13 @@ namespace Emby.Server.Implementations.Cryptography public string DefaultHashMethod => "PBKDF2"; - [Obsolete("Use System.Security.Cryptography.MD5 directly")] - public Guid GetMD5(string str) - => new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); - - [Obsolete("Use System.Security.Cryptography.SHA1 directly")] - public byte[] ComputeSHA1(byte[] bytes) - { - using (var provider = SHA1.Create()) - { - return provider.ComputeHash(bytes); - } - } - - [Obsolete("Use System.Security.Cryptography.MD5 directly")] - public byte[] ComputeMD5(Stream str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(str); - } - } - - [Obsolete("Use System.Security.Cryptography.MD5 directly")] - public byte[] ComputeMD5(byte[] bytes) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(bytes); - } - } - public IEnumerable<string> GetSupportedHashMethods() => _supportedHashMethods; private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) { - //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 + // 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) { using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) @@ -104,7 +69,7 @@ namespace Emby.Server.Implementations.Cryptography { if (hashMethod == DefaultHashMethod) { - return PBKDF2(hashMethod, bytes, salt, _defaultIterations); + return PBKDF2(hashMethod, bytes, salt, DefaultIterations); } else if (_supportedHashMethods.Contains(hashMethod)) { @@ -129,26 +94,14 @@ namespace Emby.Server.Implementations.Cryptography } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) - => PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations); - - public byte[] ComputeHash(PasswordHash hash) - { - int iterations = _defaultIterations; - if (!hash.Parameters.ContainsKey("iterations")) - { - hash.Parameters.Add("iterations", iterations.ToString(CultureInfo.InvariantCulture)); - } - else if (!int.TryParse(hash.Parameters["iterations"], out iterations)) - { - throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}"); - } - - return PBKDF2(hash.Id, hash.Hash, hash.Salt, iterations); - } + => PBKDF2(DefaultHashMethod, bytes, salt, DefaultIterations); public byte[] GenerateSalt() + => GenerateSalt(DefaultSaltLength); + + public byte[] GenerateSalt(int length) { - byte[] salt = new byte[64]; + byte[] salt = new byte[length]; _randomNumberGenerator.GetBytes(salt); return salt; } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index b48193c58..2c71f0457 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -21,7 +21,7 @@ <ItemGroup> <PackageReference Include="IPNetwork2" Version="2.4.0.126" /> - <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" /> + <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" /> @@ -33,7 +33,7 @@ <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="sharpcompress" Version="0.23.0" /> + <PackageReference Include="sharpcompress" Version="0.24.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" /> </ItemGroup> diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 0dd4d4ca5..0e6083773 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -83,7 +83,16 @@ namespace Emby.Server.Implementations.HttpClientManager var request = new HttpRequestMessage(method, url); - AddRequestHeaders(request, options); + foreach (var header in options.RequestHeaders) + { + request.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + if (options.EnableDefaultUserAgent + && !request.Headers.TryGetValues(HeaderNames.UserAgent, out _)) + { + request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn()); + } switch (options.DecompressionMethod) { @@ -121,26 +130,6 @@ namespace Emby.Server.Implementations.HttpClientManager return request; } - private void AddRequestHeaders(HttpRequestMessage request, HttpRequestOptions options) - { - var hasUserAgent = false; - - foreach (var header in options.RequestHeaders) - { - if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) - { - hasUserAgent = true; - } - - request.Headers.Add(header.Key, header.Value); - } - - if (!hasUserAgent && options.EnableDefaultUserAgent) - { - request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn()); - } - } - /// <summary> /// Gets the response internal. /// </summary> diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 2890cca7c..2c7e81361 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -181,7 +181,7 @@ namespace Emby.Server.Implementations.HttpServer var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; Headers[HeaderNames.ContentRange] = rangeString; - _logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); + _logger.LogDebug("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); } public async Task WriteToAsync(HttpResponse response, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 2282b8efb..c95b00ede 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -2,24 +2,30 @@ using System; using System.Linq; using System.Text; using System.Threading.Tasks; +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 { public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser { private readonly ICryptoProvider _cryptographyProvider; + public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider) { _cryptographyProvider = cryptographyProvider; } + /// <inheritdoc /> public string Name => "Default"; + /// <inheritdoc /> public bool IsEnabled => true; + /// <inheritdoc /> // This is dumb and an artifact of the backwards way auth providers were designed. // This version of authenticate was never meant to be called, but needs to be here for interface compat // Only the providers that don't provide local user support use this @@ -28,6 +34,7 @@ namespace Emby.Server.Implementations.Library throw new NotImplementedException(); } + /// <inheritdoc /> // This is the version that we need to use for local users. Because reasons. public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser) { @@ -46,10 +53,9 @@ namespace Emby.Server.Implementations.Library }); } - ConvertPasswordFormat(resolvedUser); byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - PasswordHash readyHash = new PasswordHash(resolvedUser.Password); + PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id) { @@ -76,72 +82,31 @@ namespace Emby.Server.Implementations.Library }); } - // This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change - // but at least they are in the new format. - private void ConvertPasswordFormat(User user) - { - if (string.IsNullOrEmpty(user.Password)) - { - return; - } - - if (user.Password.IndexOf('$') == -1) - { - string hash = user.Password; - user.Password = string.Format("$SHA1${0}", hash); - } - - if (user.EasyPassword != null - && user.EasyPassword.IndexOf('$') == -1) - { - string hash = user.EasyPassword; - user.EasyPassword = string.Format("$SHA1${0}", hash); - } - } - + /// <inheritdoc /> public bool HasPassword(User user) => !string.IsNullOrEmpty(user.Password); + /// <inheritdoc /> public Task ChangePassword(User user, string newPassword) { - ConvertPasswordFormat(user); - - // This is needed to support changing a no password user to a password user - if (string.IsNullOrEmpty(user.Password)) + if (string.IsNullOrEmpty(newPassword)) { - PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider); - newPasswordHash.Salt = _cryptographyProvider.GenerateSalt(); - newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod; - newPasswordHash.Hash = GetHashedChangeAuth(newPassword, newPasswordHash); - user.Password = newPasswordHash.ToString(); + user.Password = null; return Task.CompletedTask; } - PasswordHash passwordHash = new PasswordHash(user.Password); - if (passwordHash.Id == "SHA1" - && passwordHash.Salt.Length == 0) - { - passwordHash.Salt = _cryptographyProvider.GenerateSalt(); - passwordHash.Id = _cryptographyProvider.DefaultHashMethod; - passwordHash.Hash = GetHashedChangeAuth(newPassword, passwordHash); - } - else if (newPassword != null) - { - passwordHash.Hash = GetHashed(user, newPassword); - } - - user.Password = passwordHash.ToString(); + PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword); + user.Password = newPasswordHash.ToString(); return Task.CompletedTask; } + /// <inheritdoc /> public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) { - ConvertPasswordFormat(user); - if (newPassword != null) { - newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword)); + newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString(); } if (string.IsNullOrWhiteSpace(newPasswordHash)) @@ -152,21 +117,12 @@ namespace Emby.Server.Implementations.Library user.EasyPassword = newPasswordHash; } + /// <inheritdoc /> public string GetEasyPasswordHash(User user) { - // This should be removed in the future. This was added to let user login after - // Jellyfin 10.3.3 failed to save a well formatted PIN. - ConvertPasswordFormat(user); - return string.IsNullOrEmpty(user.EasyPassword) ? null - : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash); - } - - internal byte[] GetHashedChangeAuth(string newPassword, PasswordHash passwordHash) - { - passwordHash.Hash = Encoding.UTF8.GetBytes(newPassword); - return _cryptographyProvider.ComputeHash(passwordHash); + : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash); } /// <summary> @@ -174,54 +130,36 @@ namespace Emby.Server.Implementations.Library /// </summary> public string GetHashedString(User user, string str) { - PasswordHash passwordHash; if (string.IsNullOrEmpty(user.Password)) { - passwordHash = new PasswordHash(_cryptographyProvider); - } - else - { - ConvertPasswordFormat(user); - passwordHash = new PasswordHash(user.Password); + return _cryptographyProvider.CreatePasswordHash(str).ToString(); } - if (passwordHash.Salt != null) - { - // the password is modern format with PBKDF and we should take advantage of that - passwordHash.Hash = Encoding.UTF8.GetBytes(str); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); - } - else - { - // the password has no salt and should be called with the older method for safety - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))); - } + // TODO: make use of iterations parameter? + PasswordHash passwordHash = PasswordHash.Parse(user.Password); + return new PasswordHash( + passwordHash.Id, + _cryptographyProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(str), + passwordHash.Salt), + passwordHash.Salt, + passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); } public byte[] GetHashed(User user, string str) { - PasswordHash passwordHash; if (string.IsNullOrEmpty(user.Password)) { - passwordHash = new PasswordHash(_cryptographyProvider); - } - else - { - ConvertPasswordFormat(user); - passwordHash = new PasswordHash(user.Password); + return _cryptographyProvider.CreatePasswordHash(str).Hash; } - if (passwordHash.Salt != null) - { - // the password is modern format with PBKDF and we should take advantage of that - passwordHash.Hash = Encoding.UTF8.GetBytes(str); - return _cryptographyProvider.ComputeHash(passwordHash); - } - else - { - // the password has no salt and should be called with the older method for safety - return _cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)); - } + // TODO: make use of iterations parameter? + PasswordHash passwordHash = PasswordHash.Parse(user.Password); + return _cryptographyProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(str), + passwordHash.Salt); } } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index a7ea13ca6..ac6b4a209 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Cryptography; using MediaBrowser.Common.Events; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -23,7 +24,6 @@ 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,6 +31,7 @@ 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 { @@ -353,11 +354,11 @@ namespace Emby.Server.Implementations.Library UpdateUser(user); } - UpdateInvalidLoginAttemptCount(user, 0); + ResetInvalidLoginAttemptCount(user); } else { - UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1); + IncrementInvalidLoginAttemptCount(user); } _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); @@ -450,53 +451,38 @@ namespace Emby.Server.Implementations.Library } } - private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser( + string username, + string password, + string hashedPassword, + User user, + string remoteEndPoint) { bool success = false; IAuthenticationProvider authenticationProvider = null; - if (password != null && user != null) + foreach (var provider in GetAuthenticationProviders(user)) { - // Doesn't look like this is even possible to be used, because of password == null checks below - hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); - } + var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + var updatedUsername = providerAuthResult.username; + success = providerAuthResult.success; - if (password == null) - { - // legacy - success = string.Equals(user.Password, hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - foreach (var provider in GetAuthenticationProviders(user)) + if (success) { - var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - var updatedUsername = providerAuthResult.username; - success = providerAuthResult.success; - - if (success) - { - authenticationProvider = provider; - username = updatedUsername; - break; - } + authenticationProvider = provider; + username = updatedUsername; + break; } } - if (user != null - && !success + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) { - if (password == null) - { - // legacy - success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); - } + success = string.Equals( + GetLocalPasswordHash(user), + _defaultAuthenticationProvider.GetHashedString(user, password), + StringComparison.OrdinalIgnoreCase); } return (authenticationProvider, username, success); @@ -506,44 +492,31 @@ namespace Emby.Server.Implementations.Library { return string.IsNullOrEmpty(user.EasyPassword) ? null - : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash); + : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash); } - private void UpdateInvalidLoginAttemptCount(User user, int newValue) + private void ResetInvalidLoginAttemptCount(User user) { - if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) - { - return; - } - - user.Policy.InvalidLoginAttemptCount = newValue; - - // Check for users without a value here and then fill in the default value - // also protect from an always lockout if misconfigured - if (user.Policy.LoginAttemptsBeforeLockout == null || user.Policy.LoginAttemptsBeforeLockout == 0) - { - user.Policy.LoginAttemptsBeforeLockout = user.Policy.IsAdministrator ? 5 : 3; - } - - var maxCount = user.Policy.LoginAttemptsBeforeLockout; - - var fireLockout = false; + user.Policy.InvalidLoginAttemptCount = 0; + UpdateUserPolicy(user, user.Policy, false); + } - // -1 can be used to specify no lockout value - if (maxCount != -1 && newValue >= maxCount) + private void IncrementInvalidLoginAttemptCount(User user) + { + int invalidLogins = ++user.Policy.InvalidLoginAttemptCount; + int maxInvalidLogins = user.Policy.LoginAttemptsBeforeLockout; + if (maxInvalidLogins > 0 + && invalidLogins >= maxInvalidLogins) { - _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); user.Policy.IsDisabled = true; - - fireLockout = true; + UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user)); + _logger.LogWarning( + "Disabling user {UserName} due to {Attempts} unsuccessful login attempts.", + user.Name, + invalidLogins); } UpdateUserPolicy(user, user.Policy, false); - - if (fireLockout) - { - UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user)); - } } /// <summary> diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index d7411af50..da0013f12 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _streamHelper = streamHelper; _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers.json")); - _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers.json"), _logger); + _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers.json")); _timerProvider.TimerFired += _timerProvider_TimerFired; _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 9c45ee36a..9055a70a6 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -10,67 +10,64 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public class ItemDataProvider<T> where T : class { - private readonly object _fileDataLock = new object(); - private List<T> _items; private readonly IJsonSerializer _jsonSerializer; - protected readonly ILogger Logger; private readonly string _dataPath; - protected readonly Func<T, T, bool> EqualityComparer; + private readonly object _fileDataLock = new object(); + private T[] _items; - public ItemDataProvider(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer) + public ItemDataProvider( + IJsonSerializer jsonSerializer, + ILogger logger, + string dataPath, + Func<T, T, bool> equalityComparer) { + _jsonSerializer = jsonSerializer; Logger = logger; _dataPath = dataPath; EqualityComparer = equalityComparer; - _jsonSerializer = jsonSerializer; } - public IReadOnlyList<T> GetAll() - { - lock (_fileDataLock) - { - if (_items == null) - { - if (!File.Exists(_dataPath)) - { - return new List<T>(); - } - - Logger.LogInformation("Loading live tv data from {0}", _dataPath); - _items = GetItemsFromFile(_dataPath); - } + protected ILogger Logger { get; } - return _items.ToList(); - } - } + protected Func<T, T, bool> EqualityComparer { get; } - private List<T> GetItemsFromFile(string path) + private void EnsureLoaded() { - try + if (_items != null) { - return _jsonSerializer.DeserializeFromFile<List<T>>(path); + return; } - catch (Exception ex) + + if (File.Exists(_dataPath)) { - Logger.LogError(ex, "Error deserializing {Path}", path); + Logger.LogInformation("Loading live tv data from {Path}", _dataPath); + + try + { + _items = _jsonSerializer.DeserializeFromFile<T[]>(_dataPath); + return; + } + catch (Exception ex) + { + Logger.LogError(ex, "Error deserializing {Path}", _dataPath); + } } - return new List<T>(); + _items = Array.Empty<T>(); } - private void UpdateList(List<T> newList) + private void SaveList() { - if (newList == null) - { - throw new ArgumentNullException(nameof(newList)); - } - Directory.CreateDirectory(Path.GetDirectoryName(_dataPath)); + _jsonSerializer.SerializeToFile(_items, _dataPath); + } + public IReadOnlyList<T> GetAll() + { lock (_fileDataLock) { - _jsonSerializer.SerializeToFile(newList, _dataPath); - _items = newList; + EnsureLoaded(); + return (T[])_items.Clone(); } } @@ -81,18 +78,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new ArgumentNullException(nameof(item)); } - var list = GetAll().ToList(); - - var index = list.FindIndex(i => EqualityComparer(i, item)); - - if (index == -1) + lock (_fileDataLock) { - throw new ArgumentException("item not found"); - } + EnsureLoaded(); - list[index] = item; + var index = Array.FindIndex(_items, i => EqualityComparer(i, item)); + if (index == -1) + { + throw new ArgumentException("item not found"); + } - UpdateList(list); + _items[index] = item; + + SaveList(); + } } public virtual void Add(T item) @@ -102,37 +101,58 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new ArgumentNullException(nameof(item)); } - var list = GetAll().ToList(); - - if (list.Any(i => EqualityComparer(i, item))) + lock (_fileDataLock) { - throw new ArgumentException("item already exists"); - } + EnsureLoaded(); - list.Add(item); + if (_items.Any(i => EqualityComparer(i, item))) + { + throw new ArgumentException("item already exists", nameof(item)); + } - UpdateList(list); + int oldLen = _items.Length; + var newList = new T[oldLen + 1]; + _items.CopyTo(newList, 0); + newList[oldLen] = item; + _items = newList; + + SaveList(); + } } - public void AddOrUpdate(T item) + public virtual void AddOrUpdate(T item) { - var list = GetAll().ToList(); - - if (!list.Any(i => EqualityComparer(i, item))) - { - Add(item); - } - else + lock (_fileDataLock) { - Update(item); + EnsureLoaded(); + + int index = Array.FindIndex(_items, i => EqualityComparer(i, item)); + if (index == -1) + { + int oldLen = _items.Length; + var newList = new T[oldLen + 1]; + _items.CopyTo(newList, 0); + newList[oldLen] = item; + _items = newList; + } + else + { + _items[index] = item; + } + + SaveList(); } } public virtual void Delete(T item) { - var list = GetAll().Where(i => !EqualityComparer(i, item)).ToList(); + lock (_fileDataLock) + { + EnsureLoaded(); + _items = _items.Where(i => !EqualityComparer(i, item)).ToArray(); - UpdateList(list); + SaveList(); + } } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 3c807a8ea..d09b281d4 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -14,21 +14,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public class TimerManager : ItemDataProvider<TimerInfo> { private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase); - private readonly ILogger _logger; - public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired; - - public TimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1) + public TimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath) : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) { - _logger = logger1; } + public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired; + public void RestartTimers() { StopTimers(); - foreach (var item in GetAll().ToList()) + foreach (var item in GetAll()) { AddOrUpdateSystemTimer(item); } @@ -64,16 +62,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - var list = GetAll().ToList(); + base.AddOrUpdate(item); + } - if (!list.Any(i => EqualityComparer(i, item))) - { - base.Add(item); - } - else - { - base.Update(item); - } + public override void AddOrUpdate(TimerInfo item) + { + base.AddOrUpdate(item); + AddOrUpdateSystemTimer(item); } public override void Add(TimerInfo item) @@ -89,8 +84,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private static bool ShouldStartTimer(TimerInfo item) { - if (item.Status == RecordingStatus.Completed || - item.Status == RecordingStatus.Cancelled) + if (item.Status == RecordingStatus.Completed + || item.Status == RecordingStatus.Cancelled) { return false; } @@ -126,12 +121,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (_timers.TryAdd(item.Id, timer)) { - _logger.LogInformation("Creating recording timer for {id}, {name}. Timer will fire in {minutes} minutes", item.Id, item.Name, dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + Logger.LogInformation( + "Creating recording timer for {Id}, {Name}. Timer will fire in {Minutes} minutes", + item.Id, + item.Name, + dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); } else { timer.Dispose(); - _logger.LogWarning("Timer already exists for item {id}", item.Id); + Logger.LogWarning("Timer already exists for item {Id}", item.Id); } } diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index 387604f03..b12d391c1 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -1,11 +1,11 @@ { "Albums": "Álbuns", - "AppDeviceValues": "Aplicação {0}, Dispositivo:{1}", + "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}", "Application": "Aplicação", "Artists": "Artistas", "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", "Books": "Livros", - "CameraImageUploadedFrom": "Uma nova imagem proveniente de uma câmara foi enviada a partir de {0}", + "CameraImageUploadedFrom": "Uma nova imagem de câmara foi enviada a partir de {0}", "Channels": "Canais", "ChapterNameValue": "Capítulo {0}", "Collections": "Coleções", @@ -16,7 +16,7 @@ "Folders": "Pastas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas do Álbum", - "HeaderCameraUploads": "Camera Uploads", + "HeaderCameraUploads": "Envios a partir da câmara", "HeaderContinueWatching": "Continuar a Ver", "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteArtists": "Artistas Favoritos", @@ -27,7 +27,7 @@ "HeaderNextUp": "A Seguir", "HeaderRecordingGroups": "Grupos de Gravação", "HomeVideos": "Home videos", - "Inherit": "Inherit", + "Inherit": "Herdar", "ItemAddedWithName": "{0} foi adicionado à biblioteca", "ItemRemovedWithName": "{0} foi removido da biblioteca", "LabelIpAddressValue": "Endereço IP: {0}", @@ -49,7 +49,7 @@ "NotificationOptionApplicationUpdateInstalled": "Atualização de aplicação instalada", "NotificationOptionAudioPlayback": "Reprodução Iniciada", "NotificationOptionAudioPlaybackStopped": "Reprodução Parada", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionCameraImageUploaded": "Imagem da câmara enviada", "NotificationOptionInstallationFailed": "Falha na instalação", "NotificationOptionNewLibraryContent": "Novo conteúdo adicionado", "NotificationOptionPluginError": "Falha na extensão", diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 9e00eba62..3cc95e46e 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -3,7 +3,7 @@ "AppDeviceValues": "Uygulama: {0}, Aygıt: {1}", "Application": "Uygulama", "Artists": "Sanatçılar", - "AuthenticationSucceededWithUserName": "{0} başarı ile giriş yaptı", + "AuthenticationSucceededWithUserName": "{0} kimlik başarıyla doğrulandı", "Books": "Kitaplar", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", "Channels": "Kanallar", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", + "NotificationOptionInstallationFailed": "Yükleme hatası", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", "NotificationOptionPluginInstalled": "Plugin installed", diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 63aa6a557..d41b096e1 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -19,8 +19,8 @@ "HeaderCameraUploads": "相机上传", "HeaderContinueWatching": "继续观看", "HeaderFavoriteAlbums": "最爱的专辑", - "HeaderFavoriteArtists": "最爱作家", - "HeaderFavoriteEpisodes": "最爱的集", + "HeaderFavoriteArtists": "最爱的艺术家", + "HeaderFavoriteEpisodes": "最爱的剧集", "HeaderFavoriteShows": "最爱的节目", "HeaderFavoriteSongs": "最爱的歌曲", "HeaderLiveTV": "电视直播", diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 2f84b91ec..7947edeeb 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; @@ -19,6 +18,7 @@ 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 { @@ -454,7 +454,7 @@ namespace Emby.Server.Implementations.Updates { cancellationToken.ThrowIfCancellationRequested(); - var hash = HexHelper.ToHexString(md5.ComputeHash(stream)); + var hash = ToHexString(md5.ComputeHash(stream)); if (!string.Equals(package.checksum, hash, StringComparison.OrdinalIgnoreCase)) { _logger.LogDebug("{0}, {1}", package.checksum, hash); |
