aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
authorClaus Vium <cvium@users.noreply.github.com>2020-12-04 13:50:44 +0100
committerGitHub <noreply@github.com>2020-12-04 13:50:44 +0100
commitdca3f62ff85ba62af95831848718e9764d163306 (patch)
tree5be2338b7ec18013fb18feb3325d3228b3e5da59 /Emby.Server.Implementations
parenteed1a40b1935e00e04f6b27c7d307bf110b83b31 (diff)
parent9afd19b06e025992ee5159a08d7af8bd736a828e (diff)
Merge branch 'master' into PlugsVersionNumberFix
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs297
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs10
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs29
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs71
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj5
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs11
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs7
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthService.cs9
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs4
-rw-r--r--Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs130
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs61
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs89
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs3
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs3
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs51
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs76
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/da.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/el.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/es.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/fi.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr-CA.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/hu.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/id.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/it.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt-PT.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/ro.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/sk.json33
-rw-r--r--Emby.Server.Implementations/Localization/Core/ta.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/tr.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/uk.json9
-rw-r--r--Emby.Server.Implementations/Localization/Core/vi.json12
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json5
-rw-r--r--Emby.Server.Implementations/Localization/countries.json6
-rw-r--r--Emby.Server.Implementations/MediaEncoder/EncodingManager.cs20
-rw-r--r--Emby.Server.Implementations/Networking/NetworkManager.cs556
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs6
-rw-r--r--Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs2
-rw-r--r--Emby.Server.Implementations/ResourceFileManager.cs45
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs62
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs24
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs40
-rw-r--r--Emby.Server.Implementations/Udp/UdpServer.cs2
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs129
51 files changed, 736 insertions, 1170 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index cf7751c36..43e5198ed 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -1,14 +1,12 @@
#pragma warning disable CS1591
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
-using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
@@ -46,10 +44,11 @@ using Emby.Server.Implementations.SyncPlay;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
using Jellyfin.Api.Helpers;
+using Jellyfin.Networking.Configuration;
+using Jellyfin.Networking.Manager;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@@ -82,7 +81,6 @@ using MediaBrowser.Controller.SyncPlay;
using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
@@ -97,6 +95,7 @@ using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Plugins.Tmdb;
using MediaBrowser.Providers.Subtitles;
using MediaBrowser.XbmcMetadata.Providers;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -117,14 +116,12 @@ namespace Emby.Server.Implementations
private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
private readonly IFileSystem _fileSystemManager;
- private readonly INetworkManager _networkManager;
private readonly IXmlSerializer _xmlSerializer;
private readonly IJsonSerializer _jsonSerializer;
private readonly IStartupOptions _startupOptions;
private IMediaEncoder _mediaEncoder;
private ISessionManager _sessionManager;
- private IHttpClientFactory _httpClientFactory;
private string[] _urlPrefixes;
/// <summary>
@@ -159,6 +156,11 @@ namespace Emby.Server.Implementations
}
/// <summary>
+ /// Gets the <see cref="INetworkManager"/> singleton instance.
+ /// </summary>
+ public INetworkManager NetManager { get; internal set; }
+
+ /// <summary>
/// Occurs when [has pending restart changed].
/// </summary>
public event EventHandler HasPendingRestartChanged;
@@ -212,7 +214,7 @@ namespace Emby.Server.Implementations
private readonly List<IDisposable> _disposableParts = new List<IDisposable>();
/// <summary>
- /// Gets the configuration manager.
+ /// Gets or sets the configuration manager.
/// </summary>
/// <value>The configuration manager.</value>
protected IConfigurationManager ConfigurationManager { get; set; }
@@ -245,14 +247,12 @@ namespace Emby.Server.Implementations
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
/// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
public ApplicationHost(
IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
IStartupOptions options,
IFileSystem fileSystem,
- INetworkManager networkManager,
IServiceCollection serviceCollection)
{
_xmlSerializer = new MyXmlSerializer();
@@ -260,14 +260,17 @@ namespace Emby.Server.Implementations
ServiceCollection = serviceCollection;
- _networkManager = networkManager;
- networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
-
ApplicationPaths = applicationPaths;
LoggerFactory = loggerFactory;
_fileSystemManager = fileSystem;
ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
+ // Have to migrate settings here as migration subsystem not yet initialised.
+ MigrateNetworkConfiguration();
+
+ // Have to pre-register the NetworkConfigurationFactory, as the configuration sub-system is not yet initialised.
+ ConfigurationManager.RegisterConfiguration<NetworkConfigurationFactory>();
+ NetManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
Logger = LoggerFactory.CreateLogger<ApplicationHost>();
@@ -281,8 +284,6 @@ namespace Emby.Server.Implementations
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
- _networkManager.NetworkChanged += OnNetworkChanged;
-
CertificateInfo = new CertificateInfo
{
Path = ServerConfigurationManager.Configuration.CertificatePath,
@@ -295,6 +296,22 @@ namespace Emby.Server.Implementations
ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
}
+ /// <summary>
+ /// Temporary function to migration network settings out of system.xml and into network.xml.
+ /// TODO: remove at the point when a fixed migration path has been decided upon.
+ /// </summary>
+ private void MigrateNetworkConfiguration()
+ {
+ string path = Path.Combine(ConfigurationManager.CommonApplicationPaths.ConfigurationDirectoryPath, "network.xml");
+ if (!File.Exists(path))
+ {
+ var networkSettings = new NetworkConfiguration();
+ ClassMigrationHelper.CopyProperties(ServerConfigurationManager.Configuration, networkSettings);
+ _xmlSerializer.SerializeToFile(networkSettings, path);
+ Logger?.LogDebug("Successfully migrated network settings.");
+ }
+ }
+
public string ExpandVirtualPath(string path)
{
var appPaths = ApplicationPaths;
@@ -311,16 +328,6 @@ namespace Emby.Server.Implementations
.Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase);
}
- private string[] GetConfiguredLocalSubnets()
- {
- return ServerConfigurationManager.Configuration.LocalNetworkSubnets;
- }
-
- private void OnNetworkChanged(object sender, EventArgs e)
- {
- _validAddressResults.Clear();
- }
-
/// <inheritdoc />
public Version ApplicationVersion { get; }
@@ -487,14 +494,15 @@ namespace Emby.Server.Implementations
/// <inheritdoc/>
public void Init()
{
- HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
- HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
+ var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration();
+ HttpPort = networkConfiguration.HttpServerPortNumber;
+ HttpsPort = networkConfiguration.HttpsPortNumber;
// Safeguard against invalid configuration
if (HttpPort == HttpsPort)
{
- HttpPort = ServerConfiguration.DefaultHttpPort;
- HttpsPort = ServerConfiguration.DefaultHttpsPort;
+ HttpPort = NetworkConfiguration.DefaultHttpPort;
+ HttpsPort = NetworkConfiguration.DefaultHttpsPort;
}
DiscoverTypes();
@@ -523,7 +531,7 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton(_fileSystemManager);
ServiceCollection.AddSingleton<TmdbClientManager>();
- ServiceCollection.AddSingleton(_networkManager);
+ ServiceCollection.AddSingleton(NetManager);
ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
@@ -627,7 +635,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
- ServiceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>();
ServiceCollection.AddSingleton<EncodingHelper>();
ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
@@ -649,7 +656,6 @@ namespace Emby.Server.Implementations
_mediaEncoder = Resolve<IMediaEncoder>();
_sessionManager = Resolve<ISessionManager>();
- _httpClientFactory = Resolve<IHttpClientFactory>();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
@@ -770,8 +776,6 @@ namespace Emby.Server.Implementations
if (Plugins != null)
{
- var pluginBuilder = new StringBuilder();
-
foreach (var plugin in Plugins)
{
if (_pluginsManifests != null && plugin is IPluginAssembly assemblyPlugin)
@@ -791,13 +795,8 @@ namespace Emby.Server.Implementations
}
}
- pluginBuilder.Append(plugin.Name)
- .Append(' ')
- .Append(plugin.Version)
- .AppendLine();
+ Logger.LogInformation("Loaded plugin: {PluginName} {PluginVersion}", plugin.Name, plugin.Version);
}
-
- Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
}
_urlPrefixes = GetUrlPrefixes().ToArray();
@@ -921,9 +920,10 @@ namespace Emby.Server.Implementations
// Don't do anything if these haven't been set yet
if (HttpPort != 0 && HttpsPort != 0)
{
+ var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration();
// Need to restart if ports have changed
- if (ServerConfigurationManager.Configuration.HttpServerPortNumber != HttpPort ||
- ServerConfigurationManager.Configuration.HttpsPortNumber != HttpsPort)
+ if (networkConfiguration.HttpServerPortNumber != HttpPort ||
+ networkConfiguration.HttpsPortNumber != HttpsPort)
{
if (ServerConfigurationManager.Configuration.IsPortAuthorized)
{
@@ -1053,7 +1053,7 @@ namespace Emby.Server.Implementations
metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1];
int versionIndex = dir.LastIndexOf('_');
- if (versionIndex != -1 && Version.TryParse(dir.Substring(versionIndex + 1), out Version parsedVersion))
+ if (versionIndex != -1 && Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version parsedVersion))
{
// Versioned folder.
versions.Add(new LocalPlugin(Guid.Empty, metafile, parsedVersion, dir));
@@ -1167,6 +1167,9 @@ namespace Emby.Server.Implementations
// Xbmc
yield return typeof(ArtistNfoProvider).Assembly;
+ // Network
+ yield return typeof(NetworkManager).Assembly;
+
foreach (var i in GetAssembliesWithPartsInternal())
{
yield return i;
@@ -1178,13 +1181,10 @@ namespace Emby.Server.Implementations
/// <summary>
/// Gets the system status.
/// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="source">Where this request originated.</param>
/// <returns>SystemInfo.</returns>
- public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
+ public SystemInfo GetSystemInfo(IPAddress source)
{
- var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
- var transcodingTempPath = ConfigurationManager.GetTranscodePath();
-
return new SystemInfo
{
HasPendingRestart = HasPendingRestart,
@@ -1204,9 +1204,9 @@ namespace Emby.Server.Implementations
CanSelfRestart = CanSelfRestart,
CanLaunchWebBrowser = CanLaunchWebBrowser,
HasUpdateAvailable = HasUpdateAvailable,
- TranscodingTempPath = transcodingTempPath,
+ TranscodingTempPath = ConfigurationManager.GetTranscodePath(),
ServerName = FriendlyName,
- LocalAddress = localAddress,
+ LocalAddress = GetSmartApiUrl(source),
SupportsLibraryMonitor = true,
EncoderLocation = _mediaEncoder.EncoderLocation,
SystemArchitecture = RuntimeInformation.OSArchitecture,
@@ -1215,14 +1215,12 @@ namespace Emby.Server.Implementations
}
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
- => _networkManager.GetMacAddresses()
+ => NetManager.GetMacAddresses()
.Select(i => new WakeOnLanInfo(i))
.ToList();
- public async Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken)
+ public PublicSystemInfo GetPublicSystemInfo(IPAddress source)
{
- var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
-
return new PublicSystemInfo
{
Version = ApplicationVersionString,
@@ -1230,193 +1228,98 @@ namespace Emby.Server.Implementations
Id = SystemId,
OperatingSystem = OperatingSystem.Id.ToString(),
ServerName = FriendlyName,
- LocalAddress = localAddress,
+ LocalAddress = GetSmartApiUrl(source),
StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted
};
}
/// <inheritdoc/>
- public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps;
+ public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.GetNetworkConfiguration().EnableHttps;
/// <inheritdoc/>
- public async Task<string> GetLocalApiUrl(CancellationToken cancellationToken)
+ public string GetSmartApiUrl(IPAddress ipAddress, int? port = null)
{
- try
+ // Published server ends with a /
+ if (_startupOptions.PublishedServerUrl != null)
{
- // Return the first matched address, if found, or the first known local address
- var addresses = await GetLocalIpAddressesInternal(false, 1, cancellationToken).ConfigureAwait(false);
- if (addresses.Count == 0)
- {
- return null;
- }
-
- return GetLocalApiUrl(addresses[0]);
+ // Published server ends with a '/', so we need to remove it.
+ return _startupOptions.PublishedServerUrl.ToString().Trim('/');
}
- catch (Exception ex)
+
+ string smart = NetManager.GetBindInterface(ipAddress, out port);
+ // If the smartAPI doesn't start with http then treat it as a host or ip.
+ if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
- Logger.LogError(ex, "Error getting local Ip address information");
+ return smart.Trim('/');
}
- return null;
+ return GetLocalApiUrl(smart.Trim('/'), null, port);
}
- /// <summary>
- /// Removes the scope id from IPv6 addresses.
- /// </summary>
- /// <param name="address">The IPv6 address.</param>
- /// <returns>The IPv6 address without the scope id.</returns>
- private ReadOnlySpan<char> RemoveScopeId(ReadOnlySpan<char> address)
+ /// <inheritdoc/>
+ public string GetSmartApiUrl(HttpRequest request, int? port = null)
{
- var index = address.IndexOf('%');
- if (index == -1)
+ // Published server ends with a /
+ if (_startupOptions.PublishedServerUrl != null)
{
- return address;
+ // Published server ends with a '/', so we need to remove it.
+ return _startupOptions.PublishedServerUrl.ToString().Trim('/');
}
- return address.Slice(0, index);
- }
-
- /// <inheritdoc />
- public string GetLocalApiUrl(IPAddress ipAddress)
- {
- if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
+ string smart = NetManager.GetBindInterface(request, out port);
+ // If the smartAPI doesn't start with http then treat it as a host or ip.
+ if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
- var str = RemoveScopeId(ipAddress.ToString());
- Span<char> span = new char[str.Length + 2];
- span[0] = '[';
- str.CopyTo(span.Slice(1));
- span[^1] = ']';
-
- return GetLocalApiUrl(span);
+ return smart.Trim('/');
}
- return GetLocalApiUrl(ipAddress.ToString());
+ return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
}
/// <inheritdoc/>
- public string GetLoopbackHttpApiUrl()
- {
- return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort);
- }
-
- /// <inheritdoc/>
- public string GetLocalApiUrl(ReadOnlySpan<char> host, string scheme = null, int? port = null)
- {
- // NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
- // not. For consistency, always trim the trailing slash.
- return new UriBuilder
- {
- Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
- Host = host.ToString(),
- Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
- Path = ServerConfigurationManager.Configuration.BaseUrl
- }.ToString().TrimEnd('/');
- }
-
- public Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken)
+ public string GetSmartApiUrl(string hostname, int? port = null)
{
- return GetLocalIpAddressesInternal(true, 0, cancellationToken);
- }
-
- private async Task<List<IPAddress>> GetLocalIpAddressesInternal(bool allowLoopback, int limit, CancellationToken cancellationToken)
- {
- var addresses = ServerConfigurationManager
- .Configuration
- .LocalNetworkAddresses
- .Select(x => NormalizeConfiguredLocalAddress(x))
- .Where(i => i != null)
- .ToList();
-
- if (addresses.Count == 0)
+ // Published server ends with a /
+ if (_startupOptions.PublishedServerUrl != null)
{
- addresses.AddRange(_networkManager.GetLocalIpAddresses());
+ // Published server ends with a '/', so we need to remove it.
+ return _startupOptions.PublishedServerUrl.ToString().Trim('/');
}
- var resultList = new List<IPAddress>();
+ string smart = NetManager.GetBindInterface(hostname, out port);
- foreach (var address in addresses)
+ // If the smartAPI doesn't start with http then treat it as a host or ip.
+ if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
- if (!allowLoopback)
- {
- if (address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback))
- {
- continue;
- }
- }
-
- if (await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false))
- {
- resultList.Add(address);
-
- if (limit > 0 && resultList.Count >= limit)
- {
- return resultList;
- }
- }
+ return smart.Trim('/');
}
- return resultList;
+ return GetLocalApiUrl(smart.Trim('/'), null, port);
}
- public IPAddress NormalizeConfiguredLocalAddress(ReadOnlySpan<char> address)
+ /// <inheritdoc/>
+ public string GetLoopbackHttpApiUrl()
{
- var index = address.Trim('/').IndexOf('/');
- if (index != -1)
- {
- address = address.Slice(index + 1);
- }
-
- if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
+ if (NetManager.IsIP6Enabled)
{
- return result;
+ return GetLocalApiUrl("::1", Uri.UriSchemeHttp, HttpPort);
}
- return null;
+ return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort);
}
- private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
-
- private async Task<bool> IsLocalIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken)
+ /// <inheritdoc/>
+ public string GetLocalApiUrl(string host, string scheme = null, int? port = null)
{
- if (address.Equals(IPAddress.Loopback)
- || address.Equals(IPAddress.IPv6Loopback))
- {
- return true;
- }
-
- var apiUrl = GetLocalApiUrl(address) + "/system/ping";
-
- if (_validAddressResults.TryGetValue(apiUrl, out var cachedResult))
- {
- return cachedResult;
- }
-
- try
- {
- using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
-
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var result = await System.Text.Json.JsonSerializer.DeserializeAsync<string>(stream, JsonDefaults.GetOptions(), cancellationToken).ConfigureAwait(false);
- var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
-
- _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
- Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid);
- return valid;
- }
- catch (OperationCanceledException)
- {
- Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled");
- throw;
- }
- catch (Exception ex)
+ // NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
+ // not. For consistency, always trim the trailing slash.
+ return new UriBuilder
{
- Logger.LogDebug(ex, "Ping test result to {0}. Success: {1}", apiUrl, false);
-
- _validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false);
- return false;
- }
+ Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
+ Host = host,
+ Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
+ Path = ServerConfigurationManager.GetNetworkConfiguration().BaseUrl
+ }.ToString().TrimEnd('/');
}
public string FriendlyName =>
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 19045b72b..57684a429 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -540,18 +540,18 @@ namespace Emby.Server.Implementations.Channels
{
IncludeItemTypes = new[] { nameof(Channel) },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
- }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
+ }).Select(i => GetChannelFeatures(i)).ToArray();
}
/// <inheritdoc />
- public ChannelFeatures GetChannelFeatures(string id)
+ public ChannelFeatures GetChannelFeatures(Guid? id)
{
- if (string.IsNullOrEmpty(id))
+ if (!id.HasValue)
{
throw new ArgumentNullException(nameof(id));
}
- var channel = GetChannel(id);
+ var channel = GetChannel(id.Value);
var channelProvider = GetChannelProvider(channel);
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
@@ -634,7 +634,7 @@ namespace Emby.Server.Implementations.Channels
{
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
- if (query.ChannelIds.Length > 0)
+ if (query.ChannelIds.Count > 0)
{
// Avoid implicitly captured closure
var ids = query.ChannelIds;
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 638c7a9b4..50c7a07d4 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -3611,12 +3611,12 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add($"type in ({inClause})");
}
- if (query.ChannelIds.Length == 1)
+ if (query.ChannelIds.Count == 1)
{
whereClauses.Add("ChannelId=@ChannelId");
statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
}
- else if (query.ChannelIds.Length > 1)
+ else if (query.ChannelIds.Count > 1)
{
var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
whereClauses.Add($"ChannelId in ({inClause})");
@@ -4076,7 +4076,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(clause);
}
- if (query.GenreIds.Length > 0)
+ if (query.GenreIds.Count > 0)
{
var clauses = new List<string>();
var index = 0;
@@ -4097,7 +4097,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(clause);
}
- if (query.Genres.Length > 0)
+ if (query.Genres.Count > 0)
{
var clauses = new List<string>();
var index = 0;
@@ -4519,17 +4519,17 @@ namespace Emby.Server.Implementations.Data
if (query.HasImdbId.HasValue)
{
- whereClauses.Add("ProviderIds like '%imdb=%'");
+ whereClauses.Add(GetProviderIdClause(query.HasImdbId.Value, "imdb"));
}
if (query.HasTmdbId.HasValue)
{
- whereClauses.Add("ProviderIds like '%tmdb=%'");
+ whereClauses.Add(GetProviderIdClause(query.HasTmdbId.Value, "tmdb"));
}
if (query.HasTvdbId.HasValue)
{
- whereClauses.Add("ProviderIds like '%tvdb=%'");
+ whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb"));
}
var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList();
@@ -4769,6 +4769,21 @@ namespace Emby.Server.Implementations.Data
return whereClauses;
}
+ /// <summary>
+ /// Formats a where clause for the specified provider.
+ /// </summary>
+ /// <param name="includeResults">Whether or not to include items with this provider's ids.</param>
+ /// <param name="provider">Provider name.</param>
+ /// <returns>Formatted SQL clause.</returns>
+ private string GetProviderIdClause(bool includeResults, string provider)
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "ProviderIds {0} like '%{1}=%'",
+ includeResults ? string.Empty : "not",
+ provider);
+ }
+
private List<string> GetItemByNameTypesInQuery(InternalItemsQuery query)
{
var list = new List<string>();
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index f98c694c4..da5047d24 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -1,61 +1,38 @@
#pragma warning disable CS1591
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session;
-using Microsoft.Extensions.Caching.Memory;
namespace Emby.Server.Implementations.Devices
{
public class DeviceManager : IDeviceManager
{
- private readonly IMemoryCache _memoryCache;
- private readonly IJsonSerializer _json;
private readonly IUserManager _userManager;
- private readonly IServerConfigurationManager _config;
private readonly IAuthenticationRepository _authRepo;
- private readonly object _capabilitiesSyncLock = new object();
+ private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new ();
- public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
-
- public DeviceManager(
- IAuthenticationRepository authRepo,
- IJsonSerializer json,
- IUserManager userManager,
- IServerConfigurationManager config,
- IMemoryCache memoryCache)
+ public DeviceManager(IAuthenticationRepository authRepo, IUserManager userManager)
{
- _json = json;
_userManager = userManager;
- _config = config;
- _memoryCache = memoryCache;
_authRepo = authRepo;
}
+ public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
+
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
{
- var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- lock (_capabilitiesSyncLock)
- {
- _memoryCache.Set(deviceId, capabilities);
- _json.SerializeToFile(capabilities, path);
- }
+ _capabilitiesMap[deviceId] = capabilities;
}
public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
@@ -72,33 +49,13 @@ namespace Emby.Server.Implementations.Devices
public ClientCapabilities GetCapabilities(string id)
{
- if (_memoryCache.TryGetValue(id, out ClientCapabilities result))
- {
- return result;
- }
-
- lock (_capabilitiesSyncLock)
- {
- var path = Path.Combine(GetDevicePath(id), "capabilities.json");
- try
- {
- return _json.DeserializeFromFile<ClientCapabilities>(path) ?? new ClientCapabilities();
- }
- catch
- {
- }
- }
-
- return new ClientCapabilities();
+ return _capabilitiesMap.TryGetValue(id, out ClientCapabilities result)
+ ? result
+ : new ClientCapabilities();
}
public DeviceInfo GetDevice(string id)
{
- return GetDevice(id, true);
- }
-
- private DeviceInfo GetDevice(string id, bool includeCapabilities)
- {
var session = _authRepo.Get(new AuthenticationInfoQuery
{
DeviceId = id
@@ -154,16 +111,6 @@ namespace Emby.Server.Implementations.Devices
};
}
- private string GetDevicesPath()
- {
- return Path.Combine(_config.ApplicationPaths.DataPath, "devices");
- }
-
- private string GetDevicePath(string id)
- {
- return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture));
- }
-
public bool CanAccessDevice(User user, string deviceId)
{
if (user == null)
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index d360bb00f..91c4648c6 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -22,7 +22,6 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="IPNetwork2" Version="2.5.226" />
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
@@ -37,8 +36,8 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Mono.Nat" Version="3.0.1" />
- <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
- <PackageReference Include="ServiceStack.Text.Core" Version="5.10.0" />
+ <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" />
+ <PackageReference Include="ServiceStack.Text.Core" Version="5.10.2" />
<PackageReference Include="sharpcompress" Version="0.26.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.0" />
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index 2e8cc76d2..14201ead2 100644
--- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
+using Jellyfin.Networking.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins;
@@ -56,7 +57,7 @@ namespace Emby.Server.Implementations.EntryPoints
private string GetConfigIdentifier()
{
const char Separator = '|';
- var config = _config.Configuration;
+ var config = _config.GetNetworkConfiguration();
return new StringBuilder(32)
.Append(config.EnableUPnP).Append(Separator)
@@ -93,7 +94,8 @@ namespace Emby.Server.Implementations.EntryPoints
private void Start()
{
- if (!_config.Configuration.EnableUPnP || !_config.Configuration.EnableRemoteAccess)
+ var config = _config.GetNetworkConfiguration();
+ if (!config.EnableUPnP || !config.EnableRemoteAccess)
{
return;
}
@@ -156,11 +158,12 @@ namespace Emby.Server.Implementations.EntryPoints
private IEnumerable<Task> CreatePortMaps(INatDevice device)
{
- yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
+ var config = _config.GetNetworkConfiguration();
+ yield return CreatePortMap(device, _appHost.HttpPort, config.PublicPort);
if (_appHost.ListenWithHttps)
{
- yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
+ yield return CreatePortMap(device, _appHost.HttpsPort, config.PublicHttpsPort);
}
}
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index ff64e217a..ae1b51b4c 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -44,7 +45,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
private readonly List<BaseItem> _itemsRemoved = new List<BaseItem>();
private readonly List<BaseItem> _itemsUpdated = new List<BaseItem>();
- private readonly Dictionary<Guid, DateTime> _lastProgressMessageTimes = new Dictionary<Guid, DateTime>();
+ private readonly ConcurrentDictionary<Guid, DateTime> _lastProgressMessageTimes = new ConcurrentDictionary<Guid, DateTime>();
public LibraryChangedNotifier(
ILibraryManager libraryManager,
@@ -98,7 +99,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
- _lastProgressMessageTimes[item.Id] = DateTime.UtcNow;
+ _lastProgressMessageTimes.AddOrUpdate(item.Id, key => DateTime.UtcNow, (key, existing) => DateTime.UtcNow);
var dict = new Dictionary<string, string>();
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
@@ -140,6 +141,8 @@ namespace Emby.Server.Implementations.EntryPoints
private void OnProviderRefreshCompleted(object sender, GenericEventArgs<BaseItem> e)
{
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
+
+ _lastProgressMessageTimes.TryRemove(e.Argument.Id, out DateTime removed);
}
private static bool EnableRefreshMessage(BaseItem item)
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
index df7a034e8..4a0fc8239 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
@@ -20,9 +21,15 @@ namespace Emby.Server.Implementations.HttpServer.Security
public AuthorizationInfo Authenticate(HttpRequest request)
{
var auth = _authorizationContext.GetAuthorizationInfo(request);
+
+ if (!auth.HasToken)
+ {
+ throw new AuthenticationException("Request does not contain a token.");
+ }
+
if (!auth.IsAuthenticated)
{
- throw new AuthenticationException("Invalid token.");
+ throw new SecurityException("Invalid token.");
}
if (auth.User?.HasPermission(PermissionKind.IsDisabled) ?? false)
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index fdf2e3908..d62e2eefe 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -102,7 +102,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
DeviceId = deviceId,
Version = version,
Token = token,
- IsAuthenticated = false
+ IsAuthenticated = false,
+ HasToken = false
};
if (string.IsNullOrWhiteSpace(token))
@@ -111,6 +112,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
return authInfo;
}
+ authInfo.HasToken = true;
var result = _authRepo.Get(new AuthenticationInfoQuery
{
AccessToken = token
diff --git a/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs b/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs
new file mode 100644
index 000000000..d4e790c9a
--- /dev/null
+++ b/Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Concurrent;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Events;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Net;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Server.Implementations.Library
+{
+ /// <summary>
+ /// A library post scan/refresh task for pre-fetching remote images.
+ /// </summary>
+ public class ImageFetcherPostScanTask : ILibraryPostScanTask
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly IProviderManager _providerManager;
+ private readonly ILogger<ImageFetcherPostScanTask> _logger;
+ private readonly SemaphoreSlim _imageFetcherLock;
+
+ private ConcurrentDictionary<Guid, (BaseItem item, ItemUpdateType updateReason)> _queuedItems;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ImageFetcherPostScanTask"/> class.
+ /// </summary>
+ /// <param name="libraryManager">An instance of <see cref="ILibraryManager"/>.</param>
+ /// <param name="providerManager">An instance of <see cref="IProviderManager"/>.</param>
+ /// <param name="logger">An instance of <see cref="ILogger{ImageFetcherPostScanTask}"/>.</param>
+ public ImageFetcherPostScanTask(
+ ILibraryManager libraryManager,
+ IProviderManager providerManager,
+ ILogger<ImageFetcherPostScanTask> logger)
+ {
+ _libraryManager = libraryManager;
+ _providerManager = providerManager;
+ _logger = logger;
+ _queuedItems = new ConcurrentDictionary<Guid, (BaseItem item, ItemUpdateType updateReason)>();
+ _imageFetcherLock = new SemaphoreSlim(1, 1);
+ _libraryManager.ItemAdded += OnLibraryManagerItemAddedOrUpdated;
+ _libraryManager.ItemUpdated += OnLibraryManagerItemAddedOrUpdated;
+ _providerManager.RefreshCompleted += OnProviderManagerRefreshCompleted;
+ }
+
+ /// <inheritdoc />
+ public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ // Sometimes a library scan will cause this to run twice if there's an item refresh going on.
+ await _imageFetcherLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ var now = DateTime.UtcNow;
+ var itemGuids = _queuedItems.Keys.ToList();
+
+ for (var i = 0; i < itemGuids.Count; i++)
+ {
+ if (!_queuedItems.TryGetValue(itemGuids[i], out var queuedItem))
+ {
+ continue;
+ }
+
+ var itemId = queuedItem.item.Id.ToString("N", CultureInfo.InvariantCulture);
+ var itemType = queuedItem.item.GetType();
+ _logger.LogDebug(
+ "Updating remote images for item {ItemId} with media type {ItemMediaType}",
+ itemId,
+ itemType);
+ try
+ {
+ await _libraryManager.UpdateImagesAsync(queuedItem.item, queuedItem.updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to fetch images for {Type} item with id {ItemId}", itemType, itemId);
+ }
+
+ _queuedItems.TryRemove(queuedItem.item.Id, out _);
+ }
+
+ if (itemGuids.Count > 0)
+ {
+ _logger.LogInformation(
+ "Finished updating/pre-fetching {NumberOfImages} images. Elapsed time: {TimeElapsed}s.",
+ itemGuids.Count.ToString(CultureInfo.InvariantCulture),
+ (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ _logger.LogDebug("No images were updated.");
+ }
+ }
+ finally
+ {
+ _imageFetcherLock.Release();
+ }
+ }
+
+ private void OnLibraryManagerItemAddedOrUpdated(object sender, ItemChangeEventArgs itemChangeEventArgs)
+ {
+ if (!_queuedItems.ContainsKey(itemChangeEventArgs.Item.Id) && itemChangeEventArgs.Item.ImageInfos.Length > 0)
+ {
+ _queuedItems.AddOrUpdate(
+ itemChangeEventArgs.Item.Id,
+ (itemChangeEventArgs.Item, itemChangeEventArgs.UpdateReason),
+ (key, existingValue) => existingValue);
+ }
+ }
+
+ private void OnProviderManagerRefreshCompleted(object sender, GenericEventArgs<BaseItem> e)
+ {
+ if (!_queuedItems.ContainsKey(e.Argument.Id) && e.Argument.ImageInfos.Length > 0)
+ {
+ _queuedItems.AddOrUpdate(
+ e.Argument.Id,
+ (e.Argument, ItemUpdateType.None),
+ (key, existingValue) => existingValue);
+ }
+
+ // The RefreshCompleted event is a bit awkward in that it seems to _only_ be fired on
+ // the item that was refreshed regardless of children refreshes. So we take it as a signal
+ // that the refresh is entirely completed.
+ Run(null, CancellationToken.None).GetAwaiter().GetResult();
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index d83873441..5b926b0f4 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -858,7 +858,21 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Person}.</returns>
public Person GetPerson(string name)
{
- return CreateItemByName<Person>(Person.GetPath, name, new DtoOptions(true));
+ var path = Person.GetPath(name);
+ var id = GetItemByNameId<Person>(path);
+ if (!(GetItemById(id) is Person item))
+ {
+ item = new Person
+ {
+ Name = name,
+ Id = id,
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow,
+ Path = path
+ };
+ }
+
+ return item;
}
/// <summary>
@@ -1503,7 +1517,7 @@ namespace Emby.Server.Implementations.Library
{
if (query.AncestorIds.Length == 0 &&
query.ParentId.Equals(Guid.Empty) &&
- query.ChannelIds.Length == 0 &&
+ query.ChannelIds.Count == 0 &&
query.TopParentIds.Length == 0 &&
string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) &&
string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) &&
@@ -1941,19 +1955,9 @@ namespace Emby.Server.Implementations.Library
}
/// <inheritdoc />
- public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+ public Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
- foreach (var item in items)
- {
- if (item.IsFileProtocol)
- {
- ProviderManager.SaveMetadata(item, updateReason);
- }
-
- item.DateLastSaved = DateTime.UtcNow;
-
- await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
- }
+ RunMetadataSavers(items, updateReason);
_itemRepository.SaveItems(items, cancellationToken);
@@ -1984,12 +1988,27 @@ namespace Emby.Server.Implementations.Library
}
}
}
+
+ return Task.CompletedTask;
}
/// <inheritdoc />
public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
=> UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
+ public void RunMetadataSavers(IReadOnlyList<BaseItem> items, ItemUpdateType updateReason)
+ {
+ foreach (var item in items)
+ {
+ if (item.IsFileProtocol)
+ {
+ ProviderManager.SaveMetadata(item, updateReason);
+ }
+
+ item.DateLastSaved = DateTime.UtcNow;
+ }
+ }
+
/// <summary>
/// Reports the item removed.
/// </summary>
@@ -2443,9 +2462,19 @@ namespace Emby.Server.Implementations.Library
public BaseItem GetParentItem(string parentId, Guid? userId)
{
- if (!string.IsNullOrEmpty(parentId))
+ if (string.IsNullOrEmpty(parentId))
+ {
+ return GetParentItem((Guid?)null, userId);
+ }
+
+ return GetParentItem(new Guid(parentId), userId);
+ }
+
+ public BaseItem GetParentItem(Guid? parentId, Guid? userId)
+ {
+ if (parentId.HasValue)
{
- return GetItemById(new Guid(parentId));
+ return GetItemById(parentId.Value);
}
if (userId.HasValue && userId != Guid.Empty)
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index c850e3a08..1d9529dff 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -156,8 +156,8 @@ namespace Emby.Server.Implementations.Library
ExcludeItemTypes = excludeItemTypes.ToArray(),
IncludeItemTypes = includeItemTypes.ToArray(),
Limit = query.Limit,
- IncludeItemsByName = string.IsNullOrEmpty(query.ParentId),
- ParentId = string.IsNullOrEmpty(query.ParentId) ? Guid.Empty : new Guid(query.ParentId),
+ IncludeItemsByName = !query.ParentId.HasValue,
+ ParentId = query.ParentId ?? Guid.Empty,
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
Recursive = true,
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 5d17ba1de..1084ddf74 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
-using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -19,7 +18,6 @@ using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -36,6 +34,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly IApplicationHost _appHost;
private readonly ICryptoProvider _cryptoProvider;
+ private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
+ private DateTime _lastErrorResponse;
+
public SchedulesDirect(
ILogger<SchedulesDirect> logger,
IJsonSerializer jsonSerializer,
@@ -50,8 +51,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_cryptoProvider = cryptoProvider;
}
- private string UserAgent => _appHost.ApplicationUserAgent;
-
/// <inheritdoc />
public string Name => "Schedules Direct";
@@ -307,7 +306,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (details.contentRating != null && details.contentRating.Count > 0)
{
- info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-").Replace("--", "-");
+ info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-", StringComparison.Ordinal)
+ .Replace("--", "-", StringComparison.Ordinal);
var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
if (invalid.Contains(info.OfficialRating, StringComparer.OrdinalIgnoreCase))
@@ -450,7 +450,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private async Task<List<ScheduleDirect.ShowImages>> GetImageForPrograms(
ListingsProviderInfo info,
- List<string> programIds,
+ IReadOnlyList<string> programIds,
CancellationToken cancellationToken)
{
if (programIds.Count == 0)
@@ -458,23 +458,21 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return new List<ScheduleDirect.ShowImages>();
}
- var imageIdString = "[";
-
- foreach (var i in programIds)
+ StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
+ foreach (ReadOnlySpan<char> i in programIds)
{
- var imageId = i.Substring(0, 10);
-
- if (!imageIdString.Contains(imageId, StringComparison.Ordinal))
- {
- imageIdString += "\"" + imageId + "\",";
- }
+ str.Append('"')
+ .Append(i.Slice(0, 10))
+ .Append("\",");
}
- imageIdString = imageIdString.TrimEnd(',') + "]";
+ // Remove last ,
+ str.Length--;
+ str.Append(']');
using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs")
{
- Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json)
+ Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json)
};
try
@@ -539,9 +537,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return lineups;
}
- private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
- private DateTime _lastErrorResponse;
-
private async Task<string> GetToken(ListingsProviderInfo info, CancellationToken cancellationToken)
{
var username = info.Username;
@@ -564,8 +559,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return null;
}
- NameValuePair savedToken;
- if (!_tokens.TryGetValue(username, out savedToken))
+ if (!_tokens.TryGetValue(username, out NameValuePair savedToken))
{
savedToken = new NameValuePair();
_tokens.TryAdd(username, savedToken);
@@ -647,13 +641,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
var hashedPasswordBytes = _cryptoProvider.ComputeHash("SHA1", Encoding.ASCII.GetBytes(password), Array.Empty<byte>());
- string hashedPassword = Hex.Encode(hashedPasswordBytes);
+ // TODO: remove ToLower when Convert.ToHexString supports lowercase
+ // Schedules Direct requires the hex to be lowercase
+ string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant();
options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + hashedPassword + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(stream).ConfigureAwait(false);
- if (root.message == "OK")
+ if (string.Equals(root.message, "OK", StringComparison.Ordinal))
{
_logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
return root.token;
@@ -777,24 +773,28 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups/" + listingsId);
options.Headers.TryAddWithoutValidation("token", token);
- var list = new List<ChannelInfo>();
-
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(stream).ConfigureAwait(false);
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
_logger.LogInformation("Mapping Stations to Channel");
- var allStations = root.stations ?? Enumerable.Empty<ScheduleDirect.Station>();
+ var allStations = root.stations ?? new List<ScheduleDirect.Station>();
- foreach (ScheduleDirect.Map map in root.map)
+ var map = root.map;
+ int len = map.Count;
+ var array = new List<ChannelInfo>(len);
+ for (int i = 0; i < len; i++)
{
- var channelNumber = GetChannelNumber(map);
+ var channelNumber = GetChannelNumber(map[i]);
- var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
+ var station = allStations.Find(item => string.Equals(item.stationID, map[i].stationID, StringComparison.OrdinalIgnoreCase));
if (station == null)
{
- station = new ScheduleDirect.Station { stationID = map.stationID };
+ station = new ScheduleDirect.Station
+ {
+ stationID = map[i].stationID
+ };
}
var channelInfo = new ChannelInfo
@@ -810,32 +810,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
channelInfo.ImageUrl = station.logo.URL;
}
- list.Add(channelInfo);
- }
-
- return list;
- }
-
- private ScheduleDirect.Station GetStation(List<ScheduleDirect.Station> allStations, string channelNumber, string channelName)
- {
- if (!string.IsNullOrWhiteSpace(channelName))
- {
- channelName = NormalizeName(channelName);
-
- var result = allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase));
-
- if (result != null)
- {
- return result;
- }
- }
-
- if (!string.IsNullOrWhiteSpace(channelNumber))
- {
- return allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase));
+ array[i] = channelInfo;
}
- return null;
+ return array;
}
private static string NormalizeName(string value)
@@ -1044,7 +1022,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- //
public class Title
{
public string title120 { get; set; }
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index 8a0c0043a..3a738fd5d 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -76,7 +76,6 @@ namespace Emby.Server.Implementations.LiveTv
}
var list = sources.ToList();
- var serverUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
foreach (var source in list)
{
@@ -103,7 +102,7 @@ namespace Emby.Server.Implementations.LiveTv
// Dummy this up so that direct play checks can still run
if (string.IsNullOrEmpty(source.Path) && source.Protocol == MediaProtocol.Http)
{
- source.Path = serverUrl;
+ source.Path = _appHost.GetSmartApiUrl(string.Empty);
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index c0a4d1228..b6444b172 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -237,8 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (!inside)
{
- buffer[bufferIndex] = let;
- bufferIndex++;
+ buffer[bufferIndex++] = let;
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index d4a88e299..cdc8c6870 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -111,11 +111,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken)
{
- using (var client = new TcpClient(new IPEndPoint(remoteIp, HdHomeRunPort)))
- using (var stream = client.GetStream())
- {
- return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false);
- }
+ using var client = new TcpClient();
+ client.Connect(remoteIp, HdHomeRunPort);
+
+ using var stream = client.GetStream();
+ return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false);
}
private static async Task<bool> CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken)
@@ -142,7 +142,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
_remoteEndPoint = new IPEndPoint(remoteIp, HdHomeRunPort);
- _tcpClient = new TcpClient(_remoteEndPoint);
+ _tcpClient = new TcpClient();
+ _tcpClient.Connect(_remoteEndPoint);
if (!_lockkey.HasValue)
{
@@ -221,30 +222,30 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return;
}
- using (var tcpClient = new TcpClient(_remoteEndPoint))
- using (var stream = tcpClient.GetStream())
+ using var tcpClient = new TcpClient();
+ tcpClient.Connect(_remoteEndPoint);
+
+ using var stream = tcpClient.GetStream();
+ var commandList = commands.GetCommands();
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
+ try
{
- var commandList = commands.GetCommands();
- byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
- try
+ foreach (var command in commandList)
{
- foreach (var command in commandList)
- {
- var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
- await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
- int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
+ await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
+ int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
- // parse response to make sure it worked
- if (!ParseReturnMessage(buffer, receivedBytes, out _))
- {
- return;
- }
+ // parse response to make sure it worked
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
+ {
+ return;
}
}
- finally
- {
- ArrayPool<byte>.Shared.Return(buffer);
- }
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 858c10030..cf653f87d 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Net;
+using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@@ -50,6 +52,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
EnableStreamSharing = true;
}
+ /// <summary>
+ /// Returns an unused UDP port number in the range specified.
+ /// Temporarily placed here until future network PR merged.
+ /// </summary>
+ /// <param name="range">Upper and Lower boundary of ports to select.</param>
+ /// <returns>System.Int32.</returns>
+ private static int GetUdpPortFromRange((int Min, int Max) range)
+ {
+ var properties = IPGlobalProperties.GetIPGlobalProperties();
+
+ // Get active udp listeners.
+ var udpListenerPorts = properties.GetActiveUdpListeners()
+ .Where(n => n.Port >= range.Min && n.Port <= range.Max)
+ .Select(n => n.Port);
+
+ return Enumerable
+ .Range(range.Min, range.Max)
+ .FirstOrDefault(i => !udpListenerPorts.Contains(i));
+ }
+
public override async Task Open(CancellationToken openCancellationToken)
{
LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
@@ -57,7 +79,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var mediaSource = OriginalMediaSource;
var uri = new Uri(mediaSource.Path);
- var localPort = _networkManager.GetRandomUnusedUdpPort();
+ // Temporary code to reduce PR size. This will be updated by a future network pr.
+ var localPort = GetUdpPortFromRange((49152, 65535));
Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
@@ -70,7 +93,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try
{
await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort).ConfigureAwait(false);
- localAddress = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address;
+ localAddress = ((IPEndPoint)tcpClient.Client.LocalEndPoint).Address;
tcpClient.Close();
}
catch (Exception ex)
@@ -80,6 +103,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
+ if (localAddress.IsIPv4MappedToIPv6) {
+ localAddress = localAddress.MapToIPv4();
+ }
+
var udpClient = new UdpClient(localPort, AddressFamily.InterNetwork);
var hdHomerunManager = new HdHomerunManager();
@@ -110,12 +137,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var taskCompletionSource = new TaskCompletionSource<bool>();
- await StartStreaming(
+ _ = StartStreaming(
udpClient,
hdHomerunManager,
remoteAddress,
taskCompletionSource,
- LiveStreamCancellationTokenSource.Token).ConfigureAwait(false);
+ LiveStreamCancellationTokenSource.Token);
// OpenedMediaSource.Protocol = MediaProtocol.File;
// OpenedMediaSource.Path = tempFile;
@@ -136,33 +163,30 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return TempFilePath;
}
- private Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
+ private async Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
- return Task.Run(async () =>
+ using (udpClient)
+ using (hdHomerunManager)
{
- using (udpClient)
- using (hdHomerunManager)
+ try
{
- try
- {
- await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
- }
- catch (OperationCanceledException ex)
- {
- Logger.LogInformation("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
- openTaskCompletionSource.TrySetException(ex);
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error opening live stream:");
- openTaskCompletionSource.TrySetException(ex);
- }
-
- EnableStreamSharing = false;
+ await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException ex)
+ {
+ Logger.LogInformation("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
+ openTaskCompletionSource.TrySetException(ex);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error opening live stream:");
+ openTaskCompletionSource.TrySetException(ex);
}
- await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
- });
+ EnableStreamSharing = false;
+ }
+
+ await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
}
private async Task CopyTo(UdpClient udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 1d6c26c13..c82b67b41 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
extInf = line.Substring(ExtInfPrefix.Length).Trim();
_logger.LogInformation("Found m3u channel: {0}", extInf);
}
- else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith("#", StringComparison.OrdinalIgnoreCase))
+ else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith('#'))
{
var channel = GetChannelnfo(extInf, tunerHostId, line);
if (string.IsNullOrWhiteSpace(channel.Id))
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index 2de447ad9..f7507e6ba 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var extension = "ts";
var requiresRemux = false;
- var contentType = response.Content.Headers.ContentType.ToString();
+ var contentType = response.Content.Headers.ContentType?.ToString() ?? string.Empty;
if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1)
{
requiresRemux = true;
diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json
index b29ad94ef..4ee4eb989 100644
--- a/Emby.Server.Implementations/Localization/Core/da.json
+++ b/Emby.Server.Implementations/Localization/Core/da.json
@@ -113,5 +113,10 @@
"TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end en dag gammel.",
"TaskCleanTranscode": "Rengør Transcode Mappen",
"TaskRefreshPeople": "Genopfrisk Personer",
- "TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek."
+ "TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek.",
+ "TaskCleanActivityLogDescription": "Sletter linjer i aktivitetsloggen ældre end den konfigureret alder.",
+ "TaskCleanActivityLog": "Ryd Aktivitetslog",
+ "Undefined": "Udefineret",
+ "Forced": "Tvunget",
+ "Default": "Standard"
}
diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json
index c45cc11cb..23d45b473 100644
--- a/Emby.Server.Implementations/Localization/Core/el.json
+++ b/Emby.Server.Implementations/Localization/Core/el.json
@@ -113,5 +113,10 @@
"TaskCleanTranscode": "Καθαρισμός Kαταλόγου Διακωδικοποιητή",
"TaskUpdatePluginsDescription": "Κατεβάζει και εγκαθιστά ενημερώσεις για τις προσθήκες που έχουν ρυθμιστεί για αυτόματη ενημέρωση.",
"TaskUpdatePlugins": "Ενημέρωση Προσθηκών",
- "TaskRefreshPeopleDescription": "Ενημερώνει μεταδεδομένα για ηθοποιούς και σκηνοθέτες στην βιβλιοθήκη των πολυμέσων σας."
+ "TaskRefreshPeopleDescription": "Ενημερώνει μεταδεδομένα για ηθοποιούς και σκηνοθέτες στην βιβλιοθήκη των πολυμέσων σας.",
+ "TaskCleanActivityLogDescription": "Διαγράφει καταχωρήσεις απο το αρχείο καταγραφής παλαιότερες από την επιλεγμένη ηλικία.",
+ "TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων",
+ "Undefined": "Απροσδιόριστο",
+ "Forced": "Εξαναγκασμένο",
+ "Default": "Προεπιλογή"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index ab54c0ea6..05181116d 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -113,5 +113,9 @@
"TasksChannelsCategory": "Canales de Internet",
"TasksApplicationCategory": "Aplicación",
"TasksLibraryCategory": "Biblioteca",
- "TasksMaintenanceCategory": "Mantenimiento"
+ "TasksMaintenanceCategory": "Mantenimiento",
+ "TaskCleanActivityLogDescription": "Elimina entradas del registro de actividad que sean más antiguas al periodo establecido.",
+ "TaskCleanActivityLog": "Limpiar registro de actividades",
+ "Undefined": "Sin definir",
+ "Forced": "Forzado"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json
index fe674cf36..16fde325f 100644
--- a/Emby.Server.Implementations/Localization/Core/es.json
+++ b/Emby.Server.Implementations/Localization/Core/es.json
@@ -70,7 +70,7 @@
"ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciada",
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
- "Shows": "Mostrar",
+ "Shows": "Series de Televisión",
"Songs": "Canciones",
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
"SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}",
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index 8e219a9ce..61bef29ed 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -112,5 +112,7 @@
"TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
"TasksChannelsCategory": "Internet kanavat",
"TasksApplicationCategory": "Sovellus",
- "TasksLibraryCategory": "Kirjasto"
+ "TasksLibraryCategory": "Kirjasto",
+ "Forced": "Pakotettu",
+ "Default": "Oletus"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json
index 3d7592e3c..5aa65a525 100644
--- a/Emby.Server.Implementations/Localization/Core/fr-CA.json
+++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json
@@ -113,5 +113,6 @@
"TaskCleanCache": "Nettoyer le répertoire des fichiers temporaires",
"TasksApplicationCategory": "Application",
"TaskCleanCacheDescription": "Supprime les fichiers temporaires qui ne sont plus nécessaire pour le système.",
- "TasksChannelsCategory": "Canaux Internet"
+ "TasksChannelsCategory": "Canaux Internet",
+ "Default": "Par défaut"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index 3d5d69f36..1e195378f 100644
--- a/Emby.Server.Implementations/Localization/Core/fr.json
+++ b/Emby.Server.Implementations/Localization/Core/fr.json
@@ -93,8 +93,8 @@
"ValueSpecialEpisodeName": "Spécial - {0}",
"VersionNumber": "Version {0}",
"TasksChannelsCategory": "Chaines en ligne",
- "TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
- "TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquant",
+ "TaskDownloadMissingSubtitlesDescription": "Recherche les sous-titres manquants sur internet en se basant sur la configuration des métadonnées.",
+ "TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquants",
"TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
"TaskRefreshChannels": "Rafraîchir les chaines",
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index 804dabe57..e5707e78c 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -115,5 +115,8 @@
"TaskRefreshChannels": "Csatornák frissítése",
"TaskCleanTranscodeDescription": "Törli az egy napnál régebbi átkódolási fájlokat.",
"TaskCleanActivityLogDescription": "A beállítottnál korábbi bejegyzések törlése a tevékenységnaplóból.",
- "TaskCleanActivityLog": "Tevékenységnapló törlése"
+ "TaskCleanActivityLog": "Tevékenységnapló törlése",
+ "Undefined": "Meghatározatlan",
+ "Forced": "Kényszerített",
+ "Default": "Alapértelmezett"
}
diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json
index ef3ed2580..105ef7be9 100644
--- a/Emby.Server.Implementations/Localization/Core/id.json
+++ b/Emby.Server.Implementations/Localization/Core/id.json
@@ -112,5 +112,10 @@
"TaskRefreshPeople": "Muat ulang Orang",
"TaskCleanLogsDescription": "Menghapus file log yang lebih dari {0} hari.",
"TaskCleanLogs": "Bersihkan Log Direktori",
- "TaskRefreshLibrary": "Pindai Pustaka Media"
+ "TaskRefreshLibrary": "Pindai Pustaka Media",
+ "TaskCleanActivityLogDescription": "Menghapus log aktivitas yang lebih tua dari umur yang dikonfigurasi.",
+ "TaskCleanActivityLog": "Bersihkan Log Aktivitas",
+ "Undefined": "Tidak terdefinisi",
+ "Forced": "Dipaksa",
+ "Default": "Bawaan"
}
diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json
index 9e37ddc27..110f8043d 100644
--- a/Emby.Server.Implementations/Localization/Core/it.json
+++ b/Emby.Server.Implementations/Localization/Core/it.json
@@ -115,5 +115,8 @@
"TasksLibraryCategory": "Libreria",
"TasksMaintenanceCategory": "Manutenzione",
"TaskCleanActivityLog": "Attività di Registro Completate",
- "TaskCleanActivityLogDescription": "Elimina gli inserimenti nel registro delle attività più vecchie dell’età configurata."
+ "TaskCleanActivityLogDescription": "Elimina gli inserimenti nel registro delle attività più vecchie dell’età configurata.",
+ "Undefined": "Non Definito",
+ "Forced": "Forzato",
+ "Default": "Predefinito"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index e1e88cc9b..b6672a554 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -87,7 +87,7 @@
"UserOnlineFromDevice": "{0} heeft verbinding met {1}",
"UserPasswordChangedWithName": "Wachtwoord voor {0} is gewijzigd",
"UserPolicyUpdatedWithName": "Gebruikersbeleid gewijzigd voor {0}",
- "UserStartedPlayingItemWithValues": "{0} heeft afspelen van {1} gestart op {2}",
+ "UserStartedPlayingItemWithValues": "{0} speelt {1} af op {2}",
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
"ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
"ValueSpecialEpisodeName": "Speciaal - {0}",
@@ -115,5 +115,8 @@
"TasksLibraryCategory": "Bibliotheek",
"TasksMaintenanceCategory": "Onderhoud",
"TaskCleanActivityLogDescription": "Verwijder activiteiten logs ouder dan de ingestelde tijd.",
- "TaskCleanActivityLog": "Leeg activiteiten logboek"
+ "TaskCleanActivityLog": "Leeg activiteiten logboek",
+ "Undefined": "Niet gedefinieerd",
+ "Forced": "Geforceerd",
+ "Default": "Standaard"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json
index 90a4941c5..8c41edf96 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-PT.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json
@@ -113,5 +113,10 @@
"TasksChannelsCategory": "Canais da Internet",
"TasksApplicationCategory": "Aplicação",
"TasksLibraryCategory": "Biblioteca",
- "TasksMaintenanceCategory": "Manutenção"
+ "TasksMaintenanceCategory": "Manutenção",
+ "TaskCleanActivityLogDescription": "Apaga as entradas do registo de atividade anteriores à data configurada.",
+ "TaskCleanActivityLog": "Limpar registo de atividade",
+ "Undefined": "Indefinido",
+ "Forced": "Forçado",
+ "Default": "Padrão"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index 2079940cd..f1a78b2d3 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -112,5 +112,10 @@
"TaskUpdatePluginsDescription": "Download e instala as atualizações para plug-ins configurados para atualização automática.",
"TaskRefreshPeopleDescription": "Atualiza os metadados para atores e diretores na tua biblioteca de media.",
"TaskRefreshPeople": "Atualizar pessoas",
- "TaskRefreshLibraryDescription": "Pesquisa a tua biblioteca de media por novos ficheiros e atualiza os metadados."
+ "TaskRefreshLibraryDescription": "Pesquisa a tua biblioteca de media por novos ficheiros e atualiza os metadados.",
+ "TaskCleanActivityLog": "Limpar registo de atividade",
+ "Undefined": "Indefinido",
+ "Forced": "Forçado",
+ "Default": "Predefinição",
+ "TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado."
}
diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json
index 5e4022292..510aac11c 100644
--- a/Emby.Server.Implementations/Localization/Core/ro.json
+++ b/Emby.Server.Implementations/Localization/Core/ro.json
@@ -114,5 +114,8 @@
"TasksLibraryCategory": "Librărie",
"TasksMaintenanceCategory": "Mentenanță",
"TaskCleanActivityLogDescription": "Șterge intrările din jurnalul de activitate mai vechi de data configurată.",
- "TaskCleanActivityLog": "Curăță Jurnalul de Activitate"
+ "TaskCleanActivityLog": "Curăță Jurnalul de Activitate",
+ "Undefined": "Nedefinit",
+ "Forced": "Forțat",
+ "Default": "Implicit"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index c0db2cf7f..ca6172fce 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -115,5 +115,8 @@
"TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.",
"TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе.",
"TaskCleanActivityLogDescription": "Удаляет записи журнала активности старше установленного возраста.",
- "TaskCleanActivityLog": "Очистить журнал активности"
+ "TaskCleanActivityLog": "Очистить журнал активности",
+ "Undefined": "Не определено",
+ "Forced": "Форсир-ые",
+ "Default": "По умолчанию"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json
index 8e5026944..99fbd3954 100644
--- a/Emby.Server.Implementations/Localization/Core/sk.json
+++ b/Emby.Server.Implementations/Localization/Core/sk.json
@@ -2,7 +2,7 @@
"Albums": "Albumy",
"AppDeviceValues": "Aplikácia: {0}, Zariadenie: {1}",
"Application": "Aplikácia",
- "Artists": "Umelci",
+ "Artists": "Interpreti",
"AuthenticationSucceededWithUserName": "{0} úspešne overený",
"Books": "Knihy",
"CameraImageUploadedFrom": "Z {0} bola nahraná nová fotografia",
@@ -15,13 +15,13 @@
"Favorites": "Obľúbené",
"Folders": "Priečinky",
"Genres": "Žánre",
- "HeaderAlbumArtists": "Umelci albumu",
+ "HeaderAlbumArtists": "Interpreti albumu",
"HeaderContinueWatching": "Pokračovať v pozeraní",
"HeaderFavoriteAlbums": "Obľúbené albumy",
- "HeaderFavoriteArtists": "Obľúbení umelci",
+ "HeaderFavoriteArtists": "Obľúbení interpreti",
"HeaderFavoriteEpisodes": "Obľúbené epizódy",
"HeaderFavoriteShows": "Obľúbené seriály",
- "HeaderFavoriteSongs": "Obľúbené piesne",
+ "HeaderFavoriteSongs": "Obľúbené skladby",
"HeaderLiveTV": "Živá TV",
"HeaderNextUp": "Nasleduje",
"HeaderRecordingGroups": "Skupiny nahrávok",
@@ -33,13 +33,13 @@
"LabelRunningTimeValue": "Dĺžka: {0}",
"Latest": "Najnovšie",
"MessageApplicationUpdated": "Jellyfin Server bol aktualizovaný",
- "MessageApplicationUpdatedTo": "Jellyfin Server bol aktualizový na verziu {0}",
+ "MessageApplicationUpdatedTo": "Jellyfin Server bol aktualizovaný na verziu {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Sekcia {0} konfigurácie servera bola aktualizovaná",
"MessageServerConfigurationUpdated": "Konfigurácia servera bola aktualizovaná",
"MixedContent": "Zmiešaný obsah",
"Movies": "Filmy",
"Music": "Hudba",
- "MusicVideos": "Hudobné videá",
+ "MusicVideos": "Hudobné videoklipy",
"NameInstallFailed": "Inštalácia {0} zlyhala",
"NameSeasonNumber": "Séria {0}",
"NameSeasonUnknown": "Neznáma séria",
@@ -71,7 +71,7 @@
"ScheduledTaskStartedWithName": "{0} zahájených",
"ServerNameNeedsToBeRestarted": "{0} vyžaduje reštart",
"Shows": "Seriály",
- "Songs": "Piesne",
+ "Songs": "Skladby",
"StartupEmbyServerIsLoading": "Jellyfin Server sa spúšťa. Prosím, skúste to o chvíľu znova.",
"SubtitleDownloadFailureForItem": "Sťahovanie titulkov pre {0} zlyhalo",
"SubtitleDownloadFailureFromForItem": "Sťahovanie titulkov z {0} pre {1} zlyhalo",
@@ -89,29 +89,34 @@
"UserPolicyUpdatedWithName": "Používateľské zásady pre {0} boli aktualizované",
"UserStartedPlayingItemWithValues": "{0} spustil prehrávanie {1} na {2}",
"UserStoppedPlayingItemWithValues": "{0} ukončil prehrávanie {1} na {2}",
- "ValueHasBeenAddedToLibrary": "{0} bol pridané do vašej knižnice médií",
+ "ValueHasBeenAddedToLibrary": "{0} bol pridaný do vašej knižnice médií",
"ValueSpecialEpisodeName": "Špeciál - {0}",
"VersionNumber": "Verzia {0}",
"TaskDownloadMissingSubtitlesDescription": "Vyhľadá na internete chýbajúce titulky podľa toho, ako sú nakonfigurované metadáta.",
"TaskDownloadMissingSubtitles": "Stiahnuť chýbajúce titulky",
"TaskRefreshChannelsDescription": "Obnoví informácie o internetových kanáloch.",
"TaskRefreshChannels": "Obnoviť kanály",
- "TaskCleanTranscodeDescription": "Vymaže súbory transkódovania, ktoré sú staršie ako jeden deň.",
- "TaskCleanTranscode": "Vyčistiť priečinok pre transkódovanie",
+ "TaskCleanTranscodeDescription": "Vymaže prekódované súbory, ktoré sú staršie ako jeden deň.",
+ "TaskCleanTranscode": "Vyčistiť priečinok pre prekódovanie",
"TaskUpdatePluginsDescription": "Stiahne a nainštaluje aktualizácie pre zásuvné moduly, ktoré sú nastavené tak, aby sa aktualizovali automaticky.",
"TaskUpdatePlugins": "Aktualizovať zásuvné moduly",
"TaskRefreshPeopleDescription": "Aktualizuje metadáta pre hercov a režisérov vo vašej mediálnej knižnici.",
"TaskRefreshPeople": "Obnoviť osoby",
- "TaskCleanLogsDescription": "Vymaže log súbory, ktoré su staršie ako {0} deň/dni/dní.",
+ "TaskCleanLogsDescription": "Vymaže log súbory, ktoré sú staršie ako {0} deň/dni/dní.",
"TaskCleanLogs": "Vyčistiť priečinok s logmi",
"TaskRefreshLibraryDescription": "Hľadá vo vašej mediálnej knižnici nové súbory a obnovuje metadáta.",
"TaskRefreshLibrary": "Prehľadávať knižnicu medií",
"TaskRefreshChapterImagesDescription": "Vytvorí náhľady pre videá, ktoré majú kapitoly.",
"TaskRefreshChapterImages": "Extrahovať obrázky kapitol",
- "TaskCleanCacheDescription": "Vymaže cache súbory, ktoré nie sú už potrebné pre systém.",
- "TaskCleanCache": "Vyčistiť Cache priečinok",
+ "TaskCleanCacheDescription": "Vymaže súbory vyrovnávacej pamäte, ktoré už nie sú potrebné pre systém.",
+ "TaskCleanCache": "Vyčistiť priečinok vyrovnávacej pamäte",
"TasksChannelsCategory": "Internetové kanály",
"TasksApplicationCategory": "Aplikácia",
"TasksLibraryCategory": "Knižnica",
- "TasksMaintenanceCategory": "Údržba"
+ "TasksMaintenanceCategory": "Údržba",
+ "TaskCleanActivityLogDescription": "Vymaže záznamy aktivít v logu, ktoré sú staršie ako zadaná doba.",
+ "TaskCleanActivityLog": "Vyčistiť log aktivít",
+ "Undefined": "Nedefinované",
+ "Forced": "Vynútené",
+ "Default": "Predvolené"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json
index 5fcdb1f74..c737ba42b 100644
--- a/Emby.Server.Implementations/Localization/Core/ta.json
+++ b/Emby.Server.Implementations/Localization/Core/ta.json
@@ -21,7 +21,7 @@
"Inherit": "மரபுரிமையாகப் பெறு",
"HeaderRecordingGroups": "பதிவு குழுக்கள்",
"Folders": "கோப்புறைகள்",
- "FailedLoginAttemptWithUserName": "{0} இலிருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது",
+ "FailedLoginAttemptWithUserName": "{0} இல் இருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது",
"DeviceOnlineWithName": "{0} இணைக்கப்பட்டது",
"DeviceOfflineWithName": "{0} துண்டிக்கப்பட்டது",
"Collections": "தொகுப்புகள்",
@@ -99,7 +99,7 @@
"MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0} புதுப்பிக்கப்பட்டது",
"TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
"UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
- "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
+ "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இல் இருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
"TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
"TaskCleanTranscodeDescription": "ஒரு நாளைக்கு மேற்பட்ட பழைய டிரான்ஸ்கோட் கோப்புகளை நீக்குகிறது.",
"TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json
index 54d3a65f0..885663eed 100644
--- a/Emby.Server.Implementations/Localization/Core/tr.json
+++ b/Emby.Server.Implementations/Localization/Core/tr.json
@@ -12,7 +12,7 @@
"DeviceOfflineWithName": "{0} bağlantısı kesildi",
"DeviceOnlineWithName": "{0} bağlı",
"FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
- "Favorites": "Favoriler",
+ "Favorites": "Favorilerim",
"Folders": "Klasörler",
"Genres": "Türler",
"HeaderAlbumArtists": "Albüm Sanatçıları",
@@ -115,5 +115,7 @@
"TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar",
"TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.",
"TaskCleanActivityLog": "İşlem Günlüğünü Temizle",
- "TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi."
+ "TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi.",
+ "Undefined": "Bilinmeyen",
+ "Default": "Varsayılan"
}
diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json
index 06cc5f633..b6073bf6a 100644
--- a/Emby.Server.Implementations/Localization/Core/uk.json
+++ b/Emby.Server.Implementations/Localization/Core/uk.json
@@ -27,7 +27,7 @@
"Channels": "Канали",
"CameraImageUploadedFrom": "Нова фотографія завантажена з {0}",
"Books": "Книги",
- "AuthenticationSucceededWithUserName": "{0} успішно авторизований",
+ "AuthenticationSucceededWithUserName": "{0} успішно автентифіковано",
"Artists": "Виконавці",
"Application": "Додаток",
"AppDeviceValues": "Додаток: {0}, Пристрій: {1}",
@@ -112,5 +112,10 @@
"MessageServerConfigurationUpdated": "Конфігурація сервера оновлена",
"MessageNamedServerConfigurationUpdatedWithValue": "Розділ конфігурації сервера {0} оновлено",
"Inherit": "Успадкувати",
- "HeaderRecordingGroups": "Групи запису"
+ "HeaderRecordingGroups": "Групи запису",
+ "Forced": "Примусово",
+ "TaskCleanActivityLogDescription": "Видаляє старші за встановлений термін записи з журналу активності.",
+ "TaskCleanActivityLog": "Очистити журнал активності",
+ "Undefined": "Не визначено",
+ "Default": "За замовчуванням"
}
diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json
index 0549995c8..40368d464 100644
--- a/Emby.Server.Implementations/Localization/Core/vi.json
+++ b/Emby.Server.Implementations/Localization/Core/vi.json
@@ -16,7 +16,7 @@
"Albums": "Albums",
"Artists": "Các Nghệ Sĩ",
"TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.",
- "TaskDownloadMissingSubtitles": "Tải xuống phụ đề bị thiếu",
+ "TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu",
"TaskRefreshChannelsDescription": "Làm mới thông tin kênh internet.",
"TaskRefreshChannels": "Làm Mới Kênh",
"TaskCleanTranscodeDescription": "Xóa các tệp chuyển mã cũ hơn một ngày.",
@@ -24,11 +24,11 @@
"TaskUpdatePluginsDescription": "Tải xuống và cài đặt các bản cập nhật cho các plugin được định cấu hình để cập nhật tự động.",
"TaskUpdatePlugins": "Cập Nhật Plugins",
"TaskRefreshPeopleDescription": "Cập nhật thông tin chi tiết cho diễn viên và đạo diễn trong thư viện phương tiện của bạn.",
- "TaskRefreshPeople": "Làm mới Người dùng",
+ "TaskRefreshPeople": "Làm Mới Người Dùng",
"TaskCleanLogsDescription": "Xóa tập tin nhật ký cũ hơn {0} ngày.",
- "TaskCleanLogs": "Làm sạch nhật ký",
- "TaskRefreshLibraryDescription": "Quét thư viện phương tiện của bạn để tìm các tệp mới và làm mới thông tin chi tiết.",
- "TaskRefreshLibrary": "Quét Thư viện Phương tiện",
+ "TaskCleanLogs": "Làm Sạch Thư Mục Nhật Ký",
+ "TaskRefreshLibraryDescription": "Quét thư viện phương tiện của bạn để tìm tệp mới và làm mới dữ liệu mô tả.",
+ "TaskRefreshLibrary": "Quét Thư Viện Phương Tiện",
"TaskRefreshChapterImagesDescription": "Tạo hình thu nhỏ cho video có các phân cảnh.",
"TaskRefreshChapterImages": "Trích Xuất Ảnh Phân Cảnh",
"TaskCleanCacheDescription": "Xóa các tệp cache không còn cần thiết của hệ thống.",
@@ -80,7 +80,7 @@
"NotificationOptionApplicationUpdateAvailable": "Bản cập nhật ứng dụng hiện sẵn có",
"NewVersionIsAvailable": "Một phiên bản mới của Jellyfin Server sẵn có để tải.",
"NameSeasonUnknown": "Không Rõ Mùa",
- "NameSeasonNumber": "Mùa {0}",
+ "NameSeasonNumber": "Phần {0}",
"NameInstallFailed": "{0} cài đặt thất bại",
"MusicVideos": "Video Nhạc",
"Music": "Nhạc",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index d2e3d77a3..6494c0b54 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -114,5 +114,8 @@
"TasksApplicationCategory": "應用程式",
"TasksMaintenanceCategory": "維護",
"TaskCleanActivityLogDescription": "刪除超過所設時間的活動紀錄。",
- "TaskCleanActivityLog": "清除活動紀錄"
+ "TaskCleanActivityLog": "清除活動紀錄",
+ "Undefined": "未定義的",
+ "Forced": "強制",
+ "Default": "原本"
}
diff --git a/Emby.Server.Implementations/Localization/countries.json b/Emby.Server.Implementations/Localization/countries.json
index 581e9f835..b08a3ae79 100644
--- a/Emby.Server.Implementations/Localization/countries.json
+++ b/Emby.Server.Implementations/Localization/countries.json
@@ -558,6 +558,12 @@
"TwoLetterISORegionName": "OM"
},
{
+ "DisplayName": "Palestine",
+ "Name": "PS",
+ "ThreeLetterISORegionName": "PSE",
+ "TwoLetterISORegionName": "PS"
+ },
+ {
"DisplayName": "Panama",
"Name": "PA",
"ThreeLetterISORegionName": "PAN",
diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
index 438bbe24a..f27305cbe 100644
--- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
@@ -81,12 +82,7 @@ namespace Emby.Server.Implementations.MediaEncoder
return false;
}
- if (video.VideoType == VideoType.Iso)
- {
- return false;
- }
-
- if (video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd)
+ if (video.VideoType == VideoType.Dvd)
{
return false;
}
@@ -140,15 +136,19 @@ namespace Emby.Server.Implementations.MediaEncoder
// Add some time for the first chapter to make sure we don't end up with a black image
var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(_firstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
- var protocol = MediaProtocol.File;
-
- var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, null, Array.Empty<string>());
+ var inputPath = video.Path;
Directory.CreateDirectory(Path.GetDirectoryName(path));
var container = video.Container;
+ var mediaSource = new MediaSourceInfo
+ {
+ VideoType = video.VideoType,
+ IsoType = video.IsoType,
+ Protocol = video.PathProtocol.Value,
+ };
- var tempFile = await _encoder.ExtractVideoImage(inputPath, container, protocol, video.GetDefaultVideoStream(), video.Video3DFormat, time, cancellationToken).ConfigureAwait(false);
+ var tempFile = await _encoder.ExtractVideoImage(inputPath, container, mediaSource, video.GetDefaultVideoStream(), video.Video3DFormat, time, cancellationToken).ConfigureAwait(false);
File.Copy(tempFile, path, true);
try
diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs
deleted file mode 100644
index 089ec30e6..000000000
--- a/Emby.Server.Implementations/Networking/NetworkManager.cs
+++ /dev/null
@@ -1,556 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.NetworkInformation;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.Networking
-{
- /// <summary>
- /// Class to take care of network interface management.
- /// </summary>
- public class NetworkManager : INetworkManager
- {
- private readonly ILogger<NetworkManager> _logger;
-
- private IPAddress[] _localIpAddresses;
- private readonly object _localIpAddressSyncLock = new object();
-
- private readonly object _subnetLookupLock = new object();
- private readonly Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
-
- private List<PhysicalAddress> _macAddresses;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="NetworkManager"/> class.
- /// </summary>
- /// <param name="logger">Logger to use for messages.</param>
- public NetworkManager(ILogger<NetworkManager> logger)
- {
- _logger = logger;
-
- NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged;
- NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
- }
-
- /// <inheritdoc/>
- public event EventHandler NetworkChanged;
-
- /// <inheritdoc/>
- public Func<string[]> LocalSubnetsFn { get; set; }
-
- private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
- {
- _logger.LogDebug("NetworkAvailabilityChanged");
- OnNetworkChanged();
- }
-
- private void OnNetworkAddressChanged(object sender, EventArgs e)
- {
- _logger.LogDebug("NetworkAddressChanged");
- OnNetworkChanged();
- }
-
- private void OnNetworkChanged()
- {
- lock (_localIpAddressSyncLock)
- {
- _localIpAddresses = null;
- _macAddresses = null;
- }
-
- NetworkChanged?.Invoke(this, EventArgs.Empty);
- }
-
- /// <inheritdoc/>
- public IPAddress[] GetLocalIpAddresses()
- {
- lock (_localIpAddressSyncLock)
- {
- if (_localIpAddresses == null)
- {
- var addresses = GetLocalIpAddressesInternal().ToArray();
-
- _localIpAddresses = addresses;
- }
-
- return _localIpAddresses;
- }
- }
-
- private List<IPAddress> GetLocalIpAddressesInternal()
- {
- var list = GetIPsDefault().ToList();
-
- if (list.Count == 0)
- {
- list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList();
- }
-
- var listClone = new List<IPAddress>();
-
- var subnets = LocalSubnetsFn();
-
- foreach (var i in list)
- {
- if (i.IsIPv6LinkLocal || i.ToString().StartsWith("169.254.", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- if (Array.IndexOf(subnets, $"[{i}]") == -1)
- {
- listClone.Add(i);
- }
- }
-
- return listClone
- .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1)
- // .ThenBy(i => listClone.IndexOf(i))
- .GroupBy(i => i.ToString())
- .Select(x => x.First())
- .ToList();
- }
-
- /// <inheritdoc/>
- public bool IsInPrivateAddressSpace(string endpoint)
- {
- return IsInPrivateAddressSpace(endpoint, true);
- }
-
- // Checks if the address in endpoint is an RFC1918, RFC1122, or RFC3927 address
- private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets)
- {
- if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- // IPV6
- if (endpoint.Split('.').Length > 4)
- {
- // Handle ipv4 mapped to ipv6
- var originalEndpoint = endpoint;
- endpoint = endpoint.Replace("::ffff:", string.Empty, StringComparison.OrdinalIgnoreCase);
-
- if (string.Equals(endpoint, originalEndpoint, StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- }
-
- // Private address space:
-
- if (string.Equals(endpoint, "localhost", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- if (!IPAddress.TryParse(endpoint, out var ipAddress))
- {
- return false;
- }
-
- byte[] octet = ipAddress.GetAddressBytes();
-
- if ((octet[0] == 10) ||
- (octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918
- (octet[0] == 192 && octet[1] == 168) || // RFC1918
- (octet[0] == 127) || // RFC1122
- (octet[0] == 169 && octet[1] == 254)) // RFC3927
- {
- return true;
- }
-
- if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint))
- {
- return true;
- }
-
- return false;
- }
-
- /// <inheritdoc/>
- public bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint)
- {
- if (endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase))
- {
- var endpointFirstPart = endpoint.Split('.')[0];
-
- var subnets = GetSubnets(endpointFirstPart);
-
- foreach (var subnet_Match in subnets)
- {
- // logger.LogDebug("subnet_Match:" + subnet_Match);
-
- if (endpoint.StartsWith(subnet_Match + ".", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
- // Gives a list of possible subnets from the system whose interface ip starts with endpointFirstPart
- private List<string> GetSubnets(string endpointFirstPart)
- {
- lock (_subnetLookupLock)
- {
- if (_subnetLookup.TryGetValue(endpointFirstPart, out var subnets))
- {
- return subnets;
- }
-
- subnets = new List<string>();
-
- foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
- {
- foreach (var unicastIPAddressInformation in adapter.GetIPProperties().UnicastAddresses)
- {
- if (unicastIPAddressInformation.Address.AddressFamily == AddressFamily.InterNetwork && endpointFirstPart == unicastIPAddressInformation.Address.ToString().Split('.')[0])
- {
- int subnet_Test = 0;
- foreach (string part in unicastIPAddressInformation.IPv4Mask.ToString().Split('.'))
- {
- if (part.Equals("0", StringComparison.Ordinal))
- {
- break;
- }
-
- subnet_Test++;
- }
-
- var subnet_Match = string.Join(".", unicastIPAddressInformation.Address.ToString().Split('.').Take(subnet_Test).ToArray());
-
- // TODO: Is this check necessary?
- if (adapter.OperationalStatus == OperationalStatus.Up)
- {
- subnets.Add(subnet_Match);
- }
- }
- }
- }
-
- _subnetLookup[endpointFirstPart] = subnets;
-
- return subnets;
- }
- }
-
- /// <inheritdoc/>
- public bool IsInLocalNetwork(string endpoint)
- {
- return IsInLocalNetworkInternal(endpoint, true);
- }
-
- /// <inheritdoc/>
- public bool IsAddressInSubnets(string addressString, string[] subnets)
- {
- return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets);
- }
-
- /// <inheritdoc/>
- public bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC)
- {
- byte[] octet = address.GetAddressBytes();
-
- if ((octet[0] == 127) || // RFC1122
- (octet[0] == 169 && octet[1] == 254)) // RFC3927
- {
- // don't use on loopback or 169 interfaces
- return false;
- }
-
- string addressString = address.ToString();
- string excludeAddress = "[" + addressString + "]";
- var subnets = LocalSubnetsFn();
-
- // Include any address if LAN subnets aren't specified
- if (subnets.Length == 0)
- {
- return true;
- }
-
- // Exclude any addresses if they appear in the LAN list in [ ]
- if (Array.IndexOf(subnets, excludeAddress) != -1)
- {
- return false;
- }
-
- return IsAddressInSubnets(address, addressString, subnets);
- }
-
- /// <summary>
- /// Checks if the give address falls within the ranges given in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format.
- /// </summary>
- /// <param name="address">IPAddress version of the address.</param>
- /// <param name="addressString">The address to check.</param>
- /// <param name="subnets">If true, check against addresses in the LAN settings which have [] arroud and return true if it matches the address give in address.</param>
- /// <returns><c>false</c>if the address isn't in the subnets, <c>true</c> otherwise.</returns>
- private static bool IsAddressInSubnets(IPAddress address, string addressString, string[] subnets)
- {
- foreach (var subnet in subnets)
- {
- var normalizedSubnet = subnet.Trim();
- // Is the subnet a host address and does it match the address being passes?
- if (string.Equals(normalizedSubnet, addressString, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- // Parse CIDR subnets and see if address falls within it.
- if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
- {
- try
- {
- var ipNetwork = IPNetwork.Parse(normalizedSubnet);
- if (ipNetwork.Contains(address))
- {
- return true;
- }
- }
- catch
- {
- // Ignoring - invalid subnet passed encountered.
- }
- }
- }
-
- return false;
- }
-
- private bool IsInLocalNetworkInternal(string endpoint, bool resolveHost)
- {
- if (string.IsNullOrEmpty(endpoint))
- {
- throw new ArgumentNullException(nameof(endpoint));
- }
-
- if (IPAddress.TryParse(endpoint, out var address))
- {
- var addressString = address.ToString();
-
- var localSubnetsFn = LocalSubnetsFn;
- if (localSubnetsFn != null)
- {
- var localSubnets = localSubnetsFn();
- foreach (var subnet in localSubnets)
- {
- // Only validate if there's at least one valid entry.
- if (!string.IsNullOrWhiteSpace(subnet))
- {
- return IsAddressInSubnets(address, addressString, localSubnets) || IsInPrivateAddressSpace(addressString, false);
- }
- }
- }
-
- int lengthMatch = 100;
- if (address.AddressFamily == AddressFamily.InterNetwork)
- {
- lengthMatch = 4;
- if (IsInPrivateAddressSpace(addressString, true))
- {
- return true;
- }
- }
- else if (address.AddressFamily == AddressFamily.InterNetworkV6)
- {
- lengthMatch = 9;
- if (IsInPrivateAddressSpace(endpoint, true))
- {
- return true;
- }
- }
-
- // Should be even be doing this with ipv6?
- if (addressString.Length >= lengthMatch)
- {
- var prefix = addressString.Substring(0, lengthMatch);
-
- if (GetLocalIpAddresses().Any(i => i.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
- {
- return true;
- }
- }
- }
- else if (resolveHost)
- {
- if (Uri.TryCreate(endpoint, UriKind.RelativeOrAbsolute, out var uri))
- {
- try
- {
- var host = uri.DnsSafeHost;
- _logger.LogDebug("Resolving host {0}", host);
-
- address = GetIpAddresses(host).GetAwaiter().GetResult().FirstOrDefault();
-
- if (address != null)
- {
- _logger.LogDebug("{0} resolved to {1}", host, address);
-
- return IsInLocalNetworkInternal(address.ToString(), false);
- }
- }
- catch (InvalidOperationException)
- {
- // Can happen with reverse proxy or IIS url rewriting?
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error resolving hostname");
- }
- }
- }
-
- return false;
- }
-
- private static Task<IPAddress[]> GetIpAddresses(string hostName)
- {
- return Dns.GetHostAddressesAsync(hostName);
- }
-
- private IEnumerable<IPAddress> GetIPsDefault()
- {
- IEnumerable<NetworkInterface> interfaces;
-
- try
- {
- interfaces = NetworkInterface.GetAllNetworkInterfaces()
- .Where(x => x.OperationalStatus == OperationalStatus.Up
- || x.OperationalStatus == OperationalStatus.Unknown);
- }
- catch (NetworkInformationException ex)
- {
- _logger.LogError(ex, "Error in GetAllNetworkInterfaces");
- return Enumerable.Empty<IPAddress>();
- }
-
- return interfaces.SelectMany(network =>
- {
- var ipProperties = network.GetIPProperties();
-
- // Exclude any addresses if they appear in the LAN list in [ ]
-
- return ipProperties.UnicastAddresses
- .Select(i => i.Address)
- .Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6);
- }).GroupBy(i => i.ToString())
- .Select(x => x.First());
- }
-
- private static async Task<IEnumerable<IPAddress>> GetLocalIpAddressesFallback()
- {
- var host = await Dns.GetHostEntryAsync(Dns.GetHostName()).ConfigureAwait(false);
-
- // Reverse them because the last one is usually the correct one
- // It's not fool-proof so ultimately the consumer will have to examine them and decide
- return host.AddressList
- .Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6)
- .Reverse();
- }
-
- /// <summary>
- /// Gets a random port number that is currently available.
- /// </summary>
- /// <returns>System.Int32.</returns>
- public int GetRandomUnusedTcpPort()
- {
- var listener = new TcpListener(IPAddress.Any, 0);
- listener.Start();
- var port = ((IPEndPoint)listener.LocalEndpoint).Port;
- listener.Stop();
- return port;
- }
-
- /// <inheritdoc/>
- public int GetRandomUnusedUdpPort()
- {
- var localEndPoint = new IPEndPoint(IPAddress.Any, 0);
- using (var udpClient = new UdpClient(localEndPoint))
- {
- return ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
- }
- }
-
- /// <inheritdoc/>
- public List<PhysicalAddress> GetMacAddresses()
- {
- return _macAddresses ??= GetMacAddressesInternal().ToList();
- }
-
- private static IEnumerable<PhysicalAddress> GetMacAddressesInternal()
- => NetworkInterface.GetAllNetworkInterfaces()
- .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback)
- .Select(x => x.GetPhysicalAddress())
- .Where(x => !x.Equals(PhysicalAddress.None));
-
- /// <inheritdoc/>
- public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
- {
- IPAddress network1 = GetNetworkAddress(address1, subnetMask);
- IPAddress network2 = GetNetworkAddress(address2, subnetMask);
- return network1.Equals(network2);
- }
-
- private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
- {
- byte[] ipAdressBytes = address.GetAddressBytes();
- byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
-
- if (ipAdressBytes.Length != subnetMaskBytes.Length)
- {
- throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
- }
-
- byte[] broadcastAddress = new byte[ipAdressBytes.Length];
- for (int i = 0; i < broadcastAddress.Length; i++)
- {
- broadcastAddress[i] = (byte)(ipAdressBytes[i] & subnetMaskBytes[i]);
- }
-
- return new IPAddress(broadcastAddress);
- }
-
- /// <inheritdoc/>
- public IPAddress GetLocalIpSubnetMask(IPAddress address)
- {
- NetworkInterface[] interfaces;
-
- try
- {
- var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown };
-
- interfaces = NetworkInterface.GetAllNetworkInterfaces()
- .Where(i => validStatuses.Contains(i.OperationalStatus))
- .ToArray();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in GetAllNetworkInterfaces");
- return null;
- }
-
- foreach (NetworkInterface ni in interfaces)
- {
- foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
- {
- if (ip.Address.Equals(address) && ip.IPv4Mask != null)
- {
- return ip.IPv4Mask;
- }
- }
- }
-
- return null;
- }
- }
-}
diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
index d3b64fb31..932f721ab 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
@@ -150,7 +150,7 @@ namespace Emby.Server.Implementations.Playlists
await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None)
.ConfigureAwait(false);
- if (options.ItemIdList.Length > 0)
+ if (options.ItemIdList.Count > 0)
{
await AddToPlaylistInternal(playlist.Id, options.ItemIdList, user, new DtoOptions(false)
{
@@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.Playlists
return Playlist.GetPlaylistItems(playlistMediaType, items, user, options);
}
- public Task AddToPlaylistAsync(Guid playlistId, ICollection<Guid> itemIds, Guid userId)
+ public Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId)
{
var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId);
@@ -194,7 +194,7 @@ namespace Emby.Server.Implementations.Playlists
});
}
- private async Task AddToPlaylistInternal(Guid playlistId, ICollection<Guid> newItemIds, User user, DtoOptions options)
+ private async Task AddToPlaylistInternal(Guid playlistId, IReadOnlyCollection<Guid> newItemIds, User user, DtoOptions options)
{
// Retrieve the existing playlist
var playlist = _libraryManager.GetItemById(playlistId) as Playlist
diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
index 140a67541..7bed06de3 100644
--- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
+++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
@@ -243,7 +243,7 @@ namespace Emby.Server.Implementations.QuickConnect
Span<byte> bytes = stackalloc byte[length];
_rng.GetBytes(bytes);
- return Hex.Encode(bytes);
+ return Convert.ToHexString(bytes);
}
/// <inheritdoc/>
diff --git a/Emby.Server.Implementations/ResourceFileManager.cs b/Emby.Server.Implementations/ResourceFileManager.cs
deleted file mode 100644
index 22fc62293..000000000
--- a/Emby.Server.Implementations/ResourceFileManager.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.IO;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.IO;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations
-{
- public class ResourceFileManager : IResourceFileManager
- {
- private readonly IFileSystem _fileSystem;
- private readonly ILogger<ResourceFileManager> _logger;
-
- public ResourceFileManager(ILogger<ResourceFileManager> logger, IFileSystem fileSystem)
- {
- _logger = logger;
- _fileSystem = fileSystem;
- }
-
- public string GetResourcePath(string basePath, string virtualPath)
- {
- var fullPath = Path.Combine(basePath, virtualPath.Replace('/', Path.DirectorySeparatorChar));
-
- try
- {
- fullPath = Path.GetFullPath(fullPath);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error retrieving full path");
- }
-
- // Don't allow file system access outside of the source folder
- if (!_fileSystem.ContainsSubPath(basePath, fullPath))
- {
- throw new SecurityException("Access denied");
- }
-
- return fullPath;
- }
- }
-}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
index 26ef19354..b13fc7fc6 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
@@ -5,10 +5,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
@@ -23,8 +23,12 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
private readonly ILocalizationManager _localization;
/// <summary>
- /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class.
+ /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask"/> class.
/// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger{DeleteTranscodeFileTask}"/> interface.</param>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
+ /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
public DeleteTranscodeFileTask(
ILogger<DeleteTranscodeFileTask> logger,
IFileSystem fileSystem,
@@ -37,11 +41,42 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
_localization = localization;
}
+ /// <inheritdoc />
+ public string Name => _localization.GetLocalizedString("TaskCleanTranscode");
+
+ /// <inheritdoc />
+ public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription");
+
+ /// <inheritdoc />
+ public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+
+ /// <inheritdoc />
+ public string Key => "DeleteTranscodeFiles";
+
+ /// <inheritdoc />
+ public bool IsHidden => false;
+
+ /// <inheritdoc />
+ public bool IsEnabled => true;
+
+ /// <inheritdoc />
+ public bool IsLogged => true;
+
/// <summary>
/// Creates the triggers that define when the task will run.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
- public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() => new List<TaskTriggerInfo>();
+ public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+ {
+ return new[]
+ {
+ new TaskTriggerInfo
+ {
+ Type = TaskTriggerInfo.TriggerInterval,
+ IntervalTicks = TimeSpan.FromHours(24).Ticks
+ }
+ };
+ }
/// <summary>
/// Returns the task to be executed.
@@ -131,26 +166,5 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
_logger.LogError(ex, "Error deleting file {path}", path);
}
}
-
- /// <inheritdoc />
- public string Name => _localization.GetLocalizedString("TaskCleanTranscode");
-
- /// <inheritdoc />
- public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription");
-
- /// <inheritdoc />
- public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
-
- /// <inheritdoc />
- public string Key => "DeleteTranscodeFiles";
-
- /// <inheritdoc />
- public bool IsHidden => false;
-
- /// <inheritdoc />
- public bool IsEnabled => true;
-
- /// <inheritdoc />
- public bool IsLogged => true;
}
}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 607b322f2..afddfa856 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -58,8 +58,7 @@ namespace Emby.Server.Implementations.Session
/// <summary>
/// The active connections.
/// </summary>
- private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections =
- new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections = new (StringComparer.OrdinalIgnoreCase);
private Timer _idleTimer;
@@ -196,7 +195,7 @@ namespace Emby.Server.Implementations.Session
{
if (!string.IsNullOrEmpty(info.DeviceId))
{
- var capabilities = GetSavedCapabilities(info.DeviceId);
+ var capabilities = _deviceManager.GetCapabilities(info.DeviceId);
if (capabilities != null)
{
@@ -1677,27 +1676,10 @@ namespace Emby.Server.Implementations.Session
SessionInfo = session
});
- try
- {
- SaveCapabilities(session.DeviceId, capabilities);
- }
- catch (Exception ex)
- {
- _logger.LogError("Error saving device capabilities", ex);
- }
+ _deviceManager.SaveCapabilities(session.DeviceId, capabilities);
}
}
- private ClientCapabilities GetSavedCapabilities(string deviceId)
- {
- return _deviceManager.GetCapabilities(deviceId);
- }
-
- private void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
- {
- _deviceManager.SaveCapabilities(deviceId, capabilities);
- }
-
/// <summary>
/// Converts a BaseItem to a BaseItemInfo.
/// </summary>
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index ccd1446dd..447c587f9 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -56,13 +56,11 @@ namespace Emby.Server.Implementations.TV
return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request);
}
- var parentIdGuid = string.IsNullOrEmpty(request.ParentId) ? (Guid?)null : new Guid(request.ParentId);
-
BaseItem[] parents;
- if (parentIdGuid.HasValue)
+ if (request.ParentId.HasValue)
{
- var parent = _libraryManager.GetItemById(parentIdGuid.Value);
+ var parent = _libraryManager.GetItemById(request.ParentId.Value);
if (parent != null)
{
@@ -146,28 +144,10 @@ namespace Emby.Server.Implementations.TV
var allNextUp = seriesKeys
.Select(i => GetNextUp(i, currentUser, dtoOptions));
- // allNextUp = allNextUp.OrderByDescending(i => i.Item1);
-
- // If viewing all next up for all series, remove first episodes
- // But if that returns empty, keep those first episodes (avoid completely empty view)
- var alwaysEnableFirstEpisode = !string.IsNullOrEmpty(request.SeriesId);
- var anyFound = false;
-
return allNextUp
.Where(i =>
{
- if (alwaysEnableFirstEpisode || i.Item1 != DateTime.MinValue)
- {
- anyFound = true;
- return true;
- }
-
- if (!anyFound && i.Item1 == DateTime.MinValue)
- {
- return true;
- }
-
- return false;
+ return i.Item1 != DateTime.MinValue;
})
.Select(i => i.Item2())
.Where(i => i != null);
@@ -210,7 +190,7 @@ namespace Emby.Server.Implementations.TV
Func<Episode> getEpisode = () =>
{
- return _libraryManager.GetItemList(new InternalItemsQuery(user)
+ var nextEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
@@ -223,6 +203,18 @@ namespace Emby.Server.Implementations.TV
MinSortName = lastWatchedEpisode?.SortName,
DtoOptions = dtoOptions
}).Cast<Episode>().FirstOrDefault();
+
+ if (nextEpisode != null)
+ {
+ var userData = _userDataManager.GetUserData(user, nextEpisode);
+
+ if (userData.PlaybackPositionTicks > 0)
+ {
+ return null;
+ }
+ }
+
+ return nextEpisode;
};
if (lastWatchedEpisode != null)
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index b7a59cee2..4fd7ac0c1 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Udp
{
string localUrl = !string.IsNullOrEmpty(_config[AddressOverrideConfigKey])
? _config[AddressOverrideConfigKey]
- : await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
+ : _appHost.GetSmartApiUrl(((IPEndPoint)endpoint).Address);
if (!string.IsNullOrEmpty(localUrl))
{
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 851e7bd68..ef346dd5d 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -6,13 +6,15 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
-using System.Runtime.Serialization;
+using System.Net.Http.Json;
using System.Security.Cryptography;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@@ -21,8 +23,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.Events.Updates;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
using Microsoft.Extensions.Logging;
@@ -40,9 +40,9 @@ namespace Emby.Server.Implementations.Updates
private readonly IApplicationPaths _appPaths;
private readonly IEventManager _eventManager;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
+ private readonly JsonSerializerOptions _jsonSerializerOptions;
/// <summary>
/// Gets the application host.
@@ -70,7 +70,6 @@ namespace Emby.Server.Implementations.Updates
IApplicationPaths appPaths,
IEventManager eventManager,
IHttpClientFactory httpClientFactory,
- IJsonSerializer jsonSerializer,
IServerConfigurationManager config,
IFileSystem fileSystem,
IZipClient zipClient)
@@ -83,33 +82,43 @@ namespace Emby.Server.Implementations.Updates
_appPaths = appPaths;
_eventManager = eventManager;
_httpClientFactory = httpClientFactory;
- _jsonSerializer = jsonSerializer;
_config = config;
_fileSystem = fileSystem;
_zipClient = zipClient;
+ _jsonSerializerOptions = JsonDefaults.GetOptions();
}
/// <inheritdoc />
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
/// <inheritdoc />
- public async Task<IReadOnlyList<PackageInfo>> GetPackages(string manifest, CancellationToken cancellationToken = default)
+ public async Task<IList<PackageInfo>> GetPackages(string manifestName, string manifest, CancellationToken cancellationToken = default)
{
try
{
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .GetAsync(manifest, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
-
- try
+ var packages = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetFromJsonAsync<List<PackageInfo>>(new Uri(manifest), _jsonSerializerOptions, cancellationToken).ConfigureAwait(false);
+ if (packages == null)
{
- return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
+ return Array.Empty<PackageInfo>();
}
- catch (SerializationException ex)
+
+ // Store the repository and repository url with each version, as they may be spread apart.
+ foreach (var entry in packages)
{
- _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest);
- return Array.Empty<PackageInfo>();
+ foreach (var ver in entry.versions)
+ {
+ ver.repositoryName = manifestName;
+ ver.repositoryUrl = manifest;
+ }
}
+
+ return packages;
+ }
+ catch (JsonException ex)
+ {
+ _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest);
+ return Array.Empty<PackageInfo>();
}
catch (UriFormatException ex)
{
@@ -123,17 +132,75 @@ namespace Emby.Server.Implementations.Updates
}
}
+ private static void MergeSort(IList<VersionInfo> source, IList<VersionInfo> dest)
+ {
+ int sLength = source.Count - 1;
+ int dLength = dest.Count;
+ int s = 0, d = 0;
+ var sourceVersion = source[0].VersionNumber;
+ var destVersion = dest[0].VersionNumber;
+
+ while (d < dLength)
+ {
+ if (sourceVersion.CompareTo(destVersion) >= 0)
+ {
+ if (s < sLength)
+ {
+ sourceVersion = source[++s].VersionNumber;
+ }
+ else
+ {
+ // Append all of destination to the end of source.
+ while (d < dLength)
+ {
+ source.Add(dest[d++]);
+ }
+
+ break;
+ }
+ }
+ else
+ {
+ source.Insert(s++, dest[d++]);
+ if (d >= dLength)
+ {
+ break;
+ }
+
+ sLength++;
+ destVersion = dest[d].VersionNumber;
+ }
+ }
+ }
+
/// <inheritdoc />
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
{
var result = new List<PackageInfo>();
foreach (RepositoryInfo repository in _config.Configuration.PluginRepositories)
{
- foreach (var package in await GetPackages(repository.Url, cancellationToken).ConfigureAwait(true))
+ if (repository.Enabled)
{
- package.repositoryName = repository.Name;
- package.repositoryUrl = repository.Url;
- result.Add(package);
+ // Where repositories have the same content, the details of the first is taken.
+ foreach (var package in await GetPackages(repository.Name, repository.Url, cancellationToken).ConfigureAwait(true))
+ {
+ if (!Guid.TryParse(package.guid, out var packageGuid))
+ {
+ // Package doesn't have a valid GUID, skip.
+ continue;
+ }
+
+ var existing = FilterPackages(result, package.name, packageGuid).FirstOrDefault();
+ if (existing != null)
+ {
+ // Assumption is both lists are ordered, so slot these into the correct place.
+ MergeSort(existing.versions, package.versions);
+ }
+ else
+ {
+ result.Add(package);
+ }
+ }
}
}
@@ -144,7 +211,8 @@ namespace Emby.Server.Implementations.Updates
public IEnumerable<PackageInfo> FilterPackages(
IEnumerable<PackageInfo> availablePackages,
string name = null,
- Guid guid = default)
+ Guid guid = default,
+ Version specificVersion = null)
{
if (name != null)
{
@@ -156,6 +224,11 @@ namespace Emby.Server.Implementations.Updates
availablePackages = availablePackages.Where(x => Guid.Parse(x.guid) == guid);
}
+ if (specificVersion != null)
+ {
+ availablePackages = availablePackages.Where(x => x.versions.Where(y => y.VersionNumber.Equals(specificVersion)).Any());
+ }
+
return availablePackages;
}
@@ -167,7 +240,7 @@ namespace Emby.Server.Implementations.Updates
Version minVersion = null,
Version specificVersion = null)
{
- var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
+ var package = FilterPackages(availablePackages, name, guid, specificVersion).FirstOrDefault();
// Package not found in repository
if (package == null)
@@ -181,21 +254,21 @@ namespace Emby.Server.Implementations.Updates
if (specificVersion != null)
{
- availableVersions = availableVersions.Where(x => new Version(x.version) == specificVersion);
+ availableVersions = availableVersions.Where(x => x.VersionNumber.Equals(specificVersion));
}
else if (minVersion != null)
{
- availableVersions = availableVersions.Where(x => new Version(x.version) >= minVersion);
+ availableVersions = availableVersions.Where(x => x.VersionNumber >= minVersion);
}
- foreach (var v in availableVersions.OrderByDescending(x => x.version))
+ foreach (var v in availableVersions.OrderByDescending(x => x.VersionNumber))
{
yield return new InstallationInfo
{
Changelog = v.changelog,
Guid = new Guid(package.guid),
Name = package.name,
- Version = new Version(v.version),
+ Version = v.VersionNumber,
SourceUrl = v.sourceUrl,
Checksum = v.checksum
};
@@ -333,7 +406,7 @@ namespace Emby.Server.Implementations.Updates
string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .GetAsync(package.SourceUrl, cancellationToken).ConfigureAwait(false);
+ .GetAsync(new Uri(package.SourceUrl), cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
// CA5351: Do Not Use Broken Cryptographic Algorithms
@@ -341,7 +414,7 @@ namespace Emby.Server.Implementations.Updates
using var md5 = MD5.Create();
cancellationToken.ThrowIfCancellationRequested();
- var hash = Hex.Encode(md5.ComputeHash(stream));
+ var hash = Convert.ToHexString(md5.ComputeHash(stream));
if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase))
{
_logger.LogError(