Prechádzať zdrojové kódy

Multiple Stream changes

* Remove useless MemoryStream in DlnaHttpClient
* Use HttpContent.ReadFromJsonAsync extension
* Call ConfigureAwait for IAsyncDisposable
* Use HttpContent.CopyToAsync where possible
Bond_009 1 rok pred
rodič
commit
d7748cfa04
25 zmenil súbory, kde vykonal 197 pridanie a 216 odobranie
  1. 28 27
      Emby.Dlna/PlayTo/DlnaHttpClient.cs
  2. 5 2
      Emby.Server.Implementations/Channels/ChannelManager.cs
  3. 0 2
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  4. 13 5
      Emby.Server.Implementations/Library/LiveStreamHelper.cs
  5. 10 5
      Emby.Server.Implementations/Library/MediaSourceManager.cs
  6. 9 5
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  7. 7 18
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  8. 30 31
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  9. 20 17
      Emby.Server.Implementations/Localization/LocalizationManager.cs
  10. 13 5
      Emby.Server.Implementations/Plugins/PluginManager.cs
  11. 2 3
      Emby.Server.Implementations/Updates/InstallationManager.cs
  12. 0 2
      Jellyfin.Api/Jellyfin.Api.csproj
  13. 6 3
      MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
  14. 5 10
      MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
  15. 8 13
      MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
  16. 16 19
      MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
  17. 3 8
      tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
  18. 2 2
      tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
  19. 3 4
      tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
  20. 3 9
      tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
  21. 2 3
      tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
  22. 3 6
      tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
  23. 3 6
      tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
  24. 4 9
      tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
  25. 2 2
      tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs

+ 28 - 27
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;
+                    }
                 }
             }
         }

+ 5 - 2
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 />

+ 0 - 2
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' ">

+ 13 - 5
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);
                 }
             }
 

+ 10 - 5
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);
                 }

+ 9 - 5
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))
                         {

+ 7 - 18
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>();

+ 30 - 31
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
+                        });
+                    }
                 }
             }
 

+ 20 - 17
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);
+                        }
                     }
                 }
 

+ 13 - 5
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))

+ 2 - 3
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);
         }

+ 0 - 2
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>

+ 6 - 3
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;
+            }
         }
     }
 }

+ 5 - 10
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);
             }
         }
 

+ 8 - 13
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);
             }
         }
 

+ 16 - 19
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) };
                 }
             }
 

+ 3 - 8
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;
         }

+ 2 - 2
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]

+ 3 - 4
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);
         }

+ 3 - 9
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)));
         }

+ 2 - 3
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);
     }
 }

+ 3 - 6
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);

+ 3 - 6
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);

+ 4 - 9
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);
     }
 }

+ 2 - 2
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);
         }
     }
 }