From 4be476ec5312387f87134915d0fd132b2ad5fa3f Mon Sep 17 00:00:00 2001
From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com>
Date: Thu, 18 Jun 2020 01:29:47 -0500
Subject: Move all settings into the main server configuration
Decreased the timeout from 30 minutes to 5.
Public lookup values have been replaced with the short code.
---
MediaBrowser.Model/Configuration/ServerConfiguration.cs | 6 ++++++
1 file changed, 6 insertions(+)
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index afbe02dd3..76b290606 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -76,6 +76,11 @@ namespace MediaBrowser.Model.Configuration
/// true if this instance is port authorized; otherwise, false.
public bool IsPortAuthorized { get; set; }
+ ///
+ /// Gets or sets if quick connect is available for use on this server.
+ ///
+ public bool QuickConnectAvailable { get; set; }
+
public bool AutoRunWebApp { get; set; }
public bool EnableRemoteAccess { get; set; }
@@ -281,6 +286,7 @@ namespace MediaBrowser.Model.Configuration
AutoRunWebApp = true;
EnableRemoteAccess = true;
+ QuickConnectAvailable = false;
EnableUPnP = false;
MinResumePct = 5;
--
cgit v1.2.3
From 7b862bba5aad345f0bc8d76bfb950590471ae232 Mon Sep 17 00:00:00 2001
From: nyanmisaka
Date: Sat, 25 Jul 2020 00:57:34 +0800
Subject: add Tonemapping relaying on nvdec and ocl
---
.../MediaEncoding/EncodingHelper.cs | 109 +++++++++++++++++++--
.../Probing/MediaStreamInfo.cs | 14 +++
.../Probing/ProbeResultNormalizer.cs | 10 ++
.../Configuration/EncodingOptions.cs | 29 +++++-
MediaBrowser.Model/Entities/MediaStream.cs | 18 ++--
5 files changed, 166 insertions(+), 14 deletions(-)
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 0242338b7..4a33f22e6 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -454,6 +454,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);
@@ -511,6 +512,25 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
}
+
+ if (state.IsVideoRequest
+ && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ var codec = state.VideoStream.Codec.ToLowerInvariant();
+ 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 ");
+ }
+ }
}
arg.Append("-i ")
@@ -994,11 +1014,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;
@@ -1599,46 +1641,54 @@ namespace MediaBrowser.Controller.MediaEncoding
{
outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
- var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
+ var index = outputSizeParam.IndexOf("hwupload,tonemap_opencl", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Substring(index);
}
else
{
- index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase);
+ index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Substring(index);
}
else
{
- index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
+ index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Substring(index);
}
else
{
- index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
+ index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Substring(index);
}
else
{
- index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
+ index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Substring(index);
}
else
{
- index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase);
+ index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Substring(index);
}
+ else
+ {
+ index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase);
+ if (index != -1)
+ {
+ outputSizeParam = outputSizeParam.Substring(index);
+ }
+ }
}
}
}
@@ -2058,12 +2108,58 @@ 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;
+ // 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)
{
@@ -2081,7 +2177,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/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
index a2ea0766a..d9658cba2 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -279,6 +279,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 c85d963ed..3815e00d4 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -703,6 +703,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.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 9a30f7e9f..843ff3ff9 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -29,6 +29,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; }
@@ -53,8 +69,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;
DeinterlaceMethod = "yadif";
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 1b37cfc93..19eb79acc 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.
///
--
cgit v1.2.3
From 7df2affb238e92c9b03813f5aa11530cb37dcbdc Mon Sep 17 00:00:00 2001
From: Orry Verducci
Date: Mon, 10 Aug 2020 22:14:47 +0100
Subject: Add double rate deinterlacing option
---
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 3 +++
MediaBrowser.Model/Configuration/EncodingOptions.cs | 3 +++
2 files changed, 6 insertions(+)
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 913e171f1..81606aa0c 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -2071,6 +2071,9 @@ namespace MediaBrowser.Controller.MediaEncoding
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ // 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);
+
// When the input may or may not be hardware VAAPI decodable
if (isVaapiH264Encoder)
{
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 9a30f7e9f..d7785f39c 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -35,6 +35,8 @@ namespace MediaBrowser.Model.Configuration
public string EncoderPreset { get; set; }
+ public bool DeinterlaceDoubleRate { get; set; }
+
public string DeinterlaceMethod { get; set; }
public bool EnableDecodingColorDepth10Hevc { get; set; }
@@ -57,6 +59,7 @@ namespace MediaBrowser.Model.Configuration
VaapiDevice = "/dev/dri/renderD128";
H264Crf = 23;
H265Crf = 28;
+ DeinterlaceDoubleRate = false;
DeinterlaceMethod = "yadif";
EnableDecodingColorDepth10Hevc = true;
EnableDecodingColorDepth10Vp9 = true;
--
cgit v1.2.3
From d7caf88df67b2cccedc9bcbc19e80d95b997fb49 Mon Sep 17 00:00:00 2001
From: nyanmisaka
Date: Tue, 25 Aug 2020 02:20:46 +0800
Subject: expose max_muxing_queue_size to user
---
Jellyfin.Api/Controllers/DynamicHlsController.cs | 7 ++++++-
MediaBrowser.Model/Configuration/EncodingOptions.cs | 3 +++
2 files changed, 9 insertions(+), 1 deletion(-)
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index b115ac6cd..fec7f2579 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -1354,15 +1354,20 @@ namespace Jellyfin.Api.Controllers
segmentFormat = "mpegts";
}
+ var maxMuxingQueueSize = encodingOptions.MaxMuxingQueueSize >= 128 && encodingOptions.MaxMuxingQueueSize <= int.MaxValue
+ ? encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
+ : "128";
+
return string.Format(
CultureInfo.InvariantCulture,
- "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size 2048 -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
+ "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -individual_header_trailer 0 -hls_segment_type {8} -start_number {9} -hls_segment_filename \"{10}\" -hls_playlist_type vod -hls_list_size 0 -y \"{11}\"",
inputModifier,
_encodingHelper.GetInputArgument(state, encodingOptions),
threads,
mapArgs,
GetVideoArguments(state, encodingOptions, startNumber),
GetAudioArguments(state, encodingOptions),
+ maxMuxingQueueSize,
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
segmentFormat,
startNumberParam,
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 9a30f7e9f..2e548e8b4 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration
public double DownMixAudioBoost { get; set; }
+ public int MaxMuxingQueueSize { get; set; }
+
public bool EnableThrottling { get; set; }
public int ThrottleDelaySeconds { get; set; }
@@ -50,6 +52,7 @@ namespace MediaBrowser.Model.Configuration
public EncodingOptions()
{
DownMixAudioBoost = 2;
+ MaxMuxingQueueSize = 2048;
EnableThrottling = false;
ThrottleDelaySeconds = 180;
EncodingThreadCount = -1;
--
cgit v1.2.3
From 3c0484cc9730c06892b996d0b884a05ecada07af Mon Sep 17 00:00:00 2001
From: crobibero
Date: Sun, 30 Aug 2020 09:32:14 -0600
Subject: Allow for dynamic cors response
---
.../Extensions/ApiServiceCollectionExtensions.cs | 8 ++-
.../Middleware/DynamicCorsMiddleware.cs | 68 ++++++++++++++++++++++
Jellyfin.Server/Models/ServerCorsPolicy.cs | 43 +++++++++-----
Jellyfin.Server/Startup.cs | 8 ++-
.../Configuration/ServerConfiguration.cs | 6 ++
5 files changed, 115 insertions(+), 18 deletions(-)
create mode 100644 Jellyfin.Server/Middleware/DynamicCorsMiddleware.cs
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 0fd599cfc..b2f861542 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -135,13 +135,17 @@ namespace Jellyfin.Server.Extensions
///
/// The service collection.
/// The base url for the API.
+ /// The configured cors hosts.
/// The MVC builder.
- public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl)
+ public static IMvcBuilder AddJellyfinApi(
+ this IServiceCollection serviceCollection,
+ string baseUrl,
+ string[] corsHosts)
{
return serviceCollection
.AddCors(options =>
{
- options.AddPolicy(ServerCorsPolicy.DefaultPolicyName, ServerCorsPolicy.DefaultPolicy);
+ options.AddPolicy(ServerCorsPolicy.DefaultPolicyName, new ServerCorsPolicy(corsHosts).Policy);
})
.Configure(options =>
{
diff --git a/Jellyfin.Server/Middleware/DynamicCorsMiddleware.cs b/Jellyfin.Server/Middleware/DynamicCorsMiddleware.cs
new file mode 100644
index 000000000..4fad898a7
--- /dev/null
+++ b/Jellyfin.Server/Middleware/DynamicCorsMiddleware.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Cors.Infrastructure;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Jellyfin.Server.Middleware
+{
+ ///
+ /// Dynamic cors middleware.
+ ///
+ public class DynamicCorsMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+ private readonly CorsMiddleware _corsMiddleware;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Next request delegate.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// The cors policy name.
+ public DynamicCorsMiddleware(
+ RequestDelegate next,
+ ICorsService corsService,
+ ILoggerFactory loggerFactory,
+ string policyName)
+ {
+ _corsMiddleware = new CorsMiddleware(next, corsService, loggerFactory, policyName);
+ _next = next;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ ///
+ /// Invoke request.
+ ///
+ /// Request context.
+ /// Instance of the interface.
+ /// Task.
+ ///
+ public async Task Invoke(HttpContext context, ICorsPolicyProvider corsPolicyProvider)
+ {
+ // Only execute if is preflight request.
+ if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase))
+ {
+ // Invoke original cors middleware.
+ await _corsMiddleware.Invoke(context, corsPolicyProvider).ConfigureAwait(false);
+ if (context.Response.Headers.TryGetValue(HeaderNames.AccessControlAllowOrigin, out var headerValue)
+ && string.Equals(headerValue, "*", StringComparison.Ordinal))
+ {
+ context.Response.Headers[HeaderNames.AccessControlAllowOrigin] = context.Request.Host.Value;
+ _logger.LogDebug("Overwriting CORS response header: {HeaderName}: {HeaderValue}", HeaderNames.AccessControlAllowOrigin, context.Request.Host.Value);
+
+ if (!context.Response.Headers.ContainsKey(HeaderNames.AccessControlAllowCredentials))
+ {
+ context.Response.Headers[HeaderNames.AccessControlAllowCredentials] = "true";
+ }
+ }
+ }
+
+ // Call the next delegate/middleware in the pipeline
+ await this._next(context).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/Jellyfin.Server/Models/ServerCorsPolicy.cs b/Jellyfin.Server/Models/ServerCorsPolicy.cs
index ae010c042..3a45db3b4 100644
--- a/Jellyfin.Server/Models/ServerCorsPolicy.cs
+++ b/Jellyfin.Server/Models/ServerCorsPolicy.cs
@@ -1,30 +1,47 @@
-using Microsoft.AspNetCore.Cors.Infrastructure;
+using System;
+using Microsoft.AspNetCore.Cors.Infrastructure;
namespace Jellyfin.Server.Models
{
///
/// Server Cors Policy.
///
- public static class ServerCorsPolicy
+ public class ServerCorsPolicy
{
///
/// Default policy name.
///
- public const string DefaultPolicyName = "DefaultCorsPolicy";
+ public const string DefaultPolicyName = nameof(ServerCorsPolicy);
///
- /// Default Policy. Allow Everything.
+ /// Initializes a new instance of the class.
///
- public static readonly CorsPolicy DefaultPolicy = new CorsPolicy
+ /// The configured cors hosts.
+ public ServerCorsPolicy(string[] corsHosts)
{
- // Allow any origin
- Origins = { "*" },
+ var builder = new CorsPolicyBuilder()
+ .AllowAnyMethod()
+ .AllowAnyHeader();
- // Allow any method
- Methods = { "*" },
+ // No hosts configured or only default configured.
+ if (corsHosts.Length == 0
+ || (corsHosts.Length == 1
+ && string.Equals(corsHosts[0], "*", StringComparison.Ordinal)))
+ {
+ builder.AllowAnyOrigin();
+ }
+ else
+ {
+ builder.WithOrigins(corsHosts)
+ .AllowCredentials();
+ }
- // Allow any header
- Headers = { "*" }
- };
+ Policy = builder.Build();
+ }
+
+ ///
+ /// Gets the cors policy.
+ ///
+ public CorsPolicy Policy { get; }
}
-}
\ No newline at end of file
+}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index d0dd183c6..76f5e69ce 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -38,7 +38,9 @@ namespace Jellyfin.Server
{
services.AddResponseCompression();
services.AddHttpContextAccessor();
- services.AddJellyfinApi(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/'));
+ services.AddJellyfinApi(
+ _serverConfigurationManager.Configuration.BaseUrl.TrimStart('/'),
+ _serverConfigurationManager.Configuration.CorsHosts);
services.AddJellyfinApiSwagger();
@@ -78,11 +80,11 @@ namespace Jellyfin.Server
app.UseAuthentication();
app.UseJellyfinApiSwagger(_serverConfigurationManager);
app.UseRouting();
- app.UseCors(ServerCorsPolicy.DefaultPolicyName);
+ app.UseMiddleware(ServerCorsPolicy.DefaultPolicyName);
app.UseAuthorization();
if (_serverConfigurationManager.Configuration.EnableMetrics)
{
- // Must be registered after any middleware that could chagne HTTP response codes or the data will be bad
+ // Must be registered after any middleware that could change HTTP response codes or the data will be bad
app.UseHttpMetrics();
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index c66091f9d..a743277d7 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -264,6 +264,11 @@ namespace MediaBrowser.Model.Configuration
///
public long SlowResponseThresholdMs { get; set; }
+ ///
+ /// Gets or sets the cors hosts.
+ ///
+ public string[] CorsHosts { get; set; }
+
///
/// Initializes a new instance of the class.
///
@@ -372,6 +377,7 @@ namespace MediaBrowser.Model.Configuration
EnableSlowResponseWarning = true;
SlowResponseThresholdMs = 500;
+ CorsHosts = new[] { "*" };
}
}
--
cgit v1.2.3
From 1feee6f95e00cf579ab16c7ca004947534545d9b Mon Sep 17 00:00:00 2001
From: crobibero
Date: Wed, 2 Sep 2020 08:03:15 -0600
Subject: Properly host static files and set base url
---
Jellyfin.Api/Controllers/DashboardController.cs | 102 +--------------------
.../Extensions/ApiApplicationBuilderExtensions.cs | 21 +----
.../Extensions/ApiServiceCollectionExtensions.cs | 6 +-
Jellyfin.Server/Program.cs | 2 +-
Jellyfin.Server/Startup.cs | 21 ++++-
.../Configuration/IApplicationPaths.cs | 5 +-
.../Configuration/ServerConfiguration.cs | 6 --
7 files changed, 27 insertions(+), 136 deletions(-)
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index 33abe3ccd..3f0fc2e91 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -15,6 +15,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
{
@@ -26,38 +27,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.
///
@@ -169,87 +152,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");
- }
-
- var stream = new FileStream(_resourceFileManager.GetResourcePath(basePath, path), FileMode.Open, FileAccess.Read);
- return File(stream, 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.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
index a1a68bcf0..e23de86d9 100644
--- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
@@ -34,38 +34,23 @@ 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}" } };
-
- // BaseUrl is empty, ignore
- if (apiDocBaseUrl.Length != 0)
- {
- // Update all relative paths to remove baseUrl.
- var updatedPaths = new OpenApiPaths();
- foreach (var (key, value) in swagger.Paths)
- {
- var relativePath = key;
- relativePath = relativePath.Remove(0, apiDocBaseUrl.Length);
- updatedPaths.Add(relativePath, value);
- }
-
- swagger.Paths = updatedPaths;
- }
});
})
.UseSwaggerUI(c =>
{
c.DocumentTitle = "Jellyfin API";
c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API");
- c.RoutePrefix = $"{baseUrl}api-docs/swagger";
+ 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.RoutePrefix = "api-docs/redoc";
});
}
}
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 0160a05f9..283c870fd 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -135,10 +135,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 =>
@@ -151,7 +150,6 @@ namespace Jellyfin.Server.Extensions
})
.AddMvc(opts =>
{
- opts.UseGeneralRoutePrefix(baseUrl);
opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter());
opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 14cc5f4c2..223bb2558 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 cbc1c040c..73c00260e 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -9,9 +9,12 @@ using MediaBrowser.Common;
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;
@@ -44,7 +47,7 @@ namespace Jellyfin.Server
{
services.AddResponseCompression();
services.AddHttpContextAccessor();
- services.AddJellyfinApi(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/'), _applicationHost.GetApiPluginAssemblies());
+ services.AddJellyfinApi(_applicationHost.GetApiPluginAssemblies());
services.AddJellyfinApiSwagger();
@@ -75,10 +78,12 @@ namespace Jellyfin.Server
/// The application builder.
/// The webhost environment.
/// The server application host.
+ /// The application config.
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
- IServerApplicationHost serverApplicationHost)
+ IServerApplicationHost serverApplicationHost,
+ IConfiguration appConfig)
{
if (env.IsDevelopment())
{
@@ -95,6 +100,16 @@ namespace Jellyfin.Server
// TODO app.UseMiddleware();
+ app.UsePathBase(_serverConfigurationManager.Configuration.BaseUrl);
+ if (appConfig.HostWebClient())
+ {
+ app.UseStaticFiles(new StaticFileOptions
+ {
+ FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
+ RequestPath = "/web"
+ });
+ }
+
app.UseAuthentication();
app.UseJellyfinApiSwagger(_serverConfigurationManager);
app.UseRouting();
@@ -102,7 +117,7 @@ namespace Jellyfin.Server
app.UseAuthorization();
if (_serverConfigurationManager.Configuration.EnableMetrics)
{
- // Must be registered after any middleware that could chagne HTTP response codes or the data will be bad
+ // Must be registered after any middleware that could change HTTP response codes or the data will be bad
app.UseHttpMetrics();
}
diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
index 4cea61682..57c654667 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.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 33975bc1e..97748bd0c 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.
///
--
cgit v1.2.3
From 78cab77f819e8d8b283f95f2e48f635bcf66fea5 Mon Sep 17 00:00:00 2001
From: cvium
Date: Thu, 10 Sep 2020 11:05:46 +0200
Subject: Add Known Proxies to system configuration
---
.../Extensions/ApiServiceCollectionExtensions.cs | 13 +++++++++++--
Jellyfin.Server/Startup.cs | 3 ++-
MediaBrowser.Model/Configuration/ServerConfiguration.cs | 6 ++++++
3 files changed, 19 insertions(+), 3 deletions(-)
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 9319b573a..873e22819 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net;
using System.Reflection;
using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
@@ -17,7 +18,6 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Controllers;
using Jellyfin.Server.Configuration;
using Jellyfin.Server.Formatters;
-using Jellyfin.Server.Middleware;
using MediaBrowser.Common.Json;
using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authentication;
@@ -28,6 +28,7 @@ using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
+using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
namespace Jellyfin.Server.Extensions
{
@@ -136,8 +137,9 @@ namespace Jellyfin.Server.Extensions
///
/// The service collection.
/// An IEnumerable containing all plugin assemblies with API controllers.
+ /// A list of all known proxies to trust for X-Forwarded-For.
/// The MVC builder.
- public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable pluginAssemblies)
+ public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable pluginAssemblies, IReadOnlyList knownProxies)
{
IMvcBuilder mvcBuilder = serviceCollection
.AddCors()
@@ -145,6 +147,13 @@ namespace Jellyfin.Server.Extensions
.Configure(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
+ for (var i = 0; i < knownProxies.Count; i++)
+ {
+ if (IPAddress.TryParse(knownProxies[i], out var address))
+ {
+ options.KnownProxies.Add(address);
+ }
+ }
})
.AddMvc(opts =>
{
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 9e969c0c1..2f4620aa6 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -52,7 +52,7 @@ namespace Jellyfin.Server
{
options.HttpsPort = _serverApplicationHost.HttpsPort;
});
- services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies());
+ services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.Configuration.KnownProxies);
services.AddJellyfinApiSwagger();
@@ -103,6 +103,7 @@ namespace Jellyfin.Server
mainApp.UseDeveloperExceptionPage();
}
+ mainApp.UseForwardedHeaders();
mainApp.UseMiddleware();
mainApp.UseMiddleware();
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 68dc1cc83..48d1a7346 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -268,6 +268,11 @@ namespace MediaBrowser.Model.Configuration
///
public string[] CorsHosts { get; set; }
+ ///
+ /// Gets or sets the known proxies.
+ ///
+ public string[] KnownProxies { get; set; }
+
///
/// Initializes a new instance of the class.
///
@@ -378,6 +383,7 @@ namespace MediaBrowser.Model.Configuration
EnableSlowResponseWarning = true;
SlowResponseThresholdMs = 500;
CorsHosts = new[] { "*" };
+ KnownProxies = Array.Empty();
}
}
--
cgit v1.2.3
From 5cca8bffea339f1043d692f337ab002dbee3a03b Mon Sep 17 00:00:00 2001
From: spookbits <71300703+spooksbit@users.noreply.github.com>
Date: Wed, 16 Sep 2020 13:17:14 -0400
Subject: Removed browser auto-load functionality from the server. Added
profiles in launchSettings to start either the web client or the swagger API
page. Removed --noautorunwebapp as this is the default functionality.
---
CONTRIBUTORS.md | 1 +
.../Browser/BrowserLauncher.cs | 51 -------------
.../EntryPoints/StartupWizard.cs | 83 ----------------------
Emby.Server.Implementations/IStartupOptions.cs | 5 --
Jellyfin.Server/Jellyfin.Server.csproj | 2 +-
Jellyfin.Server/Properties/launchSettings.json | 8 ++-
Jellyfin.Server/StartupOptions.cs | 4 --
.../Configuration/ServerConfiguration.cs | 3 -
.../JellyfinApplicationFactory.cs | 3 +-
9 files changed, 10 insertions(+), 150 deletions(-)
delete mode 100644 Emby.Server.Implementations/Browser/BrowserLauncher.cs
delete mode 100644 Emby.Server.Implementations/EntryPoints/StartupWizard.cs
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index f1fe65064..2ec147ba2 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -103,6 +103,7 @@
- [sl1288](https://github.com/sl1288)
- [sorinyo2004](https://github.com/sorinyo2004)
- [sparky8251](https://github.com/sparky8251)
+ - [spookbits](https://github.com/spookbits)
- [stanionascu](https://github.com/stanionascu)
- [stevehayles](https://github.com/stevehayles)
- [SuperSandro2000](https://github.com/SuperSandro2000)
diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs
deleted file mode 100644
index f8108d1c2..000000000
--- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.Browser
-{
- ///
- /// Assists in opening application URLs in an external browser.
- ///
- public static class BrowserLauncher
- {
- ///
- /// Opens the home page of the web client.
- ///
- /// The app host.
- public static void OpenWebApp(IServerApplicationHost appHost)
- {
- TryOpenUrl(appHost, "/web/index.html");
- }
-
- ///
- /// Opens the swagger API page.
- ///
- /// The app host.
- public static void OpenSwaggerPage(IServerApplicationHost appHost)
- {
- TryOpenUrl(appHost, "/api-docs/swagger");
- }
-
- ///
- /// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
- ///
- /// The application host.
- /// The URL to open, relative to the server base URL.
- private static void TryOpenUrl(IServerApplicationHost appHost, string relativeUrl)
- {
- try
- {
- string baseUrl = appHost.GetLocalApiUrl("localhost");
- appHost.LaunchUrl(baseUrl + relativeUrl);
- }
- catch (Exception ex)
- {
- var logger = appHost.Resolve>();
- logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
- }
- }
- }
-}
diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs
deleted file mode 100644
index 2e738deeb..000000000
--- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using System.Threading.Tasks;
-using Emby.Server.Implementations.Browser;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Extensions;
-using MediaBrowser.Controller.Plugins;
-using Microsoft.Extensions.Configuration;
-
-namespace Emby.Server.Implementations.EntryPoints
-{
- ///
- /// Class StartupWizard.
- ///
- public sealed class StartupWizard : IServerEntryPoint
- {
- private readonly IServerApplicationHost _appHost;
- private readonly IConfiguration _appConfig;
- private readonly IServerConfigurationManager _config;
- private readonly IStartupOptions _startupOptions;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The application host.
- /// The application configuration.
- /// The configuration manager.
- /// The application startup options.
- public StartupWizard(
- IServerApplicationHost appHost,
- IConfiguration appConfig,
- IServerConfigurationManager config,
- IStartupOptions startupOptions)
- {
- _appHost = appHost;
- _appConfig = appConfig;
- _config = config;
- _startupOptions = startupOptions;
- }
-
- ///
- public Task RunAsync()
- {
- Run();
- return Task.CompletedTask;
- }
-
- private void Run()
- {
- if (!_appHost.CanLaunchWebBrowser)
- {
- return;
- }
-
- // Always launch the startup wizard if possible when it has not been completed
- if (!_config.Configuration.IsStartupWizardCompleted && _appConfig.HostWebClient())
- {
- BrowserLauncher.OpenWebApp(_appHost);
- return;
- }
-
- // Do nothing if the web app is configured to not run automatically
- if (!_config.Configuration.AutoRunWebApp || _startupOptions.NoAutoRunWebApp)
- {
- return;
- }
-
- // Launch the swagger page if the web client is not hosted, otherwise open the web client
- if (_appConfig.HostWebClient())
- {
- BrowserLauncher.OpenWebApp(_appHost);
- }
- else
- {
- BrowserLauncher.OpenSwaggerPage(_appHost);
- }
- }
-
- ///
- public void Dispose()
- {
- }
- }
-}
diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs
index e7e72c686..4bef59543 100644
--- a/Emby.Server.Implementations/IStartupOptions.cs
+++ b/Emby.Server.Implementations/IStartupOptions.cs
@@ -16,11 +16,6 @@ namespace Emby.Server.Implementations
///
bool IsService { get; }
- ///
- /// Gets the value of the --noautorunwebapp command line option.
- ///
- bool NoAutoRunWebApp { get; }
-
///
/// Gets the value of the --package-name command line option.
///
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 0ac309a0b..b66314a8e 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -1,4 +1,4 @@
-
+
diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json
index b6e2bcf97..ebdd0deda 100644
--- a/Jellyfin.Server/Properties/launchSettings.json
+++ b/Jellyfin.Server/Properties/launchSettings.json
@@ -2,14 +2,20 @@
"profiles": {
"Jellyfin.Server": {
"commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "web",
+ "applicationUrl": "http://localhost:8096",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Jellyfin.Server (nowebclient)": {
"commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "api-docs/swagger",
+ "applicationUrl": "http://localhost:8096",
"environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
+ "ASPNETCORE_ENVIRONMENT": "Development"
},
"commandLineArgs": "--nowebclient"
}
diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs
index 41a1430d2..b63434092 100644
--- a/Jellyfin.Server/StartupOptions.cs
+++ b/Jellyfin.Server/StartupOptions.cs
@@ -63,10 +63,6 @@ namespace Jellyfin.Server
[Option("service", Required = false, HelpText = "Run as headless service.")]
public bool IsService { get; set; }
- ///
- [Option("noautorunwebapp", Required = false, HelpText = "Run headless if startup wizard is complete.")]
- public bool NoAutoRunWebApp { get; set; }
-
///
[Option("package-name", Required = false, HelpText = "Used when packaging Jellyfin (example, synology).")]
public string? PackageName { get; set; }
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 48d1a7346..8b78ad842 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -83,8 +83,6 @@ namespace MediaBrowser.Model.Configuration
///
public bool QuickConnectAvailable { get; set; }
- public bool AutoRunWebApp { get; set; }
-
public bool EnableRemoteAccess { get; set; }
///
@@ -306,7 +304,6 @@ namespace MediaBrowser.Model.Configuration
DisableLiveTvChannelUserDataName = true;
EnableNewOmdbSupport = true;
- AutoRunWebApp = true;
EnableRemoteAccess = true;
QuickConnectAvailable = false;
diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
index 77f1640fa..bd3d35687 100644
--- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
+++ b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
@@ -47,8 +47,7 @@ namespace Jellyfin.Api.Tests
// Specify the startup command line options
var commandLineOpts = new StartupOptions
{
- NoWebClient = true,
- NoAutoRunWebApp = true
+ NoWebClient = true
};
// Use a temporary directory for the application paths
--
cgit v1.2.3
From 72cd6ab0712a0a532e0dda2b26afe793ad34111b Mon Sep 17 00:00:00 2001
From: cvium
Date: Fri, 18 Sep 2020 15:24:56 +0200
Subject: Remove dummy season and missing episode provider in a futile attempt
to remove cruft
---
MediaBrowser.Model/Configuration/LibraryOptions.cs | 2 -
.../Plugins/TheTvdb/TvdbClientManager.cs | 26 --
MediaBrowser.Providers/TV/DummySeasonProvider.cs | 229 ------------
.../TV/MissingEpisodeProvider.cs | 404 ---------------------
MediaBrowser.Providers/TV/SeriesMetadataService.cs | 41 +--
5 files changed, 1 insertion(+), 701 deletions(-)
delete mode 100644 MediaBrowser.Providers/TV/DummySeasonProvider.cs
delete mode 100644 MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index 890469d36..54ef49ea6 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -25,8 +25,6 @@ namespace MediaBrowser.Model.Configuration
public bool EnableInternetProviders { get; set; }
- public bool ImportMissingEpisodes { get; set; }
-
public bool EnableAutomaticSeriesGrouping { get; set; }
public bool EnableEmbeddedTitles { get; set; }
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
index f22d484ab..5e9a4a225 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
@@ -80,32 +80,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
return TryGetValue(cacheKey, language, () => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
}
- public async Task> GetAllEpisodesAsync(int tvdbId, string language,
- CancellationToken cancellationToken)
- {
- // Traverse all episode pages and join them together
- var episodes = new List();
- var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), language, cancellationToken)
- .ConfigureAwait(false);
- episodes.AddRange(episodePage.Data);
- if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue)
- {
- return episodes;
- }
-
- int next = episodePage.Links.Next.Value;
- int last = episodePage.Links.Last.Value;
-
- for (var page = next; page <= last; ++page)
- {
- episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), language, cancellationToken)
- .ConfigureAwait(false);
- episodes.AddRange(episodePage.Data);
- }
-
- return episodes;
- }
-
public Task> GetSeriesByImdbIdAsync(
string imdbId,
string language,
diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
deleted file mode 100644
index 905cbefd3..000000000
--- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs
+++ /dev/null
@@ -1,229 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Providers.TV
-{
- public class DummySeasonProvider
- {
- private readonly ILogger _logger;
- private readonly ILocalizationManager _localization;
- private readonly ILibraryManager _libraryManager;
- private readonly IFileSystem _fileSystem;
-
- public DummySeasonProvider(
- ILogger logger,
- ILocalizationManager localization,
- ILibraryManager libraryManager,
- IFileSystem fileSystem)
- {
- _logger = logger;
- _localization = localization;
- _libraryManager = libraryManager;
- _fileSystem = fileSystem;
- }
-
- public async Task Run(Series series, CancellationToken cancellationToken)
- {
- var seasonsRemoved = RemoveObsoleteSeasons(series);
-
- var hasNewSeasons = await AddDummySeasonFolders(series, cancellationToken).ConfigureAwait(false);
-
- if (hasNewSeasons)
- {
- // var directoryService = new DirectoryService(_fileSystem);
-
- // await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false);
-
- // await series.ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(directoryService))
- // .ConfigureAwait(false);
- }
-
- return seasonsRemoved || hasNewSeasons;
- }
-
- private async Task AddDummySeasonFolders(Series series, CancellationToken cancellationToken)
- {
- var episodesInSeriesFolder = series.GetRecursiveChildren(i => i is Episode)
- .Cast()
- .Where(i => !i.IsInSeasonFolder)
- .ToList();
-
- var hasChanges = false;
-
- List seasons = null;
-
- // Loop through the unique season numbers
- foreach (var seasonNumber in episodesInSeriesFolder.Select(i => i.ParentIndexNumber ?? -1)
- .Where(i => i >= 0)
- .Distinct()
- .ToList())
- {
- if (seasons == null)
- {
- seasons = series.Children.OfType().ToList();
- }
-
- var existingSeason = seasons
- .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber);
-
- if (existingSeason == null)
- {
- await AddSeason(series, seasonNumber, false, cancellationToken).ConfigureAwait(false);
- hasChanges = true;
- seasons = null;
- }
- else if (existingSeason.IsVirtualItem)
- {
- existingSeason.IsVirtualItem = false;
- await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
- seasons = null;
- }
- }
-
- // Unknown season - create a dummy season to put these under
- if (episodesInSeriesFolder.Any(i => !i.ParentIndexNumber.HasValue))
- {
- if (seasons == null)
- {
- seasons = series.Children.OfType().ToList();
- }
-
- var existingSeason = seasons
- .FirstOrDefault(i => !i.IndexNumber.HasValue);
-
- if (existingSeason == null)
- {
- await AddSeason(series, null, false, cancellationToken).ConfigureAwait(false);
-
- hasChanges = true;
- seasons = null;
- }
- else if (existingSeason.IsVirtualItem)
- {
- existingSeason.IsVirtualItem = false;
- await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
- seasons = null;
- }
- }
-
- return hasChanges;
- }
-
- ///
- /// Adds the season.
- ///
- public async Task AddSeason(
- Series series,
- int? seasonNumber,
- bool isVirtualItem,
- CancellationToken cancellationToken)
- {
- string seasonName;
- if (seasonNumber == null)
- {
- seasonName = _localization.GetLocalizedString("NameSeasonUnknown");
- }
- else if (seasonNumber == 0)
- {
- seasonName = _libraryManager.GetLibraryOptions(series).SeasonZeroDisplayName;
- }
- else
- {
- seasonName = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("NameSeasonNumber"),
- seasonNumber.Value);
- }
-
- _logger.LogInformation("Creating Season {0} entry for {1}", seasonName, series.Name);
-
- var season = new Season
- {
- Name = seasonName,
- IndexNumber = seasonNumber,
- Id = _libraryManager.GetNewItemId(
- series.Id + (seasonNumber ?? -1).ToString(CultureInfo.InvariantCulture) + seasonName,
- typeof(Season)),
- IsVirtualItem = isVirtualItem,
- SeriesId = series.Id,
- SeriesName = series.Name
- };
-
- season.SetParent(series);
-
- series.AddChild(season, cancellationToken);
-
- await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
-
- return season;
- }
-
- private bool RemoveObsoleteSeasons(Series series)
- {
- var existingSeasons = series.Children.OfType().ToList();
-
- var physicalSeasons = existingSeasons
- .Where(i => i.LocationType != LocationType.Virtual)
- .ToList();
-
- var virtualSeasons = existingSeasons
- .Where(i => i.LocationType == LocationType.Virtual)
- .ToList();
-
- var seasonsToRemove = virtualSeasons
- .Where(i =>
- {
- if (i.IndexNumber.HasValue)
- {
- var seasonNumber = i.IndexNumber.Value;
-
- // If there's a physical season with the same number, delete it
- if (physicalSeasons.Any(p => p.IndexNumber.HasValue && (p.IndexNumber.Value == seasonNumber)))
- {
- return true;
- }
- }
-
- // If there are no episodes with this season number, delete it
- if (!i.GetEpisodes().Any())
- {
- return true;
- }
-
- return false;
- })
- .ToList();
-
- var hasChanges = false;
-
- foreach (var seasonToRemove in seasonsToRemove)
- {
- _logger.LogInformation("Removing virtual season {0} {1}", series.Name, seasonToRemove.IndexNumber);
-
- _libraryManager.DeleteItem(
- seasonToRemove,
- new DeleteOptions
- {
- DeleteFileLocation = true
- },
- false);
-
- hasChanges = true;
- }
-
- return hasChanges;
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
deleted file mode 100644
index c833b1227..000000000
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ /dev/null
@@ -1,404 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Providers.Plugins.TheTvdb;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Providers.TV
-{
- public class MissingEpisodeProvider
- {
- private const double UnairedEpisodeThresholdDays = 2;
-
- private readonly IServerConfigurationManager _config;
- private readonly ILogger _logger;
- private readonly ILibraryManager _libraryManager;
- private readonly ILocalizationManager _localization;
- private readonly IFileSystem _fileSystem;
- private readonly TvdbClientManager _tvdbClientManager;
-
- public MissingEpisodeProvider(
- ILogger logger,
- IServerConfigurationManager config,
- ILibraryManager libraryManager,
- ILocalizationManager localization,
- IFileSystem fileSystem,
- TvdbClientManager tvdbClientManager)
- {
- _logger = logger;
- _config = config;
- _libraryManager = libraryManager;
- _localization = localization;
- _fileSystem = fileSystem;
- _tvdbClientManager = tvdbClientManager;
- }
-
- public async Task Run(Series series, bool addNewItems, CancellationToken cancellationToken)
- {
- var tvdbIdString = series.GetProviderId(MetadataProvider.Tvdb);
- if (string.IsNullOrEmpty(tvdbIdString))
- {
- return false;
- }
-
- var episodes = await _tvdbClientManager.GetAllEpisodesAsync(
- int.Parse(tvdbIdString, CultureInfo.InvariantCulture),
- series.GetPreferredMetadataLanguage(),
- cancellationToken).ConfigureAwait(false);
-
- var episodeLookup = episodes
- .Select(i =>
- {
- if (!DateTime.TryParse(i.FirstAired, out var firstAired))
- {
- firstAired = default;
- }
-
- var seasonNumber = i.AiredSeason.GetValueOrDefault(-1);
- var episodeNumber = i.AiredEpisodeNumber.GetValueOrDefault(-1);
- return (seasonNumber, episodeNumber, firstAired);
- })
- .Where(i => i.seasonNumber != -1 && i.episodeNumber != -1)
- .OrderBy(i => i.seasonNumber)
- .ThenBy(i => i.episodeNumber)
- .ToList();
-
- var allRecursiveChildren = series.GetRecursiveChildren();
-
- var hasBadData = HasInvalidContent(allRecursiveChildren);
-
- // Be conservative here to avoid creating missing episodes for ones they already have
- var addMissingEpisodes = !hasBadData && _libraryManager.GetLibraryOptions(series).ImportMissingEpisodes;
-
- var anySeasonsRemoved = RemoveObsoleteOrMissingSeasons(allRecursiveChildren, episodeLookup);
-
- if (anySeasonsRemoved)
- {
- // refresh this
- allRecursiveChildren = series.GetRecursiveChildren();
- }
-
- var anyEpisodesRemoved = RemoveObsoleteOrMissingEpisodes(allRecursiveChildren, episodeLookup, addMissingEpisodes);
-
- if (anyEpisodesRemoved)
- {
- // refresh this
- allRecursiveChildren = series.GetRecursiveChildren();
- }
-
- var hasNewEpisodes = false;
-
- if (addNewItems && series.IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(series), TvdbSeriesProvider.Current.Name))
- {
- hasNewEpisodes = await AddMissingEpisodes(series, allRecursiveChildren, addMissingEpisodes, episodeLookup, cancellationToken)
- .ConfigureAwait(false);
- }
-
- if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved)
- {
- return true;
- }
-
- return false;
- }
-
- ///
- /// Returns true if a series has any seasons or episodes without season or episode numbers
- /// If this data is missing no virtual items will be added in order to prevent possible duplicates.
- ///
- private bool HasInvalidContent(IList allItems)
- {
- return allItems.OfType().Any(i => !i.IndexNumber.HasValue) ||
- allItems.OfType().Any(i =>
- {
- if (!i.ParentIndexNumber.HasValue)
- {
- return true;
- }
-
- // You could have episodes under season 0 with no number
- return false;
- });
- }
-
- private async Task AddMissingEpisodes(
- Series series,
- IEnumerable allItems,
- bool addMissingEpisodes,
- IReadOnlyCollection<(int seasonNumber, int episodenumber, DateTime firstAired)> episodeLookup,
- CancellationToken cancellationToken)
- {
- var existingEpisodes = allItems.OfType().ToList();
-
- var seasonCounts = episodeLookup.GroupBy(e => e.seasonNumber).ToDictionary(g => g.Key, g => g.Count());
-
- var hasChanges = false;
-
- foreach (var tuple in episodeLookup)
- {
- if (tuple.seasonNumber <= 0 || tuple.episodenumber <= 0)
- {
- // Ignore episode/season zeros
- continue;
- }
-
- var existingEpisode = GetExistingEpisode(existingEpisodes, seasonCounts, tuple);
-
- if (existingEpisode != null)
- {
- continue;
- }
-
- var airDate = tuple.firstAired;
-
- var now = DateTime.UtcNow.AddDays(-UnairedEpisodeThresholdDays);
-
- if ((airDate < now && addMissingEpisodes) || airDate > now)
- {
- // tvdb has a lot of nearly blank episodes
- _logger.LogInformation("Creating virtual missing/unaired episode {0} {1}x{2}", series.Name, tuple.seasonNumber, tuple.episodenumber);
- await AddEpisode(series, tuple.seasonNumber, tuple.episodenumber, cancellationToken).ConfigureAwait(false);
-
- hasChanges = true;
- }
- }
-
- return hasChanges;
- }
-
- ///
- /// Removes the virtual entry after a corresponding physical version has been added.
- ///
- private bool RemoveObsoleteOrMissingEpisodes(
- IEnumerable allRecursiveChildren,
- IEnumerable<(int seasonNumber, int episodeNumber, DateTime firstAired)> episodeLookup,
- bool allowMissingEpisodes)
- {
- var existingEpisodes = allRecursiveChildren.OfType();
-
- var physicalEpisodes = new List();
- var virtualEpisodes = new List();
- foreach (var episode in existingEpisodes)
- {
- if (episode.LocationType == LocationType.Virtual)
- {
- virtualEpisodes.Add(episode);
- }
- else
- {
- physicalEpisodes.Add(episode);
- }
- }
-
- var episodesToRemove = virtualEpisodes
- .Where(i =>
- {
- if (!i.IndexNumber.HasValue || !i.ParentIndexNumber.HasValue)
- {
- return true;
- }
-
- var seasonNumber = i.ParentIndexNumber.Value;
- var episodeNumber = i.IndexNumber.Value;
-
- // If there's a physical episode with the same season and episode number, delete it
- if (physicalEpisodes.Any(p =>
- p.ParentIndexNumber.HasValue && p.ParentIndexNumber.Value == seasonNumber &&
- p.ContainsEpisodeNumber(episodeNumber)))
- {
- return true;
- }
-
- // If the episode no longer exists in the remote lookup, delete it
- if (!episodeLookup.Any(e => e.seasonNumber == seasonNumber && e.episodeNumber == episodeNumber))
- {
- return true;
- }
-
- // If it's missing, but not unaired, remove it
- return !allowMissingEpisodes && i.IsMissingEpisode &&
- (!i.PremiereDate.HasValue ||
- i.PremiereDate.Value.ToLocalTime().Date.AddDays(UnairedEpisodeThresholdDays) <
- DateTime.Now.Date);
- });
-
- var hasChanges = false;
-
- foreach (var episodeToRemove in episodesToRemove)
- {
- _libraryManager.DeleteItem(
- episodeToRemove,
- new DeleteOptions
- {
- DeleteFileLocation = true
- },
- false);
-
- hasChanges = true;
- }
-
- return hasChanges;
- }
-
- ///
- /// Removes the obsolete or missing seasons.
- ///
- /// All recursive children.
- /// The episode lookup.
- /// .
- private bool RemoveObsoleteOrMissingSeasons(
- IList allRecursiveChildren,
- IEnumerable<(int seasonNumber, int episodeNumber, DateTime firstAired)> episodeLookup)
- {
- var existingSeasons = allRecursiveChildren.OfType().ToList();
-
- var physicalSeasons = new List();
- var virtualSeasons = new List();
- foreach (var season in existingSeasons)
- {
- if (season.LocationType == LocationType.Virtual)
- {
- virtualSeasons.Add(season);
- }
- else
- {
- physicalSeasons.Add(season);
- }
- }
-
- var allEpisodes = allRecursiveChildren.OfType().ToList();
-
- var seasonsToRemove = virtualSeasons
- .Where(i =>
- {
- if (i.IndexNumber.HasValue)
- {
- var seasonNumber = i.IndexNumber.Value;
-
- // If there's a physical season with the same number, delete it
- if (physicalSeasons.Any(p => p.IndexNumber.HasValue && p.IndexNumber.Value == seasonNumber && string.Equals(p.Series.PresentationUniqueKey, i.Series.PresentationUniqueKey, StringComparison.Ordinal)))
- {
- return true;
- }
-
- // If the season no longer exists in the remote lookup, delete it, but only if an existing episode doesn't require it
- return episodeLookup.All(e => e.seasonNumber != seasonNumber) && allEpisodes.All(s => s.ParentIndexNumber != seasonNumber || s.IsInSeasonFolder);
- }
-
- // Season does not have a number
- // Remove if there are no episodes directly in series without a season number
- return allEpisodes.All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder);
- });
-
- var hasChanges = false;
-
- foreach (var seasonToRemove in seasonsToRemove)
- {
- _libraryManager.DeleteItem(
- seasonToRemove,
- new DeleteOptions
- {
- DeleteFileLocation = true
- },
- false);
-
- hasChanges = true;
- }
-
- return hasChanges;
- }
-
- ///
- /// Adds the episode.
- ///
- /// The series.
- /// The season number.
- /// The episode number.
- /// The cancellation token.
- /// Task.
- private async Task AddEpisode(Series series, int seasonNumber, int episodeNumber, CancellationToken cancellationToken)
- {
- var season = series.Children.OfType()
- .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber);
-
- if (season == null)
- {
- var provider = new DummySeasonProvider(_logger, _localization, _libraryManager, _fileSystem);
- season = await provider.AddSeason(series, seasonNumber, true, cancellationToken).ConfigureAwait(false);
- }
-
- var name = "Episode " + episodeNumber.ToString(CultureInfo.InvariantCulture);
-
- var episode = new Episode
- {
- Name = name,
- IndexNumber = episodeNumber,
- ParentIndexNumber = seasonNumber,
- Id = _libraryManager.GetNewItemId(
- series.Id + seasonNumber.ToString(CultureInfo.InvariantCulture) + name,
- typeof(Episode)),
- IsVirtualItem = true,
- SeasonId = season?.Id ?? Guid.Empty,
- SeriesId = series.Id
- };
-
- season.AddChild(episode, cancellationToken);
-
- await episode.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Gets the existing episode.
- ///
- /// The existing episodes.
- ///
- ///
- /// Episode.
- private Episode GetExistingEpisode(
- IEnumerable existingEpisodes,
- IReadOnlyDictionary seasonCounts,
- (int seasonNumber, int episodeNumber, DateTime firstAired) episodeTuple)
- {
- var seasonNumber = episodeTuple.seasonNumber;
- var episodeNumber = episodeTuple.episodeNumber;
-
- while (true)
- {
- var episode = GetExistingEpisode(existingEpisodes, seasonNumber, episodeNumber);
- if (episode != null)
- {
- return episode;
- }
-
- seasonNumber--;
-
- if (seasonCounts.ContainsKey(seasonNumber))
- {
- episodeNumber += seasonCounts[seasonNumber];
- }
- else
- {
- break;
- }
- }
-
- return null;
- }
-
- private Episode GetExistingEpisode(IEnumerable existingEpisodes, int season, int episode)
- => existingEpisodes.FirstOrDefault(i => i.ParentIndexNumber == season && i.ContainsEpisodeNumber(episode));
- }
-}
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index a2c0e62c1..c8fc568a2 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -1,65 +1,26 @@
#pragma warning disable CS1591
-using System;
-using System.Threading;
-using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Providers.Manager;
-using MediaBrowser.Providers.Plugins.TheTvdb;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV
{
public class SeriesMetadataService : MetadataService
{
- private readonly ILocalizationManager _localization;
- private readonly TvdbClientManager _tvdbClientManager;
-
public SeriesMetadataService(
IServerConfigurationManager serverConfigurationManager,
ILogger logger,
IProviderManager providerManager,
IFileSystem fileSystem,
- ILibraryManager libraryManager,
- ILocalizationManager localization,
- TvdbClientManager tvdbClientManager)
+ ILibraryManager libraryManager)
: base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
{
- _localization = localization;
- _tvdbClientManager = tvdbClientManager;
- }
-
- ///
- protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
- {
- await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
-
- var seasonProvider = new DummySeasonProvider(Logger, _localization, LibraryManager, FileSystem);
- await seasonProvider.Run(item, cancellationToken).ConfigureAwait(false);
-
- // TODO why does it not register this itself omg
- var provider = new MissingEpisodeProvider(
- Logger,
- ServerConfigurationManager,
- LibraryManager,
- _localization,
- FileSystem,
- _tvdbClientManager);
-
- try
- {
- await provider.Run(item, true, CancellationToken.None).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error in DummySeasonProvider for {ItemPath}", item.Path);
- }
}
///
--
cgit v1.2.3
From ceecc80bb3816ca41d470e3b537a1bf7b632e8e2 Mon Sep 17 00:00:00 2001
From: crobibero
Date: Sun, 1 Nov 2020 18:32:41 -0700
Subject: Allow configuration of ActivityLogRetention
---
.../ScheduledTasks/Tasks/CleanActivityLogTask.cs | 16 +++++++++++++---
MediaBrowser.Model/Configuration/ServerConfiguration.cs | 6 ++++++
2 files changed, 19 insertions(+), 3 deletions(-)
(limited to 'MediaBrowser.Model/Configuration')
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs
index 50bc091c8..4abbf784b 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
@@ -16,18 +17,22 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
private readonly ILocalizationManager _localization;
private readonly IActivityManager _activityManager;
+ private readonly IServerConfigurationManager _serverConfigurationManager;
///
/// Initializes a new instance of the class.
///
/// Instance of the interface.
/// Instance of the interface.
+ /// Instance of the interface.
public CleanActivityLogTask(
ILocalizationManager localization,
- IActivityManager activityManager)
+ IActivityManager activityManager,
+ IServerConfigurationManager serverConfigurationManager)
{
_localization = localization;
_activityManager = activityManager;
+ _serverConfigurationManager = serverConfigurationManager;
}
///
@@ -54,8 +59,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
///
public Task Execute(CancellationToken cancellationToken, IProgress progress)
{
- // TODO allow configure
- var startDate = DateTime.UtcNow.AddDays(-30);
+ var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays;
+ if (!retentionDays.HasValue || retentionDays <= 0)
+ {
+ throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}");
+ }
+
+ var startDate = DateTime.UtcNow.AddDays(retentionDays.Value * -1);
return _activityManager.CleanAsync(startDate);
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 8b78ad842..23a5201f7 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -271,6 +271,11 @@ namespace MediaBrowser.Model.Configuration
///
public string[] KnownProxies { get; set; }
+ ///
+ /// Gets or sets the number of days we should retain activity logs.
+ ///
+ public int? ActivityLogRetentionDays { get; set; }
+
///
/// Initializes a new instance of the class.
///
@@ -381,6 +386,7 @@ namespace MediaBrowser.Model.Configuration
SlowResponseThresholdMs = 500;
CorsHosts = new[] { "*" };
KnownProxies = Array.Empty();
+ ActivityLogRetentionDays = 30;
}
}
--
cgit v1.2.3