From 3ba709fcc32d7255a2cb2466dde8c2479130a2bc Mon Sep 17 00:00:00 2001
From: Frank
Date: Sat, 1 Jun 2019 15:03:48 -0700
Subject: [PATCH 001/262] Fix #1432. Add support for encoding with libx265 and
hevc_nvenc.
---
CONTRIBUTORS.md | 1 +
.../Playback/BaseStreamingService.cs | 2 +-
.../Playback/Hls/BaseHlsService.cs | 2 +-
.../Playback/Hls/DynamicHlsService.cs | 10 +-
.../Playback/Hls/VideoHlsService.cs | 10 +-
.../BaseProgressiveStreamingService.cs | 3 +-
.../Playback/Progressive/VideoService.cs | 2 +-
.../MediaEncoding/EncodingHelper.cs | 139 ++++++++++++------
.../MediaEncoding/EncodingJobOptions.cs | 6 +-
.../Configuration/EncodingOptions.cs | 4 +-
10 files changed, 124 insertions(+), 55 deletions(-)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 81857e57c7..16eea11634 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -24,6 +24,7 @@
- [Lynxy](https://github.com/Lynxy)
- [fasheng](https://github.com/fasheng)
- [ploughpuff](https://github.com/ploughpuff)
+ - [fhriley](https://github.com/fhriley)
# Emby Contributors
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index ae259a4f59..4865f30880 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -154,7 +154,7 @@ namespace MediaBrowser.Api.Playback
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
- protected virtual string GetDefaultH264Preset()
+ protected virtual string GetDefaultEncoderPreset()
{
return "superfast";
}
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 1acc42ea52..836e47dad5 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -303,7 +303,7 @@ namespace MediaBrowser.Api.Playback.Hls
return args;
}
- protected override string GetDefaultH264Preset()
+ protected override string GetDefaultEncoderPreset()
{
return "veryfast";
}
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 45f003cae8..1d6cbb8228 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -895,9 +895,13 @@ namespace MediaBrowser.Api.Playback.Hls
// See if we can save come cpu cycles by avoiding encoding
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{
- if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
+ if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
- args += " -bsf:v h264_mp4toannexb";
+ string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
+ if (!string.IsNullOrEmpty(bitStreamArgs))
+ {
+ args += " " + bitStreamArgs;
+ }
}
//args += " -flags -global_header";
@@ -909,7 +913,7 @@ namespace MediaBrowser.Api.Playback.Hls
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
- args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
+ args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset()) + keyFrameArg;
//args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index eb1bbfb74b..d441332c75 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -91,10 +91,14 @@ namespace MediaBrowser.Api.Playback.Hls
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
// if h264_mp4toannexb is ever added, do not use it for live tv
- if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) &&
+ if (state.VideoStream != null &&
!string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
- args += " -bsf:v h264_mp4toannexb";
+ string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
+ if (!string.IsNullOrEmpty(bitStreamArgs))
+ {
+ args += " " + bitStreamArgs;
+ }
}
}
else
@@ -104,7 +108,7 @@ namespace MediaBrowser.Api.Playback.Hls
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
- args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
+ args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset()) + keyFrameArg;
// Add resolution params, if specified
if (!hasGraphicalSubs)
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index 83a3f3e3c6..c15681654b 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -77,7 +77,8 @@ namespace MediaBrowser.Api.Playback.Progressive
{
var videoCodec = state.VideoRequest.VideoCodec;
- if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase))
{
return ".ts";
}
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index ab19fdc261..cfc8a283d9 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -120,7 +120,7 @@ namespace MediaBrowser.Api.Playback.Progressive
protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
{
- return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset());
+ return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultEncoderPreset());
}
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index e378c2b89d..f4370f0a52 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -34,8 +34,16 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{
- var defaultEncoder = "libx264";
+ return GetH264OrH265Encoder("libx264", "h264", state, encodingOptions);
+ }
+ public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
+ {
+ return GetH264OrH265Encoder("libx265", "hevc", state, encodingOptions);
+ }
+
+ private string GetH264OrH265Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions)
+ {
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
@@ -45,14 +53,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var codecMap = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
- {"qsv", "h264_qsv"},
- {"h264_qsv", "h264_qsv"},
- {"nvenc", "h264_nvenc"},
- {"amf", "h264_amf"},
- {"omx", "h264_omx"},
- {"h264_v4l2m2m", "h264_v4l2m2m"},
- {"mediacodec", "h264_mediacodec"},
- {"vaapi", "h264_vaapi"}
+ {"qsv", hwEncoder + "_qsv"},
+ {hwEncoder + "_qsv", hwEncoder + "_qsv"},
+ {"nvenc", hwEncoder + "_nvenc"},
+ {"amf", hwEncoder + "_amf"},
+ {"omx", hwEncoder + "_omx"},
+ {hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m"},
+ {"mediacodec", hwEncoder + "_mediacodec"},
+ {"vaapi", hwEncoder + "_vaapi"}
};
if (!string.IsNullOrEmpty(hwType)
@@ -119,6 +127,11 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(codec))
{
+ if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetH265Encoder(state, encodingOptions);
+ }
if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
{
return GetH264Encoder(state, encodingOptions);
@@ -476,6 +489,30 @@ namespace MediaBrowser.Controller.MediaEncoding
codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
}
+ public bool IsH265(MediaStream stream)
+ {
+ var codec = stream.Codec ?? string.Empty;
+
+ return codec.IndexOf("265", StringComparison.OrdinalIgnoreCase) != -1 ||
+ codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1;
+ }
+
+ public string GetBitStreamArgs(MediaStream stream)
+ {
+ if (IsH264(stream))
+ {
+ return "-bsf:v h264_mp4toannexb";
+ }
+ else if (IsH265(stream))
+ {
+ return "-bsf:v hevc_mp4toannexb";
+ }
+ else
+ {
+ return null;
+ }
+ }
+
public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
{
var bitrate = state.OutputVideoBitrate;
@@ -494,7 +531,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(" -b:v {0}", bitrate.Value.ToString(_usCulture));
}
- if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
{
// h264
return string.Format(" -maxrate {0} -bufsize {1}",
@@ -515,8 +553,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// Clients may direct play higher than level 41, but there's no reason to transcode higher
if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel)
- && string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
- && requestLevel > 41)
+ && requestLevel > 41
+ && (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)))
{
return "41";
}
@@ -616,49 +656,53 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// Gets the video bitrate to specify on the command line
///
- public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultH264Preset)
+ public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset)
{
var param = string.Empty;
var isVc1 = state.VideoStream != null &&
string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
+ var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
- if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
{
- if (!string.IsNullOrEmpty(encodingOptions.H264Preset))
+ if (!string.IsNullOrEmpty(encodingOptions.EncoderPreset))
{
- param += "-preset " + encodingOptions.H264Preset;
+ param += "-preset " + encodingOptions.EncoderPreset;
}
else
{
- param += "-preset " + defaultH264Preset;
+ param += "-preset " + defaultPreset;
}
- if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51)
+ int encodeCrf = encodingOptions.H264Crf;
+ if (isLibX265)
{
- param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture);
+ encodeCrf = encodingOptions.H265Crf;
+ }
+ if (encodeCrf >= 0 && encodeCrf <= 51)
+ {
+ param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
}
else
{
- param += " -crf 23";
+ string defaultCrf = "23";
+ if (isLibX265)
+ {
+ defaultCrf = "28";
+ }
+ param += " -crf " + defaultCrf;
}
}
- else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
- {
- param += "-preset fast";
-
- param += " -crf 28";
- }
-
// h264 (h264_qsv)
else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" };
- if (valid_h264_qsv.Contains(encodingOptions.H264Preset, StringComparer.OrdinalIgnoreCase))
+ if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparer.OrdinalIgnoreCase))
{
- param += "-preset " + encodingOptions.H264Preset;
+ param += "-preset " + encodingOptions.EncoderPreset;
}
else
{
@@ -670,9 +714,10 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// h264 (h264_nvenc)
- else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase))
{
- switch (encodingOptions.H264Preset)
+ switch (encodingOptions.EncoderPreset)
{
case "veryslow":
@@ -786,7 +831,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
// also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
+ string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
{
switch (level)
{
@@ -823,9 +869,10 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
// nvenc doesn't decode with param -level set ?!
- else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase))
{
- //param += "";
+ // todo param += "";
}
else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
@@ -838,6 +885,11 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -x264opts:0 subme=0:me_range=4:rc_lookahead=10:me=dia:no_chroma_me:8x8dct=0:partitions=none";
}
+ if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
+ {
+ // todo
+ }
+
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) &&
@@ -1715,7 +1767,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var videoStream = state.VideoStream;
- if (state.DeInterlace("h264", true) && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ if ((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) &&
+ !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
var inputFramerate = videoStream == null ? null : videoStream.RealFrameRate;
@@ -2376,7 +2429,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return args;
}
- public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultH264Preset)
+ public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultPreset)
{
// Get the output codec name
var videoCodec = GetVideoEncoder(state, encodingOptions);
@@ -2400,7 +2453,7 @@ namespace MediaBrowser.Controller.MediaEncoding
GetInputArgument(state, encodingOptions),
keyFrame,
GetMapArgs(state),
- GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultH264Preset),
+ GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
threads,
GetProgressiveVideoAudioArguments(state, encodingOptions),
GetSubtitleEmbedArguments(state),
@@ -2425,7 +2478,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty;
}
- public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultH264Preset)
+ public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultPreset)
{
var args = "-codec:v:0 " + videoCodec;
@@ -2436,11 +2489,15 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
- if (state.VideoStream != null && IsH264(state.VideoStream) &&
+ if (state.VideoStream != null &&
string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
- args += " -bsf:v h264_mp4toannexb";
+ string bitStreamArgs = GetBitStreamArgs(state.VideoStream);
+ if (!string.IsNullOrEmpty(bitStreamArgs))
+ {
+ args += " " + bitStreamArgs;
+ }
}
if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
@@ -2486,7 +2543,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
}
- var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset);
+ var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
if (!string.IsNullOrEmpty(qualityParam))
{
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
index be97c75a25..d64feb2f7c 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
@@ -118,14 +118,14 @@ namespace MediaBrowser.Controller.MediaEncoding
/// Gets or sets the profile.
///
/// The profile.
- [ApiMember(Name = "Profile", Description = "Optional. Specify a specific h264 profile, e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "Profile", Description = "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Profile { get; set; }
///
/// Gets or sets the level.
///
/// The level.
- [ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "Level", Description = "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Level { get; set; }
///
@@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// Gets or sets the video codec.
///
/// The video codec.
- [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string VideoCodec { get; set; }
public string SubtitleCodec { get; set; }
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 285ff4ba58..9ae10d9809 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -18,7 +18,8 @@ namespace MediaBrowser.Model.Configuration
public string EncoderAppPathDisplay { get; set; }
public string VaapiDevice { get; set; }
public int H264Crf { get; set; }
- public string H264Preset { get; set; }
+ public int H265Crf { get; set; }
+ public string EncoderPreset { get; set; }
public string DeinterlaceMethod { get; set; }
public bool EnableHardwareEncoding { get; set; }
public bool EnableSubtitleExtraction { get; set; }
@@ -34,6 +35,7 @@ namespace MediaBrowser.Model.Configuration
// 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";
H264Crf = 23;
+ H265Crf = 28;
EnableHardwareEncoding = true;
EnableSubtitleExtraction = true;
HardwareDecodingCodecs = new string[] { "h264", "vc1" };
From 65a0ca2f32e2eba640fbfead46ffb5bfd02e2c88 Mon Sep 17 00:00:00 2001
From: Bond_009
Date: Fri, 14 Jun 2019 18:38:14 +0200
Subject: [PATCH 002/262] Improvements to InstallationManager
---
.../Activity/ActivityLogEntryPoint.cs | 2 +-
.../Emby.Server.Implementations.csproj | 5 +
.../ScheduledTasks/Tasks/PluginUpdateTask.cs | 19 +-
.../Updates/InstallationManager.cs | 177 +++++++-----------
MediaBrowser.Api/PackageService.cs | 10 +-
.../Updates/IInstallationManager.cs | 16 +-
.../Updates/PackageVersionInfo.cs | 22 ++-
MediaBrowser.Providers/Omdb/OmdbProvider.cs | 3 +-
8 files changed, 109 insertions(+), 145 deletions(-)
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index 0530a251cc..cd3af90644 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -346,7 +346,7 @@ namespace Emby.Server.Implementations.Activity
});
}
- private void OnPluginUpdated(object sender, GenericEventArgs> e)
+ private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
{
CreateLogEntry(new ActivityLogEntry
{
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index d4e17c42ae..6c50698b2a 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -46,6 +46,11 @@
false
+
+
+ latest
+
+
true
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
index c6431c311c..bde7d5c81d 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
@@ -1,4 +1,3 @@
-using MediaBrowser.Common;
using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Net;
using System;
@@ -25,13 +24,10 @@ namespace Emby.Server.Implementations.ScheduledTasks
private readonly IInstallationManager _installationManager;
- private readonly IApplicationHost _appHost;
-
- public PluginUpdateTask(ILogger logger, IInstallationManager installationManager, IApplicationHost appHost)
+ public PluginUpdateTask(ILogger logger, IInstallationManager installationManager)
{
_logger = logger;
_installationManager = installationManager;
- _appHost = appHost;
}
///
@@ -40,14 +36,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// IEnumerable{BaseTaskTrigger}.
public IEnumerable GetDefaultTriggers()
{
- return new[] {
-
- // At startup
- new TaskTriggerInfo {Type = TaskTriggerInfo.TriggerStartup},
+ // At startup
+ yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerStartup };
- // Every so often
- new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
- };
+ // Every so often
+ yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks };
}
///
@@ -72,7 +65,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
try
{
- await _installationManager.InstallPackage(package, true, new SimpleProgress(), cancellationToken).ConfigureAwait(false);
+ await _installationManager.InstallPackage(package, new SimpleProgress(), cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 6833c20c3b..b3d76124bd 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
@@ -33,7 +34,7 @@ namespace Emby.Server.Implementations.Updates
///
/// The current installations
///
- public List> CurrentInstallations { get; set; }
+ private List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations { get; set; }
///
/// The completed installations
@@ -47,49 +48,15 @@ namespace Emby.Server.Implementations.Updates
///
public event EventHandler> PluginUninstalled;
- ///
- /// Called when [plugin uninstalled].
- ///
- /// The plugin.
- private void OnPluginUninstalled(IPlugin plugin)
- {
- PluginUninstalled?.Invoke(this, new GenericEventArgs { Argument = plugin });
- }
-
///
/// Occurs when [plugin updated].
///
- public event EventHandler>> PluginUpdated;
- ///
- /// Called when [plugin updated].
- ///
- /// The plugin.
- /// The new version.
- private void OnPluginUpdated(IPlugin plugin, PackageVersionInfo newVersion)
- {
- _logger.LogInformation("Plugin updated: {0} {1} {2}", newVersion.name, newVersion.versionStr ?? string.Empty, newVersion.classification);
-
- PluginUpdated?.Invoke(this, new GenericEventArgs> { Argument = new Tuple(plugin, newVersion) });
-
- _applicationHost.NotifyPendingRestart();
- }
+ public event EventHandler> PluginUpdated;
///
/// Occurs when [plugin updated].
///
public event EventHandler> PluginInstalled;
- ///
- /// Called when [plugin installed].
- ///
- /// The package.
- private void OnPluginInstalled(PackageVersionInfo package)
- {
- _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
-
- PluginInstalled?.Invoke(this, new GenericEventArgs { Argument = package });
-
- _applicationHost.NotifyPendingRestart();
- }
///
/// The _logger
@@ -111,7 +78,7 @@ namespace Emby.Server.Implementations.Updates
private readonly IZipClient _zipClient;
public InstallationManager(
- ILoggerFactory loggerFactory,
+ ILogger logger,
IApplicationHost appHost,
IApplicationPaths appPaths,
IHttpClient httpClient,
@@ -120,15 +87,15 @@ namespace Emby.Server.Implementations.Updates
IFileSystem fileSystem,
IZipClient zipClient)
{
- if (loggerFactory == null)
+ if (logger == null)
{
- throw new ArgumentNullException(nameof(loggerFactory));
+ throw new ArgumentNullException(nameof(logger));
}
- CurrentInstallations = new List>();
+ _currentInstallations = new List<(InstallationInfo, CancellationTokenSource)>();
_completedInstallationsInternal = new ConcurrentBag();
- _logger = loggerFactory.CreateLogger(nameof(InstallationManager));
+ _logger = logger;
_applicationHost = appHost;
_appPaths = appPaths;
_httpClient = httpClient;
@@ -138,21 +105,12 @@ namespace Emby.Server.Implementations.Updates
_zipClient = zipClient;
}
- private static Version GetPackageVersion(PackageVersionInfo version)
- {
- return new Version(ValueOrDefault(version.versionStr, "0.0.0.1"));
- }
-
- private static string ValueOrDefault(string str, string def)
- {
- return string.IsNullOrEmpty(str) ? def : str;
- }
-
///
/// Gets all available packages.
///
/// Task{List{PackageInfo}}.
- public async Task> GetAvailablePackages(CancellationToken cancellationToken,
+ public async Task> GetAvailablePackages(
+ CancellationToken cancellationToken,
bool withRegistration = true,
string packageType = null,
Version applicationVersion = null)
@@ -172,22 +130,14 @@ namespace Emby.Server.Implementations.Updates
{
Url = "https://repo.jellyfin.org/releases/plugin/manifest.json",
CancellationToken = cancellationToken,
- Progress = new SimpleProgress(),
CacheLength = GetCacheLength()
- }, "GET").ConfigureAwait(false))
+ }, HttpMethod.Get).ConfigureAwait(false))
+ using (var stream = response.Content)
{
- using (var stream = response.Content)
- {
- return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false));
- }
+ return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false));
}
}
- private PackageVersionClass GetSystemUpdateLevel()
- {
- return _applicationHost.SystemUpdateLevel;
- }
-
private static TimeSpan GetCacheLength()
{
return TimeSpan.FromMinutes(3);
@@ -211,7 +161,7 @@ namespace Emby.Server.Implementations.Updates
}
package.versions = versions
- .OrderByDescending(GetPackageVersion)
+ .OrderByDescending(x => x.Version)
.ToArray();
if (package.versions.Length == 0)
@@ -294,7 +244,7 @@ namespace Emby.Server.Implementations.Updates
return null;
}
- return package.versions.FirstOrDefault(v => GetPackageVersion(v).Equals(version) && v.classification == classification);
+ return package.versions.FirstOrDefault(v => v.Version == version && v.classification == classification);
}
///
@@ -331,7 +281,7 @@ namespace Emby.Server.Implementations.Updates
}
return package.versions
- .OrderByDescending(GetPackageVersion)
+ .OrderByDescending(x => x.Version)
.FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion));
}
@@ -346,14 +296,14 @@ namespace Emby.Server.Implementations.Updates
{
var catalog = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
- var systemUpdateLevel = GetSystemUpdateLevel();
+ var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
// Figure out what needs to be installed
return _applicationHost.Plugins.Select(p =>
{
var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, systemUpdateLevel);
- return latestPluginInfo != null && GetPackageVersion(latestPluginInfo) > p.Version ? latestPluginInfo : null;
+ return latestPluginInfo != null && latestPluginInfo.Version > p.Version ? latestPluginInfo : null;
}).Where(i => i != null)
.Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase)));
@@ -368,7 +318,7 @@ namespace Emby.Server.Implementations.Updates
/// The cancellation token.
/// Task.
/// package
- public async Task InstallPackage(PackageVersionInfo package, bool isPlugin, IProgress progress, CancellationToken cancellationToken)
+ public async Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken)
{
if (package == null)
{
@@ -391,12 +341,12 @@ namespace Emby.Server.Implementations.Updates
var innerCancellationTokenSource = new CancellationTokenSource();
- var tuple = new Tuple(installationInfo, innerCancellationTokenSource);
+ var tuple = (installationInfo, innerCancellationTokenSource);
// Add it to the in-progress list
- lock (CurrentInstallations)
+ lock (_currentInstallations)
{
- CurrentInstallations.Add(tuple);
+ _currentInstallations.Add(tuple);
}
var innerProgress = new ActionableProgress();
@@ -421,11 +371,11 @@ namespace Emby.Server.Implementations.Updates
try
{
- await InstallPackageInternal(package, isPlugin, innerProgress, linkedToken).ConfigureAwait(false);
+ await InstallPackageInternal(package, innerProgress, linkedToken).ConfigureAwait(false);
- lock (CurrentInstallations)
+ lock (_currentInstallations)
{
- CurrentInstallations.Remove(tuple);
+ _currentInstallations.Remove(tuple);
}
_completedInstallationsInternal.Add(installationInfo);
@@ -434,9 +384,9 @@ namespace Emby.Server.Implementations.Updates
}
catch (OperationCanceledException)
{
- lock (CurrentInstallations)
+ lock (_currentInstallations)
{
- CurrentInstallations.Remove(tuple);
+ _currentInstallations.Remove(tuple);
}
_logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.versionStr);
@@ -449,9 +399,9 @@ namespace Emby.Server.Implementations.Updates
{
_logger.LogError(ex, "Package installation failed");
- lock (CurrentInstallations)
+ lock (_currentInstallations)
{
- CurrentInstallations.Remove(tuple);
+ _currentInstallations.Remove(tuple);
}
PackageInstallationFailed?.Invoke(this, new InstallationFailedEventArgs
@@ -477,16 +427,12 @@ namespace Emby.Server.Implementations.Updates
/// The progress.
/// The cancellation token.
/// Task.
- private async Task InstallPackageInternal(PackageVersionInfo package, bool isPlugin, IProgress progress, CancellationToken cancellationToken)
+ private async Task InstallPackageInternal(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken)
{
- IPlugin plugin = null;
-
- if (isPlugin)
- {
- // Set last update time if we were installed before
- plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
+ // Set last update time if we were installed before
+ IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase));
- }
+
string targetPath = plugin == null ? null : plugin.AssemblyFilePath;
@@ -494,17 +440,20 @@ namespace Emby.Server.Implementations.Updates
await PerformPackageInstallation(progress, targetPath, package, cancellationToken).ConfigureAwait(false);
// Do plugin-specific processing
- if (isPlugin)
+ if (plugin == null)
{
- if (plugin == null)
- {
- OnPluginInstalled(package);
- }
- else
- {
- OnPluginUpdated(plugin, package);
- }
+ _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
+
+ PluginInstalled?.Invoke(this, new GenericEventArgs(package));
}
+ else
+ {
+ _logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
+
+ PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, PackageVersionInfo)>((plugin, package)));
+ }
+
+ _applicationHost.NotifyPendingRestart();
}
private async Task PerformPackageInstallation(IProgress progress, string target, PackageVersionInfo package, CancellationToken cancellationToken)
@@ -622,11 +571,34 @@ namespace Emby.Server.Implementations.Updates
_config.SaveConfiguration();
}
- OnPluginUninstalled(plugin);
+ PluginUninstalled?.Invoke(this, new GenericEventArgs { Argument = plugin });
_applicationHost.NotifyPendingRestart();
}
+ ///
+ public bool CancelInstallation(Guid id)
+ {
+ lock (_currentInstallations)
+ {
+ var install = _currentInstallations.Find(x => x.Item1.Id == id);
+ if (install == default((InstallationInfo, CancellationTokenSource)))
+ {
+ return false;
+ }
+
+ install.Item2.Cancel();
+ _currentInstallations.Remove(install);
+ return true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
///
/// Releases unmanaged and - optionally - managed resources.
///
@@ -635,21 +607,16 @@ namespace Emby.Server.Implementations.Updates
{
if (dispose)
{
- lock (CurrentInstallations)
+ lock (_currentInstallations)
{
- foreach (var tuple in CurrentInstallations)
+ foreach (var tuple in _currentInstallations)
{
tuple.Item2.Dispose();
}
- CurrentInstallations.Clear();
+ _currentInstallations.Clear();
}
}
}
-
- public void Dispose()
- {
- Dispose(true);
- }
}
}
diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs
index fbb876deae..cf1e08d53a 100644
--- a/MediaBrowser.Api/PackageService.cs
+++ b/MediaBrowser.Api/PackageService.cs
@@ -197,7 +197,7 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name));
}
- await _installationManager.InstallPackage(package, true, new SimpleProgress(), CancellationToken.None);
+ await _installationManager.InstallPackage(package, new SimpleProgress(), CancellationToken.None);
}
///
@@ -206,13 +206,7 @@ namespace MediaBrowser.Api
/// The request.
public void Delete(CancelPackageInstallation request)
{
- var info = _installationManager.CurrentInstallations.FirstOrDefault(i => i.Item1.Id.Equals(request.Id));
-
- if (info != null)
- {
- info.Item2.Cancel();
- }
+ _installationManager.CancelInstallation(new Guid(request.Id));
}
}
-
}
diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs
index a263be35f9..3472a5692f 100644
--- a/MediaBrowser.Common/Updates/IInstallationManager.cs
+++ b/MediaBrowser.Common/Updates/IInstallationManager.cs
@@ -15,11 +15,6 @@ namespace MediaBrowser.Common.Updates
event EventHandler PackageInstallationFailed;
event EventHandler PackageInstallationCancelled;
- ///
- /// The current installations
- ///
- List> CurrentInstallations { get; set; }
-
///
/// The completed installations
///
@@ -33,7 +28,7 @@ namespace MediaBrowser.Common.Updates
///
/// Occurs when [plugin updated].
///
- event EventHandler>> PluginUpdated;
+ event EventHandler> PluginUpdated;
///
/// Occurs when [plugin updated].
@@ -107,7 +102,7 @@ namespace MediaBrowser.Common.Updates
/// The cancellation token.
/// Task.
/// package
- Task InstallPackage(PackageVersionInfo package, bool isPlugin, IProgress progress, CancellationToken cancellationToken);
+ Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken);
///
/// Uninstalls a plugin
@@ -115,5 +110,12 @@ namespace MediaBrowser.Common.Updates
/// The plugin.
///
void UninstallPlugin(IPlugin plugin);
+
+ ///
+ /// Cancels the installation
+ ///
+ /// The id of the package that is being installed
+ /// Returns true if the install was cancelled
+ bool CancelInstallation(Guid id);
}
}
diff --git a/MediaBrowser.Model/Updates/PackageVersionInfo.cs b/MediaBrowser.Model/Updates/PackageVersionInfo.cs
index be531770d4..7ef07c0dfd 100644
--- a/MediaBrowser.Model/Updates/PackageVersionInfo.cs
+++ b/MediaBrowser.Model/Updates/PackageVersionInfo.cs
@@ -30,23 +30,25 @@ namespace MediaBrowser.Model.Updates
/// The _version
///
private Version _version;
+
///
/// Gets or sets the version.
/// Had to make this an interpreted property since Protobuf can't handle Version
///
/// The version.
[IgnoreDataMember]
- public Version version => _version ?? (_version = new Version(ValueOrDefault(versionStr, "0.0.0.1")));
-
- ///
- /// Values the or default.
- ///
- /// The STR.
- /// The def.
- /// System.String.
- private static string ValueOrDefault(string str, string def)
+ public Version Version
{
- return string.IsNullOrEmpty(str) ? def : str;
+ get
+ {
+ if (_version == null)
+ {
+ var ver = versionStr;
+ _version = new Version(string.IsNullOrEmpty(ver) ? "0.0.0.1" : ver);
+ }
+
+ return _version;
+ }
}
///
diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
index 19dce34d69..f8b8765802 100644
--- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -347,7 +348,7 @@ namespace MediaBrowser.Providers.Omdb
CancellationToken = cancellationToken,
BufferContent = true,
EnableDefaultUserAgent = true
- }, "GET");
+ }, HttpMethod.Get);
}
internal string GetDataFilePath(string imdbId)
From 18e6cd429ae026d87259eb05c45bf049616235dc Mon Sep 17 00:00:00 2001
From: SenorSmartyPants
Date: Sat, 22 Jun 2019 16:11:47 -0500
Subject: [PATCH 003/262] Update TVDB provider to search based on series
display order
---
.../TV/TheTVDB/TvDbClientManager.cs | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs
index 1d1fbd00f1..9fd72b65c1 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs
@@ -158,8 +158,20 @@ namespace MediaBrowser.Providers.TV.TheTVDB
// Prefer SxE over premiere date as it is more robust
if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue)
{
- episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value;
- episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value;
+ switch (searchInfo.SeriesDisplayOrder)
+ {
+ case "dvd":
+ episodeQuery.DvdEpisode = searchInfo.IndexNumber.Value;
+ episodeQuery.DvdSeason = searchInfo.ParentIndexNumber.Value;
+ break;
+ case "absolute":
+ episodeQuery.AbsoluteNumber = searchInfo.IndexNumber.Value;
+ break;
+ default: //aired order
+ episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value;
+ episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value;
+ break;
+ }
}
else if (searchInfo.PremiereDate.HasValue)
{
From ecb8d8991b1ea4e1e26a69c7c9e0a217927d27d4 Mon Sep 17 00:00:00 2001
From: Bond_009
Date: Fri, 28 Jun 2019 12:22:33 +0200
Subject: [PATCH 004/262] Fix whitespace
---
Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +-
Emby.Server.Implementations/Updates/InstallationManager.cs | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 6c50698b2a..48c915edee 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -48,7 +48,7 @@
- latest
+ latest
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index b3d76124bd..9bc85633d4 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -433,7 +433,6 @@ namespace Emby.Server.Implementations.Updates
IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase));
-
string targetPath = plugin == null ? null : plugin.AssemblyFilePath;
// Do the install
From 3b9766f58c15f6300a99151f9064775deccb66a3 Mon Sep 17 00:00:00 2001
From: crankdoofus <52436708+crankdoofus@users.noreply.github.com>
Date: Sat, 6 Jul 2019 11:41:33 +1000
Subject: [PATCH 005/262] Added option for NSIS
This change will
1. download NSIS zip,
2. unzip in temp folder,
3. use nsis to build the installer
---
deployment/windows/build-jellyfin.ps1 | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1
index 2999912b3a..bac907047a 100644
--- a/deployment/windows/build-jellyfin.ps1
+++ b/deployment/windows/build-jellyfin.ps1
@@ -1,5 +1,6 @@
[CmdletBinding()]
param(
+ [switch]$MakeNSIS,
[switch]$InstallFFMPEG,
[switch]$InstallNSSM,
[switch]$GenerateZip,
@@ -96,6 +97,23 @@ function Install-NSSM {
Remove-Item "$tempdir/nssm.zip" -Force -ErrorAction Continue | Write-Verbose
}
+function Make-NSIS {
+ param(
+ [string]$InstallLocation
+ )
+ Write-Verbose "Downloading NSIS"
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+ Invoke-WebRequest -Uri https://nchc.dl.sourceforge.net/project/nsis/NSIS%203/3.04/nsis-3.04.zip -UseBasicParsing -OutFile "$tempdir/nsis.zip" | Write-Verbose
+
+ Expand-Archive "$tempdir/nsis.zip" -DestinationPath "$tempdir/nsis/" | Write-Verbose
+ $env:InstallLocation = $InstallLocation
+ & "$tempdir/nsis/nsis-3.04/makensis.exe" ".\deployment\windows\jellyfin.nsi"
+ Copy-Item .\deployment\windows\Jellyfin.Installer.*.exe $InstallLocation\..\
+
+ Remove-Item "$tempdir/nsis/" -Recurse -Force -ErrorAction Continue | Write-Verbose
+ Remove-Item "$tempdir/nsis.zip" -Force -ErrorAction Continue | Write-Verbose
+}
+
Write-Verbose "Starting Build Process: Selected Environment is $WindowsVersion-$Architecture"
Build-JellyFin
if($InstallFFMPEG.IsPresent -or ($InstallFFMPEG -eq $true)){
@@ -106,6 +124,10 @@ if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){
Write-Verbose "Starting NSSM Install"
Install-NSSM $InstallLocation $Architecture
}
+if($MakeNSIS.IsPresent -or ($MakeNSIS -eq $true)){
+ Write-Verbose "Starting NSIS Package creation"
+ Make-NSIS $InstallLocation
+}
Copy-Item .\deployment\windows\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1
Copy-Item .\deployment\windows\install.bat $InstallLocation\install.bat
if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){
From 1fd827fa775e706704ef976f5b8d179416df41a6 Mon Sep 17 00:00:00 2001
From: crankdoofus <52436708+crankdoofus@users.noreply.github.com>
Date: Sat, 6 Jul 2019 11:43:20 +1000
Subject: [PATCH 006/262] Create jellyfin.nsi
---
deployment/windows/jellyfin.nsi | 185 ++++++++++++++++++++++++++++++++
1 file changed, 185 insertions(+)
create mode 100644 deployment/windows/jellyfin.nsi
diff --git a/deployment/windows/jellyfin.nsi b/deployment/windows/jellyfin.nsi
new file mode 100644
index 0000000000..b4a927610a
--- /dev/null
+++ b/deployment/windows/jellyfin.nsi
@@ -0,0 +1,185 @@
+!verbose 4
+;--------------------------------
+;Include Modern UI
+
+ !include "MUI2.nsh"
+ Var JellyfinVersion
+ Var defaultEmbyDataDir
+ Var JELLYFINDATADIR
+ Var ServiceInstalled
+;--------------------------------
+;General
+
+ ;Name and file
+ !getdllversion "$%InstallLocation%\jellyfin.dll" expv_
+ !echo "jellyfin.dll version is ${expv_1}.${expv_2}.${expv_3}.${expv_4}"
+ Name "Jellyfin ${expv_1}.${expv_2}.${expv_3}.${expv_4}"
+ OutFile "Jellyfin.Installer.${expv_1}.${expv_2}.${expv_3}.${expv_4}.exe"
+ BrandingText "Jellyfin ${expv_1}.${expv_2}.${expv_3}.${expv_4} Installer"
+ VIProductVersion "${expv_1}.${expv_2}.${expv_3}.${expv_4}"
+ VIFileVersion "${expv_1}.${expv_2}.${expv_3}.${expv_4}"
+ VIAddVersionKey "ProductName" "Jellyfin"
+ VIAddVersionKey "FileVersion" "${expv_1}.${expv_2}.${expv_3}.${expv_4}"
+
+ ;Default installation folder
+ InstallDir "$APPDATA\Jellyfin"
+
+ ;Get installation folder from registry if available
+ InstallDirRegKey HKLM "Software\Jellyfin" "InstallLocation"
+
+ ;Request application privileges for Windows Vista
+ RequestExecutionLevel admin
+ CRCCheck on
+ !define MUI_ABORTWARNING
+
+;--------------------------------
+;Pages
+
+; !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt"
+ !insertmacro MUI_PAGE_COMPONENTS
+ !insertmacro MUI_PAGE_DIRECTORY
+
+ !define MUI_PAGE_HEADER_TEXT "MUI_PAGE_HEADER_TEXT"
+ !define MUI_PAGE_HEADER_SUBTEXT "MUI_PAGE_HEADER_SUBTEXT"
+ !define MUI_DIRECTORYPAGE_TEXT_TOP "MUI_DIRECTORYPAGE_TEXT_TOP"
+ !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "APP Folder"
+ !define MUI_PAGE_CUSTOMFUNCTION_PRE ShowEmbyLibraryPage
+ !define MUI_DIRECTORYPAGE_VARIABLE $defaultEmbyDataDir
+ !insertmacro MUI_PAGE_DIRECTORY
+
+
+ !insertmacro MUI_PAGE_INSTFILES
+
+ !insertmacro MUI_UNPAGE_CONFIRM
+ !insertmacro MUI_UNPAGE_INSTFILES
+
+;--------------------------------
+;Languages
+
+ !insertmacro MUI_LANGUAGE "English"
+
+
+;--------------------------------
+;Installer Sections
+
+Section "Install Jellyfin (required)" InstallJellyfin
+ SetOutPath "$INSTDIR"
+;Create uninstaller
+
+ File /r $%InstallLocation%\*
+; Write the installation path into the registry
+ WriteRegStr HKLM "Software\Jellyfin" "InstallLocation" "$INSTDIR"
+
+; Write the uninstall keys for Windows
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin" "DisplayName" "Jellyfin $JellyfinVersion"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin" "UninstallString" '"$INSTDIR\Uninstall.exe"'
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin" "DisplayIcon" '"$INSTDIR\Jellyfin.exe",0'
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin" "Publisher" "The Jellyfin project"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin" "URLInfoAbout" "https://jellyfin.github.io/"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin" "DisplayVersion" "$JellyfinVersion"
+ WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin" "NoModify" 1
+ WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin" "NoRepair" 1
+ WriteUninstaller "$INSTDIR\Uninstall.exe"
+
+SectionEnd
+
+
+Section "Jellyfin Service" InstallService
+ ExecWait '"$INSTDIR"\nssm.exe install Jellyfin "$INSTDIR"\jellyfin.exe --datadir "$JELLYFINDATADIR"' $0
+ MessageBox MB_OK "Service install Error : $0"
+ Sleep 3000
+ ExecWait '"$INSTDIR"\nssm.exe set Jellyfin Start SERVICE_DELAYED_AUTO_START' $0
+ MessageBox MB_OK "Service setting Error : $0"
+ StrCpy $ServiceInstalled "YES"
+SectionEnd
+
+Section "Desktop shortcut" DesktopShortcut
+ SetShellVarContext current
+ CreateShortCut "$DESKTOP\Jellyfin.lnk" "$INSTDIR\jellyfin.exe"
+SectionEnd
+
+;TODO
+Section "Launch Jellyfin" LaunchJellyfin
+ !echo "Binaries at : $%InstallLocation%"
+; either start the service or launch jellyfin standalone
+ StrCmp $ServiceInstalled "YES" ServiceStart Standalone
+
+ ServiceStart:
+ ExecWait 'C:\Windows\System32\sc.exe start Jellyfin' $0
+ MessageBox MB_OK "Service start Error : $0"
+ Return
+
+ Standalone:
+ ExecWait '"$INSTDIR"\jellyfin.exe' $0
+ MessageBox MB_OK "start Error : $0"
+
+SectionEnd
+
+;TODO
+Section "Migrate Emby Library" MigrateEmbyLibrary
+
+ CopyFiles $defaultEmbyDataDir/config $JELLYFINDATADIR
+ CopyFiles $defaultEmbyDataDir/cache $JELLYFINDATADIR
+ CopyFiles $defaultEmbyDataDir/data $JELLYFINDATADIR
+ CopyFiles $defaultEmbyDataDir/metadata $JELLYFINDATADIR
+ CopyFiles $defaultEmbyDataDir/root $JELLYFINDATADIR
+
+SectionEnd
+
+
+;--------------------------------
+;Descriptions
+
+ ;Language strings
+ LangString DESC_InstallJellyfin ${LANG_ENGLISH} "Install Jellyfin"
+ LangString DESC_InstallService ${LANG_ENGLISH} "Install As a Service"
+ LangString DESC_DesktopShortcut ${LANG_ENGLISH} "Create a desktop shortcut"
+ LangString DESC_LaunchJellyfin ${LANG_ENGLISH} "Start Jellyfin after Install"
+ LangString DESC_MigrateEmbyLibrary ${LANG_ENGLISH} "Migrate existing Emby Library"
+
+ ;Assign language strings to sections
+ !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
+ !insertmacro MUI_DESCRIPTION_TEXT ${InstallJellyfin} $(DESC_InstallJellyfin)
+ !insertmacro MUI_DESCRIPTION_TEXT ${InstallService} $(DESC_InstallService)
+ !insertmacro MUI_DESCRIPTION_TEXT ${LaunchJellyfin} $(DESC_LaunchJellyfin)
+ !insertmacro MUI_DESCRIPTION_TEXT ${MigrateEmbyLibrary} $(DESC_MigrateEmbyLibrary)
+ !insertmacro MUI_FUNCTION_DESCRIPTION_END
+
+;--------------------------------
+;Uninstaller Section
+
+Section "Uninstall"
+
+
+;TODO
+; stop service or running instance
+ MessageBox MB_OK "uninstall $INSTDIR, $JELLYFINDATADIR"
+
+ Delete "$INSTDIR\Uninstall.exe"
+ RMDir /r "$INSTDIR"
+ RMDir /r "$JELLYFINDATADIR"
+ DeleteRegKey HKLM "Software\Jellyfin"
+ DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin"
+ Delete "$DESKTOP\Jellyfin.lnk"
+
+SectionEnd
+
+
+Function .onInit
+ SetShellVarContext all
+ !getdllversion "$%InstallLocation%\jellyfin.dll" expv_
+ StrCpy $JellyfinVersion "${expv_1}.${expv_2}.${expv_3}.${expv_4}"
+ StrCpy $JELLYFINDATADIR "$LOCALAPPDATA\jellyfin\"
+ StrCpy $ServiceInstalled "NO"
+ SectionSetFlags ${InstallJellyfin} 17
+FunctionEnd
+
+Function ShowEmbyLibraryPage
+ SectionGetFlags ${MigrateEmbyLibrary} $R0
+ IntOp $R0 $R0 & ${SF_SELECTED}
+ IntCmp $R0 ${SF_SELECTED} show
+
+ Abort ; Dont show the Emby folder selection window if Emby migrartion is not selected
+
+ show:
+FunctionEnd
From 43989800baa4e147a67c4b278e05face3ceadb5f Mon Sep 17 00:00:00 2001
From: crankdoofus <52436708+crankdoofus@users.noreply.github.com>
Date: Sat, 6 Jul 2019 12:16:34 +1000
Subject: [PATCH 007/262] Added -Force to nsis extraction
---
deployment/windows/build-jellyfin.ps1 | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1
index bac907047a..0cfd78dfa5 100644
--- a/deployment/windows/build-jellyfin.ps1
+++ b/deployment/windows/build-jellyfin.ps1
@@ -30,7 +30,7 @@ function Build-JellyFin {
Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture"
Write-Verbose "InstallLocation: $InstallLocation"
Write-Verbose "DotNetVerbosity: $DotNetVerbosity"
- dotnet publish -c $BuildType -r `"$windowsversion-$Architecture`" MediaBrowser.sln -o $InstallLocation -v $DotNetVerbosity
+# dotnet publish -c $BuildType -r `"$windowsversion-$Architecture`" MediaBrowser.sln -o $InstallLocation -v $DotNetVerbosity
}
function Install-FFMPEG {
@@ -105,7 +105,7 @@ function Make-NSIS {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri https://nchc.dl.sourceforge.net/project/nsis/NSIS%203/3.04/nsis-3.04.zip -UseBasicParsing -OutFile "$tempdir/nsis.zip" | Write-Verbose
- Expand-Archive "$tempdir/nsis.zip" -DestinationPath "$tempdir/nsis/" | Write-Verbose
+ Expand-Archive "$tempdir/nsis.zip" -DestinationPath "$tempdir/nsis/" -Force | Write-Verbose
$env:InstallLocation = $InstallLocation
& "$tempdir/nsis/nsis-3.04/makensis.exe" ".\deployment\windows\jellyfin.nsi"
Copy-Item .\deployment\windows\Jellyfin.Installer.*.exe $InstallLocation\..\
@@ -114,6 +114,7 @@ function Make-NSIS {
Remove-Item "$tempdir/nsis.zip" -Force -ErrorAction Continue | Write-Verbose
}
+
Write-Verbose "Starting Build Process: Selected Environment is $WindowsVersion-$Architecture"
Build-JellyFin
if($InstallFFMPEG.IsPresent -or ($InstallFFMPEG -eq $true)){
From de9ee10abc8d0401f11dc5efed7adc4d36695db8 Mon Sep 17 00:00:00 2001
From: crankdoofus <52436708+crankdoofus@users.noreply.github.com>
Date: Sat, 6 Jul 2019 12:18:20 +1000
Subject: [PATCH 008/262] Uncomment accidental commenting of compilation
---
deployment/windows/build-jellyfin.ps1 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1
index 0cfd78dfa5..7b04c93103 100644
--- a/deployment/windows/build-jellyfin.ps1
+++ b/deployment/windows/build-jellyfin.ps1
@@ -30,7 +30,7 @@ function Build-JellyFin {
Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture"
Write-Verbose "InstallLocation: $InstallLocation"
Write-Verbose "DotNetVerbosity: $DotNetVerbosity"
-# dotnet publish -c $BuildType -r `"$windowsversion-$Architecture`" MediaBrowser.sln -o $InstallLocation -v $DotNetVerbosity
+ dotnet publish -c $BuildType -r `"$windowsversion-$Architecture`" MediaBrowser.sln -o $InstallLocation -v $DotNetVerbosity
}
function Install-FFMPEG {
From a6819ffd1d7ed36f88316d7bfe82cf2e1979bd68 Mon Sep 17 00:00:00 2001
From: crankdoofus <52436708+crankdoofus@users.noreply.github.com>
Date: Sat, 6 Jul 2019 12:19:57 +1000
Subject: [PATCH 009/262] Cleaned up code
---
deployment/windows/jellyfin.nsi | 118 ++++++++++++++++++--------------
1 file changed, 65 insertions(+), 53 deletions(-)
diff --git a/deployment/windows/jellyfin.nsi b/deployment/windows/jellyfin.nsi
index b4a927610a..6187cf9fdd 100644
--- a/deployment/windows/jellyfin.nsi
+++ b/deployment/windows/jellyfin.nsi
@@ -1,25 +1,30 @@
+; Shows a lot of debug information while compiling
+; This can be removed once stable.
!verbose 4
;--------------------------------
;Include Modern UI
!include "MUI2.nsh"
- Var JellyfinVersion
- Var defaultEmbyDataDir
- Var JELLYFINDATADIR
- Var ServiceInstalled
+ Var _JELLYFINVERSION_
+ Var _DEFAULTEMBYDATADIR_
+ Var _JELLYFINDATADIR_
+ Var _SERVICEINSTALLED_
;--------------------------------
;General
- ;Name and file
- !getdllversion "$%InstallLocation%\jellyfin.dll" expv_
- !echo "jellyfin.dll version is ${expv_1}.${expv_2}.${expv_3}.${expv_4}"
- Name "Jellyfin ${expv_1}.${expv_2}.${expv_3}.${expv_4}"
- OutFile "Jellyfin.Installer.${expv_1}.${expv_2}.${expv_3}.${expv_4}.exe"
- BrandingText "Jellyfin ${expv_1}.${expv_2}.${expv_3}.${expv_4} Installer"
- VIProductVersion "${expv_1}.${expv_2}.${expv_3}.${expv_4}"
- VIFileVersion "${expv_1}.${expv_2}.${expv_3}.${expv_4}"
+; Align installer version with jellyfin.dll version
+ !getdllversion "$%InstallLocation%\jellyfin.dll" ver_
+ !echo "jellyfin.dll version is ${ver_1}.${ver_2}.${ver_3}.${ver_4}" ;!echo will print it while building
+
+ Name "Jellyfin ${ver_1}.${ver_2}.${ver_3}.${ver_4}"
+ OutFile "Jellyfin.Installer.${ver_1}.${ver_2}.${ver_3}.${ver_4}.exe"
+ BrandingText "Jellyfin ${ver_1}.${ver_2}.${ver_3}.${ver_4} Installer"
+
+; installer attributes
+ VIProductVersion "${ver_1}.${ver_2}.${ver_3}.${ver_4}"
+ VIFileVersion "${ver_1}.${ver_2}.${ver_3}.${ver_4}"
VIAddVersionKey "ProductName" "Jellyfin"
- VIAddVersionKey "FileVersion" "${expv_1}.${expv_2}.${expv_3}.${expv_4}"
+ VIAddVersionKey "FileVersion" "${ver_1}.${ver_2}.${ver_3}.${ver_4}"
;Default installation folder
InstallDir "$APPDATA\Jellyfin"
@@ -35,19 +40,21 @@
;--------------------------------
;Pages
-; !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt"
+;TODO
+;find a license to displayed before installer is started
+; !insertmacro MUI_PAGE_LICENSE "
Date: Sat, 6 Jul 2019 18:02:00 +1000
Subject: [PATCH 010/262] Changed order to include install scripts in installer
---
deployment/windows/build-jellyfin.ps1 | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1
index 7b04c93103..9a6be81d02 100644
--- a/deployment/windows/build-jellyfin.ps1
+++ b/deployment/windows/build-jellyfin.ps1
@@ -125,12 +125,12 @@ if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){
Write-Verbose "Starting NSSM Install"
Install-NSSM $InstallLocation $Architecture
}
+Copy-Item .\deployment\windows\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1
+Copy-Item .\deployment\windows\install.bat $InstallLocation\install.bat
if($MakeNSIS.IsPresent -or ($MakeNSIS -eq $true)){
Write-Verbose "Starting NSIS Package creation"
Make-NSIS $InstallLocation
}
-Copy-Item .\deployment\windows\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1
-Copy-Item .\deployment\windows\install.bat $InstallLocation\install.bat
if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){
Compress-Archive -Path $InstallLocation -DestinationPath "$InstallLocation/jellyfin.zip" -Force
}
From cea6a2217ecf1aaa7e1cb59bc90c2867f68dda7a Mon Sep 17 00:00:00 2001
From: crankdoofus <52436708+crankdoofus@users.noreply.github.com>
Date: Sat, 6 Jul 2019 18:34:48 +1000
Subject: [PATCH 011/262] Correct Service handling & LocalAppData folder
The service is now completely controlled by nssm as with the install-jellyfin.ps1
The LocalAppData had the global context, its now
Corrected order of Mandatory and Optional components.
---
deployment/windows/jellyfin.nsi | 39 +++++++++++++++++++--------------
1 file changed, 22 insertions(+), 17 deletions(-)
diff --git a/deployment/windows/jellyfin.nsi b/deployment/windows/jellyfin.nsi
index 6187cf9fdd..57008a6085 100644
--- a/deployment/windows/jellyfin.nsi
+++ b/deployment/windows/jellyfin.nsi
@@ -70,6 +70,8 @@
Section "Install Jellyfin (required)" InstallJellyfin
SetOutPath "$INSTDIR"
+ SetShellVarContext current
+ StrCpy $_JELLYFINDATADIR_ "$LOCALAPPDATA\jellyfin\"
; Pack all the files that were just compiled
File /r $%InstallLocation%\*
@@ -92,31 +94,27 @@ Section "Install Jellyfin (required)" InstallJellyfin
SectionEnd
-;TODO
-; This section hasn't been tested completely
-Section /o "Jellyfin Service" InstallService
- ExecWait '"$INSTDIR"\nssm.exe install Jellyfin "$INSTDIR"\jellyfin.exe --datadir "$_JELLYFINDATADIR_"' $0
- DetailPrint "Jellyfin Service install, $0"
- Sleep 3000
- ExecWait '"$INSTDIR"\nssm.exe set Jellyfin Start SERVICE_DELAYED_AUTO_START' $0
- DetailPrint "Jellyfin Service setting, $0"
- StrCpy $_SERVICEINSTALLED_ "YES"
-SectionEnd
-
Section "Jellyfin desktop shortcut" DesktopShortcut
SetShellVarContext current
DetailPrint "Creating desktop shortcut"
CreateShortCut "$DESKTOP\Jellyfin.lnk" "$INSTDIR\jellyfin.exe"
SectionEnd
-;TODO
-; This section hasn't been tested completely.
+Section /o "Jellyfin Service" InstallService
+ ExecWait '"$INSTDIR\nssm.exe" install Jellyfin "$INSTDIR\jellyfin.exe" --datadir "$_JELLYFINDATADIR_"' $0
+ DetailPrint "Jellyfin Service install, $0"
+ Sleep 3000
+ ExecWait '"$INSTDIR\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START' $0
+ DetailPrint "Jellyfin Service setting, $0"
+ StrCpy $_SERVICEINSTALLED_ "YES"
+SectionEnd
+
Section /o "Start Jellyfin after installation" LaunchJellyfin
; either start the service or launch jellyfin standalone
StrCmp $_SERVICEINSTALLED_ "YES" ServiceStart Standalone
ServiceStart:
- ExecWait 'C:\Windows\System32\sc.exe start Jellyfin' $0
+ ExecWait '"$INSTDIR\nssm.exe" start Jellyfin' $0
DetailPrint "Jellyfin service start, $0"
Return
@@ -162,9 +160,14 @@ SectionEnd
;Uninstaller Section
Section "Uninstall"
+ SetShellVarContext current
+ StrCpy $_JELLYFINDATADIR_ "$LOCALAPPDATA\jellyfin\"
;TODO
-; stop service or running instance
-; Figure out a way to stop Jellyfin - either standalone or service when uninstaller is invoked
+; stop running instance
+ ExecWait '"$INSTDIR\nssm.exe" stop Jellyfin' $0
+ DetailPrint "Jellyfin service stop, $0"
+ ExecWait '"$INSTDIR\nssm.exe" remove Jellyfin confirm' $0
+ DetailPrint "Jellyfin Service remove, $0"
Delete "$INSTDIR\Uninstall.exe"
RMDir /r "$INSTDIR"
@@ -181,7 +184,9 @@ Function .onInit
; Align installer version with jellyfin.dll version
!getdllversion "$%InstallLocation%\jellyfin.dll" ver_
StrCpy $_JELLYFINVERSION_ "${ver_1}.${ver_2}.${ver_3}.${ver_4}"
- StrCpy $_JELLYFINDATADIR_ "$LOCALAPPDATA\jellyfin\"
+ SetShellVarContext current
+ StrCpy $_JELLYFINDATADIR_ "$LOCALAPPDATA\jellyfin\"
+ DetailPrint "_JELLYFINDATADIR_ : $_JELLYFINDATADIR_"
StrCpy $_SERVICEINSTALLED_ "NO"
SectionSetFlags ${InstallJellyfin} 17 ; this makes the InstallJellyfin section mandatory
FunctionEnd
From 0f897589ed6349bb3c88919b06861daa80aec1be Mon Sep 17 00:00:00 2001
From: Bond_009
Date: Tue, 21 May 2019 19:28:34 +0200
Subject: [PATCH 012/262] Streamline authentication proccess
---
.../Cryptography/CryptographyProvider.cs | 88 +++---
.../Library/DefaultAuthenticationProvider.cs | 115 ++++----
.../Library/DefaultPasswordResetProvider.cs | 257 +++++++++---------
.../Library/InvalidAuthProvider.cs | 11 +-
.../Library/UserManager.cs | 49 ++--
MediaBrowser.Api/LiveTv/LiveTvService.cs | 19 --
.../Authentication/AuthenticationException.cs | 28 ++
.../Authentication/IAuthenticationProvider.cs | 3 +-
.../Authentication/IPasswordResetProvider.cs | 1 +
.../Cryptography/ICryptoProvider.cs | 6 +-
.../Cryptography/PasswordHash.cs | 164 ++++++-----
11 files changed, 372 insertions(+), 369 deletions(-)
create mode 100644 MediaBrowser.Controller/Authentication/AuthenticationException.cs
diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
index 6d7193ce20..f726dae2ee 100644
--- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
+++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
@@ -8,7 +8,7 @@ using MediaBrowser.Model.Cryptography;
namespace Emby.Server.Implementations.Cryptography
{
- public class CryptographyProvider : ICryptoProvider
+ public class CryptographyProvider : ICryptoProvider, IDisposable
{
private static readonly HashSet _supportedHashMethods = new HashSet()
{
@@ -28,26 +28,28 @@ namespace Emby.Server.Implementations.Cryptography
"System.Security.Cryptography.SHA512"
};
- public string DefaultHashMethod => "PBKDF2";
-
private RandomNumberGenerator _randomNumberGenerator;
private const int _defaultIterations = 1000;
+ private bool _disposed = false;
+
public CryptographyProvider()
{
- //FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
- //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
- //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
- //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
+ // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
+ // Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
+ // there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
+ // Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
_randomNumberGenerator = RandomNumberGenerator.Create();
}
- public Guid GetMD5(string str)
- {
- return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
- }
+ public string DefaultHashMethod => "PBKDF2";
+ [Obsolete("Use System.Security.Cryptography.MD5 directly")]
+ public Guid GetMD5(string str)
+ => new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
+
+ [Obsolete("Use System.Security.Cryptography.SHA1 directly")]
public byte[] ComputeSHA1(byte[] bytes)
{
using (var provider = SHA1.Create())
@@ -56,6 +58,7 @@ namespace Emby.Server.Implementations.Cryptography
}
}
+ [Obsolete("Use System.Security.Cryptography.MD5 directly")]
public byte[] ComputeMD5(Stream str)
{
using (var provider = MD5.Create())
@@ -64,6 +67,7 @@ namespace Emby.Server.Implementations.Cryptography
}
}
+ [Obsolete("Use System.Security.Cryptography.MD5 directly")]
public byte[] ComputeMD5(byte[] bytes)
{
using (var provider = MD5.Create())
@@ -73,9 +77,7 @@ namespace Emby.Server.Implementations.Cryptography
}
public IEnumerable GetSupportedHashMethods()
- {
- return _supportedHashMethods;
- }
+ => _supportedHashMethods;
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
{
@@ -93,14 +95,10 @@ namespace Emby.Server.Implementations.Cryptography
}
public byte[] ComputeHash(string hashMethod, byte[] bytes)
- {
- return ComputeHash(hashMethod, bytes, Array.Empty());
- }
+ => ComputeHash(hashMethod, bytes, Array.Empty());
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
- {
- return ComputeHash(DefaultHashMethod, bytes);
- }
+ => ComputeHash(DefaultHashMethod, bytes);
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
{
@@ -125,37 +123,27 @@ namespace Emby.Server.Implementations.Cryptography
}
}
}
- else
- {
- throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
- }
+
+ throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
+
}
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
- {
- return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
- }
+ => PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
public byte[] ComputeHash(PasswordHash hash)
{
int iterations = _defaultIterations;
if (!hash.Parameters.ContainsKey("iterations"))
{
- hash.Parameters.Add("iterations", _defaultIterations.ToString(CultureInfo.InvariantCulture));
+ hash.Parameters.Add("iterations", iterations.ToString(CultureInfo.InvariantCulture));
}
- else
+ else if (!int.TryParse(hash.Parameters["iterations"], out iterations))
{
- try
- {
- iterations = int.Parse(hash.Parameters["iterations"]);
- }
- catch (Exception e)
- {
- throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e);
- }
+ throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}");
}
- return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations);
+ return PBKDF2(hash.Id, hash.Hash, hash.Salt, iterations);
}
public byte[] GenerateSalt()
@@ -164,5 +152,29 @@ namespace Emby.Server.Implementations.Cryptography
_randomNumberGenerator.GetBytes(salt);
return salt;
}
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ _randomNumberGenerator.Dispose();
+ }
+
+ _randomNumberGenerator = null;
+
+ _disposed = true;
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
index fe09b07ff6..b07244fda1 100644
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
@@ -11,9 +11,9 @@ namespace Emby.Server.Implementations.Library
public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
{
private readonly ICryptoProvider _cryptographyProvider;
- public DefaultAuthenticationProvider(ICryptoProvider crypto)
+ public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider)
{
- _cryptographyProvider = crypto;
+ _cryptographyProvider = cryptographyProvider;
}
public string Name => "Default";
@@ -28,17 +28,17 @@ namespace Emby.Server.Implementations.Library
throw new NotImplementedException();
}
- // This is the verson that we need to use for local users. Because reasons.
+ // This is the version that we need to use for local users. Because reasons.
public Task Authenticate(string username, string password, User resolvedUser)
{
bool success = false;
if (resolvedUser == null)
{
- throw new Exception("Invalid username or password");
+ throw new ArgumentNullException(nameof(resolvedUser));
}
// As long as jellyfin supports passwordless users, we need this little block here to accomodate
- if (IsPasswordEmpty(resolvedUser, password))
+ if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password))
{
return Task.FromResult(new ProviderAuthenticationResult
{
@@ -50,37 +50,24 @@ namespace Emby.Server.Implementations.Library
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
- byte[] calculatedHash;
- string calculatedHashString;
- if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
+ if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
+ || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
{
- if (string.IsNullOrEmpty(readyHash.Salt))
- {
- calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes);
- calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty);
- }
- else
- {
- calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes);
- calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty);
- }
+ byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.Salt);
- if (calculatedHashString == readyHash.Hash)
+ if (calculatedHash.SequenceEqual(readyHash.Hash))
{
success = true;
- // throw new Exception("Invalid username or password");
}
}
else
{
- throw new Exception(string.Format($"Requested crypto method not available in provider: {readyHash.Id}"));
+ throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}");
}
- // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);
-
if (!success)
{
- throw new Exception("Invalid username or password");
+ throw new AuthenticationException("Invalid username or password");
}
return Task.FromResult(new ProviderAuthenticationResult
@@ -98,29 +85,22 @@ namespace Emby.Server.Implementations.Library
return;
}
- if (!user.Password.Contains("$"))
+ if (user.Password.IndexOf('$') == -1)
{
string hash = user.Password;
user.Password = string.Format("$SHA1${0}", hash);
}
- if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
+ if (user.EasyPassword != null
+ && user.EasyPassword.IndexOf('$') == -1)
{
string hash = user.EasyPassword;
user.EasyPassword = string.Format("$SHA1${0}", hash);
}
}
- public Task HasPassword(User user)
- {
- var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user));
- return Task.FromResult(hasConfiguredPassword);
- }
-
- private bool IsPasswordEmpty(User user, string password)
- {
- return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password));
- }
+ public bool HasPassword(User user)
+ => !string.IsNullOrEmpty(user.Password);
public Task ChangePassword(User user, string newPassword)
{
@@ -129,30 +109,24 @@ namespace Emby.Server.Implementations.Library
if (string.IsNullOrEmpty(user.Password))
{
PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider);
- newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
- newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes);
+ newPasswordHash.Salt = _cryptographyProvider.GenerateSalt();
newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod;
- newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash);
+ newPasswordHash.Hash = GetHashedChangeAuth(newPassword, newPasswordHash);
user.Password = newPasswordHash.ToString();
return Task.CompletedTask;
}
PasswordHash passwordHash = new PasswordHash(user.Password);
- if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt))
+ if (passwordHash.Id == "SHA1"
+ && passwordHash.Salt.Length == 0)
{
- passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
- passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes);
+ passwordHash.Salt = _cryptographyProvider.GenerateSalt();
passwordHash.Id = _cryptographyProvider.DefaultHashMethod;
- passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash);
+ passwordHash.Hash = GetHashedChangeAuth(newPassword, passwordHash);
}
else if (newPassword != null)
{
- passwordHash.Hash = GetHashedString(user, newPassword);
- }
-
- if (string.IsNullOrWhiteSpace(passwordHash.Hash))
- {
- throw new ArgumentNullException(nameof(passwordHash.Hash));
+ passwordHash.Hash = GetHashed(user, newPassword);
}
user.Password = passwordHash.ToString();
@@ -160,11 +134,6 @@ namespace Emby.Server.Implementations.Library
return Task.CompletedTask;
}
- public string GetPasswordHash(User user)
- {
- return user.Password;
- }
-
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
{
ConvertPasswordFormat(user);
@@ -190,13 +159,13 @@ namespace Emby.Server.Implementations.Library
return string.IsNullOrEmpty(user.EasyPassword)
? null
- : (new PasswordHash(user.EasyPassword)).Hash;
+ : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash);
}
- public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
+ internal byte[] GetHashedChangeAuth(string newPassword, PasswordHash passwordHash)
{
- passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
- return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
+ passwordHash.Hash = Encoding.UTF8.GetBytes(newPassword);
+ return _cryptographyProvider.ComputeHash(passwordHash);
}
///
@@ -215,10 +184,10 @@ namespace Emby.Server.Implementations.Library
passwordHash = new PasswordHash(user.Password);
}
- if (passwordHash.SaltBytes != null)
+ if (passwordHash.Salt != null)
{
// the password is modern format with PBKDF and we should take advantage of that
- passwordHash.HashBytes = Encoding.UTF8.GetBytes(str);
+ passwordHash.Hash = Encoding.UTF8.GetBytes(str);
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
}
else
@@ -227,5 +196,31 @@ namespace Emby.Server.Implementations.Library
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)));
}
}
+
+ public byte[] GetHashed(User user, string str)
+ {
+ PasswordHash passwordHash;
+ if (string.IsNullOrEmpty(user.Password))
+ {
+ passwordHash = new PasswordHash(_cryptographyProvider);
+ }
+ else
+ {
+ ConvertPasswordFormat(user);
+ passwordHash = new PasswordHash(user.Password);
+ }
+
+ if (passwordHash.Salt != null)
+ {
+ // the password is modern format with PBKDF and we should take advantage of that
+ passwordHash.Hash = Encoding.UTF8.GetBytes(str);
+ return _cryptographyProvider.ComputeHash(passwordHash);
+ }
+ else
+ {
+ // the password has no salt and should be called with the older method for safety
+ return _cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str));
+ }
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
index e218749d90..c7044820c7 100644
--- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
@@ -1,132 +1,125 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Users;
-
-namespace Emby.Server.Implementations.Library
-{
- public class DefaultPasswordResetProvider : IPasswordResetProvider
- {
- public string Name => "Default Password Reset Provider";
-
- public bool IsEnabled => true;
-
- private readonly string _passwordResetFileBase;
- private readonly string _passwordResetFileBaseDir;
- private readonly string _passwordResetFileBaseName = "passwordreset";
-
- private readonly IJsonSerializer _jsonSerializer;
- private readonly IUserManager _userManager;
- private readonly ICryptoProvider _crypto;
-
- public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
- {
- _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
- _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
- _jsonSerializer = jsonSerializer;
- _userManager = userManager;
- _crypto = cryptoProvider;
- }
-
- public async Task RedeemPasswordResetPin(string pin)
- {
- SerializablePasswordReset spr;
- HashSet usersreset = new HashSet();
- foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
- {
- using (var str = File.OpenRead(resetfile))
- {
- spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false);
- }
-
- if (spr.ExpirationDate < DateTime.Now)
- {
- File.Delete(resetfile);
- }
- else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
- {
- var resetUser = _userManager.GetUserByName(spr.UserName);
- if (resetUser == null)
- {
- throw new Exception($"User with a username of {spr.UserName} not found");
- }
-
- await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
- usersreset.Add(resetUser.Name);
- File.Delete(resetfile);
- }
- }
-
- if (usersreset.Count < 1)
- {
- throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
- }
- else
- {
- return new PinRedeemResult
- {
- Success = true,
- UsersReset = usersreset.ToArray()
- };
- }
- }
-
- public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
- {
- string pin = string.Empty;
- using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
- {
- byte[] bytes = new byte[4];
- cryptoRandom.GetBytes(bytes);
- pin = BitConverter.ToString(bytes);
- }
-
- DateTime expireTime = DateTime.Now.AddMinutes(30);
- string filePath = _passwordResetFileBase + user.InternalId + ".json";
- SerializablePasswordReset spr = new SerializablePasswordReset
- {
- ExpirationDate = expireTime,
- Pin = pin,
- PinFile = filePath,
- UserName = user.Name
- };
-
- try
- {
- using (FileStream fileStream = File.OpenWrite(filePath))
- {
- _jsonSerializer.SerializeToStream(spr, fileStream);
- await fileStream.FlushAsync().ConfigureAwait(false);
- }
- }
- catch (Exception e)
- {
- throw new Exception($"Error serializing or writing password reset for {user.Name} to location: {filePath}", e);
- }
-
- return new ForgotPasswordResult
- {
- Action = ForgotPasswordAction.PinCode,
- PinExpirationDate = expireTime,
- PinFile = filePath
- };
- }
-
- private class SerializablePasswordReset : PasswordPinCreationResult
- {
- public string Pin { get; set; }
-
- public string UserName { get; set; }
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Cryptography;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Users;
+
+namespace Emby.Server.Implementations.Library
+{
+ public class DefaultPasswordResetProvider : IPasswordResetProvider
+ {
+ public string Name => "Default Password Reset Provider";
+
+ public bool IsEnabled => true;
+
+ private readonly string _passwordResetFileBase;
+ private readonly string _passwordResetFileBaseDir;
+ private readonly string _passwordResetFileBaseName = "passwordreset";
+
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IUserManager _userManager;
+ private readonly ICryptoProvider _crypto;
+
+ public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
+ {
+ _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
+ _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
+ _jsonSerializer = jsonSerializer;
+ _userManager = userManager;
+ _crypto = cryptoProvider;
+ }
+
+ public async Task RedeemPasswordResetPin(string pin)
+ {
+ SerializablePasswordReset spr;
+ HashSet usersreset = new HashSet();
+ foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
+ {
+ using (var str = File.OpenRead(resetfile))
+ {
+ spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false);
+ }
+
+ if (spr.ExpirationDate < DateTime.Now)
+ {
+ File.Delete(resetfile);
+ }
+ else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
+ {
+ var resetUser = _userManager.GetUserByName(spr.UserName);
+ if (resetUser == null)
+ {
+ throw new Exception($"User with a username of {spr.UserName} not found");
+ }
+
+ await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
+ usersreset.Add(resetUser.Name);
+ File.Delete(resetfile);
+ }
+ }
+
+ if (usersreset.Count < 1)
+ {
+ throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
+ }
+ else
+ {
+ return new PinRedeemResult
+ {
+ Success = true,
+ UsersReset = usersreset.ToArray()
+ };
+ }
+ }
+
+ public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
+ {
+ string pin = string.Empty;
+ using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
+ {
+ byte[] bytes = new byte[4];
+ cryptoRandom.GetBytes(bytes);
+ pin = BitConverter.ToString(bytes);
+ }
+
+ DateTime expireTime = DateTime.Now.AddMinutes(30);
+ string filePath = _passwordResetFileBase + user.InternalId + ".json";
+ SerializablePasswordReset spr = new SerializablePasswordReset
+ {
+ ExpirationDate = expireTime,
+ Pin = pin,
+ PinFile = filePath,
+ UserName = user.Name
+ };
+
+ using (FileStream fileStream = File.OpenWrite(filePath))
+ {
+ _jsonSerializer.SerializeToStream(spr, fileStream);
+ await fileStream.FlushAsync().ConfigureAwait(false);
+ }
+
+ return new ForgotPasswordResult
+ {
+ Action = ForgotPasswordAction.PinCode,
+ PinExpirationDate = expireTime,
+ PinFile = filePath
+ };
+ }
+
+ private class SerializablePasswordReset : PasswordPinCreationResult
+ {
+ public string Pin { get; set; }
+
+ public string UserName { get; set; }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
index 25d2331373..6956369dc1 100644
--- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
+++ b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Entities;
@@ -16,12 +13,12 @@ namespace Emby.Server.Implementations.Library
public Task Authenticate(string username, string password)
{
- throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
+ throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
}
- public Task HasPassword(User user)
+ public bool HasPassword(User user)
{
- return Task.FromResult(true);
+ return true;
}
public Task ChangePassword(User user, string newPassword)
@@ -31,7 +28,7 @@ namespace Emby.Server.Implementations.Library
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
{
- // Nothing here
+ // Nothing here
}
public string GetPasswordHash(User user)
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 1701ced426..c8c8a108d5 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -266,6 +266,7 @@ namespace Emby.Server.Implementations.Library
builder.Append(c);
}
}
+
return builder.ToString();
}
@@ -286,17 +287,17 @@ namespace Emby.Server.Implementations.Library
if (user != null)
{
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
- authenticationProvider = authResult.Item1;
- updatedUsername = authResult.Item2;
- success = authResult.Item3;
+ authenticationProvider = authResult.authenticationProvider;
+ updatedUsername = authResult.username;
+ success = authResult.success;
}
else
{
// user is null
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false);
- authenticationProvider = authResult.Item1;
- updatedUsername = authResult.Item2;
- success = authResult.Item3;
+ authenticationProvider = authResult.authenticationProvider;
+ updatedUsername = authResult.username;
+ success = authResult.success;
if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
{
@@ -331,22 +332,25 @@ namespace Emby.Server.Implementations.Library
if (user == null)
{
- throw new SecurityException("Invalid username or password entered.");
+ throw new AuthenticationException("Invalid username or password entered.");
}
if (user.Policy.IsDisabled)
{
- throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
+ throw new AuthenticationException(string.Format(
+ CultureInfo.InvariantCulture,
+ "The {0} account is currently disabled. Please consult with your administrator.",
+ user.Name));
}
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
- throw new SecurityException("Forbidden.");
+ throw new AuthenticationException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed())
{
- throw new SecurityException("User is not allowed access at this time.");
+ throw new AuthenticationException("User is not allowed access at this time.");
}
// Update LastActivityDate and LastLoginDate, then save
@@ -357,6 +361,7 @@ namespace Emby.Server.Implementations.Library
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
UpdateUser(user);
}
+
UpdateInvalidLoginAttemptCount(user, 0);
}
else
@@ -429,7 +434,7 @@ namespace Emby.Server.Implementations.Library
return providers;
}
- private async Task> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
+ private async Task<(string username, bool success)> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
{
try
{
@@ -444,23 +449,23 @@ namespace Emby.Server.Implementations.Library
authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false);
}
- if(authenticationResult.Username != username)
+ if (authenticationResult.Username != username)
{
_logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username);
username = authenticationResult.Username;
}
- return new Tuple(username, true);
+ return (username, true);
}
- catch (Exception ex)
+ catch (AuthenticationException ex)
{
- _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name);
+ _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name);
- return new Tuple(username, false);
+ return (username, false);
}
}
- private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
+ private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
{
string updatedUsername = null;
bool success = false;
@@ -475,15 +480,15 @@ namespace Emby.Server.Implementations.Library
if (password == null)
{
// legacy
- success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
+ success = string.Equals(user.Password, hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
}
else
{
foreach (var provider in GetAuthenticationProviders(user))
{
var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
- updatedUsername = providerAuthResult.Item1;
- success = providerAuthResult.Item2;
+ updatedUsername = providerAuthResult.username;
+ success = providerAuthResult.success;
if (success)
{
@@ -510,7 +515,7 @@ namespace Emby.Server.Implementations.Library
}
}
- return new Tuple(authenticationProvider, username, success);
+ return (authenticationProvider, username, success);
}
private void UpdateInvalidLoginAttemptCount(User user, int newValue)
@@ -593,7 +598,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
+ bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user);
bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index e41ad540ad..8a4d6e2161 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -599,7 +599,6 @@ namespace MediaBrowser.Api.LiveTv
{
public bool ValidateLogin { get; set; }
public bool ValidateListings { get; set; }
- public string Pw { get; set; }
}
[Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")]
@@ -867,28 +866,10 @@ namespace MediaBrowser.Api.LiveTv
public async Task
-New idea or improvement? Something not working right?
+New idea or improvement?
+Check out our feature request hub.
+
+
+Something not working right?
Open an Issue.
From 9fff4b060e06569ca77636643901aa42767e318d Mon Sep 17 00:00:00 2001
From: Bond_009
Date: Sun, 28 Jul 2019 23:53:19 +0200
Subject: [PATCH 027/262] Replace custom code with Asp.Net Core code
---
.../ApplicationHost.cs | 6 +-
.../HttpServer/FileWriter.cs | 218 +++---
.../HttpServer/HttpListenerHost.cs | 154 ++---
.../HttpServer/ResponseFilter.cs | 26 +-
.../HttpServer/Security/AuthService.cs | 48 +-
.../Services/HttpResult.cs | 9 +-
.../Services/ResponseHelper.cs | 31 +-
.../Services/ServiceController.cs | 4 -
.../Services/ServiceExec.cs | 2 +-
.../Services/ServiceHandler.cs | 103 +--
.../SocketSharp/RequestMono.cs | 647 ------------------
.../SocketSharp/WebSocketSharpRequest.cs | 232 +++----
.../SocketSharp/WebSocketSharpResponse.cs | 98 ---
.../Playback/BaseStreamingService.cs | 2 +-
.../Net/AuthenticatedAttribute.cs | 3 +-
.../Services/IHasRequestFilter.cs | 4 +-
MediaBrowser.Model/Services/IRequest.cs | 30 +-
17 files changed, 393 insertions(+), 1224 deletions(-)
delete mode 100644 Emby.Server.Implementations/SocketSharp/RequestMono.cs
delete mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index ef2f59d303..a89cf95d3b 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -676,7 +676,7 @@ namespace Emby.Server.Implementations
var localPath = context.Request.Path.ToString();
var req = new WebSocketSharpRequest(request, response, request.Path, Logger);
- await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false);
+ await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
}
public static IStreamHelper StreamHelper { get; set; }
@@ -785,7 +785,7 @@ namespace Emby.Server.Implementations
HttpServer = new HttpListenerHost(
this,
- LoggerFactory,
+ LoggerFactory.CreateLogger(),
ServerConfigurationManager,
_configuration,
NetworkManager,
@@ -873,7 +873,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(authContext);
serviceCollection.AddSingleton(new SessionContext(UserManager, authContext, SessionManager));
- AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager);
+ AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager);
serviceCollection.AddSingleton(AuthService);
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
index ec41cc0a91..2890cca7ce 100644
--- a/Emby.Server.Implementations/HttpServer/FileWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs
@@ -1,50 +1,43 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Net;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
-using Emby.Server.Implementations.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
public class FileWriter : IHttpResult
{
+ private static readonly CultureInfo UsCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
+
+ private static readonly string[] _skipLogExtensions = {
+ ".js",
+ ".html",
+ ".css"
+ };
+
private readonly IStreamHelper _streamHelper;
- private ILogger Logger { get; set; }
+ private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
- private string RangeHeader { get; set; }
- private bool IsHeadRequest { get; set; }
-
- private long RangeStart { get; set; }
- private long RangeEnd { get; set; }
- private long RangeLength { get; set; }
- public long TotalContentLength { get; set; }
-
- public Action OnComplete { get; set; }
- public Action OnError { get; set; }
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
- public List Cookies { get; private set; }
-
- public FileShareMode FileShare { get; set; }
-
///
/// The _options
///
private readonly IDictionary _options = new Dictionary();
- ///
- /// Gets the options.
- ///
- /// The options.
- public IDictionary Headers => _options;
- public string Path { get; set; }
+ ///
+ /// The _requested ranges
+ ///
+ private List> _requestedRanges;
public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem, IStreamHelper streamHelper)
{
@@ -57,7 +50,7 @@ namespace Emby.Server.Implementations.HttpServer
_fileSystem = fileSystem;
Path = path;
- Logger = logger;
+ _logger = logger;
RangeHeader = rangeHeader;
Headers[HeaderNames.ContentType] = contentType;
@@ -80,6 +73,88 @@ namespace Emby.Server.Implementations.HttpServer
Cookies = new List();
}
+ private string RangeHeader { get; set; }
+
+ private bool IsHeadRequest { get; set; }
+
+ private long RangeStart { get; set; }
+
+ private long RangeEnd { get; set; }
+
+ private long RangeLength { get; set; }
+
+ public long TotalContentLength { get; set; }
+
+ public Action OnComplete { get; set; }
+
+ public Action OnError { get; set; }
+
+ public List Cookies { get; private set; }
+
+ public FileShareMode FileShare { get; set; }
+
+ ///
+ /// Gets the options.
+ ///
+ /// The options.
+ public IDictionary Headers => _options;
+
+ public string Path { get; set; }
+
+ ///
+ /// Gets the requested ranges.
+ ///
+ /// The requested ranges.
+ protected List> RequestedRanges
+ {
+ get
+ {
+ if (_requestedRanges == null)
+ {
+ _requestedRanges = new List>();
+
+ // Example: bytes=0-,32-63
+ var ranges = RangeHeader.Split('=')[1].Split(',');
+
+ foreach (var range in ranges)
+ {
+ var vals = range.Split('-');
+
+ long start = 0;
+ long? end = null;
+
+ if (!string.IsNullOrEmpty(vals[0]))
+ {
+ start = long.Parse(vals[0], UsCulture);
+ }
+
+ if (!string.IsNullOrEmpty(vals[1]))
+ {
+ end = long.Parse(vals[1], UsCulture);
+ }
+
+ _requestedRanges.Add(new KeyValuePair(start, end));
+ }
+ }
+
+ return _requestedRanges;
+ }
+ }
+
+ public string ContentType { get; set; }
+
+ public IRequest RequestContext { get; set; }
+
+ public object Response { get; set; }
+
+ public int Status { get; set; }
+
+ public HttpStatusCode StatusCode
+ {
+ get => (HttpStatusCode)Status;
+ set => Status = (int)value;
+ }
+
///
/// Sets the range values.
///
@@ -106,59 +181,10 @@ namespace Emby.Server.Implementations.HttpServer
var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
Headers[HeaderNames.ContentRange] = rangeString;
- Logger.LogDebug("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString);
+ _logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString);
}
- ///
- /// The _requested ranges
- ///
- private List> _requestedRanges;
- ///
- /// Gets the requested ranges.
- ///
- /// The requested ranges.
- protected List> RequestedRanges
- {
- get
- {
- if (_requestedRanges == null)
- {
- _requestedRanges = new List>();
-
- // Example: bytes=0-,32-63
- var ranges = RangeHeader.Split('=')[1].Split(',');
-
- foreach (var range in ranges)
- {
- var vals = range.Split('-');
-
- long start = 0;
- long? end = null;
-
- if (!string.IsNullOrEmpty(vals[0]))
- {
- start = long.Parse(vals[0], UsCulture);
- }
- if (!string.IsNullOrEmpty(vals[1]))
- {
- end = long.Parse(vals[1], UsCulture);
- }
-
- _requestedRanges.Add(new KeyValuePair(start, end));
- }
- }
-
- return _requestedRanges;
- }
- }
-
- private static readonly string[] SkipLogExtensions = {
- ".js",
- ".html",
- ".css"
- };
-
- public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken)
+ public async Task WriteToAsync(HttpResponse response, CancellationToken cancellationToken)
{
try
{
@@ -176,16 +202,16 @@ namespace Emby.Server.Implementations.HttpServer
{
var extension = System.IO.Path.GetExtension(path);
- if (extension == null || !SkipLogExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+ if (extension == null || !_skipLogExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- Logger.LogDebug("Transmit file {0}", path);
+ _logger.LogDebug("Transmit file {0}", path);
}
offset = 0;
count = 0;
}
- await response.TransmitFile(path, offset, count, FileShare, _fileSystem, _streamHelper, cancellationToken).ConfigureAwait(false);
+ await TransmitFile(response.Body, path, offset, count, FileShare, cancellationToken).ConfigureAwait(false);
}
finally
{
@@ -193,18 +219,32 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- public string ContentType { get; set; }
-
- public IRequest RequestContext { get; set; }
-
- public object Response { get; set; }
-
- public int Status { get; set; }
-
- public HttpStatusCode StatusCode
+ public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
- get => (HttpStatusCode)Status;
- set => Status = (int)value;
+ var fileOpenOptions = FileOpenOptions.SequentialScan;
+
+ // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ fileOpenOptions |= FileOpenOptions.Asynchronous;
+ }
+
+ using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
+ {
+ if (offset > 0)
+ {
+ fs.Position = offset;
+ }
+
+ if (count > 0)
+ {
+ await _streamHelper.CopyToAsync(fs, stream, count, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await fs.CopyToAsync(stream, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
+ }
+ }
}
}
}
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index d8938964fa..4c233456c4 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -5,7 +5,6 @@ using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Net;
@@ -30,11 +29,7 @@ namespace Emby.Server.Implementations.HttpServer
{
public class HttpListenerHost : IHttpServer, IDisposable
{
- private string DefaultRedirectPath { get; set; }
- public string[] UrlPrefixes { get; private set; }
-
- public event EventHandler> WebSocketConnected;
-
+ private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
private readonly IServerApplicationHost _appHost;
@@ -42,18 +37,15 @@ namespace Emby.Server.Implementations.HttpServer
private readonly IXmlSerializer _xmlSerializer;
private readonly IHttpListener _socketListener;
private readonly Func> _funcParseFn;
-
- public Action[] ResponseFilters { get; set; }
-
+ private readonly string _defaultRedirectPath;
private readonly Dictionary ServiceOperationsMap = new Dictionary();
- public static HttpListenerHost Instance { get; protected set; }
-
private IWebSocketListener[] _webSocketListeners = Array.Empty();
private readonly List _webSocketConnections = new List();
+ private bool _disposed = false;
public HttpListenerHost(
IServerApplicationHost applicationHost,
- ILoggerFactory loggerFactory,
+ ILogger logger,
IServerConfigurationManager config,
IConfiguration configuration,
INetworkManager networkManager,
@@ -62,9 +54,9 @@ namespace Emby.Server.Implementations.HttpServer
IHttpListener socketListener)
{
_appHost = applicationHost;
- Logger = loggerFactory.CreateLogger("HttpServer");
+ _logger = logger;
_config = config;
- DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
+ _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
_networkManager = networkManager;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
@@ -74,12 +66,20 @@ namespace Emby.Server.Implementations.HttpServer
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
Instance = this;
- ResponseFilters = Array.Empty>();
+ ResponseFilters = Array.Empty>();
}
+ public Action[] ResponseFilters { get; set; }
+
+ public static HttpListenerHost Instance { get; protected set; }
+
+ public string[] UrlPrefixes { get; private set; }
+
public string GlobalResponse { get; set; }
- protected ILogger Logger { get; }
+ public ServiceController ServiceController { get; private set; }
+
+ public event EventHandler> WebSocketConnected;
public object CreateInstance(Type type)
{
@@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.HttpServer
/// and no more processing should be done.
///
///
- public void ApplyRequestFilters(IRequest req, IResponse res, object requestDto)
+ public void ApplyRequestFilters(IRequest req, HttpResponse res, object requestDto)
{
//Exec all RequestFilter attributes with Priority < 0
var attributes = GetRequestFilterAttributes(requestDto.GetType());
@@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
- var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, Logger)
+ var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
{
OnReceive = ProcessWebSocketMessageReceived,
Url = e.Url,
@@ -215,16 +215,16 @@ namespace Emby.Server.Implementations.HttpServer
if (logExceptionStackTrace)
{
- Logger.LogError(ex, "Error processing request");
+ _logger.LogError(ex, "Error processing request");
}
else if (logExceptionMessage)
{
- Logger.LogError(ex.Message);
+ _logger.LogError(ex.Message);
}
var httpRes = httpReq.Response;
- if (httpRes.OriginalResponse.HasStarted)
+ if (httpRes.HasStarted)
{
return;
}
@@ -233,11 +233,11 @@ namespace Emby.Server.Implementations.HttpServer
httpRes.StatusCode = statusCode;
httpRes.ContentType = "text/html";
- await Write(httpRes, NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false);
+ await httpRes.WriteAsync(NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false);
}
catch (Exception errorEx)
{
- Logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
+ _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
}
}
@@ -431,7 +431,7 @@ namespace Emby.Server.Implementations.HttpServer
{
httpRes.StatusCode = 503;
httpRes.ContentType = "text/plain";
- await Write(httpRes, "Server shutting down").ConfigureAwait(false);
+ await httpRes.WriteAsync("Server shutting down", cancellationToken).ConfigureAwait(false);
return;
}
@@ -439,7 +439,7 @@ namespace Emby.Server.Implementations.HttpServer
{
httpRes.StatusCode = 400;
httpRes.ContentType = "text/plain";
- await Write(httpRes, "Invalid host").ConfigureAwait(false);
+ await httpRes.WriteAsync("Invalid host", cancellationToken).ConfigureAwait(false);
return;
}
@@ -447,7 +447,7 @@ namespace Emby.Server.Implementations.HttpServer
{
httpRes.StatusCode = 403;
httpRes.ContentType = "text/plain";
- await Write(httpRes, "Forbidden").ConfigureAwait(false);
+ await httpRes.WriteAsync("Forbidden", cancellationToken).ConfigureAwait(false);
return;
}
@@ -460,28 +460,27 @@ namespace Emby.Server.Implementations.HttpServer
if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
{
httpRes.StatusCode = 200;
- httpRes.AddHeader("Access-Control-Allow-Origin", "*");
- httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
- httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
+ httpRes.Headers.Add("Access-Control-Allow-Origin", "*");
+ httpRes.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
+ httpRes.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
httpRes.ContentType = "text/plain";
- await Write(httpRes, string.Empty).ConfigureAwait(false);
+ await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
return;
}
urlToLog = GetUrlToLog(urlString);
- Logger.LogDebug("HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}", urlToLog, httpReq.UserAgent ?? string.Empty, httpReq.HttpMethod, httpReq.Headers);
if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
{
- RedirectToUrl(httpRes, DefaultRedirectPath);
+ httpRes.Redirect(_defaultRedirectPath);
return;
}
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
{
- RedirectToUrl(httpRes, "emby/" + DefaultRedirectPath);
+ httpRes.Redirect("emby/" + _defaultRedirectPath);
return;
}
@@ -494,9 +493,10 @@ namespace Emby.Server.Implementations.HttpServer
if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
{
- await Write(httpRes,
+ await httpRes.WriteAsync(
"EmbyPlease update your Emby bookmark to " + newUrl + "").ConfigureAwait(false);
+ newUrl + "\">" + newUrl + "