aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/codeql-analysis.yml6
-rw-r--r--Directory.Packages.props20
-rw-r--r--Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs69
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs134
-rw-r--r--Emby.Dlna/PlayTo/DlnaHttpClient.cs55
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs14
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs7
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj2
-rw-r--r--Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs29
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs18
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs15
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs14
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs25
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs61
-rw-r--r--Emby.Server.Implementations/Localization/Core/fo.json18
-rw-r--r--Emby.Server.Implementations/Localization/Core/is.json63
-rw-r--r--Emby.Server.Implementations/Localization/Core/lv.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/sk.json2
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs37
-rw-r--r--Emby.Server.Implementations/Net/SocketFactory.cs33
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs18
-rw-r--r--Emby.Server.Implementations/SystemManager.cs5
-rw-r--r--Emby.Server.Implementations/Udp/UdpServer.cs11
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs5
-rw-r--r--Jellyfin.Api/Controllers/DlnaServerController.cs16
-rw-r--r--Jellyfin.Api/Helpers/StreamingHelpers.cs20
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj2
-rw-r--r--Jellyfin.Networking/Extensions/NetworkExtensions.cs13
-rw-r--r--Jellyfin.Networking/Manager/NetworkManager.cs244
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs2
-rw-r--r--Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs3
-rw-r--r--Jellyfin.Server/Startup.cs20
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj4
-rw-r--r--MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs9
-rw-r--r--MediaBrowser.Model/Net/IPData.cs5
-rw-r--r--MediaBrowser.Model/Net/PublishedServerUriOverride.cs42
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs3
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs15
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs21
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs35
-rw-r--r--RSSDP/SsdpCommunicationsServer.cs124
-rw-r--r--RSSDP/SsdpDeviceLocator.cs42
-rw-r--r--RSSDP/SsdpDevicePublisher.cs89
-rw-r--r--deployment/Dockerfile.centos.amd642
-rw-r--r--deployment/Dockerfile.fedora.amd642
-rw-r--r--deployment/Dockerfile.ubuntu.amd642
-rw-r--r--deployment/Dockerfile.ubuntu.arm642
-rw-r--r--deployment/Dockerfile.ubuntu.armhf2
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkExtensionsTests.cs1
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs10
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs39
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs11
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs4
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs7
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs12
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs5
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs9
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs9
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs13
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs4
-rw-r--r--tests/Jellyfin.Server.Tests/ParseNetworkTests.cs5
62 files changed, 813 insertions, 706 deletions
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 4c9d68d11..8385e84bf 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -27,11 +27,11 @@ jobs:
dotnet-version: '7.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1
+ uses: github/codeql-action/init@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1
+ uses: github/codeql-action/autobuild@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1
+ uses: github/codeql-action/analyze@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 55b03cdce..29bf42328 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -27,13 +27,13 @@
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
- <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.11" />
+ <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.12" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
- <PackageVersion Include="Microsoft.Data.Sqlite" Version="7.0.11" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.11" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.11" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.11" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.11" />
+ <PackageVersion Include="Microsoft.Data.Sqlite" Version="7.0.12" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.12" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.12" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.12" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.12" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
@@ -42,8 +42,8 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
- <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.11" />
- <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.11" />
+ <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.12" />
+ <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.12" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
@@ -86,8 +86,8 @@
<PackageVersion Include="TMDbLib" Version="2.0.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
- <PackageVersion Include="xunit.runner.visualstudio" Version="2.5.1" />
+ <PackageVersion Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
- <PackageVersion Include="xunit" Version="2.5.1" />
+ <PackageVersion Include="xunit" Version="2.5.3" />
</ItemGroup>
</Project>
diff --git a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs
new file mode 100644
index 000000000..87ec14d95
--- /dev/null
+++ b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Globalization;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using Emby.Dlna.ConnectionManager;
+using Emby.Dlna.ContentDirectory;
+using Emby.Dlna.MediaReceiverRegistrar;
+using Emby.Dlna.Ssdp;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Net;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Rssdp.Infrastructure;
+
+namespace Emby.Dlna.Extensions;
+
+/// <summary>
+/// Extension methods for adding DLNA services.
+/// </summary>
+public static class DlnaServiceCollectionExtensions
+{
+ /// <summary>
+ /// Adds DLNA services to the provided <see cref="IServiceCollection"/>.
+ /// </summary>
+ /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+ /// <param name="applicationHost">The <see cref="IServerApplicationHost"/>.</param>
+ public static void AddDlnaServices(
+ this IServiceCollection services,
+ IServerApplicationHost applicationHost)
+ {
+ services.AddHttpClient(NamedClient.Dlna, c =>
+ {
+ c.DefaultRequestHeaders.UserAgent.ParseAdd(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}/{1} UPnP/1.0 {2}/{3}",
+ Environment.OSVersion.Platform,
+ Environment.OSVersion,
+ applicationHost.Name,
+ applicationHost.ApplicationVersionString));
+
+ c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", applicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0
+ c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", applicationHost.FriendlyName); // REVIEW: where does this come from?
+ })
+ .ConfigurePrimaryHttpMessageHandler(_ => new SocketsHttpHandler
+ {
+ AutomaticDecompression = DecompressionMethods.All,
+ RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8
+ });
+
+ services.AddSingleton<IDlnaManager, DlnaManager>();
+ services.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
+ services.AddSingleton<IContentDirectory, ContentDirectoryService>();
+ services.AddSingleton<IConnectionManager, ConnectionManagerService>();
+ services.AddSingleton<IMediaReceiverRegistrar, MediaReceiverRegistrarService>();
+
+ services.AddSingleton<ISsdpCommunicationsServer>(provider => new SsdpCommunicationsServer(
+ provider.GetRequiredService<ISocketFactory>(),
+ provider.GetRequiredService<INetworkManager>(),
+ provider.GetRequiredService<ILogger<SsdpCommunicationsServer>>())
+ {
+ IsShared = true
+ });
+ }
+}
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 1a4bec398..aa7012487 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -23,10 +23,8 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
-using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
using Rssdp;
using Rssdp.Infrastructure;
@@ -49,14 +47,13 @@ namespace Emby.Dlna.Main
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly ISocketFactory _socketFactory;
+ private readonly ISsdpCommunicationsServer _communicationsServer;
private readonly INetworkManager _networkManager;
- private readonly object _syncLock = new object();
+ private readonly object _syncLock = new();
private readonly bool _disabled;
private PlayToManager _manager;
private SsdpDevicePublisher _publisher;
- private ISsdpCommunicationsServer _communicationsServer;
private bool _disposed;
@@ -75,10 +72,8 @@ namespace Emby.Dlna.Main
IMediaSourceManager mediaSourceManager,
IDeviceDiscovery deviceDiscovery,
IMediaEncoder mediaEncoder,
- ISocketFactory socketFactory,
- INetworkManager networkManager,
- IUserViewManager userViewManager,
- ITVSeriesManager tvSeriesManager)
+ ISsdpCommunicationsServer communicationsServer,
+ INetworkManager networkManager)
{
_config = config;
_appHost = appHost;
@@ -93,37 +88,10 @@ namespace Emby.Dlna.Main
_mediaSourceManager = mediaSourceManager;
_deviceDiscovery = deviceDiscovery;
_mediaEncoder = mediaEncoder;
- _socketFactory = socketFactory;
+ _communicationsServer = communicationsServer;
_networkManager = networkManager;
_logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
- ContentDirectory = new ContentDirectory.ContentDirectoryService(
- dlnaManager,
- userDataManager,
- imageProcessor,
- libraryManager,
- config,
- userManager,
- loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
- httpClientFactory,
- localizationManager,
- mediaSourceManager,
- userViewManager,
- mediaEncoder,
- tvSeriesManager);
-
- ConnectionManager = new ConnectionManager.ConnectionManagerService(
- dlnaManager,
- config,
- loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
- httpClientFactory);
-
- MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
- loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
- httpClientFactory,
- config);
- Current = this;
-
var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
@@ -133,19 +101,6 @@ namespace Emby.Dlna.Main
}
}
- public static DlnaEntryPoint Current { get; private set; }
-
- /// <summary>
- /// Gets a value indicating whether the dlna server is enabled.
- /// </summary>
- public static bool Enabled { get; private set; }
-
- public IContentDirectory ContentDirectory { get; private set; }
-
- public IConnectionManager ConnectionManager { get; private set; }
-
- public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
-
public async Task RunAsync()
{
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
@@ -172,9 +127,7 @@ namespace Emby.Dlna.Main
private void ReloadComponents()
{
var options = _config.GetDlnaConfiguration();
- Enabled = options.EnableServer;
-
- StartSsdpHandler();
+ StartDeviceDiscovery();
if (options.EnableServer)
{
@@ -195,36 +148,11 @@ namespace Emby.Dlna.Main
}
}
- private void StartSsdpHandler()
- {
- try
- {
- if (_communicationsServer is null)
- {
- var enableMultiSocketBinding = OperatingSystem.IsWindows() || OperatingSystem.IsLinux();
-
- _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
- {
- IsShared = true
- };
-
- StartDeviceDiscovery(_communicationsServer);
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error starting ssdp handlers");
- }
- }
-
- private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer)
+ private void StartDeviceDiscovery()
{
try
{
- if (communicationsServer is not null)
- {
- ((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer);
- }
+ ((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer);
}
catch (Exception ex)
{
@@ -232,19 +160,6 @@ namespace Emby.Dlna.Main
}
}
- private void DisposeDeviceDiscovery()
- {
- try
- {
- _logger.LogInformation("Disposing DeviceDiscovery");
- ((DeviceDiscovery)_deviceDiscovery).Dispose();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error stopping device discovery");
- }
- }
-
public void StartDevicePublisher(Configuration.DlnaOptions options)
{
if (_publisher is not null)
@@ -317,7 +232,7 @@ namespace Emby.Dlna.Main
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
};
- SetProperies(device, fullService);
+ SetProperties(device, fullService);
_publisher.AddDevice(device);
var embeddedDevices = new[]
@@ -338,13 +253,13 @@ namespace Emby.Dlna.Main
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
};
- SetProperies(embeddedDevice, subDevice);
+ SetProperties(embeddedDevice, subDevice);
device.AddDevice(embeddedDevice);
}
}
}
- private string CreateUuid(string text)
+ private static string CreateUuid(string text)
{
if (!Guid.TryParse(text, out var guid))
{
@@ -354,15 +269,14 @@ namespace Emby.Dlna.Main
return guid.ToString("D", CultureInfo.InvariantCulture);
}
- private void SetProperies(SsdpDevice device, string fullDeviceType)
+ private static void SetProperties(SsdpDevice device, string fullDeviceType)
{
- var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase);
-
- var serviceParts = service.Split(':');
-
- var deviceTypeNamespace = serviceParts[0].Replace('.', '-');
+ var serviceParts = fullDeviceType
+ .Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase)
+ .Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase)
+ .Split(':');
- device.DeviceTypeNamespace = deviceTypeNamespace;
+ device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-');
device.DeviceClass = serviceParts[1];
device.DeviceType = serviceParts[2];
}
@@ -443,20 +357,6 @@ namespace Emby.Dlna.Main
DisposeDevicePublisher();
DisposePlayToManager();
- DisposeDeviceDiscovery();
-
- if (_communicationsServer is not null)
- {
- _logger.LogInformation("Disposing SsdpCommunicationsServer");
- _communicationsServer.Dispose();
- _communicationsServer = null;
- }
-
- ContentDirectory = null;
- ConnectionManager = null;
- MediaReceiverRegistrar = null;
- Current = null;
-
_disposed = true;
}
}
diff --git a/Emby.Dlna/PlayTo/DlnaHttpClient.cs b/Emby.Dlna/PlayTo/DlnaHttpClient.cs
index 220aa1a8d..255c51f19 100644
--- a/Emby.Dlna/PlayTo/DlnaHttpClient.cs
+++ b/Emby.Dlna/PlayTo/DlnaHttpClient.cs
@@ -55,41 +55,42 @@ namespace Emby.Dlna.PlayTo
var client = _httpClientFactory.CreateClient(NamedClient.Dlna);
using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
- await using MemoryStream ms = new MemoryStream();
- await response.Content.CopyToAsync(ms, cancellationToken).ConfigureAwait(false);
- ms.Position = 0;
- try
+ Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ await using (stream.ConfigureAwait(false))
{
- return await XDocument.LoadAsync(
- ms,
- LoadOptions.None,
- cancellationToken).ConfigureAwait(false);
- }
- catch (XmlException)
- {
- // try correcting the Xml response with common errors
- ms.Position = 0;
- using StreamReader sr = new StreamReader(ms);
- var xmlString = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
-
- // find and replace unescaped ampersands (&)
- xmlString = EscapeAmpersandRegex().Replace(xmlString, "&amp;");
-
try
{
- // retry reading Xml
- using var xmlReader = new StringReader(xmlString);
return await XDocument.LoadAsync(
- xmlReader,
+ stream,
LoadOptions.None,
cancellationToken).ConfigureAwait(false);
}
- catch (XmlException ex)
+ catch (XmlException)
{
- _logger.LogError(ex, "Failed to parse response");
- _logger.LogDebug("Malformed response: {Content}\n", xmlString);
-
- return null;
+ // try correcting the Xml response with common errors
+ stream.Position = 0;
+ using StreamReader sr = new StreamReader(stream);
+ var xmlString = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
+
+ // find and replace unescaped ampersands (&)
+ xmlString = EscapeAmpersandRegex().Replace(xmlString, "&amp;");
+
+ try
+ {
+ // retry reading Xml
+ using var xmlReader = new StringReader(xmlString);
+ return await XDocument.LoadAsync(
+ xmlReader,
+ LoadOptions.None,
+ cancellationToken).ConfigureAwait(false);
+ }
+ catch (XmlException ex)
+ {
+ _logger.LogError(ex, "Failed to parse response");
+ _logger.LogDebug("Malformed response: {Content}\n", xmlString);
+
+ return null;
+ }
}
}
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 3253ea026..c9bf7f085 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -13,9 +13,7 @@ using System.Net;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
-using Emby.Dlna;
using Emby.Dlna.Main;
-using Emby.Dlna.Ssdp;
using Emby.Naming.Common;
using Emby.Photos;
using Emby.Server.Implementations.Channels;
@@ -58,7 +56,6 @@ using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.ClientEvent;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -82,7 +79,6 @@ using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.MediaEncoding.Subtitles;
using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
@@ -450,7 +446,7 @@ namespace Emby.Server.Implementations
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
- NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
+ NetManager = new NetworkManager(ConfigurationManager, _startupConfig, LoggerFactory.CreateLogger<NetworkManager>());
// Initialize runtime stat collection
if (ConfigurationManager.Configuration.EnableMetrics)
@@ -563,8 +559,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<ISessionManager, SessionManager>();
- serviceCollection.AddSingleton<IDlnaManager, DlnaManager>();
-
serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
@@ -576,8 +570,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
- serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
-
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
@@ -913,7 +905,7 @@ namespace Emby.Server.Implementations
/// <inheritdoc/>
public string GetSmartApiUrl(HttpRequest request)
{
- // Return the host in the HTTP request as the API url
+ // Return the host in the HTTP request as the API URL if not configured otherwise
if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest)
{
int? requestPort = request.Host.Port;
@@ -948,7 +940,7 @@ namespace Emby.Server.Implementations
public string GetApiUrlForLocalAccess(IPAddress ipAddress = null, bool allowHttps = true)
{
// With an empty source, the port will be null
- var smart = NetManager.GetBindAddress(ipAddress, out _, true);
+ var smart = NetManager.GetBindAddress(ipAddress, out _, false);
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
int? port = !allowHttps ? HttpPort : null;
return GetLocalApiUrl(smart, scheme, port);
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 961e225e9..3036cbcf7 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -371,8 +371,11 @@ namespace Emby.Server.Implementations.Channels
Directory.CreateDirectory(Path.GetDirectoryName(path));
- await using FileStream createStream = File.Create(path);
- await JsonSerializer.SerializeAsync(createStream, mediaSources, _jsonOptions).ConfigureAwait(false);
+ FileStream createStream = File.Create(path);
+ await using (createStream.ConfigureAwait(false))
+ {
+ await JsonSerializer.SerializeAsync(createStream, mediaSources, _jsonOptions).ConfigureAwait(false);
+ }
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 3aab0a5e9..80263c139 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -43,8 +43,6 @@
<TargetFramework>net7.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
- <!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
- <NoWarn>AD0001</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index 54191649d..2c03f9ffd 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
/// <summary>
- /// Class UdpServerEntryPoint.
+ /// Class responsible for registering all UDP broadcast endpoints and their handlers.
/// </summary>
public sealed class UdpServerEntryPoint : IServerEntryPoint
{
@@ -35,7 +35,6 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IConfiguration _config;
private readonly IConfigurationManager _configurationManager;
private readonly INetworkManager _networkManager;
- private readonly bool _enableMultiSocketBinding;
/// <summary>
/// The UDP server.
@@ -65,7 +64,6 @@ namespace Emby.Server.Implementations.EntryPoints
_configurationManager = configurationManager;
_networkManager = networkManager;
_udpServers = new List<UdpServer>();
- _enableMultiSocketBinding = OperatingSystem.IsWindows() || OperatingSystem.IsLinux();
}
/// <inheritdoc />
@@ -80,14 +78,16 @@ namespace Emby.Server.Implementations.EntryPoints
try
{
- if (_enableMultiSocketBinding)
+ // Linux needs to bind to the broadcast addresses to get broadcast traffic
+ // Windows receives broadcast fine when binding to just the interface, it is unable to bind to broadcast addresses
+ if (OperatingSystem.IsLinux())
{
- // Add global broadcast socket
+ // Add global broadcast listener
var server = new UdpServer(_logger, _appHost, _config, IPAddress.Broadcast, PortNumber);
server.Start(_cancellationTokenSource.Token);
_udpServers.Add(server);
- // Add bind address specific broadcast sockets
+ // Add bind address specific broadcast listeners
// IPv6 is currently unsupported
var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
foreach (var intf in validInterfaces)
@@ -102,9 +102,18 @@ namespace Emby.Server.Implementations.EntryPoints
}
else
{
- var server = new UdpServer(_logger, _appHost, _config, IPAddress.Any, PortNumber);
- server.Start(_cancellationTokenSource.Token);
- _udpServers.Add(server);
+ // Add bind address specific broadcast listeners
+ // IPv6 is currently unsupported
+ var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
+ foreach (var intf in validInterfaces)
+ {
+ var intfAddress = intf.Address;
+ _logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", intfAddress, PortNumber);
+
+ var server = new UdpServer(_logger, _appHost, _config, intfAddress, PortNumber);
+ server.Start(_cancellationTokenSource.Token);
+ _udpServers.Add(server);
+ }
}
}
catch (SocketException ex)
@@ -119,7 +128,7 @@ namespace Emby.Server.Implementations.EntryPoints
{
if (_disposed)
{
- throw new ObjectDisposedException(this.GetType().Name);
+ throw new ObjectDisposedException(GetType().Name);
}
}
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index 936a08da8..59d705ace 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -48,15 +48,20 @@ namespace Emby.Server.Implementations.Library
if (!string.IsNullOrEmpty(cacheKey))
{
+ FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
try
{
- await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
// _logger.LogDebug("Found cached media info");
}
- catch
+ catch (Exception ex)
{
+ _logger.LogError(ex, "Error deserializing mediainfo cache");
+ }
+ finally
+ {
+ await jsonStream.DisposeAsync().ConfigureAwait(false);
}
}
@@ -84,10 +89,13 @@ namespace Emby.Server.Implementations.Library
if (cacheFilePath is not null)
{
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
- await using FileStream createStream = AsyncFile.OpenWrite(cacheFilePath);
- await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ FileStream createStream = AsyncFile.OpenWrite(cacheFilePath);
+ await using (createStream.ConfigureAwait(false))
+ {
+ await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ }
- // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
+ _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
}
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index c9a26a30f..91469dba9 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -625,17 +625,19 @@ namespace Emby.Server.Implementations.Library
if (!string.IsNullOrEmpty(cacheKey))
{
+ FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
try
{
- await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
-
- // _logger.LogDebug("Found cached media info");
}
catch (Exception ex)
{
_logger.LogDebug(ex, "_jsonSerializer.DeserializeFromFile threw an exception.");
}
+ finally
+ {
+ await jsonStream.DisposeAsync().ConfigureAwait(false);
+ }
}
if (mediaInfo is null)
@@ -664,8 +666,11 @@ namespace Emby.Server.Implementations.Library
if (cacheFilePath is not null)
{
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
- await using FileStream createStream = File.Create(cacheFilePath);
- await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ FileStream createStream = File.Create(cacheFilePath);
+ await using (createStream.ConfigureAwait(false))
+ {
+ await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ }
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index b9d0f170a..74b62ca3f 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -1851,7 +1851,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
+ var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
+ await using (stream.ConfigureAwait(false))
{
var settings = new XmlWriterSettings
{
@@ -1860,7 +1861,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Async = true
};
- await using (var writer = XmlWriter.Create(stream, settings))
+ var writer = XmlWriter.Create(stream, settings);
+ await using (writer.ConfigureAwait(false))
{
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false);
@@ -1914,7 +1916,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
+ var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
+ await using (stream.ConfigureAwait(false))
{
var settings = new XmlWriterSettings
{
@@ -1927,7 +1930,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var isSeriesEpisode = timer.IsProgramSeries;
- await using (var writer = XmlWriter.Create(stream, settings))
+ var writer = XmlWriter.Create(stream, settings);
+ await using (writer.ConfigureAwait(false))
{
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
@@ -1965,7 +1969,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
else
{
- await writer.WriteStartElementAsync(null, "movie", null);
+ await writer.WriteStartElementAsync(null, "movie", null).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(item.Name))
{
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 7645c6c52..6b0520ad0 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -106,8 +106,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
options.Content = JsonContent.Create(requestList, options: _jsonOptions);
options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
- await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var dailySchedules = await JsonSerializer.DeserializeAsync<IReadOnlyList<DayDto>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var dailySchedules = await response.Content.ReadFromJsonAsync<IReadOnlyList<DayDto>>(_jsonOptions, cancellationToken).ConfigureAwait(false);
if (dailySchedules is null)
{
return Array.Empty<ProgramInfo>();
@@ -122,8 +121,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions);
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
- await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var programDetails = await JsonSerializer.DeserializeAsync<IReadOnlyList<ProgramDetailsDto>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var programDetails = await innerResponse.Content.ReadFromJsonAsync<IReadOnlyList<ProgramDetailsDto>>(_jsonOptions, cancellationToken).ConfigureAwait(false);
if (programDetails is null)
{
return Array.Empty<ProgramInfo>();
@@ -482,8 +480,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
try
{
using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
- await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await JsonSerializer.DeserializeAsync<IReadOnlyList<ShowImagesDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ return await innerResponse2.Content.ReadFromJsonAsync<IReadOnlyList<ShowImagesDto>>(_jsonOptions, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -510,10 +507,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
try
{
using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
- await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
-
- var root = await JsonSerializer.DeserializeAsync<IReadOnlyList<HeadendsDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
-
+ var root = await httpResponse.Content.ReadFromJsonAsync<IReadOnlyList<HeadendsDto>>(_jsonOptions, cancellationToken).ConfigureAwait(false);
if (root is not null)
{
foreach (HeadendsDto headend in root)
@@ -649,8 +643,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await JsonSerializer.DeserializeAsync<TokenDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var root = await response.Content.ReadFromJsonAsync<TokenDto>(_jsonOptions, cancellationToken).ConfigureAwait(false);
if (string.Equals(root?.Message, "OK", StringComparison.Ordinal))
{
_logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token);
@@ -691,10 +684,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
httpResponse.EnsureSuccessStatusCode();
- await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var response = httpResponse.Content;
- var root = await JsonSerializer.DeserializeAsync<LineupsDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
-
+ var root = await httpResponse.Content.ReadFromJsonAsync<LineupsDto>(_jsonOptions, cancellationToken).ConfigureAwait(false);
return root?.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase)) ?? false;
}
catch (HttpRequestException ex)
@@ -748,8 +738,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
options.Headers.TryAddWithoutValidation("token", token);
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.DeserializeAsync<ChannelDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var root = await httpResponse.Content.ReadFromJsonAsync<ChannelDto>(_jsonOptions, cancellationToken).ConfigureAwait(false);
if (root is null)
{
return new List<ChannelInfo>();
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 7e588f681..4f96dde44 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -9,6 +9,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -76,13 +77,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, _jsonOptions, cancellationToken)
- .ConfigureAwait(false) ?? new List<Channels>();
-
+ var lineup = await response.Content.ReadFromJsonAsync<IEnumerable<Channels>>(_jsonOptions, cancellationToken).ConfigureAwait(false) ?? Enumerable.Empty<Channels>();
if (info.ImportFavoritesOnly)
{
- lineup = lineup.Where(i => i.Favorite).ToList();
+ lineup = lineup.Where(i => i.Favorite);
}
return lineup.Where(i => !i.DRM).ToList();
@@ -129,9 +127,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
.GetAsync(GetApiUrl(info) + "/discover.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, _jsonOptions, cancellationToken)
- .ConfigureAwait(false);
+ var discoverResponse = await response.Content.ReadFromJsonAsync<DiscoverResponse>(_jsonOptions, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(cacheKey))
{
@@ -175,34 +171,37 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
.ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
var tuners = new List<LiveTvTunerInfo>();
- await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false))
+ var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ await using (stream.ConfigureAwait(false))
{
- string stripedLine = StripXML(line);
- if (stripedLine.Contains("Channel", StringComparison.Ordinal))
+ using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
+ await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false))
{
- LiveTvTunerStatus status;
- var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
- var name = stripedLine.Substring(0, index - 1);
- var currentChannel = stripedLine.Substring(index + 7);
- if (string.Equals(currentChannel, "none", StringComparison.Ordinal))
+ string stripedLine = StripXML(line);
+ if (stripedLine.Contains("Channel", StringComparison.Ordinal))
{
- status = LiveTvTunerStatus.LiveTv;
- }
- else
- {
- status = LiveTvTunerStatus.Available;
- }
+ LiveTvTunerStatus status;
+ var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
+ var name = stripedLine.Substring(0, index - 1);
+ var currentChannel = stripedLine.Substring(index + 7);
+ if (string.Equals(currentChannel, "none", StringComparison.Ordinal))
+ {
+ status = LiveTvTunerStatus.LiveTv;
+ }
+ else
+ {
+ status = LiveTvTunerStatus.Available;
+ }
- tuners.Add(new LiveTvTunerInfo
- {
- Name = name,
- SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
- ProgramName = currentChannel,
- Status = status
- });
+ tuners.Add(new LiveTvTunerInfo
+ {
+ Name = name,
+ SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
+ ProgramName = currentChannel,
+ Status = status
+ });
+ }
}
}
diff --git a/Emby.Server.Implementations/Localization/Core/fo.json b/Emby.Server.Implementations/Localization/Core/fo.json
new file mode 100644
index 000000000..40aa5f71a
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/fo.json
@@ -0,0 +1,18 @@
+{
+ "Artists": "Listafólk",
+ "Collections": "Søvn",
+ "Default": "Sjálvgildi",
+ "DeviceOfflineWithName": "{0} hevur slitið sambandið",
+ "External": "Ytri",
+ "Genres": "Greinar",
+ "Albums": "Album",
+ "AppDeviceValues": "App: {0}, Eind: {1}",
+ "Application": "Nýtsluskipan",
+ "Books": "Bøkur",
+ "Channels": "Rásir",
+ "ChapterNameValue": "Kapittul {0}",
+ "DeviceOnlineWithName": "{0} er sambundið",
+ "Favorites": "Yndis",
+ "Folders": "Mappur",
+ "Forced": "Kravt"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json
index a40f49506..0f1f0b3d2 100644
--- a/Emby.Server.Implementations/Localization/Core/is.json
+++ b/Emby.Server.Implementations/Localization/Core/is.json
@@ -13,8 +13,8 @@
"HeaderFavoriteArtists": "Uppáhalds Listamenn",
"HeaderFavoriteAlbums": "Uppáhalds Plötur",
"HeaderContinueWatching": "Halda áfram að horfa",
- "HeaderAlbumArtists": "Höfundur plötu",
- "Genres": "Tegundir",
+ "HeaderAlbumArtists": "Listamaður á umslagi",
+ "Genres": "Stefnur",
"Folders": "Möppur",
"Favorites": "Uppáhalds",
"FailedLoginAttemptWithUserName": "{0} reyndi að auðkenna sig",
@@ -22,32 +22,32 @@
"DeviceOfflineWithName": "{0} hefur aftengst",
"Collections": "Söfn",
"ChapterNameValue": "Kafli {0}",
- "Channels": "Stöðvar",
- "CameraImageUploadedFrom": "Ný ljósmynd frá myndavél hefur verið hlaðið upp frá {0}",
+ "Channels": "Rásir",
+ "CameraImageUploadedFrom": "{0} hefur hlaðið upp nýrri ljósmynd úr myndavél sinni",
"Books": "Bækur",
- "AuthenticationSucceededWithUserName": "{0} auðkenning tókst",
- "Artists": "Listamaður",
+ "AuthenticationSucceededWithUserName": "Auðkenning fyrir {0} tókst",
+ "Artists": "Listamenn",
"Application": "Forrit",
"AppDeviceValues": "Snjallforrit: {0}, Tæki: {1}",
"Albums": "Plötur",
- "Plugin": "Viðbót",
- "Photos": "Myndir",
- "NotificationOptionVideoPlaybackStopped": "Myndbandafspilun stöðvuð",
- "NotificationOptionVideoPlayback": "Myndbandafspilun hafin",
+ "Plugin": "Viðbótarvirkni",
+ "Photos": "Ljósmyndir",
+ "NotificationOptionVideoPlaybackStopped": "Myndbandsafspilun stöðvuð",
+ "NotificationOptionVideoPlayback": "Myndbandsafspilun hafin",
"NotificationOptionUserLockedOut": "Notandi læstur úti",
- "NotificationOptionServerRestartRequired": "Endurræsing þjóns er nauðsynileg",
- "NotificationOptionPluginUpdateInstalled": "Viðbótar uppfærsla uppsett",
- "NotificationOptionPluginUninstalled": "Viðbót fjarlægð",
- "NotificationOptionPluginInstalled": "Viðbót sett upp",
+ "NotificationOptionServerRestartRequired": "Endurræsing þjóns er nauðsynleg",
+ "NotificationOptionPluginUpdateInstalled": "Uppfærslu á viðbótarvirkni lokið",
+ "NotificationOptionPluginUninstalled": "Viðbótarvirkni fjarlægð",
+ "NotificationOptionPluginInstalled": "Viðbótarvirkni sett upp",
"NotificationOptionPluginError": "Bilun í viðbót",
"NotificationOptionInstallationFailed": "Uppsetning tókst ekki",
- "NotificationOptionCameraImageUploaded": "Myndavélarmynd hlaðið upp",
+ "NotificationOptionCameraImageUploaded": "Ljósmynd hlaðið upp",
"NotificationOptionAudioPlaybackStopped": "Hljóðafspilun stöðvuð",
"NotificationOptionAudioPlayback": "Hljóðafspilun hafin",
"NotificationOptionApplicationUpdateInstalled": "Uppfærsla uppsett",
"NotificationOptionApplicationUpdateAvailable": "Uppfærsla í boði",
- "NameSeasonUnknown": "Sería óþekkt",
- "NameSeasonNumber": "Sería {0}",
+ "NameSeasonUnknown": "Þáttaröð óþekkt",
+ "NameSeasonNumber": "Þáttaröð {0}",
"MixedContent": "Blandað efni",
"MessageServerConfigurationUpdated": "Stillingar þjóns hafa verið uppfærðar",
"MessageApplicationUpdatedTo": "Jellyfin þjónn hefur verið uppfærður í {0}",
@@ -57,24 +57,24 @@
"User": "Notandi",
"System": "Kerfi",
"NotificationOptionNewLibraryContent": "Nýju efni bætt við",
- "NewVersionIsAvailable": "Ný útgáfa af Jellyfin þjón er fáanleg til niðurhals.",
+ "NewVersionIsAvailable": "Ný útgáfa af Jellyfin þjón er tilbúin til niðurhals.",
"NameInstallFailed": "{0} uppsetning mistókst",
"MusicVideos": "Tónlistarmyndbönd",
"Music": "Tónlist",
"Movies": "Kvikmyndir",
"UserDeletedWithName": "Notanda {0} hefur verið eytt",
"UserCreatedWithName": "Notandi {0} hefur verið stofnaður",
- "TvShows": "Þættir",
+ "TvShows": "Sjónvarpsþættir",
"Sync": "Samstilla",
"Songs": "Lög",
- "ServerNameNeedsToBeRestarted": "{0} þarf að endurræsa",
+ "ServerNameNeedsToBeRestarted": "{0} þarf að vera endurræstur",
"ScheduledTaskStartedWithName": "{0} hafin",
"ScheduledTaskFailedWithName": "{0} mistókst",
"PluginUpdatedWithName": "{0} var uppfært",
"PluginUninstalledWithName": "{0} var fjarlægt",
"PluginInstalledWithName": "{0} var sett upp",
"NotificationOptionTaskFailed": "Tímasett verkefni mistókst",
- "StartupEmbyServerIsLoading": "Jellyfin netþjónnin er að hlaðast. Vinsamlega prufaðu aftur fljótlega.",
+ "StartupEmbyServerIsLoading": "Jellyfin netþjónnin er að ræsa sig upp. Vinsamlegast reyndu aftur fljótlega.",
"VersionNumber": "Útgáfa {0}",
"ValueHasBeenAddedToLibrary": "{0} hefur verið bætt við í gagnasafnið þitt",
"UserStoppedPlayingItemWithValues": "{0} hefur lokið spilunar af {1} á {2}",
@@ -83,14 +83,14 @@
"UserPasswordChangedWithName": "Lykilorði fyrir notandann {0} hefur verið breytt",
"UserOnlineFromDevice": "{0} hefur verið virkur síðan {1}",
"UserOfflineFromDevice": "{0} hefur aftengst frá {1}",
- "UserLockedOutWithName": "Notanda {0} hefur verið heflaður aðgangur",
- "UserDownloadingItemWithValues": "{0} Hleður niður {1}",
+ "UserLockedOutWithName": "Notandi {0} hefur verið læstur úti",
+ "UserDownloadingItemWithValues": "{0} hleður niður {1}",
"SubtitleDownloadFailureFromForItem": "Tókst ekki að hala niður skjátextum frá {0} til {1}",
- "ProviderValue": "Veitandi: {0}",
+ "ProviderValue": "Efnisveita: {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Stilling {0} hefur verið uppfærð á netþjón",
- "ValueSpecialEpisodeName": "Sérstakt - {0}",
- "Shows": "Sýningar",
- "Playlists": "Spilunarlisti",
+ "ValueSpecialEpisodeName": "Sérstaktur - {0}",
+ "Shows": "Þættir",
+ "Playlists": "Efnisskrár",
"TaskRefreshChannelsDescription": "Endurhlaða upplýsingum netrása.",
"TaskRefreshChannels": "Endurhlaða Rásir",
"TaskCleanTranscodeDescription": "Eyða umkóðuðum skrám sem eru meira en einum degi eldri.",
@@ -116,5 +116,12 @@
"TaskCleanLogsDescription": "Eyðir færslu skrám sem eru meira en {0} gömul.",
"TaskCleanLogs": "Hreinsa færslu skrá",
"TaskDownloadMissingSubtitlesDescription": "Leitar á netinu að texta sem vantar miðað við uppsetningu lýsigagna.",
- "HearingImpaired": "Heyrnarskertur"
+ "HearingImpaired": "Heyrnarskertur",
+ "TaskOptimizeDatabaseDescription": "Þjappar gagnagrunni og bætir við lausu diskaplássi. Að keyra þessa aðgerð eftir skönnun safnsins, eða eftir einhverjar breytingar sem fela í sér gagnagrunnsbreytingar, gætu aukið hraðvirkni.",
+ "TaskKeyframeExtractor": "Lykilrammaplokkari",
+ "TaskKeyframeExtractorDescription": "Plokkar lykilramma úr myndbandsskrám til að búa til nákvæmari HLS uppskiptingarlista. Þetta verk getur tekið langan tíma.",
+ "TaskRefreshChapterImages": "Plokka kafla-myndir",
+ "TaskCleanActivityLogDescription": "Eyðir virkniskráningarfærslum sem hafa náð settum hámarksaldri.",
+ "Forced": "Þvingað",
+ "External": "Útvær"
}
diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json
index f7b24412a..d558cdfe0 100644
--- a/Emby.Server.Implementations/Localization/Core/lv.json
+++ b/Emby.Server.Implementations/Localization/Core/lv.json
@@ -75,7 +75,7 @@
"HeaderAlbumArtists": "Albumu Izpildītāji",
"Genres": "Žanri",
"Folders": "Mapes",
- "Favorites": "Favorīti",
+ "Favorites": "Izlase",
"FailedLoginAttemptWithUserName": "Neizdevies pieslēgšanās mēģinājums no {0}",
"DeviceOnlineWithName": "{0} ir pievienojies",
"DeviceOfflineWithName": "{0} ir atvienojies",
@@ -113,7 +113,7 @@
"TaskCleanCache": "Iztīrīt Kešošanas Mapi",
"TasksChannelsCategory": "Interneta Kanāli",
"TasksMaintenanceCategory": "Apkope",
- "Forced": "Piespiests",
+ "Forced": "Piespiedu",
"TaskCleanActivityLogDescription": "Nodzēš darbību žurnāla ierakstus, kuri ir vecāki par doto vecumu.",
"TaskCleanActivityLog": "Notīrīt Darbību Žurnālu",
"Undefined": "Nenoteikts",
diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json
index 858cc40dd..c231d76fe 100644
--- a/Emby.Server.Implementations/Localization/Core/sk.json
+++ b/Emby.Server.Implementations/Localization/Core/sk.json
@@ -124,5 +124,5 @@
"TaskKeyframeExtractorDescription": "Extrahuje kľúčové snímky z video súborov na vytvorenie presnejších HLS playlistov. Táto úloha môže trvať dlhšiu dobu.",
"TaskKeyframeExtractor": "Extraktor kľúčových snímkov",
"External": "Externé",
- "HearingImpaired": "Sluchovo Postihnutý"
+ "HearingImpaired": "Sluchovo postihnutí"
}
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 96f435399..16776b6bd 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -71,25 +71,28 @@ namespace Emby.Server.Implementations.Localization
string countryCode = resource.Substring(RatingsPath.Length, 2);
var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
- await using var stream = _assembly.GetManifestResourceStream(resource);
- using var reader = new StreamReader(stream!); // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames()
- await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
+ var stream = _assembly.GetManifestResourceStream(resource);
+ await using (stream!.ConfigureAwait(false)) // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames()
{
- if (string.IsNullOrWhiteSpace(line))
+ using var reader = new StreamReader(stream!);
+ await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{
- continue;
- }
-
- string[] parts = line.Split(',');
- if (parts.Length == 2
- && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
- {
- var name = parts[0];
- dict.Add(name, new ParentalRating(name, value));
- }
- else
- {
- _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+
+ string[] parts = line.Split(',');
+ if (parts.Length == 2
+ && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
+ {
+ var name = parts[0];
+ dict.Add(name, new ParentalRating(name, value));
+ }
+ else
+ {
+ _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
+ }
}
}
diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs
index 51e92953d..505984cfb 100644
--- a/Emby.Server.Implementations/Net/SocketFactory.cs
+++ b/Emby.Server.Implementations/Net/SocketFactory.cs
@@ -1,12 +1,15 @@
-#pragma warning disable CS1591
-
using System;
+using System.Linq;
using System.Net;
+using System.Net.NetworkInformation;
using System.Net.Sockets;
using MediaBrowser.Model.Net;
namespace Emby.Server.Implementations.Net
{
+ /// <summary>
+ /// Factory class to create different kinds of sockets.
+ /// </summary>
public class SocketFactory : ISocketFactory
{
/// <inheritdoc />
@@ -38,7 +41,8 @@ namespace Emby.Server.Implementations.Net
/// <inheritdoc />
public Socket CreateSsdpUdpSocket(IPData bindInterface, int localPort)
{
- ArgumentNullException.ThrowIfNull(bindInterface.Address);
+ var interfaceAddress = bindInterface.Address;
+ ArgumentNullException.ThrowIfNull(interfaceAddress);
if (localPort < 0)
{
@@ -49,7 +53,7 @@ namespace Emby.Server.Implementations.Net
try
{
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
- socket.Bind(new IPEndPoint(bindInterface.Address, localPort));
+ socket.Bind(new IPEndPoint(interfaceAddress, localPort));
return socket;
}
@@ -82,16 +86,25 @@ namespace Emby.Server.Implementations.Net
try
{
- var interfaceIndex = bindInterface.Index;
- var interfaceIndexSwapped = (int)IPAddress.HostToNetworkOrder(interfaceIndex);
-
socket.MulticastLoopback = false;
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
- socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, interfaceIndexSwapped);
- socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, interfaceIndex));
- socket.Bind(new IPEndPoint(multicastAddress, localPort));
+
+ if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+ {
+ socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress));
+ socket.Bind(new IPEndPoint(multicastAddress, localPort));
+ }
+ else
+ {
+ // Only create socket if interface supports multicast
+ var interfaceIndex = bindInterface.Index;
+ var interfaceIndexSwapped = IPAddress.HostToNetworkOrder(interfaceIndex);
+
+ socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, interfaceIndex));
+ socket.Bind(new IPEndPoint(bindIPAddress, localPort));
+ }
return socket;
}
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index d7189ef0c..20793ee39 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -386,11 +386,11 @@ namespace Emby.Server.Implementations.Plugins
var url = new Uri(packageInfo.ImageUrl);
imagePath = Path.Join(path, url.Segments[^1]);
- await using var fileStream = AsyncFile.OpenWrite(imagePath);
-
+ var fileStream = AsyncFile.OpenWrite(imagePath);
+ Stream? downloadStream = null;
try
{
- await using var downloadStream = await HttpClientFactory
+ downloadStream = await HttpClientFactory
.CreateClient(NamedClient.Default)
.GetStreamAsync(url)
.ConfigureAwait(false);
@@ -402,6 +402,14 @@ namespace Emby.Server.Implementations.Plugins
_logger.LogError(ex, "Failed to download image to path {Path} on disk.", imagePath);
imagePath = string.Empty;
}
+ finally
+ {
+ await fileStream.DisposeAsync().ConfigureAwait(false);
+ if (downloadStream is not null)
+ {
+ await downloadStream.DisposeAsync().ConfigureAwait(false);
+ }
+ }
}
var manifest = new PluginManifest
@@ -421,7 +429,7 @@ namespace Emby.Server.Implementations.Plugins
ImagePath = imagePath
};
- if (!await ReconcileManifest(manifest, path))
+ if (!await ReconcileManifest(manifest, path).ConfigureAwait(false))
{
// An error occurred during reconciliation and saving could be undesirable.
return false;
@@ -458,7 +466,7 @@ namespace Emby.Server.Implementations.Plugins
}
using var metaStream = File.OpenRead(metafile);
- var localManifest = await JsonSerializer.DeserializeAsync<PluginManifest>(metaStream, _jsonOptions);
+ var localManifest = await JsonSerializer.DeserializeAsync<PluginManifest>(metaStream, _jsonOptions).ConfigureAwait(false);
localManifest ??= new PluginManifest();
if (!Equals(localManifest.Id, manifest.Id))
diff --git a/Emby.Server.Implementations/SystemManager.cs b/Emby.Server.Implementations/SystemManager.cs
index 0fdfe66ca..2c477218f 100644
--- a/Emby.Server.Implementations/SystemManager.cs
+++ b/Emby.Server.Implementations/SystemManager.cs
@@ -46,10 +46,6 @@ public class SystemManager : ISystemManager
_installationManager = installationManager;
}
- private bool CanLaunchWebBrowser => Environment.UserInteractive
- && !_startupOptions.IsService
- && (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS());
-
/// <inheritdoc />
public SystemInfo GetSystemInfo(HttpRequest request)
{
@@ -67,7 +63,6 @@ public class SystemManager : ISystemManager
ItemsByNamePath = _applicationPaths.InternalMetadataPath,
InternalMetadataPath = _applicationPaths.InternalMetadataPath,
CachePath = _applicationPaths.CachePath,
- CanLaunchWebBrowser = CanLaunchWebBrowser,
TranscodingTempPath = _configurationManager.GetTranscodePath(),
ServerName = _applicationHost.FriendlyName,
LocalAddress = _applicationHost.GetSmartApiUrl(request),
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index a3bbd6df0..10376ed76 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -52,7 +52,10 @@ namespace Emby.Server.Implementations.Udp
_endpoint = new IPEndPoint(bindAddress, port);
- _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
+ {
+ MulticastLoopback = false,
+ };
_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}
@@ -74,6 +77,7 @@ namespace Emby.Server.Implementations.Udp
try
{
+ _logger.LogDebug("Sending AutoDiscovery response");
await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint, cancellationToken).ConfigureAwait(false);
}
catch (SocketException ex)
@@ -99,7 +103,8 @@ namespace Emby.Server.Implementations.Udp
{
try
{
- var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint, cancellationToken).ConfigureAwait(false);
+ var endpoint = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
+ var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, endpoint, cancellationToken).ConfigureAwait(false);
var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes);
if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
{
@@ -112,7 +117,7 @@ namespace Emby.Server.Implementations.Udp
}
catch (OperationCanceledException)
{
- // Don't throw
+ _logger.LogDebug("Broadcast socket operation cancelled");
}
}
}
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 77d385ba1..c717744b1 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -520,10 +520,9 @@ namespace Emby.Server.Implementations.Updates
// CA5351: Do Not Use Broken Cryptographic Algorithms
#pragma warning disable CA5351
- using var md5 = MD5.Create();
cancellationToken.ThrowIfCancellationRequested();
- var hash = Convert.ToHexString(md5.ComputeHash(stream));
+ var hash = Convert.ToHexString(await MD5.HashDataAsync(stream, cancellationToken).ConfigureAwait(false));
if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase))
{
_logger.LogError(
@@ -556,7 +555,7 @@ namespace Emby.Server.Implementations.Updates
reader.ExtractToDirectory(targetDir, true);
// Ensure we create one or populate existing ones with missing data.
- await _pluginManager.PopulateManifest(package.PackageInfo, package.Version, targetDir, status);
+ await _pluginManager.PopulateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false);
_pluginManager.ImportPluginFrom(targetDir);
}
diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs
index 95b296fae..42576934b 100644
--- a/Jellyfin.Api/Controllers/DlnaServerController.cs
+++ b/Jellyfin.Api/Controllers/DlnaServerController.cs
@@ -5,7 +5,6 @@ using System.IO;
using System.Net.Mime;
using System.Threading.Tasks;
using Emby.Dlna;
-using Emby.Dlna.Main;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Dlna;
@@ -33,12 +32,19 @@ public class DlnaServerController : BaseJellyfinApiController
/// Initializes a new instance of the <see cref="DlnaServerController"/> class.
/// </summary>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
- public DlnaServerController(IDlnaManager dlnaManager)
+ /// <param name="contentDirectory">Instance of the <see cref="IContentDirectory"/> interface.</param>
+ /// <param name="connectionManager">Instance of the <see cref="IConnectionManager"/> interface.</param>
+ /// <param name="mediaReceiverRegistrar">Instance of the <see cref="IMediaReceiverRegistrar"/> interface.</param>
+ public DlnaServerController(
+ IDlnaManager dlnaManager,
+ IContentDirectory contentDirectory,
+ IConnectionManager connectionManager,
+ IMediaReceiverRegistrar mediaReceiverRegistrar)
{
_dlnaManager = dlnaManager;
- _contentDirectory = DlnaEntryPoint.Current.ContentDirectory;
- _connectionManager = DlnaEntryPoint.Current.ConnectionManager;
- _mediaReceiverRegistrar = DlnaEntryPoint.Current.MediaReceiverRegistrar;
+ _contentDirectory = contentDirectory;
+ _connectionManager = connectionManager;
+ _mediaReceiverRegistrar = mediaReceiverRegistrar;
}
/// <summary>
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index a653c5795..11f6bcf6b 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -421,13 +421,12 @@ public static class StreamingHelpers
/// <param name="state">The state.</param>
/// <param name="mediaSource">The mediaSource.</param>
/// <returns>System.String.</returns>
- private static string? GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource)
+ private static string GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource)
{
- var ext = Path.GetExtension(state.RequestedUrl.AsSpan());
-
- if (ext.IsEmpty)
+ var ext = Path.GetExtension(state.RequestedUrl);
+ if (!string.IsNullOrEmpty(ext))
{
- return null;
+ return ext;
}
// Try to infer based on the desired video codec
@@ -463,10 +462,9 @@ public static class StreamingHelpers
return ".asf";
}
}
-
- // Try to infer based on the desired audio codec
- if (!state.IsVideoRequest)
+ else
{
+ // Try to infer based on the desired audio codec
var audioCodec = state.Request.AudioCodec;
if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
@@ -497,7 +495,7 @@ public static class StreamingHelpers
return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim();
}
- return null;
+ throw new InvalidOperationException("Failed to find an appropriate file extension");
}
/// <summary>
@@ -509,12 +507,12 @@ public static class StreamingHelpers
/// <param name="deviceId">The device id.</param>
/// <param name="playSessionId">The play session id.</param>
/// <returns>The complete file path, including the folder, for the transcoding file.</returns>
- private static string GetOutputFilePath(StreamState state, string? outputFileExtension, IServerConfigurationManager serverConfigurationManager, string? deviceId, string? playSessionId)
+ private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager serverConfigurationManager, string? deviceId, string? playSessionId)
{
var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}";
var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
- var ext = outputFileExtension?.ToLowerInvariant();
+ var ext = outputFileExtension.ToLowerInvariant();
var folder = serverConfigurationManager.GetTranscodePath();
return Path.Combine(folder, filename + ext);
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 6a0a4706b..7ac231885 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -8,8 +8,6 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
- <!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
- <NoWarn>AD0001</NoWarn>
</PropertyGroup>
<ItemGroup>
diff --git a/Jellyfin.Networking/Extensions/NetworkExtensions.cs b/Jellyfin.Networking/Extensions/NetworkExtensions.cs
index e45fa3bcb..2d921a482 100644
--- a/Jellyfin.Networking/Extensions/NetworkExtensions.cs
+++ b/Jellyfin.Networking/Extensions/NetworkExtensions.cs
@@ -231,12 +231,12 @@ public static partial class NetworkExtensions
}
else if (address.AddressFamily == AddressFamily.InterNetwork)
{
- result = new IPNetwork(address, Network.MinimumIPv4PrefixSize);
+ result = address.Equals(IPAddress.Any) ? Network.IPv4Any : new IPNetwork(address, Network.MinimumIPv4PrefixSize);
return true;
}
else if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
- result = new IPNetwork(address, Network.MinimumIPv6PrefixSize);
+ result = address.Equals(IPAddress.IPv6Any) ? Network.IPv6Any : new IPNetwork(address, Network.MinimumIPv6PrefixSize);
return true;
}
}
@@ -284,12 +284,15 @@ public static partial class NetworkExtensions
if (hosts.Count <= 2)
{
+ var firstPart = hosts[0];
+
// Is hostname or hostname:port
- if (FqdnGeneratedRegex().IsMatch(hosts[0]))
+ if (FqdnGeneratedRegex().IsMatch(firstPart))
{
try
{
- addresses = Dns.GetHostAddresses(hosts[0]);
+ // .NET automatically filters only supported returned addresses based on OS support.
+ addresses = Dns.GetHostAddresses(firstPart);
return true;
}
catch (SocketException)
@@ -299,7 +302,7 @@ public static partial class NetworkExtensions
}
// Is an IPv4 or IPv4:port
- if (IPAddress.TryParse(hosts[0].AsSpan().LeftPart('/'), out var address))
+ if (IPAddress.TryParse(firstPart.AsSpan().LeftPart('/'), out var address))
{
if (((address.AddressFamily == AddressFamily.InterNetwork) && (!isIPv4Enabled && isIPv6Enabled))
|| ((address.AddressFamily == AddressFamily.InterNetworkV6) && (isIPv4Enabled && !isIPv6Enabled)))
diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs
index f20e28526..9c59500d7 100644
--- a/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -15,7 +15,9 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Jellyfin.Networking.Manager
{
@@ -33,12 +35,14 @@ namespace Jellyfin.Networking.Manager
private readonly IConfigurationManager _configurationManager;
+ private readonly IConfiguration _startupConfig;
+
private readonly object _networkEventLock;
/// <summary>
/// Holds the published server URLs and the IPs to use them on.
/// </summary>
- private IReadOnlyDictionary<IPData, string> _publishedServerUrls;
+ private IReadOnlyList<PublishedServerUriOverride> _publishedServerUrls;
private IReadOnlyList<IPNetwork> _remoteAddressFilter;
@@ -76,20 +80,22 @@ namespace Jellyfin.Networking.Manager
/// <summary>
/// Initializes a new instance of the <see cref="NetworkManager"/> class.
/// </summary>
- /// <param name="configurationManager">IServerConfigurationManager instance.</param>
+ /// <param name="configurationManager">The <see cref="IConfigurationManager"/> instance.</param>
+ /// <param name="startupConfig">The <see cref="IConfiguration"/> instance holding startup parameters.</param>
/// <param name="logger">Logger to use for messages.</param>
#pragma warning disable CS8618 // Non-nullable field is uninitialized. : Values are set in UpdateSettings function. Compiler doesn't yet recognise this.
- public NetworkManager(IConfigurationManager configurationManager, ILogger<NetworkManager> logger)
+ public NetworkManager(IConfigurationManager configurationManager, IConfiguration startupConfig, ILogger<NetworkManager> logger)
{
ArgumentNullException.ThrowIfNull(logger);
ArgumentNullException.ThrowIfNull(configurationManager);
_logger = logger;
_configurationManager = configurationManager;
+ _startupConfig = startupConfig;
_initLock = new();
_interfaces = new List<IPData>();
_macAddresses = new List<PhysicalAddress>();
- _publishedServerUrls = new Dictionary<IPData, string>();
+ _publishedServerUrls = new List<PublishedServerUriOverride>();
_networkEventLock = new object();
_remoteAddressFilter = new List<IPNetwork>();
@@ -130,7 +136,7 @@ namespace Jellyfin.Networking.Manager
/// <summary>
/// Gets the Published server override list.
/// </summary>
- public IReadOnlyDictionary<IPData, string> PublishedServerUrls => _publishedServerUrls;
+ public IReadOnlyList<PublishedServerUriOverride> PublishedServerUrls => _publishedServerUrls;
/// <inheritdoc/>
public void Dispose()
@@ -170,7 +176,6 @@ namespace Jellyfin.Networking.Manager
{
if (!_eventfire)
{
- _logger.LogDebug("Network Address Change Event.");
// As network events tend to fire one after the other only fire once every second.
_eventfire = true;
OnNetworkChange();
@@ -193,11 +198,12 @@ namespace Jellyfin.Networking.Manager
}
else
{
- InitialiseInterfaces();
- InitialiseLan(networkConfig);
+ InitializeInterfaces();
+ InitializeLan(networkConfig);
EnforceBindSettings(networkConfig);
}
+ PrintNetworkInformation(networkConfig);
NetworkChanged?.Invoke(this, EventArgs.Empty);
}
finally
@@ -210,7 +216,7 @@ namespace Jellyfin.Networking.Manager
/// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
/// Generate a list of all active mac addresses that aren't loopback addresses.
/// </summary>
- private void InitialiseInterfaces()
+ private void InitializeInterfaces()
{
lock (_initLock)
{
@@ -222,7 +228,7 @@ namespace Jellyfin.Networking.Manager
try
{
var nics = NetworkInterface.GetAllNetworkInterfaces()
- .Where(i => i.SupportsMulticast && i.OperationalStatus == OperationalStatus.Up);
+ .Where(i => i.OperationalStatus == OperationalStatus.Up);
foreach (NetworkInterface adapter in nics)
{
@@ -242,34 +248,36 @@ namespace Jellyfin.Networking.Manager
{
if (IsIPv4Enabled && info.Address.AddressFamily == AddressFamily.InterNetwork)
{
- var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name);
- interfaceObject.Index = ipProperties.GetIPv4Properties().Index;
- interfaceObject.Name = adapter.Name;
+ var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name)
+ {
+ Index = ipProperties.GetIPv4Properties().Index,
+ Name = adapter.Name,
+ SupportsMulticast = adapter.SupportsMulticast
+ };
interfaces.Add(interfaceObject);
}
else if (IsIPv6Enabled && info.Address.AddressFamily == AddressFamily.InterNetworkV6)
{
- var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name);
- interfaceObject.Index = ipProperties.GetIPv6Properties().Index;
- interfaceObject.Name = adapter.Name;
+ var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name)
+ {
+ Index = ipProperties.GetIPv6Properties().Index,
+ Name = adapter.Name,
+ SupportsMulticast = adapter.SupportsMulticast
+ };
interfaces.Add(interfaceObject);
}
}
}
-#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
-#pragma warning restore CA1031 // Do not catch general exception types
{
// Ignore error, and attempt to continue.
_logger.LogError(ex, "Error encountered parsing interfaces.");
}
}
}
-#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
-#pragma warning restore CA1031 // Do not catch general exception types
{
_logger.LogError(ex, "Error obtaining interfaces.");
}
@@ -279,14 +287,14 @@ namespace Jellyfin.Networking.Manager
{
_logger.LogWarning("No interface information available. Using loopback interface(s).");
- if (IsIPv4Enabled && !IsIPv6Enabled)
+ if (IsIPv4Enabled)
{
- interfaces.Add(new IPData(IPAddress.Loopback, new IPNetwork(IPAddress.Loopback, 8), "lo"));
+ interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
}
- if (!IsIPv4Enabled && IsIPv6Enabled)
+ if (IsIPv6Enabled)
{
- interfaces.Add(new IPData(IPAddress.IPv6Loopback, new IPNetwork(IPAddress.IPv6Loopback, 128), "lo"));
+ interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
}
}
@@ -299,9 +307,9 @@ namespace Jellyfin.Networking.Manager
}
/// <summary>
- /// Initialises internal LAN cache.
+ /// Initializes internal LAN cache.
/// </summary>
- private void InitialiseLan(NetworkConfiguration config)
+ private void InitializeLan(NetworkConfiguration config)
{
lock (_initLock)
{
@@ -341,10 +349,6 @@ namespace Jellyfin.Networking.Manager
_excludedSubnets = NetworkExtensions.TryParseToSubnets(subnets, out var excludedSubnets, true)
? excludedSubnets
: new List<IPNetwork>();
-
- _logger.LogInformation("Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
- _logger.LogInformation("Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
- _logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
}
}
@@ -369,12 +373,12 @@ namespace Jellyfin.Networking.Manager
.ToHashSet();
interfaces = interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
- if (bindAddresses.Contains(IPAddress.Loopback))
+ if (bindAddresses.Contains(IPAddress.Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.Loopback)))
{
interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
}
- if (bindAddresses.Contains(IPAddress.IPv6Loopback))
+ if (bindAddresses.Contains(IPAddress.IPv6Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.IPv6Loopback)))
{
interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
}
@@ -409,15 +413,14 @@ namespace Jellyfin.Networking.Manager
interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
}
- _logger.LogInformation("Using bind addresses: {0}", interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
_interfaces = interfaces;
}
}
/// <summary>
- /// Initialises the remote address values.
+ /// Initializes the remote address values.
/// </summary>
- private void InitialiseRemote(NetworkConfiguration config)
+ private void InitializeRemote(NetworkConfiguration config)
{
lock (_initLock)
{
@@ -455,13 +458,33 @@ namespace Jellyfin.Networking.Manager
/// format is subnet=ipaddress|host|uri
/// when subnet = 0.0.0.0, any external address matches.
/// </summary>
- private void InitialiseOverrides(NetworkConfiguration config)
+ private void InitializeOverrides(NetworkConfiguration config)
{
lock (_initLock)
{
- var publishedServerUrls = new Dictionary<IPData, string>();
- var overrides = config.PublishedServerUriBySubnet;
+ var publishedServerUrls = new List<PublishedServerUriOverride>();
+
+ // Prefer startup configuration.
+ var startupOverrideKey = _startupConfig[AddressOverrideKey];
+ if (!string.IsNullOrEmpty(startupOverrideKey))
+ {
+ publishedServerUrls.Add(
+ new PublishedServerUriOverride(
+ new IPData(IPAddress.Any, Network.IPv4Any),
+ startupOverrideKey,
+ true,
+ true));
+ publishedServerUrls.Add(
+ new PublishedServerUriOverride(
+ new IPData(IPAddress.IPv6Any, Network.IPv6Any),
+ startupOverrideKey,
+ true,
+ true));
+ _publishedServerUrls = publishedServerUrls;
+ return;
+ }
+ var overrides = config.PublishedServerUriBySubnet;
foreach (var entry in overrides)
{
var parts = entry.Split('=');
@@ -475,31 +498,70 @@ namespace Jellyfin.Networking.Manager
var identifier = parts[0];
if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
{
- publishedServerUrls[new IPData(IPAddress.Broadcast, null)] = replacement;
+ // Drop any other overrides in case an "all" override exists
+ publishedServerUrls.Clear();
+ publishedServerUrls.Add(
+ new PublishedServerUriOverride(
+ new IPData(IPAddress.Any, Network.IPv4Any),
+ replacement,
+ true,
+ true));
+ publishedServerUrls.Add(
+ new PublishedServerUriOverride(
+ new IPData(IPAddress.IPv6Any, Network.IPv6Any),
+ replacement,
+ true,
+ true));
+ break;
}
else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
{
- publishedServerUrls[new IPData(IPAddress.Any, Network.IPv4Any)] = replacement;
- publishedServerUrls[new IPData(IPAddress.IPv6Any, Network.IPv6Any)] = replacement;
+ publishedServerUrls.Add(
+ new PublishedServerUriOverride(
+ new IPData(IPAddress.Any, Network.IPv4Any),
+ replacement,
+ false,
+ true));
+ publishedServerUrls.Add(
+ new PublishedServerUriOverride(
+ new IPData(IPAddress.IPv6Any, Network.IPv6Any),
+ replacement,
+ false,
+ true));
}
else if (string.Equals(identifier, "internal", StringComparison.OrdinalIgnoreCase))
{
foreach (var lan in _lanSubnets)
{
var lanPrefix = lan.Prefix;
- publishedServerUrls[new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength))] = replacement;
+ publishedServerUrls.Add(
+ new PublishedServerUriOverride(
+ new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)),
+ replacement,
+ true,
+ false));
}
}
else if (NetworkExtensions.TryParseToSubnet(identifier, out var result) && result is not null)
{
var data = new IPData(result.Prefix, result);
- publishedServerUrls[data] = replacement;
+ publishedServerUrls.Add(
+ new PublishedServerUriOverride(
+ data,
+ replacement,
+ true,
+ true));
}
else if (TryParseInterface(identifier, out var ifaces))
{
foreach (var iface in ifaces)
{
- publishedServerUrls[iface] = replacement;
+ publishedServerUrls.Add(
+ new PublishedServerUriOverride(
+ iface,
+ replacement,
+ true,
+ true));
}
}
else
@@ -521,7 +583,7 @@ namespace Jellyfin.Networking.Manager
}
/// <summary>
- /// Reloads all settings and re-initialises the instance.
+ /// Reloads all settings and re-Initializes the instance.
/// </summary>
/// <param name="configuration">The <see cref="NetworkConfiguration"/> to use.</param>
public void UpdateSettings(object configuration)
@@ -531,12 +593,12 @@ namespace Jellyfin.Networking.Manager
var config = (NetworkConfiguration)configuration;
HappyEyeballs.HttpClientExtension.UseIPv6 = config.EnableIPv6;
- InitialiseLan(config);
- InitialiseRemote(config);
+ InitializeLan(config);
+ InitializeRemote(config);
if (string.IsNullOrEmpty(MockNetworkSettings))
{
- InitialiseInterfaces();
+ InitializeInterfaces();
}
else // Used in testing only.
{
@@ -552,8 +614,10 @@ namespace Jellyfin.Networking.Manager
var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
{
- var data = new IPData(address, subnet, parts[2]);
- data.Index = index;
+ var data = new IPData(address, subnet, parts[2])
+ {
+ Index = index
+ };
interfaces.Add(data);
}
}
@@ -567,7 +631,9 @@ namespace Jellyfin.Networking.Manager
}
EnforceBindSettings(config);
- InitialiseOverrides(config);
+ InitializeOverrides(config);
+
+ PrintNetworkInformation(config, false);
}
/// <summary>
@@ -672,20 +738,13 @@ namespace Jellyfin.Networking.Manager
/// <inheritdoc/>
public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
{
- if (_interfaces.Count != 0)
+ if (_interfaces.Count > 0 || individualInterfaces)
{
return _interfaces;
}
// No bind address and no exclusions, so listen on all interfaces.
var result = new List<IPData>();
-
- if (individualInterfaces)
- {
- result.AddRange(_interfaces);
- return result;
- }
-
if (IsIPv4Enabled && IsIPv6Enabled)
{
// Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
@@ -892,31 +951,34 @@ namespace Jellyfin.Networking.Manager
bindPreference = string.Empty;
int? port = null;
- var validPublishedServerUrls = _publishedServerUrls.Where(x => x.Key.Address.Equals(IPAddress.Any)
- || x.Key.Address.Equals(IPAddress.IPv6Any)
- || x.Key.Subnet.Contains(source))
- .DistinctBy(x => x.Key)
- .OrderBy(x => x.Key.Address.Equals(IPAddress.Any)
- || x.Key.Address.Equals(IPAddress.IPv6Any))
+ // Only consider subnets including the source IP, prefering specific overrides
+ List<PublishedServerUriOverride> validPublishedServerUrls;
+ if (!isInExternalSubnet)
+ {
+ // Only use matching internal subnets
+ // Prefer more specific (bigger subnet prefix) overrides
+ validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsInternalOverride && x.Data.Subnet.Contains(source))
+ .OrderByDescending(x => x.Data.Subnet.PrefixLength)
+ .ToList();
+ }
+ else
+ {
+ // Only use matching external subnets
+ // Prefer more specific (bigger subnet prefix) overrides
+ validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsExternalOverride && x.Data.Subnet.Contains(source))
+ .OrderByDescending(x => x.Data.Subnet.PrefixLength)
.ToList();
+ }
- // Check for user override.
foreach (var data in validPublishedServerUrls)
{
- if (isInExternalSubnet && (data.Key.Address.Equals(IPAddress.Any) || data.Key.Address.Equals(IPAddress.IPv6Any)))
- {
- // External.
- bindPreference = data.Value;
- break;
- }
-
- // Get address interface.
- var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Key.Subnet.Contains(x.Address));
+ // Get interface matching override subnet
+ var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Data.Subnet.Contains(x.Address));
if (intf?.Address is not null)
{
- // Match IP address.
- bindPreference = data.Value;
+ // If matching interface is found, use override
+ bindPreference = data.OverrideUri;
break;
}
}
@@ -927,7 +989,7 @@ namespace Jellyfin.Networking.Manager
return false;
}
- // Has it got a port defined?
+ // Handle override specifying port
var parts = bindPreference.Split(':');
if (parts.Length > 1)
{
@@ -935,18 +997,12 @@ namespace Jellyfin.Networking.Manager
{
bindPreference = parts[0];
port = p;
+ _logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
+ return true;
}
}
- if (port is not null)
- {
- _logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
- }
- else
- {
- _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
- }
-
+ _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
return true;
}
@@ -1053,5 +1109,19 @@ namespace Jellyfin.Networking.Manager
_logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
return true;
}
+
+ private void PrintNetworkInformation(NetworkConfiguration config, bool debug = true)
+ {
+ var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
+ if (_logger.IsEnabled(logLevel))
+ {
+ _logger.Log(logLevel, "Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
+ _logger.Log(logLevel, "Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
+ _logger.Log(logLevel, "Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
+ _logger.Log(logLevel, "Using bind addresses: {0}", _interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
+ _logger.Log(logLevel, "Remote IP filter is {0}", config.IsRemoteIPFilterBlacklist ? "Blocklist" : "Allowlist");
+ _logger.Log(logLevel, "Filter list: {0}", _remoteAddressFilter.Select(s => s.Prefix + "/" + s.PrefixLength));
+ }
+ }
}
}
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 89dbbdd2f..cb1680558 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -282,7 +282,7 @@ namespace Jellyfin.Server.Extensions
AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength);
}
}
- else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses))
+ else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))
{
foreach (var address in addresses)
{
diff --git a/Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs b/Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs
index 3cb791b57..c9d5b54de 100644
--- a/Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs
+++ b/Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs
@@ -3,7 +3,6 @@ using System.IO;
using System.Net;
using Jellyfin.Server.Helpers;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Extensions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
@@ -36,7 +35,7 @@ public static class WebHostBuilderExtensions
return builder
.UseKestrel((builderContext, options) =>
{
- var addresses = appHost.NetManager.GetAllBindInterfaces();
+ var addresses = appHost.NetManager.GetAllBindInterfaces(true);
bool flagged = false;
foreach (var netAdd in addresses)
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index b759b6bca..2acddb243 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -1,10 +1,10 @@
using System;
-using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
+using Emby.Dlna.Extensions;
using Jellyfin.Api.Middleware;
using Jellyfin.MediaEncoding.Hls.Extensions;
using Jellyfin.Networking.Configuration;
@@ -27,7 +27,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
-using Microsoft.VisualBasic;
using Prometheus;
namespace Jellyfin.Server
@@ -120,26 +119,11 @@ namespace Jellyfin.Server
})
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
- services.AddHttpClient(NamedClient.Dlna, c =>
- {
- c.DefaultRequestHeaders.UserAgent.ParseAdd(
- string.Format(
- CultureInfo.InvariantCulture,
- "{0}/{1} UPnP/1.0 {2}/{3}",
- Environment.OSVersion.Platform,
- Environment.OSVersion,
- _serverApplicationHost.Name,
- _serverApplicationHost.ApplicationVersionString));
-
- c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", _serverApplicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0
- c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", _serverApplicationHost.FriendlyName); // REVIEW: where does this come from?
- })
- .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
-
services.AddHealthChecks()
.AddCheck<DbContextFactoryHealthCheck<JellyfinDbContext>>(nameof(JellyfinDbContext));
services.AddHlsPlaylistGenerator();
+ services.AddDlnaServices(_serverApplicationHost);
}
/// <summary>
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 3f1a098e4..7015d991f 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -38,10 +38,6 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
- </PropertyGroup>
-
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
diff --git a/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
index dea1c2f32..2a7e6be0f 100644
--- a/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
+++ b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
@@ -23,9 +23,12 @@ namespace MediaBrowser.Controller.ClientEvent
{
var fileName = $"upload_{clientName}_{clientVersion}_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}.log";
var logFilePath = Path.Combine(_applicationPaths.LogDirectoryPath, fileName);
- await using var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
- await fileContents.CopyToAsync(fileStream).ConfigureAwait(false);
- return fileName;
+ var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
+ await using (fileStream.ConfigureAwait(false))
+ {
+ await fileContents.CopyToAsync(fileStream).ConfigureAwait(false);
+ return fileName;
+ }
}
}
}
diff --git a/MediaBrowser.Model/Net/IPData.cs b/MediaBrowser.Model/Net/IPData.cs
index 985b16c6e..e9fcd6797 100644
--- a/MediaBrowser.Model/Net/IPData.cs
+++ b/MediaBrowser.Model/Net/IPData.cs
@@ -48,6 +48,11 @@ public class IPData
public int Index { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether the network supports multicast.
+ /// </summary>
+ public bool SupportsMulticast { get; set; } = false;
+
+ /// <summary>
/// Gets or sets the interface name.
/// </summary>
public string Name { get; set; }
diff --git a/MediaBrowser.Model/Net/PublishedServerUriOverride.cs b/MediaBrowser.Model/Net/PublishedServerUriOverride.cs
new file mode 100644
index 000000000..476d1ba38
--- /dev/null
+++ b/MediaBrowser.Model/Net/PublishedServerUriOverride.cs
@@ -0,0 +1,42 @@
+namespace MediaBrowser.Model.Net;
+
+/// <summary>
+/// Class holding information for a published server URI override.
+/// </summary>
+public class PublishedServerUriOverride
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PublishedServerUriOverride"/> class.
+ /// </summary>
+ /// <param name="data">The <see cref="IPData"/>.</param>
+ /// <param name="overrideUri">The override.</param>
+ /// <param name="internalOverride">A value indicating whether the override is for internal requests.</param>
+ /// <param name="externalOverride">A value indicating whether the override is for external requests.</param>
+ public PublishedServerUriOverride(IPData data, string overrideUri, bool internalOverride, bool externalOverride)
+ {
+ Data = data;
+ OverrideUri = overrideUri;
+ IsInternalOverride = internalOverride;
+ IsExternalOverride = externalOverride;
+ }
+
+ /// <summary>
+ /// Gets or sets the object's IP address.
+ /// </summary>
+ public IPData Data { get; set; }
+
+ /// <summary>
+ /// Gets or sets the override URI.
+ /// </summary>
+ public string OverrideUri { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the override should be applied to internal requests.
+ /// </summary>
+ public bool IsInternalOverride { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the override should be applied to external requests.
+ /// </summary>
+ public bool IsExternalOverride { get; set; }
+}
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index d9544b40f..aa7c03ebd 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -85,7 +85,8 @@ namespace MediaBrowser.Model.System
[Obsolete("This is always true")]
public bool CanSelfRestart { get; set; } = true;
- public bool CanLaunchWebBrowser { get; set; }
+ [Obsolete("This is always false")]
+ public bool CanLaunchWebBrowser { get; set; } = false;
/// <summary>
/// Gets or sets the program data path.
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index f3211ba45..d0bb34d52 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -943,6 +943,12 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/>
public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
{
+ ArgumentNullException.ThrowIfNull(itemId);
+ if (itemId.Equals(default))
+ {
+ throw new ArgumentException("Guid can't be empty", nameof(itemId));
+ }
+
if (_disposed)
{
return;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
index 55e2474a5..daad9706c 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
@@ -176,17 +176,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
Directory.CreateDirectory(Path.GetDirectoryName(path));
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
- var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using (stream.ConfigureAwait(false))
+ var fileStreamOptions = AsyncFile.WriteOptions;
+ fileStreamOptions.Mode = FileMode.Create;
+ var fs = new FileStream(path, fileStreamOptions);
+ await using (fs.ConfigureAwait(false))
{
- var fileStreamOptions = AsyncFile.WriteOptions;
- fileStreamOptions.Mode = FileMode.Create;
- fileStreamOptions.PreallocationSize = stream.Length;
- var xmlFileStream = new FileStream(path, fileStreamOptions);
- await using (xmlFileStream.ConfigureAwait(false))
- {
- await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
- }
+ await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
index f3385b3a9..92742b1aa 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
@@ -154,20 +154,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
- var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using (stream.ConfigureAwait(false))
+ var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+
+ var fileStreamOptions = AsyncFile.WriteOptions;
+ fileStreamOptions.Mode = FileMode.Create;
+ var xmlFileStream = new FileStream(path, fileStreamOptions);
+ await using (xmlFileStream.ConfigureAwait(false))
{
- var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- var fileStreamOptions = AsyncFile.WriteOptions;
- fileStreamOptions.Mode = FileMode.Create;
- fileStreamOptions.PreallocationSize = stream.Length;
- var xmlFileStream = new FileStream(path, fileStreamOptions);
- await using (xmlFileStream.ConfigureAwait(false))
- {
- await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
- }
+ await response.Content.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index e4bb4eaea..e84f1359b 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -8,6 +8,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading;
@@ -137,31 +138,27 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var url = OmdbProvider.GetOmdbUrl(urlQuery.ToString());
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
- var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using (stream.ConfigureAwait(false))
+ if (isSearch)
{
- if (isSearch)
+ var searchResultList = await response.Content.ReadFromJsonAsync<SearchResultList>(_jsonOptions, cancellationToken).ConfigureAwait(false);
+ if (searchResultList?.Search is not null)
{
- var searchResultList = await JsonSerializer.DeserializeAsync<SearchResultList>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- if (searchResultList?.Search is not null)
+ var resultCount = searchResultList.Search.Count;
+ var result = new RemoteSearchResult[resultCount];
+ for (var i = 0; i < resultCount; i++)
{
- var resultCount = searchResultList.Search.Count;
- var result = new RemoteSearchResult[resultCount];
- for (var i = 0; i < resultCount; i++)
- {
- result[i] = ResultToMetadataResult(searchResultList.Search[i], searchInfo, indexNumberEnd);
- }
-
- return result;
+ result[i] = ResultToMetadataResult(searchResultList.Search[i], searchInfo, indexNumberEnd);
}
+
+ return result;
}
- else
+ }
+ else
+ {
+ var result = await response.Content.ReadFromJsonAsync<SearchResult>(_jsonOptions, cancellationToken).ConfigureAwait(false);
+ if (string.Equals(result?.Response, "true", StringComparison.OrdinalIgnoreCase))
{
- var result = await JsonSerializer.DeserializeAsync<SearchResult>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- if (string.Equals(result?.Response, "true", StringComparison.OrdinalIgnoreCase))
- {
- return new[] { ResultToMetadataResult(result, searchInfo, indexNumberEnd) };
- }
+ return new[] { ResultToMetadataResult(result, searchInfo, indexNumberEnd) };
}
}
diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs
index 0dce6c3bf..a3f30c174 100644
--- a/RSSDP/SsdpCommunicationsServer.cs
+++ b/RSSDP/SsdpCommunicationsServer.cs
@@ -32,10 +32,10 @@ namespace Rssdp.Infrastructure
* port to use, we will default to 0 which allows the underlying system to auto-assign a free port.
*/
- private object _BroadcastListenSocketSynchroniser = new object();
+ private object _BroadcastListenSocketSynchroniser = new();
private List<Socket> _MulticastListenSockets;
- private object _SendSocketSynchroniser = new object();
+ private object _SendSocketSynchroniser = new();
private List<Socket> _sendSockets;
private HttpRequestParser _RequestParser;
@@ -48,7 +48,6 @@ namespace Rssdp.Infrastructure
private int _MulticastTtl;
private bool _IsShared;
- private readonly bool _enableMultiSocketBinding;
/// <summary>
/// Raised when a HTTPU request message is received by a socket (unicast or multicast).
@@ -64,9 +63,11 @@ namespace Rssdp.Infrastructure
/// Minimum constructor.
/// </summary>
/// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
- public SsdpCommunicationsServer(ISocketFactory socketFactory,
- INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
- : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding)
+ public SsdpCommunicationsServer(
+ ISocketFactory socketFactory,
+ INetworkManager networkManager,
+ ILogger logger)
+ : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger)
{
}
@@ -76,7 +77,12 @@ namespace Rssdp.Infrastructure
/// </summary>
/// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="multicastTimeToLive"/> argument is less than or equal to zero.</exception>
- public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
+ public SsdpCommunicationsServer(
+ ISocketFactory socketFactory,
+ int localPort,
+ int multicastTimeToLive,
+ INetworkManager networkManager,
+ ILogger logger)
{
if (socketFactory is null)
{
@@ -88,19 +94,18 @@ namespace Rssdp.Infrastructure
throw new ArgumentOutOfRangeException(nameof(multicastTimeToLive), "multicastTimeToLive must be greater than zero.");
}
- _BroadcastListenSocketSynchroniser = new object();
- _SendSocketSynchroniser = new object();
+ _BroadcastListenSocketSynchroniser = new();
+ _SendSocketSynchroniser = new();
_LocalPort = localPort;
_SocketFactory = socketFactory;
- _RequestParser = new HttpRequestParser();
- _ResponseParser = new HttpResponseParser();
+ _RequestParser = new();
+ _ResponseParser = new();
_MulticastTtl = multicastTimeToLive;
_networkManager = networkManager;
_logger = logger;
- _enableMultiSocketBinding = enableMultiSocketBinding;
}
/// <summary>
@@ -335,7 +340,7 @@ namespace Rssdp.Infrastructure
{
sockets = sockets.ToList();
- var tasks = sockets.Where(s => (fromlocalIPAddress is null || fromlocalIPAddress.Equals(((IPEndPoint)s.LocalEndPoint).Address)))
+ var tasks = sockets.Where(s => fromlocalIPAddress is null || fromlocalIPAddress.Equals(((IPEndPoint)s.LocalEndPoint).Address))
.Select(s => SendFromSocket(s, messageData, destination, cancellationToken));
return Task.WhenAll(tasks);
}
@@ -347,33 +352,26 @@ namespace Rssdp.Infrastructure
{
var sockets = new List<Socket>();
var multicastGroupAddress = IPAddress.Parse(SsdpConstants.MulticastLocalAdminAddress);
- if (_enableMultiSocketBinding)
- {
- // IPv6 is currently unsupported
- var validInterfaces = _networkManager.GetInternalBindAddresses()
- .Where(x => x.Address is not null)
- .Where(x => x.AddressFamily == AddressFamily.InterNetwork)
- .DistinctBy(x => x.Index);
- foreach (var intf in validInterfaces)
+ // IPv6 is currently unsupported
+ var validInterfaces = _networkManager.GetInternalBindAddresses()
+ .Where(x => x.Address is not null)
+ .Where(x => x.SupportsMulticast)
+ .Where(x => x.AddressFamily == AddressFamily.InterNetwork)
+ .DistinctBy(x => x.Index);
+
+ foreach (var intf in validInterfaces)
+ {
+ try
{
- try
- {
- var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, intf, _MulticastTtl, SsdpConstants.MulticastPort);
- _ = ListenToSocketInternal(socket);
- sockets.Add(socket);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in CreateMulticastSocketsAndListen. IP address: {0}", intf.Address);
- }
+ var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, intf, _MulticastTtl, SsdpConstants.MulticastPort);
+ _ = ListenToSocketInternal(socket);
+ sockets.Add(socket);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to create SSDP UDP multicast socket for {0} on interface {1} (index {2})", intf.Address, intf.Name, intf.Index);
}
- }
- else
- {
- var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, new IPData(IPAddress.Any, null), _MulticastTtl, SsdpConstants.MulticastPort);
- _ = ListenToSocketInternal(socket);
- sockets.Add(socket);
}
return sockets;
@@ -382,34 +380,32 @@ namespace Rssdp.Infrastructure
private List<Socket> CreateSendSockets()
{
var sockets = new List<Socket>();
- if (_enableMultiSocketBinding)
+
+ // IPv6 is currently unsupported
+ var validInterfaces = _networkManager.GetInternalBindAddresses()
+ .Where(x => x.Address is not null)
+ .Where(x => x.SupportsMulticast)
+ .Where(x => x.AddressFamily == AddressFamily.InterNetwork);
+
+ if (OperatingSystem.IsMacOS())
{
- // IPv6 is currently unsupported
- var validInterfaces = _networkManager.GetInternalBindAddresses()
- .Where(x => x.Address is not null)
- .Where(x => x.AddressFamily == AddressFamily.InterNetwork);
+ // Manually remove loopback on macOS due to https://github.com/dotnet/runtime/issues/24340
+ validInterfaces = validInterfaces.Where(x => !x.Address.Equals(IPAddress.Loopback));
+ }
- foreach (var intf in validInterfaces)
+ foreach (var intf in validInterfaces)
+ {
+ try
{
- try
- {
- var socket = _SocketFactory.CreateSsdpUdpSocket(intf, _LocalPort);
- _ = ListenToSocketInternal(socket);
- sockets.Add(socket);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in CreateSsdpUdpSocket. IPAddress: {0}", intf.Address);
- }
+ var socket = _SocketFactory.CreateSsdpUdpSocket(intf, _LocalPort);
+ _ = ListenToSocketInternal(socket);
+ sockets.Add(socket);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to create SSDP UDP sender socket for {0} on interface {1} (index {2})", intf.Address, intf.Name, intf.Index);
}
}
- else
- {
- var socket = _SocketFactory.CreateSsdpUdpSocket(new IPData(IPAddress.Any, null), _LocalPort);
- _ = ListenToSocketInternal(socket);
- sockets.Add(socket);
- }
-
return sockets;
}
@@ -423,7 +419,7 @@ namespace Rssdp.Infrastructure
{
try
{
- var result = await socket.ReceiveMessageFromAsync(receiveBuffer, SocketFlags.None, new IPEndPoint(IPAddress.Any, 0), CancellationToken.None).ConfigureAwait(false);;
+ var result = await socket.ReceiveMessageFromAsync(receiveBuffer, new IPEndPoint(IPAddress.Any, _LocalPort), CancellationToken.None).ConfigureAwait(false);;
if (result.ReceivedBytes > 0)
{
@@ -431,7 +427,7 @@ namespace Rssdp.Infrastructure
var localEndpointAdapter = _networkManager.GetAllBindInterfaces().First(a => a.Index == result.PacketInformation.Interface);
ProcessMessage(
- UTF8Encoding.UTF8.GetString(receiveBuffer, 0, result.ReceivedBytes),
+ Encoding.UTF8.GetString(receiveBuffer, 0, result.ReceivedBytes),
remoteEndpoint,
localEndpointAdapter.Address);
}
@@ -511,7 +507,7 @@ namespace Rssdp.Infrastructure
return;
}
- var handlers = this.RequestReceived;
+ var handlers = RequestReceived;
if (handlers is not null)
{
handlers(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnlocalIPAddress));
@@ -520,7 +516,7 @@ namespace Rssdp.Infrastructure
private void OnResponseReceived(HttpResponseMessage data, IPEndPoint endPoint, IPAddress localIPAddress)
{
- var handlers = this.ResponseReceived;
+ var handlers = ResponseReceived;
if (handlers is not null)
{
handlers(this, new ResponseReceivedEventArgs(data, endPoint)
diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs
index 59f4c5070..82b09c4b4 100644
--- a/RSSDP/SsdpDeviceLocator.cs
+++ b/RSSDP/SsdpDeviceLocator.cs
@@ -17,7 +17,7 @@ namespace Rssdp.Infrastructure
private ISsdpCommunicationsServer _CommunicationsServer;
private Timer _BroadcastTimer;
- private object _timerLock = new object();
+ private object _timerLock = new();
private string _OSName;
@@ -221,12 +221,12 @@ namespace Rssdp.Infrastructure
/// <seealso cref="DeviceAvailable"/>
protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IPAddress)
{
- if (this.IsDisposed)
+ if (IsDisposed)
{
return;
}
- var handlers = this.DeviceAvailable;
+ var handlers = DeviceAvailable;
if (handlers is not null)
{
handlers(this, new DeviceAvailableEventArgs(device, isNewDevice)
@@ -244,12 +244,12 @@ namespace Rssdp.Infrastructure
/// <seealso cref="DeviceUnavailable"/>
protected virtual void OnDeviceUnavailable(DiscoveredSsdpDevice device, bool expired)
{
- if (this.IsDisposed)
+ if (IsDisposed)
{
return;
}
- var handlers = this.DeviceUnavailable;
+ var handlers = DeviceUnavailable;
if (handlers is not null)
{
handlers(this, new DeviceUnavailableEventArgs(device, expired));
@@ -291,8 +291,8 @@ namespace Rssdp.Infrastructure
_CommunicationsServer = null;
if (commsServer is not null)
{
- commsServer.ResponseReceived -= this.CommsServer_ResponseReceived;
- commsServer.RequestReceived -= this.CommsServer_RequestReceived;
+ commsServer.ResponseReceived -= CommsServer_ResponseReceived;
+ commsServer.RequestReceived -= CommsServer_RequestReceived;
}
}
}
@@ -341,7 +341,7 @@ namespace Rssdp.Infrastructure
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- values["HOST"] = "239.255.255.250:1900";
+ values["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort);
values["USER-AGENT"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
values["MAN"] = "\"ssdp:discover\"";
@@ -382,17 +382,17 @@ namespace Rssdp.Infrastructure
private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress IPAddress)
{
- if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
+ if (string.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
{
return;
}
var notificationType = GetFirstHeaderStringValue("NTS", message);
- if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
+ if (string.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
{
ProcessAliveNotification(message, IPAddress);
}
- else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
+ else if (string.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
{
ProcessByeByeNotification(message);
}
@@ -420,7 +420,7 @@ namespace Rssdp.Infrastructure
private void ProcessByeByeNotification(HttpRequestMessage message)
{
var notficationType = GetFirstHeaderStringValue("NT", message);
- if (!String.IsNullOrEmpty(notficationType))
+ if (!string.IsNullOrEmpty(notficationType))
{
var usn = GetFirstHeaderStringValue("USN", message);
@@ -447,10 +447,9 @@ namespace Rssdp.Infrastructure
private string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message)
{
string retVal = null;
- IEnumerable<string> values;
if (message.Headers.Contains(headerName))
{
- message.Headers.TryGetValues(headerName, out values);
+ message.Headers.TryGetValues(headerName, out var values);
if (values is not null)
{
retVal = values.FirstOrDefault();
@@ -463,10 +462,9 @@ namespace Rssdp.Infrastructure
private string GetFirstHeaderStringValue(string headerName, HttpRequestMessage message)
{
string retVal = null;
- IEnumerable<string> values;
if (message.Headers.Contains(headerName))
{
- message.Headers.TryGetValues(headerName, out values);
+ message.Headers.TryGetValues(headerName, out var values);
if (values is not null)
{
retVal = values.FirstOrDefault();
@@ -479,10 +477,9 @@ namespace Rssdp.Infrastructure
private Uri GetFirstHeaderUriValue(string headerName, HttpRequestMessage request)
{
string value = null;
- IEnumerable<string> values;
if (request.Headers.Contains(headerName))
{
- request.Headers.TryGetValues(headerName, out values);
+ request.Headers.TryGetValues(headerName, out var values);
if (values is not null)
{
value = values.FirstOrDefault();
@@ -496,10 +493,9 @@ namespace Rssdp.Infrastructure
private Uri GetFirstHeaderUriValue(string headerName, HttpResponseMessage response)
{
string value = null;
- IEnumerable<string> values;
if (response.Headers.Contains(headerName))
{
- response.Headers.TryGetValues(headerName, out values);
+ response.Headers.TryGetValues(headerName, out var values);
if (values is not null)
{
value = values.FirstOrDefault();
@@ -529,7 +525,7 @@ namespace Rssdp.Infrastructure
foreach (var device in expiredDevices)
{
- if (this.IsDisposed)
+ if (IsDisposed)
{
return;
}
@@ -543,7 +539,7 @@ namespace Rssdp.Infrastructure
// problems.
foreach (var expiredUsn in (from expiredDevice in expiredDevices select expiredDevice.Usn).Distinct())
{
- if (this.IsDisposed)
+ if (IsDisposed)
{
return;
}
@@ -560,7 +556,7 @@ namespace Rssdp.Infrastructure
existingDevices = FindExistingDeviceNotifications(_Devices, deviceUsn);
foreach (var existingDevice in existingDevices)
{
- if (this.IsDisposed)
+ if (IsDisposed)
{
return true;
}
diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs
index 950e6fec8..65ae658a4 100644
--- a/RSSDP/SsdpDevicePublisher.cs
+++ b/RSSDP/SsdpDevicePublisher.cs
@@ -206,9 +206,9 @@ namespace Rssdp.Infrastructure
IPAddress receivedOnlocalIPAddress,
CancellationToken cancellationToken)
{
- if (String.IsNullOrEmpty(searchTarget))
+ if (string.IsNullOrEmpty(searchTarget))
{
- WriteTrace(String.Format(CultureInfo.InvariantCulture, "Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString()));
+ WriteTrace(string.Format(CultureInfo.InvariantCulture, "Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString()));
return;
}
@@ -232,7 +232,7 @@ namespace Rssdp.Infrastructure
// return;
}
- if (!Int32.TryParse(mx, out var maxWaitInterval) || maxWaitInterval <= 0)
+ if (!int.TryParse(mx, out var maxWaitInterval) || maxWaitInterval <= 0)
{
return;
}
@@ -243,27 +243,27 @@ namespace Rssdp.Infrastructure
}
// Do not block synchronously as that may tie up a threadpool thread for several seconds.
- Task.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) =>
+ Task.Delay(_Random.Next(16, maxWaitInterval * 1000)).ContinueWith((parentTask) =>
{
// Copying devices to local array here to avoid threading issues/enumerator exceptions.
IEnumerable<SsdpDevice> devices = null;
lock (_Devices)
{
- if (String.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)
+ if (string.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)
{
devices = GetAllDevicesAsFlatEnumerable().ToArray();
}
- else if (String.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (this.SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0))
+ else if (string.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0))
{
devices = _Devices.ToArray();
}
else if (searchTarget.Trim().StartsWith("uuid:", StringComparison.OrdinalIgnoreCase))
{
- devices = GetAllDevicesAsFlatEnumerable().Where(d => String.Compare(d.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0).ToArray();
+ devices = GetAllDevicesAsFlatEnumerable().Where(d => string.Compare(d.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0).ToArray();
}
else if (searchTarget.StartsWith("urn:", StringComparison.OrdinalIgnoreCase))
{
- devices = GetAllDevicesAsFlatEnumerable().Where(d => String.Compare(d.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0).ToArray();
+ devices = GetAllDevicesAsFlatEnumerable().Where(d => string.Compare(d.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0).ToArray();
}
}
@@ -299,7 +299,7 @@ namespace Rssdp.Infrastructure
if (isRootDevice)
{
SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIPAddress, cancellationToken);
- if (this.SupportPnpRootDevice)
+ if (SupportPnpRootDevice)
{
SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIPAddress, cancellationToken);
}
@@ -312,7 +312,7 @@ namespace Rssdp.Infrastructure
private string GetUsn(string udn, string fullDeviceType)
{
- return String.Format(CultureInfo.InvariantCulture, "{0}::{1}", udn, fullDeviceType);
+ return string.Format(CultureInfo.InvariantCulture, "{0}::{1}", udn, fullDeviceType);
}
private async void SendSearchResponse(
@@ -326,16 +326,17 @@ namespace Rssdp.Infrastructure
const string header = "HTTP/1.1 200 OK";
var rootDevice = device.ToRootDevice();
- var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- values["EXT"] = "";
- values["DATE"] = DateTime.UtcNow.ToString("r");
- values["HOST"] = "239.255.255.250:1900";
- values["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds;
- values["ST"] = searchTarget;
- values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
- values["USN"] = uniqueServiceName;
- values["LOCATION"] = rootDevice.Location.ToString();
+ var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+ {
+ ["EXT"] = "",
+ ["DATE"] = DateTime.UtcNow.ToString("r"),
+ ["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort),
+ ["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds,
+ ["ST"] = searchTarget,
+ ["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion),
+ ["USN"] = uniqueServiceName,
+ ["LOCATION"] = rootDevice.Location.ToString()
+ };
var message = BuildMessage(header, values);
@@ -439,7 +440,7 @@ namespace Rssdp.Infrastructure
if (isRoot)
{
SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken);
- if (this.SupportPnpRootDevice)
+ if (SupportPnpRootDevice)
{
SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), cancellationToken);
}
@@ -460,17 +461,18 @@ namespace Rssdp.Infrastructure
const string header = "NOTIFY * HTTP/1.1";
- var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- // If needed later for non-server devices, these headers will need to be dynamic
- values["HOST"] = "239.255.255.250:1900";
- values["DATE"] = DateTime.UtcNow.ToString("r");
- values["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds;
- values["LOCATION"] = rootDevice.Location.ToString();
- values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
- values["NTS"] = "ssdp:alive";
- values["NT"] = notificationType;
- values["USN"] = uniqueServiceName;
+ var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+ {
+ // If needed later for non-server devices, these headers will need to be dynamic
+ ["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort),
+ ["DATE"] = DateTime.UtcNow.ToString("r"),
+ ["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds,
+ ["LOCATION"] = rootDevice.Location.ToString(),
+ ["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion),
+ ["NTS"] = "ssdp:alive",
+ ["NT"] = notificationType,
+ ["USN"] = uniqueServiceName
+ };
var message = BuildMessage(header, values);
@@ -485,7 +487,7 @@ namespace Rssdp.Infrastructure
if (isRoot)
{
tasks.Add(SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken));
- if (this.SupportPnpRootDevice)
+ if (SupportPnpRootDevice)
{
tasks.Add(SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice"), cancellationToken));
}
@@ -506,20 +508,21 @@ namespace Rssdp.Infrastructure
{
const string header = "NOTIFY * HTTP/1.1";
- var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- // If needed later for non-server devices, these headers will need to be dynamic
- values["HOST"] = "239.255.255.250:1900";
- values["DATE"] = DateTime.UtcNow.ToString("r");
- values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
- values["NTS"] = "ssdp:byebye";
- values["NT"] = notificationType;
- values["USN"] = uniqueServiceName;
+ var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+ {
+ // If needed later for non-server devices, these headers will need to be dynamic
+ ["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort),
+ ["DATE"] = DateTime.UtcNow.ToString("r"),
+ ["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion),
+ ["NTS"] = "ssdp:byebye",
+ ["NT"] = notificationType,
+ ["USN"] = uniqueServiceName
+ };
var message = BuildMessage(header, values);
var sendCount = IsDisposed ? 1 : 3;
- WriteTrace(String.Format(CultureInfo.InvariantCulture, "Sent byebye notification"), device);
+ WriteTrace(string.Format(CultureInfo.InvariantCulture, "Sent byebye notification"), device);
return _CommsServer.SendMulticastMessage(message, sendCount, _sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken);
}
diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64
index 28cf585e9..6c2086bee 100644
--- a/deployment/Dockerfile.centos.amd64
+++ b/deployment/Dockerfile.centos.amd64
@@ -13,7 +13,7 @@ RUN yum update -yq \
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
# Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/61f29db0-10a5-4816-8fd8-ca2f71beaea3/e15fb7288eb5bc0053b91ea7b0bfd580/dotnet-sdk-7.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c4b5aad8-a416-436b-927c-3ebd5a9793ad/38efd1b64c8edc7c5f13699dd0be54e1/dotnet-sdk-7.0.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64
index 0e8166f46..16002f790 100644
--- a/deployment/Dockerfile.fedora.amd64
+++ b/deployment/Dockerfile.fedora.amd64
@@ -12,7 +12,7 @@ RUN dnf update -yq \
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make
# Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/61f29db0-10a5-4816-8fd8-ca2f71beaea3/e15fb7288eb5bc0053b91ea7b0bfd580/dotnet-sdk-7.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c4b5aad8-a416-436b-927c-3ebd5a9793ad/38efd1b64c8edc7c5f13699dd0be54e1/dotnet-sdk-7.0.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64
index 04c748d09..2befb4b55 100644
--- a/deployment/Dockerfile.ubuntu.amd64
+++ b/deployment/Dockerfile.ubuntu.amd64
@@ -17,7 +17,7 @@ RUN apt-get update -yqq \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/61f29db0-10a5-4816-8fd8-ca2f71beaea3/e15fb7288eb5bc0053b91ea7b0bfd580/dotnet-sdk-7.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c4b5aad8-a416-436b-927c-3ebd5a9793ad/38efd1b64c8edc7c5f13699dd0be54e1/dotnet-sdk-7.0.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64
index 5bc197679..497a7a3dd 100644
--- a/deployment/Dockerfile.ubuntu.arm64
+++ b/deployment/Dockerfile.ubuntu.arm64
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/61f29db0-10a5-4816-8fd8-ca2f71beaea3/e15fb7288eb5bc0053b91ea7b0bfd580/dotnet-sdk-7.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c4b5aad8-a416-436b-927c-3ebd5a9793ad/38efd1b64c8edc7c5f13699dd0be54e1/dotnet-sdk-7.0.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf
index fab869a6b..fae7b209f 100644
--- a/deployment/Dockerfile.ubuntu.armhf
+++ b/deployment/Dockerfile.ubuntu.armhf
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/61f29db0-10a5-4816-8fd8-ca2f71beaea3/e15fb7288eb5bc0053b91ea7b0bfd580/dotnet-sdk-7.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c4b5aad8-a416-436b-927c-3ebd5a9793ad/38efd1b64c8edc7c5f13699dd0be54e1/dotnet-sdk-7.0.402-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/tests/Jellyfin.Networking.Tests/NetworkExtensionsTests.cs b/tests/Jellyfin.Networking.Tests/NetworkExtensionsTests.cs
index 2ff7c7de4..072e0a8c5 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkExtensionsTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkExtensionsTests.cs
@@ -16,7 +16,6 @@ namespace Jellyfin.Networking.Tests
[InlineData("127.0.0.1:123")]
[InlineData("localhost")]
[InlineData("localhost:1345")]
- [InlineData("www.google.co.uk")]
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")]
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")]
diff --git a/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs
index 0b07a3c53..2302f90b8 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs
@@ -1,7 +1,9 @@
using System.Net;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Manager;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
using Xunit;
namespace Jellyfin.Networking.Tests
@@ -28,7 +30,8 @@ namespace Jellyfin.Networking.Tests
LocalNetworkSubnets = network.Split(',')
};
- using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger<NetworkManager>());
+ var startupConf = new Mock<IConfiguration>();
+ using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
Assert.True(networkManager.IsInLocalNetwork(ip));
}
@@ -56,9 +59,10 @@ namespace Jellyfin.Networking.Tests
LocalNetworkSubnets = network.Split(',')
};
- using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger<NetworkManager>());
+ var startupConf = new Mock<IConfiguration>();
+ using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
- Assert.False(nm.IsInLocalNetwork(ip));
+ Assert.False(networkManager.IsInLocalNetwork(ip));
}
}
}
diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
index 731cbbafb..022b8a3d0 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
@@ -7,6 +7,7 @@ using Jellyfin.Networking.Extensions;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Net;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
@@ -54,7 +55,8 @@ namespace Jellyfin.Networking.Tests
};
NetworkManager.MockNetworkSettings = interfaces;
- using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+ var startupConf = new Mock<IConfiguration>();
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
NetworkManager.MockNetworkSettings = string.Empty;
Assert.Equal(value, "[" + string.Join(",", nm.GetInternalBindAddresses().Select(x => x.Address + "/" + x.Subnet.PrefixLength)) + "]");
@@ -200,7 +202,8 @@ namespace Jellyfin.Networking.Tests
};
NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11";
- using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+ var startupConf = new Mock<IConfiguration>();
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
NetworkManager.MockNetworkSettings = string.Empty;
// Check to see if DNS resolution is working. If not, skip test.
@@ -229,24 +232,24 @@ namespace Jellyfin.Networking.Tests
[InlineData("192.168.1.1", "192.168.1.0/24", "eth16,eth11", false, "192.168.1.0/24=internal.jellyfin", "internal.jellyfin")]
// User on external network, we're bound internal and external - so result is override.
- [InlineData("8.8.8.8", "192.168.1.0/24", "eth16,eth11", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
+ [InlineData("8.8.8.8", "192.168.1.0/24", "eth16,eth11", false, "all=http://helloworld.com", "http://helloworld.com")]
// User on internal network, we're bound internal only, but the address isn't in the LAN - so return the override.
- [InlineData("10.10.10.10", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://internalButNotDefinedAsLan.com", "http://internalButNotDefinedAsLan.com")]
+ [InlineData("10.10.10.10", "192.168.1.0/24", "eth16", false, "external=http://internalButNotDefinedAsLan.com", "http://internalButNotDefinedAsLan.com")]
// User on internal network, no binding specified - so result is the 1st internal.
- [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
+ [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "external=http://helloworld.com", "eth16")]
// User on external network, internal binding only - so assumption is a proxy forward, return external override.
- [InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
+ [InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "external=http://helloworld.com", "http://helloworld.com")]
// User on external network, no binding - so result is the 1st external which is overriden.
- [InlineData("jellyfin.org", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
+ [InlineData("jellyfin.org", "192.168.1.0/24", "", false, "external=http://helloworld.com", "http://helloworld.com")]
- // User assumed to be internal, no binding - so result is the 1st internal.
- [InlineData("", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
+ // User assumed to be internal, no binding - so result is the 1st matching interface.
+ [InlineData("", "192.168.1.0/24", "", false, "all=http://helloworld.com", "eth16")]
- // User is internal, no binding - so result is the 1st internal, which is then overridden.
+ // User is internal, no binding - so result is the 1st internal interface, which is then overridden.
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "eth16=http://helloworld.com", "http://helloworld.com")]
public void TestBindInterfaceOverrides(string source, string lan, string bindAddresses, bool ipv6enabled, string publishedServers, string result)
{
@@ -264,7 +267,8 @@ namespace Jellyfin.Networking.Tests
};
NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11";
- using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+ var startupConf = new Mock<IConfiguration>();
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
NetworkManager.MockNetworkSettings = string.Empty;
if (nm.TryParseInterface(result, out IReadOnlyList<IPData>? resultObj) && resultObj is not null)
@@ -293,7 +297,9 @@ namespace Jellyfin.Networking.Tests
RemoteIPFilter = addresses.Split(','),
IsRemoteIPFilterBlacklist = false
};
- using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+ var startupConf = new Mock<IConfiguration>();
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIP)), denied);
}
@@ -314,7 +320,8 @@ namespace Jellyfin.Networking.Tests
IsRemoteIPFilterBlacklist = true
};
- using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+ var startupConf = new Mock<IConfiguration>();
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIP)), denied);
}
@@ -334,7 +341,8 @@ namespace Jellyfin.Networking.Tests
};
NetworkManager.MockNetworkSettings = interfaces;
- using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+ var startupConf = new Mock<IConfiguration>();
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
var interfaceToUse = nm.GetBindAddress(string.Empty, out _);
@@ -358,7 +366,8 @@ namespace Jellyfin.Networking.Tests
};
NetworkManager.MockNetworkSettings = interfaces;
- using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+ var startupConf = new Mock<IConfiguration>();
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
var interfaceToUse = nm.GetBindAddress(source, out _);
diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
index 5ddbd30d1..4e8aec9f1 100644
--- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
@@ -40,9 +40,7 @@ namespace Jellyfin.Server.Integration.Tests
using var authResponse = await client.SendAsync(httpRequest);
authResponse.EnsureSuccessStatusCode();
- var auth = await JsonSerializer.DeserializeAsync<AuthenticationResultDto>(
- await authResponse.Content.ReadAsStreamAsync(),
- jsonOptions);
+ var auth = await authResponse.Content.ReadFromJsonAsync<AuthenticationResultDto>(jsonOptions);
return auth!.AccessToken;
}
@@ -51,8 +49,7 @@ namespace Jellyfin.Server.Integration.Tests
{
using var response = await client.GetAsync("Users/Me");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var userDto = await JsonSerializer.DeserializeAsync<UserDto>(
- await response.Content.ReadAsStreamAsync(), JsonDefaults.Options);
+ var userDto = await response.Content.ReadFromJsonAsync<UserDto>(JsonDefaults.Options);
Assert.NotNull(userDto);
return userDto;
}
@@ -67,9 +64,7 @@ namespace Jellyfin.Server.Integration.Tests
var response = await client.GetAsync($"Users/{userId}/Items/Root");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var rootDto = await JsonSerializer.DeserializeAsync<BaseItemDto>(
- await response.Content.ReadAsStreamAsync(),
- JsonDefaults.Options);
+ var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto>(JsonDefaults.Options);
Assert.NotNull(rootDto);
return rootDto;
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
index 87136dfc8..8761cf69b 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
@@ -1,4 +1,5 @@
using System.Net;
+using System.Net.Http.Json;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
@@ -30,8 +31,7 @@ namespace Jellyfin.Server.Integration.Tests
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- var responseBody = await response.Content.ReadAsStreamAsync();
- _ = await JsonSerializer.DeserializeAsync<BrandingOptions>(responseBody);
+ await response.Content.ReadFromJsonAsync<BrandingOptions>();
}
[Theory]
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
index bd6e1b690..39d449e27 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
@@ -1,5 +1,6 @@
using System.IO;
using System.Net;
+using System.Net.Http.Json;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
@@ -64,8 +65,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var res = await response.Content.ReadAsStreamAsync();
- _ = await JsonSerializer.DeserializeAsync<ConfigurationPageInfo[]>(res, _jsonOpions);
+ _ = await response.Content.ReadFromJsonAsync<ConfigurationPageInfo[]>(_jsonOpions);
// TODO: check content
}
@@ -81,8 +81,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- var res = await response.Content.ReadAsStreamAsync();
- var data = await JsonSerializer.DeserializeAsync<ConfigurationPageInfo[]>(res, _jsonOpions);
+ var data = await response.Content.ReadFromJsonAsync<ConfigurationPageInfo[]>(_jsonOpions);
Assert.NotNull(data);
Assert.Empty(data);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
index 65e70caa0..e5d5e785c 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
@@ -93,9 +93,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- var profiles = await JsonSerializer.DeserializeAsync<DeviceProfileInfo[]>(
- await response.Content.ReadAsStreamAsync(),
- _jsonOptions);
+ var profiles = await response.Content.ReadFromJsonAsync<DeviceProfileInfo[]>(_jsonOptions);
var newProfile = profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsNew", StringComparison.Ordinal));
Assert.NotNull(newProfile);
@@ -124,9 +122,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- var profiles = await JsonSerializer.DeserializeAsync<DeviceProfileInfo[]>(
- await response.Content.ReadAsStreamAsync(),
- _jsonOptions);
+ var profiles = await response.Content.ReadFromJsonAsync<DeviceProfileInfo[]>(_jsonOptions);
Assert.Null(profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsNew", StringComparison.Ordinal)));
var newProfile = profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsUpdated", StringComparison.Ordinal));
@@ -150,9 +146,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- var profiles = await JsonSerializer.DeserializeAsync<DeviceProfileInfo[]>(
- await response.Content.ReadAsStreamAsync(),
- _jsonOptions);
+ var profiles = await response.Content.ReadFromJsonAsync<DeviceProfileInfo[]>(_jsonOptions);
Assert.Null(profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsUpdated", StringComparison.Ordinal)));
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
index a12e7ca0d..23de2489e 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Net;
+using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Extensions.Json;
@@ -56,9 +57,7 @@ public sealed class ItemsControllerTests : IClassFixture<JellyfinApplicationFact
var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var items = await JsonSerializer.DeserializeAsync<QueryResult<BaseItemDto>>(
- await response.Content.ReadAsStreamAsync(),
- _jsonOptions);
+ var items = await response.Content.ReadFromJsonAsync<QueryResult<BaseItemDto>>(_jsonOptions);
Assert.NotNull(items);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
index 2d3879bdb..36861294b 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
@@ -43,8 +43,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
- using var responseStream = await getResponse.Content.ReadAsStreamAsync();
- var newConfig = await JsonSerializer.DeserializeAsync<StartupConfigurationDto>(responseStream, _jsonOptions);
+ var newConfig = await getResponse.Content.ReadFromJsonAsync<StartupConfigurationDto>(_jsonOptions);
Assert.Equal(config.UICulture, newConfig!.UICulture);
Assert.Equal(config.MetadataCountryCode, newConfig.MetadataCountryCode);
Assert.Equal(config.PreferredMetadataLanguage, newConfig.PreferredMetadataLanguage);
@@ -60,8 +59,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
- using var contentStream = await response.Content.ReadAsStreamAsync();
- var user = await JsonSerializer.DeserializeAsync<StartupUserDto>(contentStream, _jsonOptions);
+ var user = await response.Content.ReadFromJsonAsync<StartupUserDto>(_jsonOptions);
Assert.NotNull(user);
Assert.NotNull(user.Name);
Assert.NotEmpty(user.Name);
@@ -87,8 +85,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
- var contentStream = await getResponse.Content.ReadAsStreamAsync();
- var newUser = await JsonSerializer.DeserializeAsync<StartupUserDto>(contentStream, _jsonOptions);
+ var newUser = await getResponse.Content.ReadFromJsonAsync<StartupUserDto>(_jsonOptions);
Assert.NotNull(newUser);
Assert.Equal(user.Name, newUser.Name);
Assert.NotNull(newUser.Password);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
index 79d03d539..4fcacd2ca 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
@@ -43,8 +43,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
using var response = await client.GetAsync("Users/Public");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
- await response.Content.ReadAsStreamAsync(), _jsonOpions);
+ var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOpions);
// User are hidden by default
Assert.NotNull(users);
Assert.Empty(users);
@@ -59,8 +58,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
using var response = await client.GetAsync("Users");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
- await response.Content.ReadAsStreamAsync(), _jsonOpions);
+ var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOpions);
Assert.NotNull(users);
Assert.Single(users);
Assert.False(users![0].HasConfiguredPassword);
@@ -92,8 +90,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
using var response = await CreateUserByName(client, createRequest);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var user = await JsonSerializer.DeserializeAsync<UserDto>(
- await response.Content.ReadAsStreamAsync(), _jsonOpions);
+ var user = await response.Content.ReadFromJsonAsync<UserDto>(_jsonOpions);
Assert.Equal(TestUsername, user!.Name);
Assert.False(user.HasPassword);
Assert.False(user.HasConfiguredPassword);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
index 826a0a69d..130281c6d 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Net;
+using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Extensions.Json;
@@ -85,9 +86,7 @@ public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicati
var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var rootDto = await JsonSerializer.DeserializeAsync<BaseItemDto>(
- await response.Content.ReadAsStreamAsync(),
- _jsonOptions);
+ var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto>(_jsonOptions);
Assert.NotNull(rootDto);
}
@@ -102,9 +101,7 @@ public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicati
var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}/Intros");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var rootDto = await JsonSerializer.DeserializeAsync<QueryResult<BaseItemDto>>(
- await response.Content.ReadAsStreamAsync(),
- _jsonOptions);
+ var rootDto = await response.Content.ReadFromJsonAsync<QueryResult<BaseItemDto>>(_jsonOptions);
Assert.NotNull(rootDto);
}
@@ -121,9 +118,7 @@ public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicati
var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id, rootFolderDto.Id));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var rootDto = await JsonSerializer.DeserializeAsync<BaseItemDto[]>(
- await response.Content.ReadAsStreamAsync(),
- _jsonOptions);
+ var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto[]>(_jsonOptions);
Assert.NotNull(rootDto);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
index 0ade345a1..98195a294 100644
--- a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
@@ -31,10 +31,10 @@ namespace Jellyfin.Server.Integration.Tests
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
// Write out for publishing
- var responseBody = await response.Content.ReadAsStringAsync();
string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json"));
_outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath);
- File.WriteAllText(outputPath, responseBody);
+ await using var fs = File.Create(outputPath);
+ await response.Content.CopyToAsync(fs);
}
}
}
diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
index 49516cccc..288102037 100644
--- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
+++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
@@ -7,6 +7,7 @@ using Jellyfin.Server.Extensions;
using MediaBrowser.Common.Configuration;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
@@ -119,8 +120,8 @@ namespace Jellyfin.Server.Tests
EnableIPv6 = true,
EnableIPv4 = true,
};
-
- return new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+ var startupConf = new Mock<IConfiguration>();
+ return new NetworkManager(GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
}
}
}