diff options
Diffstat (limited to 'Emby.Server.Implementations')
21 files changed, 304 insertions, 467 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f7fe2bd638..120a5adc48 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -410,13 +410,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,7 +456,7 @@ 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> @@ -460,7 +464,7 @@ namespace Emby.Server.Implementations => 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> @@ -508,11 +512,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) { @@ -749,7 +754,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 +794,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 +882,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); @@ -1415,7 +1430,7 @@ 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) { @@ -1425,7 +1440,7 @@ namespace Emby.Server.Implementations { HasPendingRestart = HasPendingRestart, IsShuttingDown = IsShuttingDown, - Version = ApplicationVersion, + Version = ApplicationVersionString, WebSocketPortNumber = HttpPort, CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(), Id = SystemId, @@ -1443,7 +1458,7 @@ namespace Emby.Server.Implementations CanSelfRestart = CanSelfRestart, CanLaunchWebBrowser = CanLaunchWebBrowser, HasUpdateAvailable = HasUpdateAvailable, - TranscodingTempPath = ApplicationPaths.TranscodingTempPath, + TranscodingTempPath = ApplicationPaths.TranscodePath, ServerName = FriendlyName, LocalAddress = localAddress, SupportsLibraryMonitor = true, @@ -1465,7 +1480,7 @@ namespace Emby.Server.Implementations return new PublicSystemInfo { - Version = ApplicationVersion, + Version = ApplicationVersionString, ProductName = ApplicationProductName, Id = SystemId, OperatingSystem = OperatingSystem.Id.ToString(), @@ -1730,7 +1745,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 +1859,7 @@ namespace Emby.Server.Implementations internal class CertificateInfo { public string Path { get; set; } + public string Password { get; set; } } } diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index c7f92b80b6..fe705cbe27 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.Configuration { var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding"); - ((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ? + ((ServerApplicationPaths)ApplicationPaths).TranscodePath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ? null : Path.Combine(encodingConfig.TranscodingTempPath, "transcodes"); } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 33402f0e33..65f8a915f9 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.Serialization; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using Emby.Server.Implementations.Playlists; using MediaBrowser.Common.Json; @@ -28,7 +26,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; @@ -659,12 +656,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 { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d6ca19e3f4..45607dc098 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -10,7 +10,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" /> @@ -32,6 +31,7 @@ <PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.0" /> + <PackageReference Include="Mono.Nat" Version="2.0.0" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.6.0" /> <PackageReference Include="sharpcompress" Version="0.24.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" /> diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index d55dc2f18b..08041eb59f 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,179 +14,106 @@ 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 readonly object _createdRulesLock = new object(); - private List<string> _createdRules = new List<string>(); - private readonly object _usnsHandledLock = new object(); - private List<string> _usnsHandled = new List<string>(); + 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(); + Stop(); - await RunAsync(); + 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 (_usnsHandledLock) - { - 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; - - try - { - var localAddressString = await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false); - - if (Uri.TryCreate(localAddressString, UriKind.Absolute, out var uri)) - { - localAddressString = uri.Host; - - if (!IPAddress.TryParse(localAddressString, out localAddress)) - { - return; - } - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error"); - return; - } + _logger.LogDebug("Stopping NAT discovery"); - if (_disposed) - { - return; - } + NatUtility.StopDiscovery(); + NatUtility.DeviceFound -= OnNatUtilityDeviceFound; - // This should never happen, but the Handle method will throw ArgumentNullException if it does - if (localAddress == null) - { - return; - } + _timer?.Dispose(); - var natManager = _natManager; - if (natManager != null) - { - await natManager.Handle(localAddress, info, endpoint, NatProtocol.Upnp).ConfigureAwait(false); - } - } + _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered; } private void ClearCreatedRules(object state) @@ -196,30 +122,24 @@ namespace Emby.Server.Implementations.EntryPoints { _createdRules.Clear(); } - - lock (_usnsHandledLock) - { - _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"); } } @@ -232,15 +152,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 (_createdRulesLock) { - if (!_createdRules.Contains(addressString)) + if (!_createdRules.Contains(address)) { - _createdRules.Add(addressString); + _createdRules.Add(address); } else { @@ -268,54 +186,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 9c0db2cf5e..7bef2ae581 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 { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e4f98acb9b..dc1a56e271 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,7 +15,6 @@ 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; @@ -539,6 +537,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/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index c95b00ede2..85110c21cf 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 { @@ -122,7 +122,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/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 13857c1e8a..528636ecd3 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -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> @@ -2175,7 +2175,6 @@ namespace Emby.Server.Implementations.Library DisplayParentId = parentId }; - CreateItem(item, null); isNew = true; @@ -2197,7 +2196,6 @@ namespace Emby.Server.Implementations.Library { // Need to force save to increment DateLastSaved ForceSave = true - }, RefreshPriority.Normal); } @@ -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/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 2b6ae12977..b4e082b065 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; } @@ -475,24 +478,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; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 49308b2b1e..d4bd598e38 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2304,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/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index d12c96392d..2ea171031f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -68,7 +68,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected void SetTempFilePath(string extension) { - TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension); + TempFilePath = Path.Combine(AppPaths.GetTranscodePath(), UniqueId + "." + extension); } public virtual Task Open(CancellationToken openCancellationToken) diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index a71dc93467..bfe32a6c2b 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 c19148921b..86fbac3805 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/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index a2fc126a25..3a6852321d 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/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 24af1839fc..637e514ed6 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/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index ba5e939828..87f8553ae0 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/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index c343a7d482..200649ba94 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks try { - DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress); + DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodePath, minDateModified, progress); } catch (DirectoryNotFoundException) { @@ -138,13 +138,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 7afeba9dd7..fe8deae595 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,16 @@ 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).ConfigureAwait(false)).ToList(); progress.Report(10); @@ -94,18 +93,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/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 46195cc744..87de9804a4 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -10,8 +10,8 @@ namespace Emby.Server.Implementations /// </summary> public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths { - private string _defaultTranscodingTempPath; - private string _transcodingTempPath; + private string _defaultTranscodePath; + private string _transcodePath; private string _internalMetadataPath; /// <summary> @@ -107,19 +107,19 @@ 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 DefaultTranscodePath => _defaultTranscodePath ?? (_defaultTranscodePath = Path.Combine(ProgramDataPath, "transcodes")); - public string TranscodingTempPath + public string TranscodePath { - get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath); - set => _transcodingTempPath = value; + get => _transcodePath ?? (_transcodePath = DefaultTranscodePath); + set => _transcodePath = value; } - public string GetTranscodingTempPath() + public string GetTranscodePath() { - var path = TranscodingTempPath; + var path = TranscodePath; - if (!string.Equals(path, DefaultTranscodingTempPath, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(path, DefaultTranscodePath, StringComparison.OrdinalIgnoreCase)) { try { @@ -136,7 +136,7 @@ namespace Emby.Server.Implementations } } - path = DefaultTranscodingTempPath; + path = DefaultTranscodePath; Directory.CreateDirectory(path); return path; } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index a1e2f9079e..1c54022682 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -19,12 +19,11 @@ 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 { @@ -51,12 +50,12 @@ namespace Emby.Server.Implementations.Updates /// <summary> /// The current installations. /// </summary> - private List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations; + private readonly List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations; /// <summary> /// The completed installations. /// </summary> - private ConcurrentBag<InstallationInfo> _completedInstallationsInternal; + private readonly ConcurrentBag<InstallationInfo> _completedInstallationsInternal; public InstallationManager( ILogger<InstallationManager> logger, @@ -86,51 +85,32 @@ namespace Emby.Server.Implementations.Updates _zipClient = zipClient; } + /// <inheritdoc /> public event EventHandler<InstallationEventArgs> PackageInstalling; + /// <inheritdoc /> public event EventHandler<InstallationEventArgs> PackageInstallationCompleted; + /// <inheritdoc /> public event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed; + /// <inheritdoc /> public event EventHandler<InstallationEventArgs> PackageInstallationCancelled; - /// <summary> - /// Occurs when a plugin is uninstalled. - /// </summary> + /// <inheritdoc /> public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled; - /// <summary> - /// Occurs when a plugin plugin is updated. - /// </summary> + /// <inheritdoc /> public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated; - /// <summary> - /// Occurs when a plugin plugin is installed. - /// </summary> + /// <inheritdoc /> public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled; + /// <inheritdoc /> public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal; - /// <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); - } - - /// <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 async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default) { using (var response = await _httpClient.SendAsync( new HttpRequestOptions @@ -138,178 +118,91 @@ 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 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); - } - - /// <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)); + return GetCompatibleVersions( + package.versions, + minVersion, + classification); } - /// <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 Task<IEnumerable<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 => + return _applicationHost.Plugins.Select(x => { - 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, x.Name, x.Id, x.Version, systemUpdateLevel); + return compatibleversions.FirstOrDefault(y => y.Version > x.Version); + }).Where(x => x != null) + .Where(x => !CompletedInstallations.Any(y => string.Equals(y.AssemblyGuid, x.guid, StringComparison.OrdinalIgnoreCase))); } /// <inheritdoc /> @@ -395,7 +288,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(); } } @@ -443,7 +336,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 @@ -459,7 +352,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( @@ -541,18 +434,19 @@ namespace Emby.Server.Implementations.Updates { 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); @@ -571,7 +465,7 @@ namespace Emby.Server.Implementations.Updates { foreach (var tuple in _currentInstallations) { - tuple.Item2.Dispose(); + tuple.token.Dispose(); } _currentInstallations.Clear(); |
