diff --git a/Dockerfile b/Dockerfile index 4fdffc7400..69af9b77b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ COPY . . ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 # because of changes in docker and systemd we need to not build in parallel at the moment # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting -RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" +RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none" FROM debian:buster-slim diff --git a/Dockerfile.arm b/Dockerfile.arm index 751ab86119..efeed25dff 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 # Discard objs - may cause failures if exists RUN find . -type d -name obj | xargs -r rm -r # Build -RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" +RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none" FROM multiarch/qemu-user-static:x86_64-arm as qemu diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 0d2e91d91c..1f2c2ec36d 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 # Discard objs - may cause failures if exists RUN find . -type d -name obj | xargs -r rm -r # Build -RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" +RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none" FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu FROM arm64v8/debian:buster-slim diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs index 12338e2b4b..f5a7eca720 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 +using System.Net.Http; using System.Threading.Tasks; using Emby.Dlna.Service; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using Microsoft.Extensions.Logging; @@ -18,8 +18,8 @@ namespace Emby.Dlna.ConnectionManager IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, - IHttpClient httpClient) - : base(logger, httpClient) + IHttpClientFactory httpClientFactory) + : base(logger, httpClientFactory) { _dlna = dlna; _config = config; diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs index 72732823ac..5760f260cf 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs @@ -2,11 +2,11 @@ using System; using System.Linq; +using System.Net.Http; using System.Threading.Tasks; using Emby.Dlna.Service; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -41,7 +41,7 @@ namespace Emby.Dlna.ContentDirectory IServerConfigurationManager config, IUserManager userManager, ILogger logger, - IHttpClient httpClient, + IHttpClientFactory httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 8538f580ca..6ed49944c0 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -80,6 +80,7 @@ + diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index b66e966dff..7d8da86ef9 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net.Http; +using System.Net.Mime; using System.Text; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; @@ -20,13 +21,13 @@ namespace Emby.Dlna.Eventing new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly ILogger _logger; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public DlnaEventManager(ILogger logger, IHttpClient httpClient) + public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory) { - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _logger = logger; } @@ -167,24 +168,17 @@ namespace Emby.Dlna.Eventing builder.Append(""); - var options = new HttpRequestOptions - { - RequestContent = builder.ToString(), - RequestContentType = "text/xml", - Url = subscription.CallbackUrl, - BufferContent = false - }; - - options.RequestHeaders.Add("NT", subscription.NotificationType); - options.RequestHeaders.Add("NTS", "upnp:propchange"); - options.RequestHeaders.Add("SID", subscription.Id); - options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture)); + using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl); + options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml); + options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType); + options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange"); + options.Headers.TryAddWithoutValidation("SID", subscription.Id); + options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture)); try { - using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false)) - { - } + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 0ad82022dd..40c2cc0e0a 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; +using System.Net.Http; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -36,7 +37,7 @@ namespace Emby.Dlna.Main private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; private readonly ISessionManager _sessionManager; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDlnaManager _dlnaManager; @@ -61,7 +62,7 @@ namespace Emby.Dlna.Main ILoggerFactory loggerFactory, IServerApplicationHost appHost, ISessionManager sessionManager, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, @@ -79,7 +80,7 @@ namespace Emby.Dlna.Main _config = config; _appHost = appHost; _sessionManager = sessionManager; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _libraryManager = libraryManager; _userManager = userManager; _dlnaManager = dlnaManager; @@ -101,7 +102,7 @@ namespace Emby.Dlna.Main config, userManager, loggerFactory.CreateLogger(), - httpClient, + httpClientFactory, localizationManager, mediaSourceManager, userViewManager, @@ -112,11 +113,11 @@ namespace Emby.Dlna.Main dlnaManager, config, loggerFactory.CreateLogger(), - httpClient); + httpClientFactory); MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService( loggerFactory.CreateLogger(), - httpClient, + httpClientFactory, config); Current = this; } @@ -364,7 +365,7 @@ namespace Emby.Dlna.Main _appHost, _imageProcessor, _deviceDiscovery, - _httpClient, + _httpClientFactory, _config, _userDataManager, _localization, diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs index 28de2fef52..e6d845e1e4 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 +using System.Net.Http; using System.Threading.Tasks; using Emby.Dlna.Service; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using Microsoft.Extensions.Logging; @@ -14,9 +14,9 @@ namespace Emby.Dlna.MediaReceiverRegistrar public MediaReceiverRegistrarService( ILogger logger, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, IServerConfigurationManager config) - : base(logger, httpClient) + : base(logger, httpClientFactory) { _config = config; } diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 5462e7abc5..c97acdb026 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net.Http; using System.Security; using System.Threading; using System.Threading.Tasks; @@ -21,7 +22,7 @@ namespace Emby.Dlna.PlayTo { private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; @@ -34,10 +35,10 @@ namespace Emby.Dlna.PlayTo private int _connectFailureCount; private bool _disposed; - public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger) + public Device(DeviceInfo deviceProperties, IHttpClientFactory httpClientFactory, ILogger logger) { Properties = deviceProperties; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _logger = logger; } @@ -236,7 +237,7 @@ namespace Emby.Dlna.PlayTo _logger.LogDebug("Setting mute"); var value = mute ? 1 : 0; - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) .ConfigureAwait(false); IsMuted = mute; @@ -271,7 +272,7 @@ namespace Emby.Dlna.PlayTo // Remote control will perform better Volume = value; - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) .ConfigureAwait(false); } @@ -292,7 +293,7 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME")) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME")) .ConfigureAwait(false); RestartTimer(true); @@ -326,7 +327,7 @@ namespace Emby.Dlna.PlayTo } var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary); - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header) .ConfigureAwait(false); await Task.Delay(50).ConfigureAwait(false); @@ -368,7 +369,7 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - return new SsdpHttpClient(_httpClient).SendCommandAsync( + return new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -397,7 +398,7 @@ namespace Emby.Dlna.PlayTo var service = GetAvTransportService(); - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) .ConfigureAwait(false); RestartTimer(true); @@ -415,7 +416,7 @@ namespace Emby.Dlna.PlayTo var service = GetAvTransportService(); - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) .ConfigureAwait(false); TransportState = TransportState.Paused; @@ -542,7 +543,7 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -592,7 +593,7 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -625,7 +626,7 @@ namespace Emby.Dlna.PlayTo return null; } - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -667,7 +668,7 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -734,7 +735,7 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -912,7 +913,7 @@ namespace Emby.Dlna.PlayTo string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); - var httpClient = new SsdpHttpClient(_httpClient); + var httpClient = new SsdpHttpClient(_httpClientFactory); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); @@ -940,7 +941,7 @@ namespace Emby.Dlna.PlayTo string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); - var httpClient = new SsdpHttpClient(_httpClient); + var httpClient = new SsdpHttpClient(_httpClientFactory); _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); @@ -969,9 +970,9 @@ namespace Emby.Dlna.PlayTo return baseUrl + url; } - public static async Task CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, ILogger logger, CancellationToken cancellationToken) + public static async Task CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken) { - var ssdpHttpClient = new SsdpHttpClient(httpClient); + var ssdpHttpClient = new SsdpHttpClient(httpClientFactory); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); @@ -1079,7 +1080,7 @@ namespace Emby.Dlna.PlayTo } } - return new Device(deviceProperties, httpClient, logger); + return new Device(deviceProperties, httpClientFactory, logger); } private static DeviceIcon CreateIcon(XElement element) diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index ff801f2630..3d1dd3e73f 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; @@ -33,7 +34,7 @@ namespace Emby.Dlna.PlayTo private readonly IDlnaManager _dlnaManager; private readonly IServerApplicationHost _appHost; private readonly IImageProcessor _imageProcessor; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IServerConfigurationManager _config; private readonly IUserDataManager _userDataManager; private readonly ILocalizationManager _localization; @@ -46,7 +47,7 @@ namespace Emby.Dlna.PlayTo private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); - public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) + public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) { _logger = logger; _sessionManager = sessionManager; @@ -56,7 +57,7 @@ namespace Emby.Dlna.PlayTo _appHost = appHost; _imageProcessor = imageProcessor; _deviceDiscovery = deviceDiscovery; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _config = config; _userDataManager = userDataManager; _localization = localization; @@ -174,7 +175,7 @@ namespace Emby.Dlna.PlayTo if (controller == null) { - var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _logger, cancellationToken).ConfigureAwait(false); + var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false); string deviceName = device.Properties.Name; diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index ab262bebfc..8683c89971 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -4,6 +4,8 @@ using System; using System.Globalization; using System.IO; using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -20,11 +22,11 @@ namespace Emby.Dlna.PlayTo private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; - public SsdpHttpClient(IHttpClient httpClient) + public SsdpHttpClient(IHttpClientFactory httpClientFactory) { - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; } public async Task SendCommandAsync( @@ -36,20 +38,18 @@ namespace Emby.Dlna.PlayTo CancellationToken cancellationToken = default) { var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); - using (var response = await PostSoapDataAsync( - url, - $"\"{service.ServiceType}#{command}\"", - postData, - header, - cancellationToken) - .ConfigureAwait(false)) - using (var stream = response.Content) - using (var reader = new StreamReader(stream, Encoding.UTF8)) - { - return XDocument.Parse( - await reader.ReadToEndAsync().ConfigureAwait(false), - LoadOptions.PreserveWhitespace); - } + using var response = await PostSoapDataAsync( + url, + $"\"{service.ServiceType}#{command}\"", + postData, + header, + cancellationToken) + .ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var reader = new StreamReader(stream, Encoding.UTF8); + return XDocument.Parse( + await reader.ReadToEndAsync().ConfigureAwait(false), + LoadOptions.PreserveWhitespace); } private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) @@ -76,49 +76,32 @@ namespace Emby.Dlna.PlayTo int eventport, int timeOut = 3600) { - var options = new HttpRequestOptions - { - Url = url, - UserAgent = USERAGENT, - LogErrorResponseBody = true, - BufferContent = false, - }; - - options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture); - options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">"; - options.RequestHeaders["NT"] = "upnp:event"; - options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture); - - using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false)) - { - } + using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url); + options.Headers.UserAgent.ParseAdd(USERAGENT); + options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(_usCulture)); + options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">"); + options.Headers.TryAddWithoutValidation("NT", "upnp:event"); + options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture)); + + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(options, HttpCompletionOption.ResponseHeadersRead) + .ConfigureAwait(false); } public async Task GetDataAsync(string url, CancellationToken cancellationToken) { - var options = new HttpRequestOptions - { - Url = url, - UserAgent = USERAGENT, - LogErrorResponseBody = true, - BufferContent = false, - - CancellationToken = cancellationToken - }; - - options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName; - - using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false)) - using (var stream = response.Content) - using (var reader = new StreamReader(stream, Encoding.UTF8)) - { - return XDocument.Parse( - await reader.ReadToEndAsync().ConfigureAwait(false), - LoadOptions.PreserveWhitespace); - } + using var options = new HttpRequestMessage(HttpMethod.Get, url); + options.Headers.UserAgent.ParseAdd(USERAGENT); + options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var reader = new StreamReader(stream, Encoding.UTF8); + return XDocument.Parse( + await reader.ReadToEndAsync().ConfigureAwait(false), + LoadOptions.PreserveWhitespace); } - private Task PostSoapDataAsync( + private Task PostSoapDataAsync( string url, string soapAction, string postData, @@ -130,29 +113,20 @@ namespace Emby.Dlna.PlayTo soapAction = $"\"{soapAction}\""; } - var options = new HttpRequestOptions - { - Url = url, - UserAgent = USERAGENT, - LogErrorResponseBody = true, - BufferContent = false, - - CancellationToken = cancellationToken - }; - - options.RequestHeaders["SOAPAction"] = soapAction; - options.RequestHeaders["Pragma"] = "no-cache"; - options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName; + using var options = new HttpRequestMessage(HttpMethod.Post, url); + options.Headers.UserAgent.ParseAdd(USERAGENT); + options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction); + options.Headers.TryAddWithoutValidation("Pragma", "no-cache"); + options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); if (!string.IsNullOrEmpty(header)) { - options.RequestHeaders["contentFeatures.dlna.org"] = header; + options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header); } - options.RequestContentType = "text/xml"; - options.RequestContent = postData; + options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml); - return _httpClient.Post(options); + return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken); } } } diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs index 40d069e7cd..a97c4d63a6 100644 --- a/Emby.Dlna/Service/BaseService.cs +++ b/Emby.Dlna/Service/BaseService.cs @@ -1,25 +1,21 @@ #pragma warning disable CS1591 +using System.Net.Http; using Emby.Dlna.Eventing; -using MediaBrowser.Common.Net; using Microsoft.Extensions.Logging; namespace Emby.Dlna.Service { public class BaseService : IDlnaEventManager { - protected BaseService(ILogger logger, IHttpClient httpClient) + protected BaseService(ILogger logger, IHttpClientFactory httpClientFactory) { Logger = logger; - HttpClient = httpClient; - - EventManager = new DlnaEventManager(logger, HttpClient); + EventManager = new DlnaEventManager(logger, httpClientFactory); } protected IDlnaEventManager EventManager { get; } - protected IHttpClient HttpClient { get; } - protected ILogger Logger { get; } public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 155bdcf65f..c37e87d969 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -49,6 +49,7 @@ using Jellyfin.Api.Helpers; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; +using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; @@ -122,8 +123,8 @@ namespace Emby.Server.Implementations private IMediaEncoder _mediaEncoder; private ISessionManager _sessionManager; + private IHttpClientFactory _httpClientFactory; private IWebSocketManager _webSocketManager; - private IHttpClient _httpClient; private string[] _urlPrefixes; @@ -526,8 +527,6 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton(_fileSystemManager); ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(_networkManager); ServiceCollection.AddSingleton(); @@ -654,8 +653,8 @@ namespace Emby.Server.Implementations _mediaEncoder = Resolve(); _sessionManager = Resolve(); + _httpClientFactory = Resolve(); _webSocketManager = Resolve(); - _httpClient = Resolve(); ((AuthenticationRepository)Resolve()).Initialize(); @@ -1300,25 +1299,17 @@ namespace Emby.Server.Implementations try { - using (var response = await _httpClient.SendAsync( - new HttpRequestOptions - { - Url = apiUrl, - LogErrorResponseBody = false, - BufferContent = false, - CancellationToken = cancellationToken - }, HttpMethod.Post).ConfigureAwait(false)) - { - using (var reader = new StreamReader(response.Content)) - { - var result = await reader.ReadToEndAsync().ConfigureAwait(false); - var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); + using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); - Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid); - return valid; - } - } + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + var result = await System.Text.Json.JsonSerializer.DeserializeAsync(stream, JsonDefaults.GetOptions(), cancellationToken).ConfigureAwait(false); + var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); + + _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); + Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid); + return valid; } catch (OperationCanceledException) { diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs deleted file mode 100644 index 25adc58126..0000000000 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ /dev/null @@ -1,335 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using MediaBrowser.Common; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; - -namespace Emby.Server.Implementations.HttpClientManager -{ - /// - /// Class HttpClientManager. - /// - public class HttpClientManager : IHttpClient - { - private readonly ILogger _logger; - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly IApplicationHost _appHost; - - /// - /// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests. - /// DON'T dispose it after use. - /// - /// The HTTP clients. - private readonly ConcurrentDictionary _httpClients = new ConcurrentDictionary(); - - /// - /// Initializes a new instance of the class. - /// - public HttpClientManager( - IApplicationPaths appPaths, - ILogger logger, - IFileSystem fileSystem, - IApplicationHost appHost) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _fileSystem = fileSystem; - _appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths)); - _appHost = appHost; - } - - /// - /// Gets the correct http client for the given url. - /// - /// The url. - /// HttpClient. - private HttpClient GetHttpClient(string url) - { - var key = GetHostFromUrl(url); - - if (!_httpClients.TryGetValue(key, out var client)) - { - client = new HttpClient() - { - BaseAddress = new Uri(url) - }; - - _httpClients.TryAdd(key, client); - } - - return client; - } - - private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method) - { - string url = options.Url; - var uriAddress = new Uri(url); - string userInfo = uriAddress.UserInfo; - if (!string.IsNullOrWhiteSpace(userInfo)) - { - _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url); - url = url.Replace(userInfo + '@', string.Empty, StringComparison.Ordinal); - } - - var request = new HttpRequestMessage(method, url); - - foreach (var header in options.RequestHeaders) - { - request.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - if (options.EnableDefaultUserAgent - && !request.Headers.TryGetValues(HeaderNames.UserAgent, out _)) - { - request.Headers.Add(HeaderNames.UserAgent, _appHost.ApplicationUserAgent); - } - - switch (options.DecompressionMethod) - { - case CompressionMethods.Deflate | CompressionMethods.Gzip: - request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" }); - break; - case CompressionMethods.Deflate: - request.Headers.Add(HeaderNames.AcceptEncoding, "deflate"); - break; - case CompressionMethods.Gzip: - request.Headers.Add(HeaderNames.AcceptEncoding, "gzip"); - break; - default: - break; - } - - if (options.EnableKeepAlive) - { - request.Headers.Add(HeaderNames.Connection, "Keep-Alive"); - } - - // request.Headers.Add(HeaderNames.CacheControl, "no-cache"); - - /* - if (!string.IsNullOrWhiteSpace(userInfo)) - { - var parts = userInfo.Split(':'); - if (parts.Length == 2) - { - request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]); - } - } - */ - - return request; - } - - /// - /// Gets the response internal. - /// - /// The options. - /// Task{HttpResponseInfo}. - public Task GetResponse(HttpRequestOptions options) - => SendAsync(options, HttpMethod.Get); - - /// - /// Performs a GET request and returns the resulting stream. - /// - /// The options. - /// Task{Stream}. - public async Task Get(HttpRequestOptions options) - { - var response = await GetResponse(options).ConfigureAwait(false); - return response.Content; - } - - /// - /// send as an asynchronous operation. - /// - /// The options. - /// The HTTP method. - /// Task{HttpResponseInfo}. - public Task SendAsync(HttpRequestOptions options, string httpMethod) - => SendAsync(options, new HttpMethod(httpMethod)); - - /// - /// send as an asynchronous operation. - /// - /// The options. - /// The HTTP method. - /// Task{HttpResponseInfo}. - public async Task SendAsync(HttpRequestOptions options, HttpMethod httpMethod) - { - if (options.CacheMode == CacheMode.None) - { - return await SendAsyncInternal(options, httpMethod).ConfigureAwait(false); - } - - var url = options.Url; - var urlHash = url.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); - - var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash); - - var response = GetCachedResponse(responseCachePath, options.CacheLength, url); - if (response != null) - { - return response; - } - - response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false); - - if (response.StatusCode == HttpStatusCode.OK) - { - await CacheResponse(response, responseCachePath).ConfigureAwait(false); - } - - return response; - } - - private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url) - { - if (File.Exists(responseCachePath) - && _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow) - { - var stream = new FileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true); - - return new HttpResponseInfo - { - ResponseUrl = url, - Content = stream, - StatusCode = HttpStatusCode.OK, - ContentLength = stream.Length - }; - } - - return null; - } - - private async Task CacheResponse(HttpResponseInfo response, string responseCachePath) - { - Directory.CreateDirectory(Path.GetDirectoryName(responseCachePath)); - - using (var fileStream = new FileStream( - responseCachePath, - FileMode.Create, - FileAccess.Write, - FileShare.None, - IODefaults.FileStreamBufferSize, - true)) - { - await response.Content.CopyToAsync(fileStream).ConfigureAwait(false); - - response.Content.Position = 0; - } - } - - private async Task SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod) - { - ValidateParams(options); - - options.CancellationToken.ThrowIfCancellationRequested(); - - var client = GetHttpClient(options.Url); - - var httpWebRequest = GetRequestMessage(options, httpMethod); - - if (!string.IsNullOrEmpty(options.RequestContent) - || httpMethod == HttpMethod.Post) - { - if (options.RequestContent != null) - { - httpWebRequest.Content = new StringContent( - options.RequestContent, - null, - options.RequestContentType); - } - else - { - httpWebRequest.Content = new ByteArrayContent(Array.Empty()); - } - } - - options.CancellationToken.ThrowIfCancellationRequested(); - - var response = await client.SendAsync( - httpWebRequest, - options.BufferContent || options.CacheMode == CacheMode.Unconditional ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead, - options.CancellationToken).ConfigureAwait(false); - - await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - - options.CancellationToken.ThrowIfCancellationRequested(); - - var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - return new HttpResponseInfo(response.Headers, response.Content.Headers) - { - Content = stream, - StatusCode = response.StatusCode, - ContentType = response.Content.Headers.ContentType?.MediaType, - ContentLength = response.Content.Headers.ContentLength, - ResponseUrl = response.Content.Headers.ContentLocation?.ToString() - }; - } - - /// - public Task Post(HttpRequestOptions options) - => SendAsync(options, HttpMethod.Post); - - private void ValidateParams(HttpRequestOptions options) - { - if (string.IsNullOrEmpty(options.Url)) - { - throw new ArgumentNullException(nameof(options)); - } - } - - /// - /// Gets the host from URL. - /// - /// The URL. - /// System.String. - private static string GetHostFromUrl(string url) - { - var index = url.IndexOf("://", StringComparison.OrdinalIgnoreCase); - - if (index != -1) - { - url = url.Substring(index + 3); - var host = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); - - if (!string.IsNullOrWhiteSpace(host)) - { - return host; - } - } - - return url; - } - - private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options) - { - if (response.IsSuccessStatusCode) - { - return; - } - - if (options.LogErrorResponseBody) - { - string msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - _logger.LogError("HTTP request failed with message: {Message}", msg); - } - - throw new HttpException(response.ReasonPhrase) - { - StatusCode = response.StatusCode - }; - } - } -} diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 0edd980316..44560d1e21 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -16,13 +16,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public class DirectRecorder : IRecorder { private readonly ILogger _logger; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IStreamHelper _streamHelper; - public DirectRecorder(ILogger logger, IHttpClient httpClient, IStreamHelper streamHelper) + public DirectRecorder(ILogger logger, IHttpClientFactory httpClientFactory, IStreamHelper streamHelper) { _logger = logger; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _streamHelper = streamHelper; } @@ -63,42 +63,28 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { - var httpRequestOptions = new HttpRequestOptions - { - Url = mediaSource.Path, - BufferContent = false, - - // Some remote urls will expect a user agent to be supplied - UserAgent = "Emby/3.0", + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetAsync(mediaSource.Path, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - // Shouldn't matter but may cause issues - DecompressionMethod = CompressionMethods.None, - CancellationToken = cancellationToken - }; + _logger.LogInformation("Opened recording stream from tuner provider"); - using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false)) - { - _logger.LogInformation("Opened recording stream from tuner provider"); + Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); - Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); + await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read); - using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - onStarted(); + onStarted(); - _logger.LogInformation("Copying recording stream to file {0}", targetFile); + _logger.LogInformation("Copying recording stream to file {0}", targetFile); - // The media source if infinite so we need to handle stopping ourselves - using var durationToken = new CancellationTokenSource(duration); - using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); + // The media source if infinite so we need to handle stopping ourselves + var durationToken = new CancellationTokenSource(duration); + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - await _streamHelper.CopyUntilCancelled( - response.Content, - output, - IODefaults.CopyToBufferSize, - cancellationTokenSource.Token).ConfigureAwait(false); - } - } + await _streamHelper.CopyUntilCancelled( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + output, + IODefaults.CopyToBufferSize, + cancellationToken).ConfigureAwait(false); _logger.LogInformation("Recording completed to file {0}", targetFile); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 5cf09b8e5b..ca60c33664 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -48,7 +49,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly IServerApplicationHost _appHost; private readonly ILogger _logger; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IServerConfigurationManager _config; private readonly IJsonSerializer _jsonSerializer; @@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, @@ -94,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _appHost = appHost; _logger = logger; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _config = config; _fileSystem = fileSystem; _libraryManager = libraryManager; @@ -1637,7 +1638,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer); } - return new DirectRecorder(_logger, _httpClient, _streamHelper); + return new DirectRecorder(_logger, _httpClientFactory, _streamHelper); } private void OnSuccessfulRecording(TimerInfo timer, string path) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 33331adafa..f9ae55af85 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -8,6 +8,8 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Mime; +using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; @@ -28,19 +30,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); private readonly IApplicationHost _appHost; public SchedulesDirect( ILogger logger, IJsonSerializer jsonSerializer, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, IApplicationHost appHost) { _logger = logger; _jsonSerializer = jsonSerializer; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _appHost = appHost; } @@ -102,95 +104,78 @@ namespace Emby.Server.Implementations.LiveTv.Listings var requestString = _jsonSerializer.SerializeToString(requestList); _logger.LogDebug("Request string for schedules is: {RequestString}", requestString); - var httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/schedules", - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true, - RequestContent = requestString - }; + using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules"); + options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json); + options.Headers.TryAddWithoutValidation("token", token); + using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false); + await using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync>(responseStream).ConfigureAwait(false); + _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); - httpOptions.RequestHeaders["token"] = token; + using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs"); + programRequestOptions.Headers.TryAddWithoutValidation("token", token); - using (var response = await Post(httpOptions, true, info).ConfigureAwait(false)) - { - var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync>(response.Content).ConfigureAwait(false); - _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); + var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct(); + programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json); - httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/programs", - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true - }; - - httpOptions.RequestHeaders["token"] = token; + using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); + await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + var programDetails = await _jsonSerializer.DeserializeFromStreamAsync>(innerResponseStream).ConfigureAwait(false); + var programDict = programDetails.ToDictionary(p => p.programID, y => y); - var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct(); - httpOptions.RequestContent = "[\"" + string.Join("\", \"", programsID) + "\"]"; + var programIdsWithImages = + programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID) + .ToList(); - using (var innerResponse = await Post(httpOptions, true, info).ConfigureAwait(false)) - { - var programDetails = await _jsonSerializer.DeserializeFromStreamAsync>(innerResponse.Content).ConfigureAwait(false); - var programDict = programDetails.ToDictionary(p => p.programID, y => y); + var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false); - var programIdsWithImages = - programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID) - .ToList(); - - var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false); + var programsInfo = new List(); + foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs)) + { + // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID + + // " which corresponds to channel " + channelNumber + " and program id " + + // schedule.programID + " which says it has images? " + + // programDict[schedule.programID].hasImageArtwork); - var programsInfo = new List(); - foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs)) + if (images != null) + { + var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10)); + if (imageIndex > -1) { - // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID + - // " which corresponds to channel " + channelNumber + " and program id " + - // schedule.programID + " which says it has images? " + - // programDict[schedule.programID].hasImageArtwork); - - if (images != null) - { - var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10)); - if (imageIndex > -1) - { - var programEntry = programDict[schedule.programID]; + var programEntry = programDict[schedule.programID]; - var allImages = images[imageIndex].data ?? new List(); - var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase)); - var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase)); + var allImages = images[imageIndex].data ?? new List(); + var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase)); + var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase)); - const double DesiredAspect = 2.0 / 3; + const double DesiredAspect = 2.0 / 3; - programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ?? - GetProgramImage(ApiUrl, allImages, true, DesiredAspect); + programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ?? + GetProgramImage(ApiUrl, allImages, true, DesiredAspect); - const double WideAspect = 16.0 / 9; + const double WideAspect = 16.0 / 9; - programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect); + programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect); - // Don't supply the same image twice - if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal)) - { - programEntry.thumbImage = null; - } - - programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect); - - // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? - // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? - // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? - // GetProgramImage(ApiUrl, data, "Banner-LOT", false); - } + // Don't supply the same image twice + if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal)) + { + programEntry.thumbImage = null; } - programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID])); - } + programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect); - return programsInfo; + // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-LOT", false); + } } + + programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID])); } + + return programsInfo; } private static int GetSizeOrder(ScheduleDirect.ImageData image) @@ -483,22 +468,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings imageIdString = imageIdString.TrimEnd(',') + "]"; - var httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/metadata/programs", - UserAgent = UserAgent, - CancellationToken = cancellationToken, - RequestContent = imageIdString, - LogErrorResponseBody = true, - }; + using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs"); + message.Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json); try { - using (var innerResponse2 = await Post(httpOptions, true, info).ConfigureAwait(false)) - { - return await _jsonSerializer.DeserializeFromStreamAsync>( - innerResponse2.Content).ConfigureAwait(false); - } + using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false); + await using var response = await innerResponse2.Content.ReadAsStreamAsync(); + return await _jsonSerializer.DeserializeFromStreamAsync>( + response).ConfigureAwait(false); } catch (Exception ex) { @@ -519,41 +497,33 @@ namespace Emby.Server.Implementations.LiveTv.Listings return lineups; } - var options = new HttpRequestOptions() - { - Url = ApiUrl + "/headends?country=" + country + "&postalcode=" + location, - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true - }; - - options.RequestHeaders["token"] = token; + using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/headends?country=" + country + "&postalcode=" + location); + options.Headers.TryAddWithoutValidation("token", token); try { - using (var httpResponse = await Get(options, false, info).ConfigureAwait(false)) - using (Stream responce = httpResponse.Content) - { - var root = await _jsonSerializer.DeserializeFromStreamAsync>(responce).ConfigureAwait(false); + using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false); + await using var response = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + + var root = await _jsonSerializer.DeserializeFromStreamAsync>(response).ConfigureAwait(false); - if (root != null) + if (root != null) + { + foreach (ScheduleDirect.Headends headend in root) { - foreach (ScheduleDirect.Headends headend in root) + foreach (ScheduleDirect.Lineup lineup in headend.lineups) { - foreach (ScheduleDirect.Lineup lineup in headend.lineups) + lineups.Add(new NameIdPair { - lineups.Add(new NameIdPair - { - Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name, - Id = lineup.uri.Substring(18) - }); - } + Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name, + Id = lineup.uri.Substring(18) + }); } } - else - { - _logger.LogInformation("No lineups available"); - } + } + else + { + _logger.LogInformation("No lineups available"); } } catch (Exception ex) @@ -634,17 +604,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - private async Task Post( - HttpRequestOptions options, + private async Task Send( + HttpRequestMessage options, bool enableRetry, - ListingsProviderInfo providerInfo) + ListingsProviderInfo providerInfo, + CancellationToken cancellationToken, + HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { - // Schedules direct requires that the client support compression and will return a 400 response without it - options.DecompressionMethod = CompressionMethods.Deflate; - try { - return await _httpClient.Post(options).ConfigureAwait(false); + return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, completionOption, cancellationToken).ConfigureAwait(false); } catch (HttpException ex) { @@ -661,39 +630,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false); - return await Post(options, false, providerInfo).ConfigureAwait(false); - } - - private async Task Get( - HttpRequestOptions options, - bool enableRetry, - ListingsProviderInfo providerInfo) - { - // Schedules direct requires that the client support compression and will return a 400 response without it - options.DecompressionMethod = CompressionMethods.Deflate; - - try - { - return await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false); - } - catch (HttpException ex) - { - _tokens.Clear(); - - if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500) - { - enableRetry = false; - } - - if (!enableRetry) - { - throw; - } - } - - options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false); - return await Get(options, false, providerInfo).ConfigureAwait(false); + options.Headers.TryAddWithoutValidation("token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false)); + return await Send(options, false, providerInfo, cancellationToken).ConfigureAwait(false); } private async Task GetTokenInternal( @@ -701,28 +639,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings string password, CancellationToken cancellationToken) { - var httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/token", - UserAgent = UserAgent, - RequestContent = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}", - CancellationToken = cancellationToken, - LogErrorResponseBody = true - }; - // _logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " + - // httpOptions.RequestContent); + using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token"); + options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json); - using (var response = await Post(httpOptions, false, null).ConfigureAwait(false)) + using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + var root = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); + if (root.message == "OK") { - var root = await _jsonSerializer.DeserializeFromStreamAsync(response.Content).ConfigureAwait(false); - if (root.message == "OK") - { - _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token); - return root.token; - } - - throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message); + _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token); + return root.token; } + + throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message); } private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken) @@ -741,20 +670,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings _logger.LogInformation("Adding new LineUp "); - var httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/lineups/" + info.ListingsId, - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true, - BufferContent = false - }; - - httpOptions.RequestHeaders["token"] = token; - - using (await _httpClient.SendAsync(httpOptions, HttpMethod.Put).ConfigureAwait(false)) - { - } + using var options = new HttpRequestMessage(HttpMethod.Put, ApiUrl + "/lineups/" + info.ListingsId); + options.Headers.TryAddWithoutValidation("token", token); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); } private async Task HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken) @@ -773,25 +691,17 @@ namespace Emby.Server.Implementations.LiveTv.Listings _logger.LogInformation("Headends on account "); - var options = new HttpRequestOptions() - { - Url = ApiUrl + "/lineups", - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true - }; - - options.RequestHeaders["token"] = token; + using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups"); + options.Headers.TryAddWithoutValidation("token", token); try { - using (var httpResponse = await Get(options, false, null).ConfigureAwait(false)) - using (var response = httpResponse.Content) - { - var root = await _jsonSerializer.DeserializeFromStreamAsync(response).ConfigureAwait(false); + using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false); + await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var response = httpResponse.Content; + var root = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); - return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase)); - } + return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase)); } catch (HttpException ex) { @@ -856,55 +766,43 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new Exception("token required"); } - var httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/lineups/" + listingsId, - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true, - }; - - httpOptions.RequestHeaders["token"] = token; + using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups/" + listingsId); + options.Headers.TryAddWithoutValidation("token", token); var list = new List(); - using (var httpResponse = await Get(httpOptions, true, info).ConfigureAwait(false)) - using (var response = httpResponse.Content) - { - var root = await _jsonSerializer.DeserializeFromStreamAsync(response).ConfigureAwait(false); - _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count); - _logger.LogInformation("Mapping Stations to Channel"); - - var allStations = root.stations ?? Enumerable.Empty(); + using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false); + await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + var root = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); + _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count); + _logger.LogInformation("Mapping Stations to Channel"); - foreach (ScheduleDirect.Map map in root.map) - { - var channelNumber = GetChannelNumber(map); + var allStations = root.stations ?? Enumerable.Empty(); - var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); - if (station == null) - { - station = new ScheduleDirect.Station - { - stationID = map.stationID - }; - } + foreach (ScheduleDirect.Map map in root.map) + { + var channelNumber = GetChannelNumber(map); - var channelInfo = new ChannelInfo - { - Id = station.stationID, - CallSign = station.callsign, - Number = channelNumber, - Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name - }; + var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); + if (station == null) + { + station = new ScheduleDirect.Station { stationID = map.stationID }; + } - if (station.logo != null) - { - channelInfo.ImageUrl = station.logo.URL; - } + var channelInfo = new ChannelInfo + { + Id = station.stationID, + CallSign = station.callsign, + Number = channelNumber, + Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name + }; - list.Add(channelInfo); + if (station.logo != null) + { + channelInfo.ImageUrl = station.logo.URL; } + + list.Add(channelInfo); } return list; diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index f33d071747..2d6f453bd4 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -25,20 +25,20 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class XmlTvListingsProvider : IListingsProvider { private readonly IServerConfigurationManager _config; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IZipClient _zipClient; public XmlTvListingsProvider( IServerConfigurationManager config, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, ILogger logger, IFileSystem fileSystem, IZipClient zipClient) { _config = config; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _logger = logger; _fileSystem = fileSystem; _zipClient = zipClient; @@ -78,28 +78,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)); - using (var res = await _httpClient.SendAsync( - new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = path, - DecompressionMethod = CompressionMethods.Gzip, - }, - HttpMethod.Get).ConfigureAwait(false)) - using (var stream = res.Content) - using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew)) + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(path, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew)) { - if (res.ContentHeaders.ContentEncoding.Contains("gzip")) - { - using (var gzStream = new GZipStream(stream, CompressionMode.Decompress)) - { - await gzStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); - } - } - else - { - await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); - } + await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } return UnzipIfNeeded(path, cacheFile); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 2b5f69d41a..28e30fac8b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost { - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IServerApplicationHost _appHost; private readonly ISocketFactory _socketFactory; private readonly INetworkManager _networkManager; @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun IServerConfigurationManager config, ILogger logger, IFileSystem fileSystem, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun IMemoryCache memoryCache) : base(config, logger, fileSystem, memoryCache) { - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _appHost = appHost; _socketFactory = socketFactory; _networkManager = networkManager; @@ -71,15 +71,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - var options = new HttpRequestOptions - { - Url = model.LineupURL, - CancellationToken = cancellationToken, - BufferContent = false - }; - - using var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false); - await using var stream = response.Content; + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var lineup = await JsonSerializer.DeserializeAsync>(stream, cancellationToken: cancellationToken) .ConfigureAwait(false) ?? new List(); @@ -133,14 +126,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun try { - using var response = await _httpClient.SendAsync( - new HttpRequestOptions - { - Url = string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)), - CancellationToken = cancellationToken, - BufferContent = false - }, HttpMethod.Get).ConfigureAwait(false); - await using var stream = response.Content; + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var discoverResponse = await JsonSerializer.DeserializeAsync(stream, cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -183,48 +172,41 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - using (var response = await _httpClient.SendAsync( - new HttpRequestOptions() - { - Url = string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), - CancellationToken = cancellationToken, - BufferContent = false - }, - HttpMethod.Get).ConfigureAwait(false)) - using (var stream = response.Content) - using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8)) - { - var tuners = new List(); - while (!sr.EndOfStream) + 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().ConfigureAwait(false); + using var sr = new StreamReader(stream, System.Text.Encoding.UTF8); + var tuners = new List(); + while (!sr.EndOfStream) + { + string line = StripXML(sr.ReadLine()); + if (line.Contains("Channel", StringComparison.Ordinal)) { - string line = StripXML(sr.ReadLine()); - if (line.Contains("Channel", StringComparison.Ordinal)) + LiveTvTunerStatus status; + var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); + var name = line.Substring(0, index - 1); + var currentChannel = line.Substring(index + 7); + if (currentChannel != "none") { - LiveTvTunerStatus status; - var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); - var name = line.Substring(0, index - 1); - var currentChannel = line.Substring(index + 7); - if (currentChannel != "none") - { - 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 - }); + status = LiveTvTunerStatus.LiveTv; + } + else + { + status = LiveTvTunerStatus.Available; } - } - return tuners; + tuners.Add(new LiveTvTunerInfo + { + Name = name, + SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber, + ProgramName = currentChannel, + Status = status + }); + } } + + return tuners; } private static string StripXML(string source) @@ -634,7 +616,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun info, streamId, FileSystem, - _httpClient, + _httpClientFactory, Logger, Config, _appHost, diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 8fc29fb4a2..8107bc427b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; @@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost { - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IServerApplicationHost _appHost; private readonly INetworkManager _networkManager; private readonly IMediaSourceManager _mediaSourceManager; @@ -37,14 +38,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts IMediaSourceManager mediaSourceManager, ILogger logger, IFileSystem fileSystem, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, IServerApplicationHost appHost, INetworkManager networkManager, IStreamHelper streamHelper, IMemoryCache memoryCache) : base(config, logger, fileSystem, memoryCache) { - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _appHost = appHost; _networkManager = networkManager; _mediaSourceManager = mediaSourceManager; @@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { var channelIdPrefix = GetFullChannelIdPrefix(info); - return await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false); + return await new M3uParser(Logger, _httpClientFactory, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false); } public Task> GetTunerInfos(CancellationToken cancellationToken) @@ -116,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { - return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config, _appHost, _streamHelper); + return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); } } @@ -125,7 +126,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task Validate(TunerHostInfo info) { - using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false)) + using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false)) { } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 875977219a..f066a749e3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Net.Http; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -19,13 +20,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public class M3uParser { private readonly ILogger _logger; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IServerApplicationHost _appHost; - public M3uParser(ILogger logger, IHttpClient httpClient, IServerApplicationHost appHost) + public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory, IServerApplicationHost appHost) { _logger = logger; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _appHost = appHost; } @@ -51,13 +52,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { - return _httpClient.Get(new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken, - // Some data providers will require a user agent - UserAgent = _appHost.ApplicationUserAgent - }); + return _httpClientFactory.CreateClient(NamedClient.Default) + .GetStreamAsync(url); } return Task.FromResult((Stream)File.OpenRead(url)); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index bc4dcd8946..6c10fca8c6 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { public class SharedHttpStream : LiveStream, IDirectStreamProvider { - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IServerApplicationHost _appHost; public SharedHttpStream( @@ -29,14 +29,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts TunerHostInfo tunerHostInfo, string originalStreamId, IFileSystem fileSystem, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, ILogger logger, IConfigurationManager configurationManager, IServerApplicationHost appHost, IStreamHelper streamHelper) : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper) { - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _appHost = appHost; OriginalStreamId = originalStreamId; EnableStreamSharing = true; @@ -55,25 +55,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var typeName = GetType().Name; Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url); - var httpRequestOptions = new HttpRequestOptions - { - Url = url, - CancellationToken = CancellationToken.None, - BufferContent = false, - DecompressionMethod = CompressionMethods.None - }; - - foreach (var header in mediaSource.RequiredHttpHeaders) - { - httpRequestOptions.RequestHeaders[header.Key] = header.Value; - } - - var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None) + .ConfigureAwait(false); var extension = "ts"; var requiresRemux = false; - var contentType = response.ContentType ?? string.Empty; + var contentType = response.Content.Headers.ContentType.ToString(); if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1) { requiresRemux = true; @@ -132,24 +121,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } - private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) + private Task StartStreaming(HttpResponseMessage response, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { return Task.Run(async () => { try { Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath); - using (response) - using (var stream = response.Content) - using (var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - await StreamHelper.CopyToAsync( - stream, - fileStream, - IODefaults.CopyToBufferSize, - () => Resolve(openTaskCompletionSource), - cancellationToken).ConfigureAwait(false); - } + using var message = response; + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read); + await StreamHelper.CopyToAsync( + stream, + fileStream, + IODefaults.CopyToBufferSize, + () => Resolve(openTaskCompletionSource), + cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException ex) { diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index d6be86da3e..ed6877f7d7 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -18,7 +18,7 @@ "MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன", "MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது", "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது", - "Inherit": "மரபரிமையாகப் பெறு", + "Inherit": "மரபுரிமையாகப் பெறு", "HeaderRecordingGroups": "பதிவு குழுக்கள்", "HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்", "Folders": "கோப்புறைகள்", @@ -31,7 +31,7 @@ "TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு", "TaskRefreshChannels": "சேனல்களை புதுப்பி", "TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி", - "TaskRefreshLibrary": "மீடியா நூலகத்தை ஆராய்", + "TaskRefreshLibrary": "ஊடக நூலகத்தை ஆராய்", "TasksChannelsCategory": "இணைய சேனல்கள்", "TasksApplicationCategory": "செயலி", "TasksLibraryCategory": "நூலகம்", @@ -46,7 +46,7 @@ "Sync": "ஒத்திசைவு", "StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.", "Songs": "பாடல்கள்", - "Shows": "தொடர்கள்", + "Shows": "நிகழ்ச்சிகள்", "ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்", "ScheduledTaskStartedWithName": "{0} துவங்கியது", "ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது", @@ -67,20 +67,20 @@ "NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது", "NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது", "NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்", - "NameSeasonUnknown": "பருவம் அறியப்படாதவை", + "NameSeasonUnknown": "அறியப்படாத பருவம்", "NameSeasonNumber": "பருவம் {0}", "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது", "MusicVideos": "இசைப்படங்கள்", "Music": "இசை", "Movies": "திரைப்படங்கள்", - "Latest": "புதியன", + "Latest": "புதியவை", "LabelRunningTimeValue": "ஓடும் நேரம்: {0}", "LabelIpAddressValue": "ஐபி முகவரி: {0}", "ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது", "ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது", - "HeaderNextUp": "அடுத்ததாக", + "HeaderNextUp": "அடுத்தது", "HeaderLiveTV": "நேரடித் தொலைக்காட்சி", - "HeaderFavoriteSongs": "பிடித்த பாட்டுகள்", + "HeaderFavoriteSongs": "பிடித்த பாடல்கள்", "HeaderFavoriteShows": "பிடித்த தொடர்கள்", "HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்", "HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்", @@ -93,25 +93,25 @@ "Channels": "சேனல்கள்", "Books": "புத்தகங்கள்", "AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது", - "Artists": "கலைஞர்", + "Artists": "கலைஞர்கள்", "Application": "செயலி", "Albums": "ஆல்பங்கள்", "NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.", - "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது", + "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0} புதுப்பிக்கப்பட்டது", "TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.", "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது", - "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0 } இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன", - "TaskDownloadMissingSubtitlesDescription": "மெட்டாடேட்டா உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.", + "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன", + "TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.", "TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.", - "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட செருகுநிரல்களுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.", - "TaskRefreshPeopleDescription": "உங்கள் மீடியா நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மெட்டாடேட்டாவை புதுப்பிக்கும்.", + "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.", + "TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.", "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.", - "TaskCleanLogs": "பதிவு அடைவு சுத்தம் செய்யுங்கள்", - "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் மீடியா நூலகத்தை ஸ்கேன் செய்து மீத்தரவை புதுப்பிக்கும்.", + "TaskCleanLogs": "பதிவு அடைவை சுத்தம் செய்யுங்கள்", + "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் ஊடக நூலகத்தை ஆராய்ந்து மீத்தரவை புதுப்பிக்கும்.", "TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.", "ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது", "UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்", "HomeVideos": "முகப்பு வீடியோக்கள்", - "UserStoppedPlayingItemWithValues": "{2} இல் {1} முடித்துவிட்டது", + "UserStoppedPlayingItemWithValues": "{0} {2} இல் {1} முடித்துவிட்டது", "UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது" } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 4f54c06dd2..f121a34938 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Updates /// private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IJsonSerializer _jsonSerializer; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; @@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Updates ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, IJsonSerializer jsonSerializer, IServerConfigurationManager config, IFileSystem fileSystem, @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Updates _logger = logger; _applicationHost = appHost; _appPaths = appPaths; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _jsonSerializer = jsonSerializer; _config = config; _fileSystem = fileSystem; @@ -116,26 +116,18 @@ namespace Emby.Server.Implementations.Updates { try { - using (var response = await _httpClient.SendAsync( - new HttpRequestOptions - { - Url = manifest, - CancellationToken = cancellationToken, - CacheMode = CacheMode.Unconditional, - CacheLength = TimeSpan.FromMinutes(3) - }, - HttpMethod.Get).ConfigureAwait(false)) - using (Stream stream = response.Content) + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetAsync(manifest, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + + try { - try - { - return await _jsonSerializer.DeserializeFromStreamAsync>(stream).ConfigureAwait(false); - } - catch (SerializationException ex) - { - _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest); - return Array.Empty(); - } + return await _jsonSerializer.DeserializeFromStreamAsync>(stream).ConfigureAwait(false); + } + catch (SerializationException ex) + { + _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest); + return Array.Empty(); } } catch (UriFormatException ex) @@ -360,42 +352,34 @@ namespace Emby.Server.Implementations.Updates // Always override the passed-in target (which is a file) and figure it out again string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetAsync(package.SourceUrl, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + // CA5351: Do Not Use Broken Cryptographic Algorithms #pragma warning disable CA5351 - using (var res = await _httpClient.SendAsync( - new HttpRequestOptions - { - Url = package.SourceUrl, - CancellationToken = cancellationToken, - // We need it to be buffered for setting the position - BufferContent = true - }, - HttpMethod.Get).ConfigureAwait(false)) - using (var stream = res.Content) - using (var md5 = MD5.Create()) - { - cancellationToken.ThrowIfCancellationRequested(); + using var md5 = MD5.Create(); + cancellationToken.ThrowIfCancellationRequested(); - var hash = Hex.Encode(md5.ComputeHash(stream)); - 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, - hash); - throw new InvalidDataException("The checksum of the received data doesn't match."); - } - - if (Directory.Exists(targetDir)) - { - Directory.Delete(targetDir, true); - } + var hash = Hex.Encode(md5.ComputeHash(stream)); + 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, + hash); + throw new InvalidDataException("The checksum of the received data doesn't match."); + } - stream.Position = 0; - _zipClient.ExtractAllFromZip(stream, targetDir, true); + if (Directory.Exists(targetDir)) + { + Directory.Delete(targetDir, true); } + stream.Position = 0; + _zipClient.ExtractAllFromZip(stream, targetDir, true); + #pragma warning restore CA5351 } diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs index a34f9eb62f..1c1fc71d79 100644 --- a/Jellyfin.Api/BaseJellyfinApiController.cs +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -1,4 +1,5 @@ using System.Net.Mime; +using MediaBrowser.Common.Json; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api @@ -8,7 +9,10 @@ namespace Jellyfin.Api /// [ApiController] [Route("[controller]")] - [Produces(MediaTypeNames.Application.Json)] + [Produces( + MediaTypeNames.Application.Json, + JsonDefaults.CamelCaseMediaType, + JsonDefaults.PascalCaseMediaType)] public class BaseJellyfinApiController : ControllerBase { } diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index 7624fd79f8..a859ac114c 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Jellyfin.Api.Controllers { @@ -28,38 +29,20 @@ namespace Jellyfin.Api.Controllers { private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; - private readonly IConfiguration _appConfig; - private readonly IServerConfigurationManager _serverConfigurationManager; - private readonly IResourceFileManager _resourceFileManager; /// /// Initializes a new instance of the class. /// /// Instance of interface. /// Instance of interface. - /// Instance of interface. - /// Instance of interface. - /// Instance of interface. public DashboardController( ILogger logger, - IServerApplicationHost appHost, - IConfiguration appConfig, - IResourceFileManager resourceFileManager, - IServerConfigurationManager serverConfigurationManager) + IServerApplicationHost appHost) { _logger = logger; _appHost = appHost; - _appConfig = appConfig; - _resourceFileManager = resourceFileManager; - _serverConfigurationManager = serverConfigurationManager; } - /// - /// Gets the path of the directory containing the static web interface content, or null if the server is not - /// hosting the web client. - /// - private string? WebClientUiPath => GetWebClientUiPath(_appConfig, _serverConfigurationManager); - /// /// Gets the configuration pages. /// @@ -172,86 +155,6 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - /// - /// Gets the robots.txt. - /// - /// Robots.txt returned. - /// The robots.txt. - [HttpGet("robots.txt")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ApiExplorerSettings(IgnoreApi = true)] - public ActionResult GetRobotsTxt() - { - return GetWebClientResource("robots.txt"); - } - - /// - /// Gets a resource from the web client. - /// - /// The resource name. - /// Web client returned. - /// Server does not host a web client. - /// The resource. - [HttpGet("web/{*resourceName}")] - [ApiExplorerSettings(IgnoreApi = true)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetWebClientResource([FromRoute] string resourceName) - { - if (!_appConfig.HostWebClient() || WebClientUiPath == null) - { - return NotFound("Server does not host a web client."); - } - - var path = resourceName; - var basePath = WebClientUiPath; - - var requestPathAndQuery = Request.GetEncodedPathAndQuery(); - // Bounce them to the startup wizard if it hasn't been completed yet - if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted - && !requestPathAndQuery.Contains("wizard", StringComparison.OrdinalIgnoreCase) - && requestPathAndQuery.Contains("index", StringComparison.OrdinalIgnoreCase)) - { - return Redirect("index.html?start=wizard#!/wizardstart.html"); - } - - return PhysicalFile(_resourceFileManager.GetResourcePath(basePath, path), MimeTypes.GetMimeType(path)); - } - - /// - /// Gets the favicon. - /// - /// Favicon.ico returned. - /// The favicon. - [HttpGet("favicon.ico")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ApiExplorerSettings(IgnoreApi = true)] - public ActionResult GetFavIcon() - { - return GetWebClientResource("favicon.ico"); - } - - /// - /// Gets the path of the directory containing the static web interface content. - /// - /// The app configuration. - /// The server configuration manager. - /// The directory path, or null if the server is not hosting the web client. - public static string? GetWebClientUiPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager) - { - if (!appConfig.HostWebClient()) - { - return null; - } - - if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath)) - { - return serverConfigManager.Configuration.DashboardSourcePath; - } - - return serverConfigManager.ApplicationPaths.WebPath; - } - private IEnumerable GetConfigPages(IPlugin plugin) { return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1)); diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 07a176edd4..7d720a5c5e 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -17,6 +17,7 @@ using Jellyfin.Api.Models.LiveTvDtos; using Jellyfin.Data.Enums; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -1071,7 +1072,7 @@ namespace Jellyfin.Api.Controllers [ProducesFile(MediaTypeNames.Application.Json)] public async Task GetSchedulesDirectCountries() { - var client = _httpClientFactory.CreateClient(); + var client = _httpClientFactory.CreateClient(NamedClient.Default); // https://json.schedulesdirect.org/20141201/available/countries // Can't dispose the response as it's required up the call chain. var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries") diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index e8c5ba7a94..5975964220 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -246,7 +247,7 @@ namespace Jellyfin.Api.Controllers /// Task. private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath) { - var httpClient = _httpClientFactory.CreateClient(); + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); using var response = await httpClient.GetAsync(url).ConfigureAwait(false); var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last(); var fullCachePath = GetFullCachePath(urlHash + "." + ext); diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index c45ac5fdc4..2bf5b48569 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -12,6 +12,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; @@ -475,7 +476,7 @@ namespace Jellyfin.Api.Controllers { StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); - var httpClient = _httpClientFactory.CreateClient(); + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false); } diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index eae2be05ba..a3f2d88ce5 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; @@ -138,7 +139,7 @@ namespace Jellyfin.Api.Helpers { StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager); - var httpClient = _httpClientFactory.CreateClient(); + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false); } diff --git a/Jellyfin.Api/MvcRoutePrefix.cs b/Jellyfin.Api/MvcRoutePrefix.cs deleted file mode 100644 index e009730947..0000000000 --- a/Jellyfin.Api/MvcRoutePrefix.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ApplicationModels; - -namespace Jellyfin.Api -{ - /// - /// Route prefixing for ASP.NET MVC. - /// - public static class MvcRoutePrefix - { - /// - /// Adds route prefixes to the MVC conventions. - /// - /// The MVC options. - /// The list of prefixes. - public static void UseGeneralRoutePrefix(this MvcOptions opts, params string[] prefixes) - { - opts.Conventions.Insert(0, new RoutePrefixConvention(prefixes)); - } - - private class RoutePrefixConvention : IApplicationModelConvention - { - private readonly AttributeRouteModel[] _routePrefixes; - - public RoutePrefixConvention(IEnumerable prefixes) - { - _routePrefixes = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p))).ToArray(); - } - - public void Apply(ApplicationModel application) - { - foreach (var controller in application.Controllers) - { - if (controller.Selectors == null) - { - continue; - } - - var newSelectors = new List(); - foreach (var selector in controller.Selectors) - { - newSelectors.AddRange(_routePrefixes.Select(routePrefix => new SelectorModel(selector) - { - AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(routePrefix, selector.AttributeRouteModel) - })); - } - - controller.Selectors.Clear(); - newSelectors.ForEach(selector => controller.Selectors.Add(selector)); - } - } - } - } -} diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 2b002c6f3c..c7fbfa4d02 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -1,6 +1,8 @@ +using System.Collections.Generic; using Jellyfin.Server.Middleware; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Builder; +using Microsoft.OpenApi.Models; namespace Jellyfin.Server.Extensions { @@ -23,6 +25,7 @@ namespace Jellyfin.Server.Extensions // specifying the Swagger JSON endpoint. var baseUrl = serverConfigurationManager.Configuration.BaseUrl.Trim('/'); + var apiDocBaseUrl = serverConfigurationManager.Configuration.BaseUrl; if (!string.IsNullOrEmpty(baseUrl)) { baseUrl += '/'; @@ -32,21 +35,25 @@ namespace Jellyfin.Server.Extensions .UseSwagger(c => { // Custom path requires {documentName}, SwaggerDoc documentName is 'api-docs' - c.RouteTemplate = $"/{baseUrl}{{documentName}}/openapi.json"; + c.RouteTemplate = "{documentName}/openapi.json"; + c.PreSerializeFilters.Add((swagger, httpReq) => + { + swagger.Servers = new List { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{apiDocBaseUrl}" } }; + }); }) .UseSwaggerUI(c => { c.DocumentTitle = "Jellyfin API"; c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API"); - c.RoutePrefix = $"{baseUrl}api-docs/swagger"; c.InjectStylesheet($"/{baseUrl}api-docs/swagger/custom.css"); + c.RoutePrefix = "api-docs/swagger"; }) .UseReDoc(c => { c.DocumentTitle = "Jellyfin API"; c.SpecUrl($"/{baseUrl}api-docs/openapi.json"); - c.RoutePrefix = $"{baseUrl}api-docs/redoc"; c.InjectStylesheet($"/{baseUrl}api-docs/redoc/custom.css"); + c.RoutePrefix = "api-docs/redoc"; }); } diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 04b6111234..31454265a0 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using Jellyfin.Api; using Jellyfin.Api.Auth; using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; using Jellyfin.Api.Auth.DownloadPolicy; @@ -19,7 +18,6 @@ using Jellyfin.Api.Controllers; using Jellyfin.Server.Filters; using Jellyfin.Server.Formatters; using Jellyfin.Server.Models; -using MediaBrowser.Common; using MediaBrowser.Common.Json; using MediaBrowser.Model.Entities; using Microsoft.AspNetCore.Authentication; @@ -136,10 +134,9 @@ namespace Jellyfin.Server.Extensions /// Extension method for adding the jellyfin API to the service collection. /// /// The service collection. - /// The base url for the API. - /// An IEnumberable containing all plugin assemblies with API controllers. + /// An IEnumerable containing all plugin assemblies with API controllers. /// The MVC builder. - public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl, IEnumerable pluginAssemblies) + public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable pluginAssemblies) { IMvcBuilder mvcBuilder = serviceCollection .AddCors(options => @@ -152,7 +149,9 @@ namespace Jellyfin.Server.Extensions }) .AddMvc(opts => { - opts.UseGeneralRoutePrefix(baseUrl); + // Allow requester to change between camelCase and PascalCase + opts.RespectBrowserAcceptHeader = true; + opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter()); opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter()); diff --git a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs index 9b347ae2c2..8043989b1e 100644 --- a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs +++ b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Formatters public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions()) { SupportedMediaTypes.Clear(); - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\"")); + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType)); } } } diff --git a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs index 0024708bad..d0110b125c 100644 --- a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs +++ b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs @@ -1,3 +1,4 @@ +using System.Net.Mime; using MediaBrowser.Common.Json; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; @@ -16,8 +17,8 @@ namespace Jellyfin.Server.Formatters { SupportedMediaTypes.Clear(); // Add application/json for default formatter - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json")); - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"PascalCase\"")); + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json)); + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.PascalCaseMediaType)); } } } diff --git a/Jellyfin.Server/Formatters/XmlOutputFormatter.cs b/Jellyfin.Server/Formatters/XmlOutputFormatter.cs index 58319657d6..01d99d7c87 100644 --- a/Jellyfin.Server/Formatters/XmlOutputFormatter.cs +++ b/Jellyfin.Server/Formatters/XmlOutputFormatter.cs @@ -16,8 +16,9 @@ namespace Jellyfin.Server.Formatters /// public XmlOutputFormatter() { + SupportedMediaTypes.Clear(); SupportedMediaTypes.Add(MediaTypeNames.Text.Xml); - SupportedMediaTypes.Add("text/xml;charset=UTF-8"); + SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); } diff --git a/Jellyfin.Server/HealthChecks/JellyfinDbHealthCheck.cs b/Jellyfin.Server/HealthChecks/JellyfinDbHealthCheck.cs deleted file mode 100644 index aea6844798..0000000000 --- a/Jellyfin.Server/HealthChecks/JellyfinDbHealthCheck.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Jellyfin.Server.Implementations; -using Microsoft.Extensions.Diagnostics.HealthChecks; - -namespace Jellyfin.Server.HealthChecks -{ - /// - /// Checks connectivity to the database. - /// - public class JellyfinDbHealthCheck : IHealthCheck - { - private readonly JellyfinDbProvider _dbProvider; - - /// - /// Initializes a new instance of the class. - /// - /// The jellyfin db provider. - public JellyfinDbHealthCheck(JellyfinDbProvider dbProvider) - { - _dbProvider = dbProvider; - } - - /// - public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) - { - await using var jellyfinDb = _dbProvider.CreateContext(); - if (await jellyfinDb.Database.CanConnectAsync(cancellationToken).ConfigureAwait(false)) - { - return HealthCheckResult.Healthy("Database connection successful."); - } - - return HealthCheckResult.Unhealthy("Unable to connect to the database."); - } - } -} diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 24ba8369a6..85d5f2a3f5 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -44,6 +44,7 @@ + diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs index 9316737bdf..ae3a3a1c54 100644 --- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs +++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs @@ -44,11 +44,7 @@ namespace Jellyfin.Server.Middleware var localPath = httpContext.Request.Path.ToString(); var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl; - if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) - || string.IsNullOrEmpty(localPath) - || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) + if (!localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) { // Always redirect back to the default path if the base prefix is invalid or missing _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index b9a90f9dbf..45959aec2d 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -169,7 +169,7 @@ namespace Jellyfin.Server // If hosting the web client, validate the client content path if (startupConfig.HostWebClient()) { - string? webContentPath = DashboardController.GetWebClientUiPath(startupConfig, appHost.ServerConfigurationManager); + string? webContentPath = appHost.ServerConfigurationManager.ApplicationPaths.WebPath; if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0) { throw new InvalidOperationException( diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 1a34de2695..597323b864 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -3,15 +3,18 @@ using System.ComponentModel; using System.Net.Http.Headers; using Jellyfin.Api.TypeConverters; using Jellyfin.Server.Extensions; -using Jellyfin.Server.HealthChecks; +using Jellyfin.Server.Implementations; using Jellyfin.Server.Middleware; using Jellyfin.Server.Models; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Prometheus; @@ -50,9 +53,7 @@ namespace Jellyfin.Server { options.HttpsPort = _serverApplicationHost.HttpsPort; }); - services.AddJellyfinApi( - _serverConfigurationManager.Configuration.BaseUrl.TrimStart('/'), - _serverApplicationHost.GetApiPluginAssemblies()); + services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies()); services.AddJellyfinApiSwagger(); @@ -79,7 +80,7 @@ namespace Jellyfin.Server .ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler()); services.AddHealthChecks() - .AddCheck("JellyfinDb"); + .AddDbContextCheck(); } /// @@ -87,57 +88,78 @@ namespace Jellyfin.Server /// /// The application builder. /// The webhost environment. + /// The application config. public void Configure( IApplicationBuilder app, - IWebHostEnvironment env) + IWebHostEnvironment env, + IConfiguration appConfig) { - if (env.IsDevelopment()) + // Only add base url redirection if a base url is set. + if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.BaseUrl)) { - app.UseDeveloperExceptionPage(); + app.UseBaseUrlRedirection(); } - app.UseMiddleware(); + // Wrap rest of configuration so everything only listens on BaseUrl. + app.Map(_serverConfigurationManager.Configuration.BaseUrl, mainApp => + { + if (env.IsDevelopment()) + { + mainApp.UseDeveloperExceptionPage(); + } - app.UseMiddleware(); + mainApp.UseMiddleware(); - app.UseWebSockets(); + mainApp.UseMiddleware(); - app.UseResponseCompression(); + mainApp.UseWebSockets(); - app.UseCors(ServerCorsPolicy.DefaultPolicyName); + mainApp.UseResponseCompression(); - if (_serverConfigurationManager.Configuration.RequireHttps - && _serverApplicationHost.ListenWithHttps) - { - app.UseHttpsRedirection(); - } + mainApp.UseCors(ServerCorsPolicy.DefaultPolicyName); - app.UseStaticFiles(); - app.UseAuthentication(); - app.UseJellyfinApiSwagger(_serverConfigurationManager); - app.UseRouting(); - app.UseAuthorization(); - if (_serverConfigurationManager.Configuration.EnableMetrics) - { - // Must be registered after any middleware that could chagne HTTP response codes or the data will be bad - app.UseHttpMetrics(); - } + if (_serverConfigurationManager.Configuration.RequireHttps + && _serverApplicationHost.ListenWithHttps) + { + mainApp.UseHttpsRedirection(); + } - app.UseLanFiltering(); - app.UseIpBasedAccessValidation(); - app.UseBaseUrlRedirection(); - app.UseWebSocketHandler(); - app.UseServerStartupMessage(); + mainApp.UseStaticFiles(); + if (appConfig.HostWebClient()) + { + mainApp.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath), + RequestPath = "/web" + }); + } + + mainApp.UseAuthentication(); + mainApp.UseJellyfinApiSwagger(_serverConfigurationManager); + mainApp.UseRouting(); + mainApp.UseAuthorization(); + + mainApp.UseLanFiltering(); + mainApp.UseIpBasedAccessValidation(); + mainApp.UseWebSocketHandler(); + mainApp.UseServerStartupMessage(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); if (_serverConfigurationManager.Configuration.EnableMetrics) { - endpoints.MapMetrics(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/') + "/metrics"); + // Must be registered after any middleware that could change HTTP response codes or the data will be bad + mainApp.UseHttpMetrics(); } - endpoints.MapHealthChecks(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/') + "/health"); + mainApp.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + if (_serverConfigurationManager.Configuration.EnableMetrics) + { + endpoints.MapMetrics("/metrics"); + } + + endpoints.MapHealthChecks("/health"); + }); }); // Add type descriptor for legacy datetime parsing. diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index 4cea616826..57c6546675 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -1,5 +1,3 @@ -using MediaBrowser.Model.Configuration; - namespace MediaBrowser.Common.Configuration { /// @@ -17,8 +15,7 @@ namespace MediaBrowser.Common.Configuration /// Gets the path to the web UI resources folder. /// /// - /// This value is not relevant if the server is configured to not host any static web content. Additionally, - /// the value for takes precedence over this one. + /// This value is not relevant if the server is configured to not host any static web content. /// string WebPath { get; } diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 5867cd4a0c..67f7e8f14a 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -9,6 +9,16 @@ namespace MediaBrowser.Common.Json /// public static class JsonDefaults { + /// + /// Pascal case json profile media type. + /// + public const string PascalCaseMediaType = "application/json; profile=\"PascalCase\""; + + /// + /// Camel case json profile media type. + /// + public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\""; + /// /// Gets the default options. /// diff --git a/MediaBrowser.Common/Net/CacheMode.cs b/MediaBrowser.Common/Net/CacheMode.cs deleted file mode 100644 index 78fa3bf9bb..0000000000 --- a/MediaBrowser.Common/Net/CacheMode.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1602 - -namespace MediaBrowser.Common.Net -{ - public enum CacheMode - { - None = 0, - Unconditional = 1 - } -} diff --git a/MediaBrowser.Common/Net/CompressionMethods.cs b/MediaBrowser.Common/Net/CompressionMethods.cs deleted file mode 100644 index 39b72609fe..0000000000 --- a/MediaBrowser.Common/Net/CompressionMethods.cs +++ /dev/null @@ -1,15 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1602 - -using System; - -namespace MediaBrowser.Common.Net -{ - [Flags] - public enum CompressionMethods - { - None = 0b00000001, - Deflate = 0b00000010, - Gzip = 0b00000100 - } -} diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs deleted file mode 100644 index 347fc98332..0000000000 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ /dev/null @@ -1,105 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Threading; -using Microsoft.Net.Http.Headers; - -namespace MediaBrowser.Common.Net -{ - /// - /// Class HttpRequestOptions. - /// - public class HttpRequestOptions - { - /// - /// Initializes a new instance of the class. - /// - public HttpRequestOptions() - { - RequestHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - - CacheMode = CacheMode.None; - DecompressionMethod = CompressionMethods.Deflate; - } - - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - - public CompressionMethods DecompressionMethod { get; set; } - - /// - /// Gets or sets the accept header. - /// - /// The accept header. - public string AcceptHeader - { - get => GetHeaderValue(HeaderNames.Accept); - set => RequestHeaders[HeaderNames.Accept] = value; - } - - /// - /// Gets or sets the cancellation token. - /// - /// The cancellation token. - public CancellationToken CancellationToken { get; set; } - - /// - /// Gets or sets the user agent. - /// - /// The user agent. - public string UserAgent - { - get => GetHeaderValue(HeaderNames.UserAgent); - set => RequestHeaders[HeaderNames.UserAgent] = value; - } - - /// - /// Gets or sets the referrer. - /// - /// The referrer. - public string Referer - { - get => GetHeaderValue(HeaderNames.Referer); - set => RequestHeaders[HeaderNames.Referer] = value; - } - - /// - /// Gets or sets the host. - /// - /// The host. - public string Host - { - get => GetHeaderValue(HeaderNames.Host); - set => RequestHeaders[HeaderNames.Host] = value; - } - - public Dictionary RequestHeaders { get; private set; } - - public string RequestContentType { get; set; } - - public string RequestContent { get; set; } - - public bool BufferContent { get; set; } - - public bool LogErrorResponseBody { get; set; } - - public bool EnableKeepAlive { get; set; } - - public CacheMode CacheMode { get; set; } - - public TimeSpan CacheLength { get; set; } - - public bool EnableDefaultUserAgent { get; set; } - - private string GetHeaderValue(string name) - { - RequestHeaders.TryGetValue(name, out var value); - - return value; - } - } -} diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs deleted file mode 100644 index d4fee6c78e..0000000000 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Http.Headers; - -namespace MediaBrowser.Common.Net -{ - /// - /// Class HttpResponseInfo. - /// - public sealed class HttpResponseInfo : IDisposable - { -#pragma warning disable CS1591 - public HttpResponseInfo() - { - } - - public HttpResponseInfo(HttpResponseHeaders headers, HttpContentHeaders contentHeader) - { - Headers = headers; - ContentHeaders = contentHeader; - } - -#pragma warning restore CS1591 - - /// - /// Gets or sets the type of the content. - /// - /// The type of the content. - public string ContentType { get; set; } - - /// - /// Gets or sets the response URL. - /// - /// The response URL. - public string ResponseUrl { get; set; } - - /// - /// Gets or sets the content. - /// - /// The content. - public Stream Content { get; set; } - - /// - /// Gets or sets the status code. - /// - /// The status code. - public HttpStatusCode StatusCode { get; set; } - - /// - /// Gets or sets the temp file path. - /// - /// The temp file path. - public string TempFilePath { get; set; } - - /// - /// Gets or sets the length of the content. - /// - /// The length of the content. - public long? ContentLength { get; set; } - - /// - /// Gets or sets the headers. - /// - /// The headers. - public HttpResponseHeaders Headers { get; set; } - - /// - /// Gets or sets the content headers. - /// - /// The content headers. - public HttpContentHeaders ContentHeaders { get; set; } - - /// - public void Dispose() - { - // backwards compatibility - } - } -} diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs deleted file mode 100644 index 534e22eddf..0000000000 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.IO; -using System.Net.Http; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net -{ - /// - /// Interface IHttpClient. - /// - public interface IHttpClient - { - /// - /// Gets the response. - /// - /// The options. - /// Task{HttpResponseInfo}. - Task GetResponse(HttpRequestOptions options); - - /// - /// Gets the specified options. - /// - /// The options. - /// Task{Stream}. - Task Get(HttpRequestOptions options); - - /// - /// Warning: Deprecated function, - /// use 'Task{HttpResponseInfo} SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead - /// Sends the asynchronous. - /// - /// The options. - /// The HTTP method. - /// Task{HttpResponseInfo}. - [Obsolete("Use 'Task{HttpResponseInfo} SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead")] - Task SendAsync(HttpRequestOptions options, string httpMethod); - - /// - /// Sends the asynchronous. - /// - /// The options. - /// The HTTP method. - /// Task{HttpResponseInfo}. - Task SendAsync(HttpRequestOptions options, HttpMethod httpMethod); - - /// - /// Posts the specified options. - /// - /// The options. - /// Task{HttpResponseInfo}. - Task Post(HttpRequestOptions options); - } -} diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index a5c22e50f4..68126bd8a8 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -60,8 +60,6 @@ namespace MediaBrowser.Controller.Entities protected BaseItem() { - ThemeSongIds = Array.Empty(); - ThemeVideoIds = Array.Empty(); Tags = Array.Empty(); Genres = Array.Empty(); Studios = Array.Empty(); @@ -100,12 +98,52 @@ namespace MediaBrowser.Controller.Entities }; [JsonIgnore] - public Guid[] ThemeSongIds { get; set; } + public Guid[] ThemeSongIds + { + get + { + if (_themeSongIds == null) + { + _themeSongIds = GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong) + .Select(song => song.Id) + .ToArray(); + } + + return _themeSongIds; + } + + private set + { + _themeSongIds = value; + } + } + [JsonIgnore] - public Guid[] ThemeVideoIds { get; set; } + public Guid[] ThemeVideoIds + { + get + { + if (_themeVideoIds == null) + { + _themeVideoIds = GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo) + .Select(song => song.Id) + .ToArray(); + } + + return _themeVideoIds; + } + + private set + { + _themeVideoIds = value; + } + } [JsonIgnore] public string PreferredMetadataCountryCode { get; set; } + [JsonIgnore] public string PreferredMetadataLanguage { get; set; } @@ -635,6 +673,9 @@ namespace MediaBrowser.Controller.Entities } private string _sortName; + private Guid[] _themeSongIds; + private Guid[] _themeVideoIds; + /// /// Gets the name of the sort. /// @@ -1582,7 +1623,8 @@ namespace MediaBrowser.Controller.Entities await Task.WhenAll(tasks).ConfigureAwait(false); - item.ThemeVideoIds = newThemeVideoIds; + // They are expected to be sorted by SortName + item.ThemeVideoIds = newThemeVideos.OrderBy(i => i.SortName).Select(i => i.Id).ToArray(); return themeVideosChanged; } @@ -1619,7 +1661,8 @@ namespace MediaBrowser.Controller.Entities await Task.WhenAll(tasks).ConfigureAwait(false); - item.ThemeSongIds = newThemeSongIds; + // They are expected to be sorted by SortName + item.ThemeSongIds = newThemeSongs.OrderBy(i => i.SortName).Select(i => i.Id).ToArray(); return themeSongsChanged; } @@ -2910,12 +2953,12 @@ namespace MediaBrowser.Controller.Entities public IEnumerable GetThemeSongs() { - return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeSong)).OrderBy(i => i.SortName); + return ThemeSongIds.Select(LibraryManager.GetItemById); } public IEnumerable GetThemeVideos() { - return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeVideo)).OrderBy(i => i.SortName); + return ThemeVideoIds.Select(LibraryManager.GetItemById); } /// diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9f6b2be8fa..2c30ca4588 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -456,6 +456,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + var isNvencHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); @@ -515,6 +516,24 @@ namespace MediaBrowser.Controller.MediaEncoding } } + if (state.IsVideoRequest + && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + var isColorDepth10 = IsColorDepth10(state); + + if (isNvencHevcDecoder && isColorDepth10 + && _mediaEncoder.SupportsHwaccel("opencl") + && encodingOptions.EnableTonemapping + && !string.IsNullOrEmpty(state.VideoStream.VideoRange) + && state.VideoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) + { + arg.Append("-init_hw_device opencl=ocl:") + .Append(encodingOptions.OpenclDevice) + .Append(' ') + .Append("-filter_hw_device ocl "); + } + } + if (state.IsVideoRequest && string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) { @@ -1003,11 +1022,33 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt yuv420p " + param; } + if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + { + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; + var videoStream = state.VideoStream; + var isColorDepth10 = IsColorDepth10(state); + + if (videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1 + && isColorDepth10 + && _mediaEncoder.SupportsHwaccel("opencl") + && encodingOptions.EnableTonemapping + && !string.IsNullOrEmpty(videoStream.VideoRange) + && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) + { + param = "-pix_fmt nv12 " + param; + } + else + { + param = "-pix_fmt yuv420p " + param; + } + } + if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt nv21 " + param; @@ -1611,64 +1652,45 @@ namespace MediaBrowser.Controller.MediaEncoding var outputSizeParam = ReadOnlySpan.Empty; var request = state.BaseRequest; - // Add resolution params, if specified - if (request.Width.HasValue - || request.Height.HasValue - || request.MaxHeight.HasValue - || request.MaxWidth.HasValue) + outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); + + // All possible beginning of video filters + // Don't break the order + string[] beginOfOutputSizeParam = new[] { - outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); + // for tonemap_opencl + "hwupload,tonemap_opencl", // hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux) - var index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase); + "hwupload=extra_hw_frames", + + // vpp_qsv + "vpp", + + // hwdownload,format=p010le (hardware decode + software encode for vaapi) + "hwdownload", + + // format=nv12|vaapi,hwupload,scale_vaapi + "format", + + // bwdif,scale=expr + "bwdif", + + // yadif,scale=expr + "yadif", + + // scale=expr + "scale" + }; + + var index = -1; + foreach (var param in beginOfOutputSizeParam) + { + index = outputSizeParam.IndexOf(param, StringComparison.OrdinalIgnoreCase); if (index != -1) { outputSizeParam = outputSizeParam.Slice(index); - } - else - { - // vpp_qsv - index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - outputSizeParam = outputSizeParam.Slice(index); - } - else - { - // hwdownload,format=p010le (hardware decode + software encode for vaapi) - index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - outputSizeParam = outputSizeParam.Slice(index); - } - else - { - // format=nv12|vaapi,hwupload,scale_vaapi - index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - outputSizeParam = outputSizeParam.Slice(index); - } - else - { - // yadif,scale=expr - index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - outputSizeParam = outputSizeParam.Slice(index); - } - else - { - // scale=expr - index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - outputSizeParam = outputSizeParam.Slice(index); - } - } - } - } - } + break; } } @@ -1747,9 +1769,9 @@ namespace MediaBrowser.Controller.MediaEncoding */ if (isLinux) { - retStr = !outputSizeParam.IsEmpty ? - " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" : - " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""; + retStr = !outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""; } } @@ -2084,8 +2106,10 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + var isColorDepth10 = IsColorDepth10(state); var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -2093,6 +2117,50 @@ namespace MediaBrowser.Controller.MediaEncoding // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30; + // Currently only with the use of NVENC decoder can we get a decent performance. + // Currently only the HEVC/H265 format is supported. + // NVIDIA Pascal and Turing or higher are recommended. + if (isNvdecHevcDecoder && isColorDepth10 + && _mediaEncoder.SupportsHwaccel("opencl") + && options.EnableTonemapping + && !string.IsNullOrEmpty(videoStream.VideoRange) + && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) + { + var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; + + if (options.TonemappingParam != 0) + { + parameters += ":param={4}"; + } + + if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) + { + parameters += ":range={5}"; + } + + // Upload the HDR10 or HLG data to the OpenCL device, + // use tonemap_opencl filter for tone mapping, + // and then download the SDR data to memory. + filters.Add("hwupload"); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + parameters, + options.TonemappingAlgorithm, + options.TonemappingDesat, + options.TonemappingThreshold, + options.TonemappingPeak, + options.TonemappingParam, + options.TonemappingRange)); + filters.Add("hwdownload"); + + if (hasGraphicalSubs || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true) + || string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) + { + filters.Add("format=nv12"); + } + } + // When the input may or may not be hardware VAAPI decodable if (isVaapiH264Encoder) { @@ -2110,7 +2178,6 @@ namespace MediaBrowser.Controller.MediaEncoding else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder) { var codec = videoStream.Codec.ToLowerInvariant(); - var isColorDepth10 = IsColorDepth10(state); // Assert 10-bit hardware VAAPI decodable if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 814edd7323..6ead93e09b 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -24,6 +24,7 @@ + diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs index b7b23deff5..8a7c032c54 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -282,6 +282,20 @@ namespace MediaBrowser.MediaEncoding.Probing [JsonPropertyName("disposition")] public IReadOnlyDictionary Disposition { get; set; } + /// + /// Gets or sets the color range. + /// + /// The color range. + [JsonPropertyName("color_range")] + public string ColorRange { get; set; } + + /// + /// Gets or sets the color space. + /// + /// The color space. + [JsonPropertyName("color_space")] + public string ColorSpace { get; set; } + /// /// Gets or sets the color transfer. /// diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 40a3b43e1c..22537a4d95 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -714,6 +714,16 @@ namespace MediaBrowser.MediaEncoding.Probing stream.RefFrames = streamInfo.Refs; } + if (!string.IsNullOrEmpty(streamInfo.ColorRange)) + { + stream.ColorRange = streamInfo.ColorRange; + } + + if (!string.IsNullOrEmpty(streamInfo.ColorSpace)) + { + stream.ColorSpace = streamInfo.ColorSpace; + } + if (!string.IsNullOrEmpty(streamInfo.ColorTransfer)) { stream.ColorTransfer = streamInfo.ColorTransfer; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 6ac5ac2ff1..0a9958b9eb 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -31,7 +32,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IMediaSourceManager _mediaSourceManager; /// @@ -46,7 +47,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, IMediaSourceManager mediaSourceManager) { _libraryManager = libraryManager; @@ -54,7 +55,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles _appPaths = appPaths; _fileSystem = fileSystem; _mediaEncoder = mediaEncoder; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _mediaSourceManager = mediaSourceManager; } @@ -750,24 +751,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - private Task GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken) + private async Task GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken) { switch (protocol) { case MediaProtocol.Http: - var opts = new HttpRequestOptions() - { - Url = path, - CancellationToken = cancellationToken, - - // Needed for seeking - BufferContent = true - }; - - return _httpClient.Get(opts); + { + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetAsync(path, cancellationToken) + .ConfigureAwait(false); + return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + } case MediaProtocol.File: - return Task.FromResult(File.OpenRead(path)); + return File.OpenRead(path); default: throw new ArgumentOutOfRangeException(nameof(protocol)); } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 41ff517a01..2cd637c5b0 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -31,6 +31,22 @@ namespace MediaBrowser.Model.Configuration public string VaapiDevice { get; set; } + public string OpenclDevice { get; set; } + + public bool EnableTonemapping { get; set; } + + public string TonemappingAlgorithm { get; set; } + + public string TonemappingRange { get; set; } + + public double TonemappingDesat { get; set; } + + public double TonemappingThreshold { get; set; } + + public double TonemappingPeak { get; set; } + + public double TonemappingParam { get; set; } + public int H264Crf { get; set; } public int H265Crf { get; set; } @@ -58,8 +74,19 @@ namespace MediaBrowser.Model.Configuration EnableThrottling = false; ThrottleDelaySeconds = 180; EncodingThreadCount = -1; - // This is a DRM device that is almost guaranteed to be there on every intel platform, plus it's the default one in ffmpeg if you don't specify anything + // This is a DRM device that is almost guaranteed to be there on every intel platform, + // plus it's the default one in ffmpeg if you don't specify anything VaapiDevice = "/dev/dri/renderD128"; + // This is the OpenCL device that is used for tonemapping. + // The left side of the dot is the platform number, and the right side is the device number on the platform. + OpenclDevice = "0.0"; + EnableTonemapping = false; + TonemappingAlgorithm = "reinhard"; + TonemappingRange = "auto"; + TonemappingDesat = 0; + TonemappingThreshold = 0.8; + TonemappingPeak = 0; + TonemappingParam = 0; H264Crf = 23; H265Crf = 28; DeinterlaceDoubleRate = false; diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 33975bc1ee..97748bd0ca 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -166,12 +166,6 @@ namespace MediaBrowser.Model.Configuration /// true if [enable dashboard response caching]; otherwise, false. public bool EnableDashboardResponseCaching { get; set; } - /// - /// Gets or sets a custom path to serve the dashboard from. - /// - /// The dashboard source path, or null if the default path should be used. - public string DashboardSourcePath { get; set; } - /// /// Gets or sets the image saving convention. /// diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index f9ec0d2388..2d37618c27 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -35,6 +35,18 @@ namespace MediaBrowser.Model.Entities /// The language. public string Language { get; set; } + /// + /// Gets or sets the color range. + /// + /// The color range. + public string ColorRange { get; set; } + + /// + /// Gets or sets the color space. + /// + /// The color space. + public string ColorSpace { get; set; } + /// /// Gets or sets the color transfer. /// @@ -47,12 +59,6 @@ namespace MediaBrowser.Model.Entities /// The color primaries. public string ColorPrimaries { get; set; } - /// - /// Gets or sets the color space. - /// - /// The color space. - public string ColorSpace { get; set; } - /// /// Gets or sets the comment. /// diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 771ca84f7c..afe7351d32 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -125,7 +125,7 @@ namespace MediaBrowser.Model.Net { ".wmv", "video/x-ms-wmv" }, // Type audio - { ".aac", "audio/mp4" }, + { ".aac", "audio/aac" }, { ".ac3", "audio/ac3" }, { ".ape", "audio/x-ape" }, { ".dsf", "audio/dsf" }, diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 155e8cb8ab..171b824ca6 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Manager /// public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken) { - var httpClient = _httpClientFactory.CreateClient(); + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); var contentType = response.Content.Headers.ContentType.MediaType; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs index c9dac9ecd8..e3a1decb88 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -96,7 +97,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { - var httpClient = _httpClientFactory.CreateClient(); + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); return httpClient.GetAsync(url, cancellationToken); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index 321144edf9..e6d89e6880 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; @@ -173,7 +174,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb Directory.CreateDirectory(Path.GetDirectoryName(path)); - using var response = await _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs index 6512668681..54851c4d07 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -137,7 +138,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public Task GetImageResponse(string url, CancellationToken cancellationToken) { - var httpClient = _httpClientFactory.CreateClient(); + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); return httpClient.GetAsync(url, cancellationToken); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index 708426500b..670c0cd05a 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; @@ -154,7 +155,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); - using var response = await _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(path)); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs index 7f10e6922b..8414c93281 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs @@ -14,6 +14,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; using MediaBrowser.Common; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -765,7 +766,7 @@ namespace MediaBrowser.Providers.Music _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); _stopWatchMusicBrainz.Restart(); - response = await _httpClientFactory.CreateClient().SendAsync(options).ConfigureAwait(false); + response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options).ConfigureAwait(false); // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs index c18725e0aa..8b8fea09e8 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -82,7 +83,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } public string Name => "The Open Movie Database"; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 102ad82e17..d53eba7e94 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -9,6 +9,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -129,7 +130,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var url = OmdbProvider.GetOmdbUrl(urlQuery); - using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false); + using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var resultList = new List(); @@ -274,7 +275,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } class SearchResult diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index c45149c3a7..32dab60a6b 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; @@ -296,7 +297,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb "i={0}&plot=short&tomatoes=true&r=json", imdbParam)); - using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false); + using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var rootObject = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(path)); @@ -334,7 +335,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb imdbParam, seasonId)); - using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false); + using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var rootObject = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(path)); diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs index 4e7c0e5a60..de2f6875f8 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Globalization; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; @@ -116,7 +117,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs index 90436c7c99..07167bda63 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; @@ -244,7 +245,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } public int Order => 0; diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs index 388a4e3e72..dc3c60dee0 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -106,7 +107,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs index ff8c3455fa..49576d488d 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; @@ -148,7 +149,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs index d287828eb5..d96840e51c 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -146,7 +147,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index c6dd8a5f3d..ca9b1d738f 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -410,7 +411,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index f434440283..f6592afe46 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; @@ -155,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs index 4da2c042f6..e627550f13 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -10,6 +10,7 @@ using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; @@ -271,7 +272,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs index 60d7a599ea..a975fb8f6a 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; @@ -204,7 +205,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index d8918bb6b9..f8bc19395f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -383,7 +384,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies internal Task GetMovieDbResponse(HttpRequestMessage message) { message.Headers.UserAgent.ParseAdd(_appHost.ApplicationUserAgent); - return _httpClientFactory.CreateClient().SendAsync(message); + return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(message); } /// @@ -392,7 +393,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index 291b360272..f2d2c8120e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; @@ -131,7 +132,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index a4b1387d3b..6bf04b81ae 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; @@ -267,7 +268,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs index 5705885b46..30b7674e33 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs @@ -7,6 +7,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; @@ -138,7 +139,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV protected Task GetResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index 787514d05d..e7e2fd05b8 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; @@ -38,7 +39,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index e59504cc6d..73ed13267d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -9,6 +9,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -124,7 +125,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } private async Task GetSeasonInfo(string seriesTmdbId, int season, string preferredMetadataLanguage, diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index f11eeb15b3..125560175e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; @@ -182,7 +183,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 0eded32330..aaba6ffc06 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -10,6 +10,7 @@ using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -551,7 +552,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs index 10374bde9e..25296387ba 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Providers; @@ -36,7 +37,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 321153c6be..76dc7df7f7 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; @@ -122,7 +123,7 @@ namespace MediaBrowser.Providers.Studios public Task GetImageResponse(string url, CancellationToken cancellationToken) { - var httpClient = _httpClientFactory.CreateClient(); + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); return httpClient.GetAsync(url, cancellationToken); } @@ -140,7 +141,7 @@ namespace MediaBrowser.Providers.Studios if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1) { - var httpClient = _httpClientFactory.CreateClient(); + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); Directory.CreateDirectory(Path.GetDirectoryName(file)); await using var response = await httpClient.GetStreamAsync(url).ConfigureAwait(false); diff --git a/debian/rules b/debian/rules index 6c7fbb779d..96541f41b8 100755 --- a/debian/rules +++ b/debian/rules @@ -40,7 +40,7 @@ override_dh_clistrip: override_dh_auto_build: dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \ - "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server + "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server override_dh_auto_clean: dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64 index cf09014045..e04442606e 100644 --- a/deployment/Dockerfile.docker.amd64 +++ b/deployment/Dockerfile.docker.amd64 @@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 # because of changes in docker and systemd we need to not build in parallel at the moment # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting -RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" +RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none" diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64 index 5454ef0247..a7ac40492a 100644 --- a/deployment/Dockerfile.docker.arm64 +++ b/deployment/Dockerfile.docker.arm64 @@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 # because of changes in docker and systemd we need to not build in parallel at the moment # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting -RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" +RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none" diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf index d99fa2a85c..b5a42f55ff 100644 --- a/deployment/Dockerfile.docker.armhf +++ b/deployment/Dockerfile.docker.armhf @@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 # because of changes in docker and systemd we need to not build in parallel at the moment # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting -RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" +RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none" diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64 index d0a5c1747a..a1e7f661a5 100755 --- a/deployment/build.linux.amd64 +++ b/deployment/build.linux.amd64 @@ -16,7 +16,7 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true" +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version} rm -rf dist/jellyfin-server_${version} diff --git a/deployment/build.macos b/deployment/build.macos index 8bb8b2ebdb..6255c80cb2 100755 --- a/deployment/build.macos +++ b/deployment/build.macos @@ -16,7 +16,7 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true" +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version} rm -rf dist/jellyfin-server_${version} diff --git a/deployment/build.portable b/deployment/build.portable index 4c81d5672c..ea40ade5d9 100755 --- a/deployment/build.portable +++ b/deployment/build.portable @@ -16,7 +16,7 @@ else fi # Build archives -dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true" +dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version} rm -rf dist/jellyfin-server_${version} diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64 index 47cfa2cd6f..afe4905769 100755 --- a/deployment/build.windows.amd64 +++ b/deployment/build.windows.amd64 @@ -23,7 +23,7 @@ fi output_dir="dist/jellyfin-server_${version}" # Build binary -dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true" +dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" # Prepare addins addin_build_dir="$( mktemp -d )" diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 291017d947..37b573e505 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -54,7 +54,7 @@ The Jellyfin media server backend. export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \ - "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server + "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE %{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf %{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json