diff --git a/Directory.Packages.props b/Directory.Packages.props index 33736482c8..e26005f8e2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -86,8 +86,8 @@ - + - + diff --git a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs new file mode 100644 index 0000000000..87ec14d958 --- /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; + +/// +/// Extension methods for adding DLNA services. +/// +public static class DlnaServiceCollectionExtensions +{ + /// + /// Adds DLNA services to the provided . + /// + /// The . + /// The . + 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(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(provider => new SsdpCommunicationsServer( + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService>()) + { + IsShared = true + }); + } +} diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 83b0ef3164..aa70124870 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(); - ContentDirectory = new ContentDirectory.ContentDirectoryService( - dlnaManager, - userDataManager, - imageProcessor, - libraryManager, - config, - userManager, - loggerFactory.CreateLogger(), - httpClientFactory, - localizationManager, - mediaSourceManager, - userViewManager, - mediaEncoder, - tvSeriesManager); - - ConnectionManager = new ConnectionManager.ConnectionManagerService( - dlnaManager, - config, - loggerFactory.CreateLogger(), - httpClientFactory); - - MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService( - loggerFactory.CreateLogger(), - httpClientFactory, - config); - Current = this; - var netConfig = config.GetConfiguration(NetworkConfigurationStore.StoreKey); _disabled = appHost.ListenWithHttps && netConfig.RequireHttps; @@ -133,19 +101,6 @@ namespace Emby.Dlna.Main } } - public static DlnaEntryPoint Current { get; private set; } - - /// - /// Gets a value indicating whether the dlna server is enabled. - /// - 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,37 +148,11 @@ namespace Emby.Dlna.Main } } - private void StartSsdpHandler() + private void StartDeviceDiscovery() { try { - if (_communicationsServer is null) - { - _communicationsServer = new SsdpCommunicationsServer( - _socketFactory, - _networkManager, - _logger) - { - IsShared = true - }; - - StartDeviceDiscovery(_communicationsServer); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error starting SSDP handlers"); - } - } - - private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer) - { - try - { - if (communicationsServer is not null) - { - ((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer); - } + ((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer); } catch (Exception ex) { @@ -233,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) @@ -318,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[] @@ -339,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)) { @@ -355,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 serviceParts = fullDeviceType + .Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase) + .Split(':'); - var deviceTypeNamespace = serviceParts[0].Replace('.', '-'); - - device.DeviceTypeNamespace = deviceTypeNamespace; + device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-'); device.DeviceClass = serviceParts[1]; device.DeviceType = serviceParts[2]; } @@ -444,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 220aa1a8dc..255c51f19a 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, "&"); - 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, "&"); + + 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 c247178ee9..c9bf7f085e 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; @@ -563,8 +559,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -576,8 +570,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 9e7b453867..8279acb058 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); + } } /// diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 3aab0a5e9d..80263c1394 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -43,8 +43,6 @@ net7.0 false true - - AD0001 diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 936a08da81..59d705acef 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(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 c9a26a30f5..91469dba99 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(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 b9d0f170ac..74b62ca3f2 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 7645c6c52d..6b0520ad0f 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>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var dailySchedules = await response.Content.ReadFromJsonAsync>(_jsonOptions, cancellationToken).ConfigureAwait(false); if (dailySchedules is null) { return Array.Empty(); @@ -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>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var programDetails = await innerResponse.Content.ReadFromJsonAsync>(_jsonOptions, cancellationToken).ConfigureAwait(false); if (programDetails is null) { return Array.Empty(); @@ -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>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); + return await innerResponse2.Content.ReadFromJsonAsync>(_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>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); - + var root = await httpResponse.Content.ReadFromJsonAsync>(_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(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var root = await response.Content.ReadFromJsonAsync(_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(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - + var root = await httpResponse.Content.ReadFromJsonAsync(_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(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var root = await httpResponse.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken).ConfigureAwait(false); if (root is null) { return new List(); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 04b0cb0177..8cd0c4ffb7 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; @@ -75,13 +76,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>(stream, _jsonOptions, cancellationToken) - .ConfigureAwait(false) ?? new List(); - + var lineup = await response.Content.ReadFromJsonAsync>(_jsonOptions, cancellationToken).ConfigureAwait(false) ?? Enumerable.Empty(); if (info.ImportFavoritesOnly) { - lineup = lineup.Where(i => i.Favorite).ToList(); + lineup = lineup.Where(i => i.Favorite); } return lineup.Where(i => !i.DRM).ToList(); @@ -128,9 +126,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(stream, _jsonOptions, cancellationToken) - .ConfigureAwait(false); + var discoverResponse = await response.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(cacheKey)) { @@ -174,34 +170,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(); - 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/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 96f4353998..16776b6bd6 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(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/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index d7189ef0ca..20793ee394 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(metaStream, _jsonOptions); + var localManifest = await JsonSerializer.DeserializeAsync(metaStream, _jsonOptions).ConfigureAwait(false); localManifest ??= new PluginManifest(); if (!Equals(localManifest.Id, manifest.Id)) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 3b4ecc962d..c717744b12 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -520,18 +520,16 @@ 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 = await md5.ComputeHashAsync(stream, cancellationToken).ConfigureAwait(false); - var hashHex = Convert.ToHexString(hash); - if (!string.Equals(package.Checksum, hashHex, StringComparison.OrdinalIgnoreCase)) + var hash = Convert.ToHexString(await MD5.HashDataAsync(stream, cancellationToken).ConfigureAwait(false)); + if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase)) { _logger.LogError( "The checksums didn't match while installing {Package}, expected: {Expected}, got: {Received}", package.Name, package.Checksum, - hashHex); + hash); throw new InvalidDataException("The checksum of the received data doesn't match."); } @@ -557,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 95b296fae9..42576934b3 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 class. /// /// Instance of the interface. - public DlnaServerController(IDlnaManager dlnaManager) + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + 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; } /// diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 6a0a4706be..7ac231885e 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -8,8 +8,6 @@ net7.0 true - - AD0001 diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 1393f76aab..2acddb243d 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; @@ -119,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>(nameof(JellyfinDbContext)); services.AddHlsPlaylistGenerator(); + services.AddDlnaServices(_serverApplicationHost); } /// diff --git a/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs index dea1c2f32a..2a7e6be0fd 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.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs index 55e2474a5a..daad9706cd 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 f3385b3a91..92742b1aa5 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 e4bb4eaead..e84f1359b7 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(_jsonOptions, cancellationToken).ConfigureAwait(false); + if (searchResultList?.Search is not null) { - var searchResultList = await JsonSerializer.DeserializeAsync(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(_jsonOptions, cancellationToken).ConfigureAwait(false); + if (string.Equals(result?.Response, "true", StringComparison.OrdinalIgnoreCase)) { - var result = await JsonSerializer.DeserializeAsync(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/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs index 5ddbd30d1e..4e8aec9f19 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( - await authResponse.Content.ReadAsStreamAsync(), - jsonOptions); + var auth = await authResponse.Content.ReadFromJsonAsync(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( - await response.Content.ReadAsStreamAsync(), JsonDefaults.Options); + var userDto = await response.Content.ReadFromJsonAsync(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( - await response.Content.ReadAsStreamAsync(), - JsonDefaults.Options); + var rootDto = await response.Content.ReadFromJsonAsync(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 87136dfc8a..8761cf69bc 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(responseBody); + await response.Content.ReadFromJsonAsync(); } [Theory] diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs index bd6e1b690a..39d449e27e 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(res, _jsonOpions); + _ = await response.Content.ReadFromJsonAsync(_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(res, _jsonOpions); + var data = await response.Content.ReadFromJsonAsync(_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 65e70caa02..e5d5e785cb 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( - await response.Content.ReadAsStreamAsync(), - _jsonOptions); + var profiles = await response.Content.ReadFromJsonAsync(_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( - await response.Content.ReadAsStreamAsync(), - _jsonOptions); + var profiles = await response.Content.ReadFromJsonAsync(_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( - await response.Content.ReadAsStreamAsync(), - _jsonOptions); + var profiles = await response.Content.ReadFromJsonAsync(_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 a12e7ca0d8..23de2489e5 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>( - await response.Content.ReadAsStreamAsync(), - _jsonOptions); + var items = await response.Content.ReadFromJsonAsync>(_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 2d3879bdb6..36861294b5 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(responseStream, _jsonOptions); + var newConfig = await getResponse.Content.ReadFromJsonAsync(_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(contentStream, _jsonOptions); + var user = await response.Content.ReadFromJsonAsync(_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(contentStream, _jsonOptions); + var newUser = await getResponse.Content.ReadFromJsonAsync(_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 79d03d539a..4fcacd2cad 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( - await response.Content.ReadAsStreamAsync(), _jsonOpions); + var users = await response.Content.ReadFromJsonAsync(_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( - await response.Content.ReadAsStreamAsync(), _jsonOpions); + var users = await response.Content.ReadFromJsonAsync(_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( - await response.Content.ReadAsStreamAsync(), _jsonOpions); + var user = await response.Content.ReadFromJsonAsync(_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 826a0a69dd..130281c6d2 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( - await response.Content.ReadAsStreamAsync(), - _jsonOptions); + var rootDto = await response.Content.ReadFromJsonAsync(_jsonOptions); Assert.NotNull(rootDto); } @@ -102,9 +101,7 @@ public sealed class UserLibraryControllerTests : IClassFixture>( - await response.Content.ReadAsStreamAsync(), - _jsonOptions); + var rootDto = await response.Content.ReadFromJsonAsync>(_jsonOptions); Assert.NotNull(rootDto); } @@ -121,9 +118,7 @@ public sealed class UserLibraryControllerTests : IClassFixture( - await response.Content.ReadAsStreamAsync(), - _jsonOptions); + var rootDto = await response.Content.ReadFromJsonAsync(_jsonOptions); Assert.NotNull(rootDto); } } diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs index c083974d3e..98195a2943 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); - await File.WriteAllTextAsync(outputPath, responseBody); + await using var fs = File.Create(outputPath); + await response.Content.CopyToAsync(fs); } } }