From 620fbf0f89d33ab5d4d66373c7f9fe4580691f22 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 9 Jan 2021 10:51:59 +0100 Subject: [PATCH 001/282] Remove CropWhitespace function --- Emby.Drawing/ImageProcessor.cs | 5 -- Jellyfin.Api/Controllers/ImageController.cs | 5 +- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 85 ++----------------- .../Drawing/ImageProcessingOptions.cs | 3 - 4 files changed, 6 insertions(+), 92 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 8a2301d2d6..af6e5b339b 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -181,11 +181,6 @@ namespace Emby.Drawing { if (!File.Exists(cacheFilePath)) { - if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath)) - { - options.CropWhiteSpace = false; - } - string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index c606d327c5..cd12963104 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -1681,7 +1681,7 @@ namespace Jellyfin.Api.Controllers int? width, int? height, int? quality, - bool? cropWhitespace, + bool? cropWhitespace, // TODO: Remove bool? addPlayedIndicator, int? blur, string? backgroundColor, @@ -1756,7 +1756,6 @@ namespace Jellyfin.Api.Controllers backgroundColor, foregroundLayer, imageInfo, - cropWhitespace.Value, outputFormats, cacheDuration, responseHeaders, @@ -1855,7 +1854,6 @@ namespace Jellyfin.Api.Controllers string? backgroundColor, string? foregroundLayer, ItemImageInfo imageInfo, - bool cropWhitespace, IReadOnlyCollection supportedFormats, TimeSpan? cacheDuration, IDictionary headers, @@ -1868,7 +1866,6 @@ namespace Jellyfin.Api.Controllers var options = new ImageProcessingOptions { - CropWhiteSpace = cropWhitespace, Height = height, ImageIndex = index ?? 0, Image = imageInfo, diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index eab5777d5d..a786c01a60 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -91,9 +91,6 @@ namespace Jellyfin.Drawing.Skia } } - private static bool IsTransparent(SKColor color) - => (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0; - /// /// Convert a to a . /// @@ -111,65 +108,6 @@ namespace Jellyfin.Drawing.Skia }; } - private static bool IsTransparentRow(SKBitmap bmp, int row) - { - for (var i = 0; i < bmp.Width; ++i) - { - if (!IsTransparent(bmp.GetPixel(i, row))) - { - return false; - } - } - - return true; - } - - private static bool IsTransparentColumn(SKBitmap bmp, int col) - { - for (var i = 0; i < bmp.Height; ++i) - { - if (!IsTransparent(bmp.GetPixel(col, i))) - { - return false; - } - } - - return true; - } - - private SKBitmap CropWhiteSpace(SKBitmap bitmap) - { - var topmost = 0; - while (topmost < bitmap.Height && IsTransparentRow(bitmap, topmost)) - { - topmost++; - } - - int bottommost = bitmap.Height; - while (bottommost >= 0 && IsTransparentRow(bitmap, bottommost - 1)) - { - bottommost--; - } - - var leftmost = 0; - while (leftmost < bitmap.Width && IsTransparentColumn(bitmap, leftmost)) - { - leftmost++; - } - - var rightmost = bitmap.Width; - while (rightmost >= 0 && IsTransparentColumn(bitmap, rightmost - 1)) - { - rightmost--; - } - - var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost); - - using var image = SKImage.FromBitmap(bitmap); - using var subset = image.Subset(newRect); - return SKBitmap.FromImage(subset); - } - /// /// The path is null. /// The path is not valid. @@ -312,22 +250,11 @@ namespace Jellyfin.Drawing.Skia return resultBitmap; } - private SKBitmap? GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin) - { - if (cropWhitespace) - { - using var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin); - return bitmap == null ? null : CropWhiteSpace(bitmap); - } - - return Decode(path, forceAnalyzeBitmap, orientation, out origin); - } - - private SKBitmap? GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation) + private SKBitmap? GetBitmap(string path, bool autoOrient, ImageOrientation? orientation) { if (autoOrient) { - var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out var origin); + var bitmap = Decode(path, true, orientation, out var origin); if (bitmap != null && origin != SKEncodedOrigin.TopLeft) { @@ -340,7 +267,7 @@ namespace Jellyfin.Drawing.Skia return bitmap; } - return GetBitmap(path, cropWhitespace, false, orientation, out _); + return Decode(path, false, orientation, out _); } private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin) @@ -466,7 +393,7 @@ namespace Jellyfin.Drawing.Skia var blur = options.Blur ?? 0; var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); - using var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation); + using var bitmap = GetBitmap(inputPath, autoOrient, orientation); if (bitmap == null) { throw new InvalidDataException($"Skia unable to read image {inputPath}"); @@ -474,9 +401,7 @@ namespace Jellyfin.Drawing.Skia var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height); - if (!options.CropWhiteSpace - && options.HasDefaultOptions(inputPath, originalImageSize) - && !autoOrient) + if (options.HasDefaultOptions(inputPath, originalImageSize) && !autoOrient) { // Just spit out the original file if all the options are default return inputPath; diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 22105b7d79..22de9a43e8 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -24,8 +24,6 @@ namespace MediaBrowser.Controller.Drawing public int ImageIndex { get; set; } - public bool CropWhiteSpace { get; set; } - public int? Width { get; set; } public int? Height { get; set; } @@ -106,7 +104,6 @@ namespace MediaBrowser.Controller.Drawing PercentPlayed.Equals(0) && !UnplayedCount.HasValue && !Blur.HasValue && - !CropWhiteSpace && string.IsNullOrEmpty(BackgroundColor) && string.IsNullOrEmpty(ForegroundLayer); } From a5e55ba85956fe54539cb22dfd95c9499aa3de6c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 6 Feb 2021 15:59:27 -0500 Subject: [PATCH 002/282] Clean up UserManager.AuthenticateUser --- .../Users/UserManager.cs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index d1de5408c4..38a074e988 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -402,27 +402,18 @@ namespace Jellyfin.Server.Implementations.Users } var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); - bool success; - IAuthenticationProvider? authenticationProvider; + var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint) + .ConfigureAwait(false); + var authenticationProvider = authResult.authenticationProvider; + var success = authResult.success; - if (user != null) + if (user is null) { - var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint) - .ConfigureAwait(false); - authenticationProvider = authResult.authenticationProvider; - success = authResult.success; - } - else - { - var authResult = await AuthenticateLocalUser(username, password, null, remoteEndPoint) - .ConfigureAwait(false); - authenticationProvider = authResult.authenticationProvider; string updatedUsername = authResult.username; - success = authResult.success; if (success - && authenticationProvider != null - && !(authenticationProvider is DefaultAuthenticationProvider)) + && authenticationProvider is not null + && authenticationProvider is not DefaultAuthenticationProvider) { // Trust the username returned by the authentication provider username = updatedUsername; From 3a9fcb6abd249759bd44b2fbcf437beee8f611c2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 12 Feb 2021 17:34:51 +0100 Subject: [PATCH 003/282] Rewrite packet writing code for HdHomerun --- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 241 +++++------------- MediaBrowser.Common/Crc32.cs | 89 +++++++ tests/Jellyfin.Common.Tests/Crc32Tests.cs | 23 ++ .../LiveTv/HdHomerunManagerTests.cs | 39 +++ 4 files changed, 210 insertions(+), 182 deletions(-) create mode 100644 MediaBrowser.Common/Crc32.cs create mode 100644 tests/Jellyfin.Common.Tests/Crc32Tests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index f09338330f..188eca1580 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -2,6 +2,7 @@ using System; using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; using System.Globalization; using System.Net; @@ -10,6 +11,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common; using MediaBrowser.Controller.LiveTv; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun @@ -120,13 +122,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private static async Task CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken) { - var lockkeyMsg = CreateGetMessage(tuner, "lockkey"); - await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false); - byte[] buffer = ArrayPool.Shared.Rent(8192); try { - int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + var msgLen = WriteGetMessage(buffer, tuner, "lockkey"); + await stream.WriteAsync(buffer.AsMemory(0, msgLen), cancellationToken).ConfigureAwait(false); + + int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); ParseReturnMessage(buffer, receivedBytes, out string returnVal); @@ -166,9 +168,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _activeTuner = i; var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue); - var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); - await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false); - int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + var lockkeyMsgLen = WriteSetMessage(buffer, i, "lockkey", lockKeyString, null); + await stream.WriteAsync(buffer.AsMemory(0, lockkeyMsgLen), cancellationToken).ConfigureAwait(false); + int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked if (!ParseReturnMessage(buffer, receivedBytes, out _)) @@ -178,9 +180,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun foreach (var command in commands.GetCommands()) { - var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue); - await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false); - receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + var channelMsgLen = WriteSetMessage(buffer, i, command.Item1, command.Item2, lockKeyValue); + await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false); + receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked if (!ParseReturnMessage(buffer, receivedBytes, out _)) @@ -191,10 +193,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort); - var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue); + var targetMsgLen = WriteSetMessage(buffer, i, "target", targetValue, lockKeyValue); - await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false); - receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(buffer.AsMemory(0, targetMsgLen), cancellationToken).ConfigureAwait(false); + receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked if (!ParseReturnMessage(buffer, receivedBytes, out _)) @@ -232,9 +234,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { foreach (var command in commandList) { - var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey); - await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false); - int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.Item1, command.Item2, _lockkey); + await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false); + int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked if (!ParseReturnMessage(buffer, receivedBytes, out _)) @@ -265,17 +267,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var stream = client.GetStream(); - var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue); - await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false); - var buffer = ArrayPool.Shared.Rent(8192); try { - await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); - var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue); + var releaseTargetLen = WriteSetMessage(buffer, _activeTuner, "target", "none", lockKeyValue); + await stream.WriteAsync(buffer.AsMemory(0, releaseTargetLen)).ConfigureAwait(false); + + await stream.ReadAsync(buffer).ConfigureAwait(false); + var releaseKeyMsgLen = WriteSetMessage(buffer, _activeTuner, "lockkey", "none", lockKeyValue); _lockkey = null; - await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false); - await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); + await stream.WriteAsync(buffer.AsMemory(0, releaseKeyMsgLen)).ConfigureAwait(false); + await stream.ReadAsync(buffer).ConfigureAwait(false); } finally { @@ -283,105 +285,65 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - private static byte[] CreateGetMessage(int tuner, string name) + internal static int WriteGetMessage(Span buffer, int tuner, string name) { - var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name)); - int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length - - var message = new byte[messageLength]; - - int offset = InsertHeaderAndName(byteName, messageLength, message); - - bool flipEndian = BitConverter.IsLittleEndian; + var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name); + int offset = WriteHeaderAndName(buffer, byteName); // calculate crc and insert at the end of the message - var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4)); - if (flipEndian) - { - Array.Reverse(crcBytes); - } - - Buffer.BlockCopy(crcBytes, 0, message, offset, 4); + var crc = Crc32.Compute(buffer.Slice(0, offset)); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), crc); - return message; + return offset + 4; } - private static byte[] CreateSetMessage(int tuner, string name, string value, uint? lockkey) + private static int WriteSetMessage(Span buffer, int tuner, string name, string value, uint? lockkey) { - var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name)); - var byteValue = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\0", value)); + var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name); + int offset = WriteHeaderAndName(buffer, byteName); - int messageLength = byteName.Length + byteValue.Length + 12; - if (lockkey.HasValue) - { - messageLength += 6; - } - - var message = new byte[messageLength]; - - int offset = InsertHeaderAndName(byteName, messageLength, message); + buffer[offset++] = GetSetValue; + offset += WriteNullTerminatedString(buffer.Slice(offset), value); - bool flipEndian = BitConverter.IsLittleEndian; - - message[offset++] = GetSetValue; - message[offset++] = Convert.ToByte(byteValue.Length); - Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length); - offset += byteValue.Length; if (lockkey.HasValue) { - message[offset++] = GetSetLockkey; - message[offset++] = 4; - var lockKeyBytes = BitConverter.GetBytes(lockkey.Value); - if (flipEndian) - { - Array.Reverse(lockKeyBytes); - } - - Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4); + buffer[offset++] = GetSetLockkey; + buffer[offset++] = 4; + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), lockkey.Value); offset += 4; } // calculate crc and insert at the end of the message - var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4)); - if (flipEndian) - { - Array.Reverse(crcBytes); - } - - Buffer.BlockCopy(crcBytes, 0, message, offset, 4); + var crc = Crc32.Compute(buffer.Slice(0, offset)); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), crc); - return message; + return offset + 4; } - private static int InsertHeaderAndName(byte[] byteName, int messageLength, byte[] message) + internal static int WriteNullTerminatedString(Span buffer, ReadOnlySpan value) { - // check to see if we need to flip endiannes - bool flipEndian = BitConverter.IsLittleEndian; - int offset = 0; + int len = Encoding.UTF8.GetBytes(value, buffer.Slice(1)) + 1; - // create header bytes - var getSetBytes = BitConverter.GetBytes(GetSetRequest); - var msgLenBytes = BitConverter.GetBytes((ushort)(messageLength - 8)); // Subtrace 4 bytes for header and 4 bytes for crc + // Write length in front of value + buffer[0] = Convert.ToByte(len); - if (flipEndian) - { - Array.Reverse(getSetBytes); - Array.Reverse(msgLenBytes); - } + // null-terminate + buffer[len++] = 0; + + return len; + } + private static int WriteHeaderAndName(Span buffer, ReadOnlySpan payload) + { // insert header bytes into message - Buffer.BlockCopy(getSetBytes, 0, message, offset, 2); - offset += 2; - Buffer.BlockCopy(msgLenBytes, 0, message, offset, 2); - offset += 2; + BinaryPrimitives.WriteUInt16BigEndian(buffer, GetSetRequest); + int offset = 2; + // Subtract 4 bytes for header and 4 bytes for crc + BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(offset), (ushort)(payload.Length + 2)); // insert tag name and length - message[offset++] = GetSetName; - message[offset++] = Convert.ToByte(byteName.Length); - - // insert name string - Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length); - offset += byteName.Length; + buffer[offset++] = GetSetName; + offset += WriteNullTerminatedString(buffer.Slice(offset), payload); return offset; } @@ -442,90 +404,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator return true; } - - private static class HdHomerunCrc - { - private static uint[] crc_table = { - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, - 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, - 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, - 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, - 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, - 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, - 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, - 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, - 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, - 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, - 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, - 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, - 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, - 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, - 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, - 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, - 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, - 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, - 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, - 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, - 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, - 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, - 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, - 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, - 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, - 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, - 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, - 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, - 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, - 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, - 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; - - public static uint GetCrc32(byte[] bytes, int numBytes) - { - var hash = 0xffffffff; - for (var i = 0; i < numBytes; i++) - { - hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff]; - } - - var tmp = ~hash & 0xffffffff; - var b0 = tmp & 0xff; - var b1 = (tmp >> 8) & 0xff; - var b2 = (tmp >> 16) & 0xff; - var b3 = (tmp >> 24) & 0xff; - return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; - } - } } } diff --git a/MediaBrowser.Common/Crc32.cs b/MediaBrowser.Common/Crc32.cs new file mode 100644 index 0000000000..599eb4c99a --- /dev/null +++ b/MediaBrowser.Common/Crc32.cs @@ -0,0 +1,89 @@ +#pragma warning disable CS1591 + +using System; + +namespace MediaBrowser.Common +{ + public static class Crc32 + { + private static readonly uint[] _crcTable = + { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + public static uint Compute(ReadOnlySpan bytes) + { + var crc = 0xffffffff; + var len = bytes.Length; + for (var i = 0; i < len; i++) + { + crc = (crc >> 8) ^ _crcTable[(bytes[i] ^ crc) & 0xff]; + } + + return ~crc; + } + } +} diff --git a/tests/Jellyfin.Common.Tests/Crc32Tests.cs b/tests/Jellyfin.Common.Tests/Crc32Tests.cs new file mode 100644 index 0000000000..d93fb839c7 --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Crc32Tests.cs @@ -0,0 +1,23 @@ +using System; +using System.Text; +using MediaBrowser.Common; +using Xunit; + +namespace Jellyfin.Common.Tests +{ + public static class Crc32Tests + { + [Fact] + public static void Compute_Empty_Zero() + { + Assert.Equal(0, Crc32.Compute(Array.Empty())); + } + + [Theory] + [InlineData(0x414fa339, "The quick brown fox jumps over the lazy dog")] + public static void Compute_Valid_Success(uint expected, string data) + { + Assert.Equal(expected, Crc32.Compute(Encoding.UTF8.GetBytes(data))); + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs new file mode 100644 index 0000000000..15cc12d64a --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs @@ -0,0 +1,39 @@ +using System; +using Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.LiveTv +{ + public class HdHomerunManagerTests + { + [Fact] + public void WriteNullTerminatedString_Empty_Success() + { + ReadOnlySpan expected = stackalloc byte[] + { + 1, 0 + }; + + Span buffer = stackalloc byte[128]; + int len = HdHomerunManager.WriteNullTerminatedString(buffer, string.Empty); + + Assert.Equal(expected.Length, len); + Assert.True(expected.SequenceEqual(buffer.Slice(0, len))); + } + + [Fact] + public void WriteNullTerminatedString_Valid_Success() + { + ReadOnlySpan expected = stackalloc byte[] + { + 10, (byte)'T', (byte)'h', (byte)'e', (byte)' ', (byte)'q', (byte)'u', (byte)'i', (byte)'c', (byte)'k', 0 + }; + + Span buffer = stackalloc byte[128]; + int len = HdHomerunManager.WriteNullTerminatedString(buffer, "The quick"); + + Assert.Equal(expected.Length, len); + Assert.True(expected.SequenceEqual(buffer.Slice(0, len))); + } + } +} From e1bc322b709756529759fbb74cb003e133f30ab6 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 12 Feb 2021 18:35:54 +0100 Subject: [PATCH 004/282] Add test for WriteGetMessage --- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 51 +++++++++++-------- .../LiveTv/HdHomerunManagerTests.cs | 19 +++++++ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 188eca1580..20a4d87fba 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -288,19 +288,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun internal static int WriteGetMessage(Span buffer, int tuner, string name) { var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name); - int offset = WriteHeaderAndName(buffer, byteName); - - // calculate crc and insert at the end of the message - var crc = Crc32.Compute(buffer.Slice(0, offset)); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), crc); - - return offset + 4; + int offset = WriteHeaderAndPayload(buffer, byteName); + return FinishPacket(buffer, offset); } private static int WriteSetMessage(Span buffer, int tuner, string name, string value, uint? lockkey) { var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name); - int offset = WriteHeaderAndName(buffer, byteName); + int offset = WriteHeaderAndPayload(buffer, byteName); buffer[offset++] = GetSetValue; offset += WriteNullTerminatedString(buffer.Slice(offset), value); @@ -313,17 +308,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun offset += 4; } - // calculate crc and insert at the end of the message - var crc = Crc32.Compute(buffer.Slice(0, offset)); - BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), crc); - - return offset + 4; + return FinishPacket(buffer, offset); } - internal static int WriteNullTerminatedString(Span buffer, ReadOnlySpan value) + internal static int WriteNullTerminatedString(Span buffer, ReadOnlySpan payload) { - int len = Encoding.UTF8.GetBytes(value, buffer.Slice(1)) + 1; + int len = Encoding.UTF8.GetBytes(payload, buffer.Slice(1)) + 1; + // TODO: variable length: this can be 2 bytes if len > 127 // Write length in front of value buffer[0] = Convert.ToByte(len); @@ -333,21 +325,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return len; } - private static int WriteHeaderAndName(Span buffer, ReadOnlySpan payload) + private static int WriteHeaderAndPayload(Span buffer, ReadOnlySpan payload) { - // insert header bytes into message + // Packet type BinaryPrimitives.WriteUInt16BigEndian(buffer, GetSetRequest); - int offset = 2; - // Subtract 4 bytes for header and 4 bytes for crc - BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(offset), (ushort)(payload.Length + 2)); - // insert tag name and length + // We write the payload length at the end + int offset = 4; + + // Tag buffer[offset++] = GetSetName; - offset += WriteNullTerminatedString(buffer.Slice(offset), payload); + + // Payload length + data + int strLen = WriteNullTerminatedString(buffer.Slice(offset), payload); + offset += strLen; return offset; } + private static int FinishPacket(Span buffer, int offset) + { + // Payload length + BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), (ushort)(offset - 4)); + + // calculate crc and insert at the end of the message + var crc = Crc32.Compute(buffer.Slice(0, offset)); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), crc); + + return offset + 4; + } + private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal) { returnVal = string.Empty; diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs index 15cc12d64a..7e04a1ec12 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs @@ -35,5 +35,24 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Assert.Equal(expected.Length, len); Assert.True(expected.SequenceEqual(buffer.Slice(0, len))); } + + [Fact] + public void WriteGetMessage_Valid_Success() + { + ReadOnlySpan expected = stackalloc byte[] + { + 0, 4, + 0, 12, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 0xc0, 0xc9, 0x87, 0x33 + }; + + Span buffer = stackalloc byte[128]; + int len = HdHomerunManager.WriteGetMessage(buffer, 0, "N"); + + Assert.Equal(expected.Length, len); + Assert.True(expected.SequenceEqual(buffer.Slice(0, len))); + } } } From dc9e2ad1a4a21677cb22d3a2c76dee40859462bb Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 13 Feb 2021 11:46:38 +0100 Subject: [PATCH 005/282] Add some more Crc32 tests --- tests/Jellyfin.Common.Tests/Crc32Tests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Jellyfin.Common.Tests/Crc32Tests.cs b/tests/Jellyfin.Common.Tests/Crc32Tests.cs index d93fb839c7..e95a2867fd 100644 --- a/tests/Jellyfin.Common.Tests/Crc32Tests.cs +++ b/tests/Jellyfin.Common.Tests/Crc32Tests.cs @@ -19,5 +19,15 @@ namespace Jellyfin.Common.Tests { Assert.Equal(expected, Crc32.Compute(Encoding.UTF8.GetBytes(data))); } + + [Theory] + [InlineData(0x414fa339, "54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67")] + [InlineData(0x190a55ad, "0000000000000000000000000000000000000000000000000000000000000000")] + [InlineData(0xff6cab0b, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")] + [InlineData(0x91267e8a, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")] + public static void Compute_ValidHex_Success(uint expected, string data) + { + Assert.Equal(expected, Crc32.Compute(Convert.FromHexString(data))); + } } } From aff0aea60fc52a2253f04749f11bcb02f6e1f67c Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 23 Feb 2021 14:14:02 +0100 Subject: [PATCH 006/282] Improve branch coverage --- .../Models/LiveTvDtos/GetProgramsDto.cs | 1 - .../JsonOmdbNotAvailableInt32Converter.cs | 2 +- .../TestPluginWithoutPages.cs | 27 ++++++++++ .../Json/JsonCommaDelimitedArrayTests.cs | 52 ++++++++++++++++++- .../Json/JsonOmdbConverterTests.cs | 27 +++++----- 5 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs index 588ce717c8..8913180e48 100644 --- a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs +++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using MediaBrowser.Common.Json.Converters; diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs index cb3d83f584..3d97a9de55 100644 --- a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Common.Json.Converters return (int?)converter.ConvertFromString(str); } - return JsonSerializer.Deserialize(ref reader, options); + return JsonSerializer.Deserialize(ref reader, options); } /// diff --git a/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs b/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs new file mode 100644 index 0000000000..2d2f78a981 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using System; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace Jellyfin.Api.Tests +{ + public class TestPluginWithoutPages : BasePlugin + { + public TestPluginWithoutPages(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + public static TestPluginWithoutPages? Instance { get; private set; } + + public override Guid Id => new Guid("ae95cbe6-bd3d-4d73-8596-490db334611e"); + + public override string Name => nameof(TestPluginWithoutPages); + + public override string Description => "Server test Plugin without web pages."; + } +} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs index 0d2bdd1af9..ca300401da 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System; +using System.Text.Json; using System.Text.Json.Serialization; using Jellyfin.Common.Tests.Models; using MediaBrowser.Model.Session; @@ -8,6 +9,27 @@ namespace Jellyfin.Common.Tests.Json { public static class JsonCommaDelimitedArrayTests { + [Fact] + public static void Deserialize_String_Null_Success() + { + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": null }", options); + Assert.Null(value?.Value); + } + + [Fact] + public static void Deserialize_Empty_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = Array.Empty() + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": """" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + [Fact] public static void Deserialize_String_Valid_Success() { @@ -48,6 +70,34 @@ namespace Jellyfin.Common.Tests.Json Assert.Equal(desiredValue.Value, value?.Value); } + [Fact] + public static void Deserialize_GenericCommandType_EmptyEntry_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Invalid_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + [Fact] public static void Deserialize_GenericCommandType_Space_Valid_Success() { diff --git a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs index faed086a12..efe8063a07 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs @@ -38,6 +38,15 @@ namespace Jellyfin.Common.Tests.Json Assert.Null(result); } + [Theory] + [InlineData("\"8\"", 8)] + [InlineData("8", 8)] + public void Deserialize_NullableInt_Success(string input, int? expected) + { + var result = JsonSerializer.Deserialize(input, _options); + Assert.Equal(result, expected); + } + [Theory] [InlineData("\"N/A\"")] [InlineData("null")] @@ -48,21 +57,11 @@ namespace Jellyfin.Common.Tests.Json } [Theory] - [InlineData("\"8\"", 8)] - [InlineData("8", 8)] - public void Deserialize_Int_Success(string input, int expected) - { - var result = JsonSerializer.Deserialize(input, _options); - Assert.Equal(result, expected); - } - - [Fact] - public void Deserialize_Normal_String_Success() + [InlineData("\"Jellyfin\"", "Jellyfin")] + public void Deserialize_Normal_String_Success(string input, string expected) { - const string Input = "\"Jellyfin\""; - const string Expected = "Jellyfin"; - var result = JsonSerializer.Deserialize(Input, _options); - Assert.Equal(Expected, result); + var result = JsonSerializer.Deserialize(input, _options); + Assert.Equal(expected, result); } [Fact] From acac21d8dc3eb9383136ff692606dc2f65adf405 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 23 Feb 2021 16:45:10 +0100 Subject: [PATCH 007/282] Improve tests --- .../EntryPoints/UdpServerEntryPoint.cs | 11 + Emby.Server.Implementations/Udp/UdpServer.cs | 2 +- .../JsonCommaDelimitedArrayConverter.cs | 2 +- .../JsonPipeDelimitedArrayConverter.cs | 2 +- .../Json/Converters/JsonVersionConverter.cs | 20 - MediaBrowser.Common/Json/JsonDefaults.cs | 1 - .../Test Data/Updates/manifest-stable.json | 684 ++++++++++++++++++ .../Updates/InstallationManagerTests.cs | 57 ++ 8 files changed, 755 insertions(+), 24 deletions(-) delete mode 100644 MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index a12a6b26c5..3624e079f5 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -1,5 +1,6 @@ #nullable enable +using System; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -51,6 +52,8 @@ namespace Emby.Server.Implementations.EntryPoints /// public Task RunAsync() { + CheckDisposed(); + try { _udpServer = new UdpServer(_logger, _appHost, _config); @@ -64,6 +67,14 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + } + /// public void Dispose() { diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 4fd7ac0c18..d01184e0b6 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.Udp /// Starts the specified port. /// /// The port. - /// + /// The cancellation token to cancel operation. public void Start(int port, CancellationToken cancellationToken) { _endpoint = new IPEndPoint(IPAddress.Any, port); diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs index 38a7e1d20f..d9f6519e98 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Common.Json.Converters /// public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, value, options); + throw new NotImplementedException(); } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs index 377db1a446..c408a3be13 100644 --- a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Common.Json.Converters /// public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, value, options); + throw new NotImplementedException(); } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs deleted file mode 100644 index 37e6f64e34..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// - /// Converts a Version object or value to/from JSON. - /// - public class JsonVersionConverter : JsonConverter - { - /// - public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => new Version(reader.GetString()); - - /// - public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) - => writer.WriteStringValue(value.ToString()); - } -} diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 2ef24a884a..a3999e7e25 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -35,7 +35,6 @@ namespace MediaBrowser.Common.Json { new JsonGuidConverter(), new JsonNullableGuidConverter(), - new JsonVersionConverter(), new JsonStringEnumConverter(), new JsonNullableStructConverterFactory(), new JsonBoolNumberConverter(), diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json new file mode 100644 index 0000000000..b766e668e3 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json @@ -0,0 +1,684 @@ +[ + { + "guid": "a4df60c5-6ab4-412a-8f79-2cab93fb2bc5", + "name": "Anime", + "description": "Manage your anime in Jellyfin. This plugin supports several different metadata providers and options for organizing your collection.\n", + "overview": "Manage your anime from Jellyfin", + "owner": "jellyfin", + "category": "Metadata", + "versions": [ + { + "version": "10.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/anime/anime_10.0.0.0.zip", + "checksum": "93e969adeba1050423fc8817ed3c36f8", + "timestamp": "2020-08-17T01:41:13Z" + }, + { + "version": "9.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/anime/anime_9.0.0.0.zip", + "checksum": "9b1cebff835813e15f414f44b40c41c8", + "timestamp": "2020-07-20T01:30:16Z" + } + ] + }, + { + "guid": "70b7b43b-471b-4159-b4be-56750c795499", + "name": "Auto Organize", + "description": "Automatically organize your media", + "overview": "Automatically organize your media", + "owner": "jellyfin", + "category": "General", + "versions": [ + { + "version": "9.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/auto-organize/auto-organize_9.0.0.0.zip", + "checksum": "ff29ac3cbe05d208b6af94cd6d9dea39", + "timestamp": "2020-12-05T22:31:12Z" + }, + { + "version": "8.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/auto-organize/auto-organize_8.0.0.0.zip", + "checksum": "460bbb45e556464a8476b18e41c097f5", + "timestamp": "2020-07-20T01:30:25Z" + } + ] + }, + { + "guid": "9c4e63f1-031b-4f25-988b-4f7d78a8b53e", + "name": "Bookshelf", + "description": "Supports several different metadata providers and options for organizing your collection.\n", + "overview": "Manage your books", + "owner": "jellyfin", + "category": "Metadata", + "versions": [ + { + "version": "5.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/bookshelf/bookshelf_5.0.0.0.zip", + "checksum": "2063fb8ab317b8d77b200fde41eb5e1e", + "timestamp": "2020-12-05T22:03:13Z" + }, + { + "version": "4.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/bookshelf/bookshelf_4.0.0.0.zip", + "checksum": "fc9f76c0815d766491e5b0f30ede55ed", + "timestamp": "2020-07-20T01:30:33Z" + } + ] + }, + { + "guid": "cfa0f7f4-4155-4d71-849b-d6598dc4c5bb", + "name": "Email", + "description": "Send SMTP email notifications", + "overview": "Send SMTP email notifications", + "owner": "jellyfin", + "category": "Notifications", + "versions": [ + { + "version": "9.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/email/email_9.0.0.0.zip", + "checksum": "cfe7afc00f3fbd6d6ab8244d7ff968ce", + "timestamp": "2020-12-05T22:20:32Z" + }, + { + "version": "7.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/email/email_7.0.0.0.zip", + "checksum": "680ca511d8ad84923cb04f024fd8eb19", + "timestamp": "2020-07-20T01:30:40Z" + } + ] + }, + { + "guid": "170a157f-ac6c-437a-abdd-ca9c25cebd39", + "name": "Fanart", + "description": "Scrape poster images for movies, shows, and artists in your library.", + "overview": "Scrape poster images from Fanart", + "owner": "jellyfin", + "category": "Metadata", + "versions": [ + { + "version": "6.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/fanart/fanart_6.0.0.0.zip", + "checksum": "ee4360bfcc8722d5a3a54cfe7eef640f", + "timestamp": "2020-12-05T22:25:43Z" + }, + { + "version": "5.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/fanart/fanart_5.0.0.0.zip", + "checksum": "f842f7d65d23f377761c907d40b89647", + "timestamp": "2020-07-20T01:30:48Z" + } + ] + }, + { + "guid": "e29621a5-fa9e-4330-982e-ef6e54c0cad2", + "name": "Gotify Notification", + "description": "You must have a Gotify server to use this plugin!\n", + "overview": "Sends notifications to your Gotify server", + "owner": "crobibero", + "category": "Notifications", + "versions": [ + { + "version": "7.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/gotify-notification/gotify-notification_7.0.0.0.zip", + "checksum": "7c5ff9e8792c8cdee7e8a2aaeb6cc093", + "timestamp": "2020-07-20T01:30:56Z" + } + ] + }, + { + "guid": "a59b5c4b-05a8-488f-bfa8-7a63fffc7639", + "name": "IPTV", + "description": "Enable IPTV support in Jellyfin", + "overview": "Enable IPTV support in Jellyfin", + "owner": "jellyfin", + "category": "Channel", + "versions": [ + { + "version": "6.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/iptv/iptv_6.0.0.0.zip", + "checksum": "9cf103bf67a4eda7c3a42d9b235f6447", + "timestamp": "2020-07-20T01:31:05Z" + } + ] + }, + { + "guid": "4682DD4C-A675-4F1B-8E7C-79ADF137A8F8", + "name": "ISO Mounter", + "description": "Mount your ISO files for Jellyfin.\n", + "overview": "Mount your ISO files for Jellyfin", + "owner": "jellyfin", + "category": "Metadata", + "versions": [ + { + "version": "1.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/iso-mounter/iso-mounter_1.0.0.0.zip", + "checksum": "847e5bc7ac34c1bf4dc5b28173170fae", + "timestamp": "2020-07-20T01:31:13Z" + } + ] + }, + { + "guid": "771e19d6-5385-4caf-b35c-28a0e865cf63", + "name": "Kodi Sync Queue", + "description": "This plugin will track all media changes while Kodi clients are offline to decrease sync times.", + "overview": "Sync all media changes with Kodi clients", + "owner": "jellyfin", + "category": "General", + "versions": [ + { + "version": "6.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/kodi-sync-queue/kodi-sync-queue_6.0.0.0.zip", + "checksum": "787c856c0d2ad2224cdd8b3094cf0329", + "timestamp": "2020-12-05T22:10:37Z" + }, + { + "version": "5.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/kodi-sync-queue/kodi-sync-queue_5.0.0.0.zip", + "checksum": "08285397aecd93ea64a4f15d38b1bd7b", + "timestamp": "2020-07-20T01:31:22Z" + } + ] + }, + { + "guid": "958aad66-3784-4d2a-b89a-a7b6fab6e25c", + "name": "LDAP Authentication", + "description": "Authenticate your Jellyfin users against an LDAP database, and optionally create users who do not yet exist automatically.\nAllows the administrator to customize most aspects of the LDAP authentication process, including customizable search attributes, username attribute, and a search filter for administrative users (set on user creation). The user, via the \"Manual Login\" process, can enter any valid attribute value, which will be mapped back to the specified username attribute automatically as well.\n", + "overview": "Authenticate users against an LDAP database", + "owner": "jellyfin", + "category": "Authentication", + "versions": [ + { + "version": "10.0.0.0", + "changelog": "Update for 10.7 support\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/ldap-authentication/ldap-authentication_10.0.0.0.zip", + "checksum": "62e7e1cd3ffae0944c14750a3c90df4f", + "timestamp": "2020-12-05T19:48:10Z" + }, + { + "version": "9.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/ldap-authentication/ldap-authentication_9.0.0.0.zip", + "checksum": "7f2f83587a65a43ebf168e4058421463", + "timestamp": "2020-07-22T15:42:57Z" + }, + { + "version": "8.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/ldap-authentication/ldap-authentication_8.0.0.0.zip", + "checksum": "8af8cee62717d63577f8b1e710839415", + "timestamp": "2020-07-20T01:31:30Z" + } + ] + }, + { + "guid": "9574ac10-bf23-49bc-949f-924f23cfa48f", + "name": "NextPVR", + "description": "Provides access to live TV, program guide, and recordings from NextPVR.\n", + "overview": "Live TV plugin for NextPVR", + "owner": "jellyfin", + "category": "LiveTV", + "versions": [ + { + "version": "5.0.0.0", + "changelog": "Updated to use NextPVR API v5, no longer compatable with API v4.\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/nextpvr/nextpvr_5.0.0.0.zip", + "checksum": "d70f694d14bf9462ba2b2ebe110068d3", + "timestamp": "2020-12-05T22:24:03Z" + }, + { + "version": "4.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/nextpvr/nextpvr_4.0.0.0.zip", + "checksum": "b15949d895ac5a8c89496581db350478", + "timestamp": "2020-07-20T01:31:38Z" + } + ] + }, + { + "guid": "4b9ed42f-5185-48b5-9803-6ff2989014c4", + "name": "Open Subtitles", + "description": "Download subtitles from the internet to use with your media files.", + "overview": "Download subtitles for your media", + "owner": "jellyfin", + "category": "Metadata", + "versions": [ + { + "version": "10.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/open-subtitles/open-subtitles_10.0.0.0.zip", + "checksum": "ed99d03ec463bf15fca1256a113f57b4", + "timestamp": "2020-12-05T21:56:19Z" + }, + { + "version": "9.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/open-subtitles/open-subtitles_9.0.0.0.zip", + "checksum": "16789b26497cea0509daf6b18c579340", + "timestamp": "2020-07-20T01:32:00Z" + } + ] + }, + { + "guid": "5c534381-91a3-43cb-907a-35aa02eb9d2c", + "name": "Playback Reporting", + "description": "Collect and show user play statistics", + "overview": "Collect and show user play statistics", + "owner": "jellyfin", + "category": "General", + "versions": [ + { + "version": "9.0.0.0", + "changelog": "Add authentication to plugin endpoints\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/playback-reporting/playback-reporting_9.0.0.0.zip", + "checksum": "ca323b3dcb2cb86cc2e72a7a0f1eee22", + "timestamp": "2020-12-05T22:15:48Z" + }, + { + "version": "8.0.0.0", + "changelog": "Add authentication to plugin endpoints\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/playback-reporting/playback-reporting_8.0.0.0.zip", + "checksum": "58644c505586542ef0b8b65e2f704bd1", + "timestamp": "2020-11-18T03:01:51Z" + }, + { + "version": "7.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/playback-reporting/playback-reporting_7.0.0.0.zip", + "checksum": "6a361ef33bca97f9155856d02ff47380", + "timestamp": "2020-07-20T01:32:09Z" + } + ] + }, + { + "guid": "de228f12-e43e-4bd9-9fc0-2830819c3b92", + "name": "Pushbullet", + "description": "Get notifications via Pushbullet.\n", + "overview": "Pushbullet notification plugin", + "owner": "jellyfin", + "category": "Notifications", + "versions": [ + { + "version": "6.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/pushbullet/pushbullet_6.0.0.0.zip", + "checksum": "248cf3d56644f1d909e75aaddbdfb3a6", + "timestamp": "2020-12-06T02:47:53Z" + }, + { + "version": "5.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/pushbullet/pushbullet_5.0.0.0.zip", + "checksum": "dabbdd86328b2922a69dfa0c9e1c8343", + "timestamp": "2020-07-20T01:32:17Z" + } + ] + }, + { + "guid": "F240D6BE-5743-441B-87F1-A70ECAC42642", + "name": "Pushover", + "description": "Send messages to a wide range of devices through Pushover.", + "overview": "Send notifications via Pushover", + "owner": "crobibero", + "category": "Notifications", + "versions": [ + { + "version": "4.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/pushover/pushover_4.0.0.0.zip", + "checksum": "56a0da16c7e48cc184987737b7e155dd", + "timestamp": "2020-07-20T01:32:25Z" + } + ] + }, + { + "guid": "d4312cd9-5c90-4f38-82e8-51da566790e8", + "name": "Reports", + "description": "Generate reports of your media library", + "overview": "Generate reports of your media library", + "owner": "jellyfin", + "category": "General", + "versions": [ + { + "version": "11.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/reports/reports_11.0.0.0.zip", + "checksum": "d71bc6a4c008e58ee70ad44c83bfd310", + "timestamp": "2020-12-05T22:00:46Z" + }, + { + "version": "10.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/reports/reports_10.0.0.0.zip", + "checksum": "3917e75839337475b42daf2ba0b5bd7b", + "timestamp": "2020-10-19T19:30:41Z" + }, + { + "version": "9.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/reports/reports_9.0.0.0.zip", + "checksum": "5b5ad8d885616a21e8d1e8eecf5ea979", + "timestamp": "2020-10-16T23:52:37Z" + } + ] + }, + { + "guid": "1fc322a1-af2e-49a5-b2eb-a89b4240f700", + "name": "ServerWMC", + "description": "Provides access to Live TV, Program Guide and Recordings from your Windows MediaCenter Server running ServerWMC. Requires ServerWMC to be installed and running on your Windows MediaCenter machine.\n", + "overview": "Jellyfin Live TV plugin for Windows MediaCenter with ServerWMC", + "owner": "jellyfin", + "category": "LiveTV", + "versions": [ + { + "version": "6.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/serverwmc/serverwmc_6.0.0.0.zip", + "checksum": "3120af0cea2c1cb8b7cf578d9b4b862c", + "timestamp": "2020-12-05T22:28:15Z" + }, + { + "version": "5.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/serverwmc/serverwmc_5.0.0.0.zip", + "checksum": "dc44b039aa1b66eaf40a44fbf02d37e2", + "timestamp": "2020-07-20T01:32:42Z" + } + ] + }, + { + "guid": "94fb77c3-55ad-4c50-bf4e-4e5497467b79", + "name": "Slack Notifications", + "description": "Get notifications via Slack.\n", + "overview": "Get notifications via Slack", + "owner": "jellyfin", + "category": "Notifications", + "versions": [ + { + "version": "7.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/slack-notifications/slack-notifications_7.0.0.0.zip", + "checksum": "1d5330a77ce7b2a9ac8e5d58088a012c", + "timestamp": "2020-12-05T22:40:02Z" + }, + { + "version": "6.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/slack-notifications/slack-notifications_6.0.0.0.zip", + "checksum": "ede4cbe064542d1ecccc5823921bee4b", + "timestamp": "2020-07-20T01:32:50Z" + } + ] + }, + { + "guid": "bc4aad2e-d3d0-4725-a5e2-fd07949e5b42", + "name": "TMDb Box Sets", + "description": "Automatically create movie box sets based on TMDb collections", + "overview": "Automatically create movie box sets based on TMDb collections", + "owner": "jellyfin", + "category": "Metadata", + "versions": [ + { + "version": "7.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tmdb-box-sets/tmdb-box-sets_7.0.0.0.zip", + "checksum": "1551792e6af4d36f2cead01153c73cf0", + "timestamp": "2020-12-05T22:07:21Z" + }, + { + "version": "6.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tmdb-box-sets/tmdb-box-sets_6.0.0.0.zip", + "checksum": "b92b68a922c5fcbb8f4d47b8601b01b6", + "timestamp": "2020-07-20T01:32:58Z" + } + ] + }, + { + "guid": "4fe3201e-d6ae-4f2e-8917-e12bda571281", + "name": "Trakt", + "description": "Record your watched media with Trakt.\n", + "overview": "Record your watched media with Trakt", + "owner": "jellyfin", + "category": "General", + "versions": [ + { + "version": "11.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/trakt/trakt_11.0.0.0.zip", + "checksum": "2257ccde1e39114644a27e0966a0bf2d", + "timestamp": "2020-12-05T19:56:12Z" + }, + { + "version": "10.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/trakt/trakt_10.0.0.0.zip", + "checksum": "ab67e6b59ea2e7860a6a3ff7b8452759", + "timestamp": "2020-07-20T01:33:06Z" + } + ] + }, + { + "guid": "3fd018e5-5e78-4e58-b280-a0c068febee0", + "name": "TVHeadend", + "description": "Manage TVHeadend from Jellyfin", + "overview": "Manage TVHeadend from Jellyfin", + "owner": "jellyfin", + "category": "LiveTV", + "versions": [ + { + "version": "7.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tvheadend/tvheadend_7.0.0.0.zip", + "checksum": "1abbfce737b6962f4b1b2255dc63e932", + "timestamp": "2021-01-05T16:20:33Z" + }, + { + "version": "6.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tvheadend/tvheadend_6.0.0.0.zip", + "checksum": "143c34fd70d7173b8912cc03ce4b517d", + "timestamp": "2020-07-20T01:33:15Z" + } + ] + }, + { + "guid": "022a3003-993f-45f1-8565-87d12af2e12a", + "name": "InfuseSync", + "description": "This plugin will track all media changes while any Infuse clients are offline to decrease sync times when logging back in to your server.", + "overview": "Blazing fast indexing for Infuse", + "owner": "Firecore LLC", + "category": "General", + "versions": [ + { + "version": "1.2.4.0", + "changelog": "New Playlist support.\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://github.com/firecore/InfuseSync/releases/download/v1.2.4/InfuseSync-jellyfin-1.2.4.zip", + "checksum": "7adde11b8c8404fd2923f59d98fb1a30", + "timestamp": "2020-10-12T08:00:00Z" + }, + { + "version": "1.2.1.3", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://github.com/firecore/InfuseSync/releases/download/v1.2.3/InfuseSync-jellyfin-1.2.3.zip", + "checksum": "d8e2c5fe736a302097bb3bac3d04b1c4", + "timestamp": "2020-09-18T12:19:00Z" + }, + { + "version": "1.2.1.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://github.com/firecore/InfuseSync/releases/download/v1.2.1/InfuseSync-jellyfin-1.2.1.zip", + "checksum": "1a853e926cc422f5d79d398d9ae18ee8", + "timestamp": "2020-08-21T10:48:00Z" + }, + { + "version": "1.2.0.0", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://github.com/firecore/InfuseSync/releases/download/v1.2.0/InfuseSync-jellyfin-1.2.0.zip", + "checksum": "2d3c7859852695a7f05adc6d3fcbc783", + "timestamp": "2020-07-20T11:51:00Z" + } + ] + }, + { + "guid": "8119f3c6-cfc2-4d9c-a0ba-028f1d93e526", + "name": "Cover Art Archive", + "description": "This plugin provides images from the Cover Art Archive https://musicbrainz.org/doc/Cover_Art_Archive and depends on the MusicBrainz metadata provider to know what images belong where\n", + "overview": "MusicBrainz Cover Art Archive", + "owner": "jellyfin", + "category": "Metadata", + "versions": [ + { + "version": "2.0.0.0", + "changelog": "changelog\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/cover-art-archive/cover-art-archive_2.0.0.0.zip", + "checksum": "bea8fa4a37b3e7ed74e22266e7597a68", + "timestamp": "2020-12-06T02:51:03Z" + }, + { + "version": "1.0.0.3", + "changelog": "changelog\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/cover-art-archive/cover-art-archive_1.0.0.3.zip", + "checksum": "c502a5c54b168810614c1c40709b9598", + "timestamp": "2020-08-06T21:21:22Z" + } + ] + }, + { + "guid": "A4A488D0-17A3-4919-8D82-7F3DE4F6B209", + "name": "TV Maze", + "description": "Get TV metadata from TV Maze\n", + "overview": "Get TV metadata from TV Maze", + "owner": "jellyfin", + "category": "Metadata", + "versions": [ + { + "version": "5.0.0.0", + "changelog": "Get additional image types\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_5.0.0.0.zip", + "checksum": "509a85e40b1d1ac36eef45673deaf606", + "timestamp": "2020-12-06T02:51:56Z" + }, + { + "version": "4.0.0.0", + "changelog": "Get additional image types\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_4.0.0.0.zip", + "checksum": "58ee9ab3f129151bdfff033ad889ad87", + "timestamp": "2020-11-24T14:44:37Z" + }, + { + "version": "3.0.0.0", + "changelog": "Remove unused dependencies \n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_3.0.0.0.zip", + "checksum": "f3b2c70b3e136fb15c917e4420f4fdec", + "timestamp": "2020-11-09T14:32:56Z" + }, + { + "version": "2.0.0.0", + "changelog": "Remove unused dependencies \n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_2.0.0.0.zip", + "checksum": "c7662ae8ae52ce8a4e8d685d55f36e80", + "timestamp": "2020-11-09T02:33:11Z" + }, + { + "version": "1.0.0.0", + "changelog": "Initial release.\n", + "targetAbi": "10.6.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_1.0.0.0.zip", + "checksum": "c90eee48c12f2c07880b4b28e507fd14", + "timestamp": "2020-11-08T19:05:32Z" + } + ] + }, + { + "guid": "a677c0da-fac5-4cde-941a-7134223f14c8", + "name": "TheTVDB", + "description": "Get TV metadata from TheTvdb\n", + "overview": "Get TV metadata from TheTvdb", + "owner": "jellyfin", + "category": "Metadata", + "versions": [ + { + "version": "2.0.0.0", + "changelog": "Remove from Jellyfin core.\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/thetvdb/thetvdb_2.0.0.0.zip", + "checksum": "e46cee334476a1b475e5c553171c4cb6", + "timestamp": "2020-12-16T20:03:28Z" + }, + { + "version": "1.0.0.0", + "changelog": "Remove from Jellyfin core.\n", + "targetAbi": "10.7.0.0", + "sourceUrl": "https://repo.jellyfin.org/releases/plugin/thetvdb/thetvdb_1.0.0.0.zip", + "checksum": "5a3dca5c0db4824d83bfd4e7e2b7bf11", + "timestamp": "2020-12-06T02:56:40Z" + } + ] + } +] \ No newline at end of file diff --git a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs new file mode 100644 index 0000000000..4fa64d8a22 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using AutoFixture; +using AutoFixture.AutoMoq; +using Emby.Server.Implementations.Updates; +using MediaBrowser.Model.Updates; +using Moq; +using Moq.Protected; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Updates +{ + public class InstallationManagerTests + { + private readonly Fixture _fixture; + private readonly InstallationManager _installationManager; + + public InstallationManagerTests() + { + var messageHandler = new Mock(); + messageHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .Returns( + (m, _) => + { + return Task.FromResult(new HttpResponseMessage() + { + Content = new StreamContent(File.OpenRead("Test Data/Updates/" + m.RequestUri?.Segments[^1])) + }); + }); + + var http = new Mock(); + http.Setup(x => x.CreateClient(It.IsAny())) + .Returns(new HttpClient(messageHandler.Object)); + _fixture = new Fixture(); + _fixture.Customize(new AutoMoqCustomization + { + ConfigureMembers = true + }).Inject(http); + _installationManager = _fixture.Create(); + } + + [Fact] + public async Task GetPackages_Valid_Success() + { + IList packages = await _installationManager.GetPackages( + "Jellyfin Stable", + "https://repo.jellyfin.org/releases/plugin/manifest-stable.json", + false); + + Assert.Equal(25, packages.Count); + } + } +} From 032d72a8a7a1c3884d07b0f3b7bd93790988d414 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 23 Feb 2021 17:30:24 +0100 Subject: [PATCH 008/282] Pls fix race condition --- Emby.Server.Implementations/ApplicationHost.cs | 10 ++++++---- Jellyfin.Server/Program.cs | 2 +- tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0a7c5c1fb0..d7ac8bc3bb 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -126,7 +126,6 @@ namespace Emby.Server.Implementations private IMediaEncoder _mediaEncoder; private ISessionManager _sessionManager; private string[] _urlPrefixes; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); /// /// Gets a value indicating whether this instance can self restart. @@ -484,8 +483,9 @@ namespace Emby.Server.Implementations /// Runs the startup tasks. /// /// . - public async Task RunStartupTasksAsync() + public async Task RunStartupTasksAsync(CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); Logger.LogInformation("Running startup tasks"); Resolve().AddTasks(GetExports(false)); @@ -501,13 +501,15 @@ namespace Emby.Server.Implementations var stopWatch = new Stopwatch(); stopWatch.Start(); - await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + await Task.WhenAny(Task.WhenAll(StartEntryPoints(entryPoints, true)), Task.Delay(-1, cancellationToken)).ConfigureAwait(false); Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); CoreStartupHasCompleted = true; stopWatch.Restart(); - await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + await Task.WhenAny(Task.WhenAll(StartEntryPoints(entryPoints, false)), Task.Delay(-1, cancellationToken)).ConfigureAwait(false); Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); stopWatch.Stop(); } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index f05cdfe9bd..81199c7752 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -202,7 +202,7 @@ namespace Jellyfin.Server throw; } - await appHost.RunStartupTasksAsync().ConfigureAwait(false); + await appHost.RunStartupTasksAsync(_tokenSource.Token).ConfigureAwait(false); stopWatch.Stop(); diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs index dbbd5ac28c..ab7e0b6e7d 100644 --- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.IO; +using System.Threading; using Emby.Server.Implementations; using Emby.Server.Implementations.IO; using Jellyfin.Server; @@ -21,7 +22,7 @@ namespace Jellyfin.Api.Tests public class JellyfinApplicationFactory : WebApplicationFactory { private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data"); - private static readonly ConcurrentBag _disposableComponents = new ConcurrentBag(); + private readonly ConcurrentBag _disposableComponents = new ConcurrentBag(); /// /// Initializes a new instance of the class. @@ -96,7 +97,7 @@ namespace Jellyfin.Api.Tests var appHost = (TestAppHost)testServer.Services.GetRequiredService(); appHost.ServiceProvider = testServer.Services; appHost.InitializeServices().GetAwaiter().GetResult(); - appHost.RunStartupTasksAsync().GetAwaiter().GetResult(); + appHost.RunStartupTasksAsync(CancellationToken.None).GetAwaiter().GetResult(); return testServer; } From 2bc1eef4ddb23385f90c8bfc6af2ecf9888d7879 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 24 Feb 2021 22:18:59 +0100 Subject: [PATCH 009/282] Clean up code --- Emby.Server.Implementations/ApplicationHost.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index d7ac8bc3bb..b1c20815ec 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -499,17 +499,22 @@ namespace Emby.Server.Implementations var entryPoints = GetExports(); + cancellationToken.ThrowIfCancellationRequested(); + var stopWatch = new Stopwatch(); stopWatch.Start(); - cancellationToken.ThrowIfCancellationRequested(); - await Task.WhenAny(Task.WhenAll(StartEntryPoints(entryPoints, true)), Task.Delay(-1, cancellationToken)).ConfigureAwait(false); + + await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false); Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); CoreStartupHasCompleted = true; - stopWatch.Restart(); + cancellationToken.ThrowIfCancellationRequested(); - await Task.WhenAny(Task.WhenAll(StartEntryPoints(entryPoints, false)), Task.Delay(-1, cancellationToken)).ConfigureAwait(false); + + stopWatch.Restart(); + + await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); stopWatch.Stop(); } From 03cc6b1d782067e58975dbea4b61e42a3c04e1aa Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 25 Feb 2021 19:02:27 -0500 Subject: [PATCH 010/282] Make styling more consistent --- Jellyfin.Server.Implementations/Users/UserManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 38a074e988..25d7524c74 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -407,12 +407,12 @@ namespace Jellyfin.Server.Implementations.Users var authenticationProvider = authResult.authenticationProvider; var success = authResult.success; - if (user is null) + if (user == null) { string updatedUsername = authResult.username; if (success - && authenticationProvider is not null + && authenticationProvider != null && authenticationProvider is not DefaultAuthenticationProvider) { // Trust the username returned by the authentication provider From 02848189e3e2824fac6ee155f3efa52084279b18 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 28 Feb 2021 00:10:36 +0100 Subject: [PATCH 011/282] MaybeNullWhen(false) -> NotNullWhen(true) --- MediaBrowser.Model/Entities/ProviderIdsExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 4aff6e3a4f..11c3dbe42e 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Model.Entities /// The name. /// The provider id. /// true if a provider id with the given name was found; otherwise false. - public static bool TryGetProviderId(this IHasProviderIds instance, string name, [MaybeNullWhen(false)] out string id) + public static bool TryGetProviderId(this IHasProviderIds instance, string name, [NotNullWhen(true)] out string? id) { if (instance == null) { @@ -39,7 +39,7 @@ namespace MediaBrowser.Model.Entities /// The provider. /// The provider id. /// true if a provider id with the given name was found; otherwise false. - public static bool TryGetProviderId(this IHasProviderIds instance, MetadataProvider provider, [MaybeNullWhen(false)] out string id) + public static bool TryGetProviderId(this IHasProviderIds instance, MetadataProvider provider, [NotNullWhen(true)] out string? id) { return instance.TryGetProviderId(provider.ToString(), out id); } From f666b7e10268a5c19228d8fdb1bd313a642b0915 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 27 Feb 2021 20:12:55 +0000 Subject: [PATCH 012/282] fix --- .../ApplicationHost.cs | 22 ++++++++++++++----- .../IStartupOptions.cs | 12 +++++----- Jellyfin.Server/CoreAppHost.cs | 4 ++++ Jellyfin.Server/Program.cs | 1 + Jellyfin.Server/StartupOptions.cs | 4 ++-- .../JellyfinApplicationFactory.cs | 2 ++ 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1b9bb86bbd..785a3e89c4 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -99,6 +99,7 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; @@ -118,6 +119,7 @@ namespace Emby.Server.Implementations private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" }; private readonly IFileSystem _fileSystemManager; + private readonly IConfiguration _startupConfig; private readonly IXmlSerializer _xmlSerializer; private readonly IStartupOptions _startupOptions; private readonly IPluginManager _pluginManager; @@ -228,6 +230,11 @@ namespace Emby.Server.Implementations /// public int HttpsPort { get; private set; } + /// + /// Gets the PublishedServerUrl setting. + /// + public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig["PublishedServerUrl"]; + /// /// Gets the server configuration manager. /// @@ -240,12 +247,14 @@ namespace Emby.Server.Implementations /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// The interface. /// Instance of the interface. /// Instance of the interface. public ApplicationHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, + IConfiguration startupConfig, IFileSystem fileSystem, IServiceCollection serviceCollection) { @@ -268,6 +277,7 @@ namespace Emby.Server.Implementations Logger = LoggerFactory.CreateLogger(); _startupOptions = options; + _startupConfig = startupConfig; // Initialize runtime stat collection if (ServerConfigurationManager.Configuration.EnableMetrics) @@ -1145,10 +1155,10 @@ namespace Emby.Server.Implementations public string GetSmartApiUrl(IPAddress ipAddress, int? port = null) { // Published server ends with a / - if (_startupOptions.PublishedServerUrl != null) + if (!string.IsNullOrEmpty(PublishedServerUrl)) { // Published server ends with a '/', so we need to remove it. - return _startupOptions.PublishedServerUrl.ToString().Trim('/'); + return PublishedServerUrl.Trim('/'); } string smart = NetManager.GetBindInterface(ipAddress, out port); @@ -1165,10 +1175,10 @@ namespace Emby.Server.Implementations public string GetSmartApiUrl(HttpRequest request, int? port = null) { // Published server ends with a / - if (_startupOptions.PublishedServerUrl != null) + if (!string.IsNullOrEmpty(PublishedServerUrl)) { // Published server ends with a '/', so we need to remove it. - return _startupOptions.PublishedServerUrl.ToString().Trim('/'); + return PublishedServerUrl.Trim('/'); } string smart = NetManager.GetBindInterface(request, out port); @@ -1185,10 +1195,10 @@ namespace Emby.Server.Implementations public string GetSmartApiUrl(string hostname, int? port = null) { // Published server ends with a / - if (_startupOptions.PublishedServerUrl != null) + if (!string.IsNullOrEmpty(PublishedServerUrl)) { // Published server ends with a '/', so we need to remove it. - return _startupOptions.PublishedServerUrl.ToString().Trim('/'); + return PublishedServerUrl.Trim('/'); } string smart = NetManager.GetBindInterface(hostname, out port); diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 4bef59543f..0b823ff063 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,5 +1,5 @@ #pragma warning disable CS1591 - +#nullable enable using System; namespace Emby.Server.Implementations @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations /// /// Gets the value of the --ffmpeg command line option. /// - string FFmpegPath { get; } + string? FFmpegPath { get; } /// /// Gets the value of the --service command line option. @@ -19,21 +19,21 @@ namespace Emby.Server.Implementations /// /// Gets the value of the --package-name command line option. /// - string PackageName { get; } + string? PackageName { get; } /// /// Gets the value of the --restartpath command line option. /// - string RestartPath { get; } + string? RestartPath { get; } /// /// Gets the value of the --restartargs command line option. /// - string RestartArgs { get; } + string? RestartArgs { get; } /// /// Gets the value of the --published-server-url command line option. /// - Uri PublishedServerUrl { get; } + string? PublishedServerUrl { get; } } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index b76aa5e141..1daa32deea 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -21,6 +21,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; using MediaBrowser.Model.IO; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -37,18 +38,21 @@ namespace Jellyfin.Server /// The to be used by the . /// The to be used by the . /// The to be used by the . + /// The to be used by the . /// The to be used by the . /// The to be used by the . public CoreAppHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, + IConfiguration startupConfig, IFileSystem fileSystem, IServiceCollection collection) : base( applicationPaths, loggerFactory, options, + startupConfig, fileSystem, collection) { diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index f05cdfe9bd..18aa91ee13 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -164,6 +164,7 @@ namespace Jellyfin.Server appPaths, _loggerFactory, options, + startupConfig, new ManagedFileSystem(_loggerFactory.CreateLogger(), appPaths), serviceCollection); diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index b634340927..6d8210527c 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -77,7 +77,7 @@ namespace Jellyfin.Server /// [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] - public Uri? PublishedServerUrl { get; set; } + public string? PublishedServerUrl { get; set; } /// /// Gets the command line options as a dictionary that can be used in the .NET configuration system. @@ -94,7 +94,7 @@ namespace Jellyfin.Server if (PublishedServerUrl != null) { - config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl.ToString()); + config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl); } if (FFmpegPath != null) diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs index 54f8eb225f..262e8f9120 100644 --- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs @@ -8,6 +8,7 @@ using MediaBrowser.Common; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; @@ -77,6 +78,7 @@ namespace Jellyfin.Api.Tests appPaths, loggerFactory, commandLineOpts, + new ConfigurationBuilder().Build(), new ManagedFileSystem(loggerFactory.CreateLogger(), appPaths), serviceCollection); _disposableComponents.Add(appHost); From 1d6f489f17919e557987b2616048dc8def790f43 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 28 Feb 2021 10:11:37 +0000 Subject: [PATCH 013/282] comment change --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 785a3e89c4..7740b4fc5b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -231,7 +231,7 @@ namespace Emby.Server.Implementations public int HttpsPort { get; private set; } /// - /// Gets the PublishedServerUrl setting. + /// Gets the value of the PublishedServerUrl setting. /// public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig["PublishedServerUrl"]; From 159ecb882f419e485a95e727b647f0e2cf6eeb3d Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 28 Feb 2021 10:14:05 +0000 Subject: [PATCH 014/282] Fixed bad sync --- Emby.Server.Implementations/ApplicationHost.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6880b18423..37047a4f72 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -137,9 +137,6 @@ namespace Emby.Server.Implementations public bool CoreStartupHasCompleted { get; private set; } - /// - public Uri PublishedServerUrl => _startupOptions.PublishedServerUrl; - public virtual bool CanLaunchWebBrowser { get From caa8e7cdf37a426b983792077542ef0bb50721a7 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 28 Feb 2021 10:16:28 +0000 Subject: [PATCH 015/282] fixed build --- Emby.Dlna/Main/DlnaEntryPoint.cs | 2 +- MediaBrowser.Controller/IServerApplicationHost.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index ec87160293..9a2d524d16 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -316,7 +316,7 @@ namespace Emby.Dlna.Main _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri); - if (_appHost.PublishedServerUrl == null) + if (!string.IsNullOrEmpty(_appHost.PublishedServerUrl)) { // DLNA will only work over http, so we must reset to http:// : {port}. uri.Scheme = "http"; diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 6378625e87..20bfa697e5 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -55,7 +55,7 @@ namespace MediaBrowser.Controller /// /// Gets the configured published server url. /// - Uri PublishedServerUrl { get; } + string PublishedServerUrl { get; } /// /// Gets the system info. From 8836242559710cbe4577451c65f91133c3da4a79 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 28 Feb 2021 10:25:14 +0000 Subject: [PATCH 016/282] fixed tests --- tests/Jellyfin.Api.Tests/TestAppHost.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Jellyfin.Api.Tests/TestAppHost.cs b/tests/Jellyfin.Api.Tests/TestAppHost.cs index 772e98d049..eb4c9b3055 100644 --- a/tests/Jellyfin.Api.Tests/TestAppHost.cs +++ b/tests/Jellyfin.Api.Tests/TestAppHost.cs @@ -4,6 +4,7 @@ using Emby.Server.Implementations; using Jellyfin.Server; using MediaBrowser.Controller; using MediaBrowser.Model.IO; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -20,18 +21,21 @@ namespace Jellyfin.Api.Tests /// The to be used by the . /// The to be used by the . /// The to be used by the . + /// The to be used by the . /// The to be used by the . /// The to be used by the . public TestAppHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, + IConfiguration startup, IFileSystem fileSystem, IServiceCollection collection) : base( applicationPaths, loggerFactory, options, + startup, fileSystem, collection) { From 16694b0cfcc51ba009a0aedce0d383ab010e8b99 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 27 Feb 2021 22:46:03 +0100 Subject: [PATCH 017/282] Add nfo thumb tag support --- .../ApplicationHost.cs | 2 + .../Providers/MetadataResult.cs | 12 ++- .../Manager/MetadataService.cs | 6 ++ .../Parsers/BaseNfoParser.cs | 78 ++++++++++++++++++- .../Parsers/EpisodeNfoParser.cs | 6 +- .../Parsers/MovieNfoParser.cs | 6 +- .../Parsers/SeasonNfoParser.cs | 6 +- .../Parsers/SeriesNfoParser.cs | 6 +- .../Providers/AlbumNfoProvider.cs | 8 +- .../Providers/ArtistNfoProvider.cs | 8 +- .../Providers/BaseVideoNfoProvider.cs | 9 ++- .../Providers/EpisodeNfoProvider.cs | 8 +- .../Providers/MovieNfoProvider.cs | 6 +- .../Providers/MusicVideoNfoProvider.cs | 6 +- .../Providers/SeasonNfoProvider.cs | 8 +- .../Providers/SeriesNfoProvider.cs | 8 +- .../Providers/VideoNfoProvider.cs | 6 +- .../Parsers/EpisodeNfoProviderTests.cs | 11 ++- .../Parsers/MovieNfoParserTests.cs | 64 ++++++++++++++- .../Parsers/MusicAlbumNfoProviderTests.cs | 11 ++- .../Parsers/MusicArtistNfoParserTests.cs | 9 ++- .../Parsers/MusicVideoNfoParserTests.cs | 9 ++- .../Parsers/SeasonNfoProviderTests.cs | 9 ++- .../Parsers/SeriesNfoParserTests.cs | 3 +- .../Test Data/Justice League.nfo | 2 + 25 files changed, 266 insertions(+), 41 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1b9bb86bbd..788e08e48b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -682,6 +682,8 @@ namespace Emby.Server.Implementations ServiceCollection.AddScoped(); ServiceCollection.AddScoped(); ServiceCollection.AddScoped(); + + ServiceCollection.AddSingleton(); } /// diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index 1c695cafa0..864cb30506 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -4,21 +4,25 @@ using System; using System.Collections.Generic; using System.Globalization; using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Providers { public class MetadataResult { - public List Images { get; set; } - - public List UserDataList { get; set; } - public MetadataResult() { Images = new List(); + RemoteImages = new List<(string url, ImageType type)>(); ResultLanguage = "en"; } + public List Images { get; set; } + + public List<(string url, ImageType type)> RemoteImages { get; set; } + + public List UserDataList { get; set; } + public List People { get; set; } public bool HasMetadata { get; set; } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 8b3ca17ca1..494e479a57 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -706,6 +706,12 @@ namespace MediaBrowser.Providers.Manager if (localItem.HasMetadata) { + foreach (var remoteImage in localItem.RemoteImages) + { + await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false); + refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate; + } + if (imageService.MergeImages(item, localItem.Images)) { refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate; diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 6f164caf37..aa89936d0c 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -27,6 +27,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers private readonly IConfigurationManager _config; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; + private readonly IDirectoryService _directoryService; private Dictionary _validProviderIds; /// @@ -37,12 +38,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public BaseNfoParser( ILogger logger, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) { Logger = logger; _config = config; @@ -50,6 +53,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers _validProviderIds = new Dictionary(); _userManager = userManager; _userDataManager = userDataManager; + _directoryService = directoryService; } protected CultureInfo UsCulture { get; } = new CultureInfo("en-US"); @@ -785,6 +789,78 @@ namespace MediaBrowser.XbmcMetadata.Parsers break; } + case "thumb": + { + var artType = reader.GetAttribute("aspect"); + var val = reader.ReadElementContentAsString(); + + // skip: + // - empty aspect tag + // - empty uri + // - tag containing '.' because we can't set images for seasons, episodes or movie sets within series or movies + if (string.IsNullOrEmpty(artType) || string.IsNullOrEmpty(val) || artType.Contains('.', StringComparison.Ordinal)) + { + break; + } + + ImageType imageType = artType switch + { + "banner" => ImageType.Banner, + "clearlogo" => ImageType.Logo, + "discart" => ImageType.Disc, + "landscape" => ImageType.Thumb, + "clearart" => ImageType.Art, + // unknown type (including "poster") --> primary + _ => ImageType.Primary, + }; + + Uri uri; + try + { + uri = new Uri(val); + } + catch (UriFormatException ex) + { + Logger.LogError(ex, "Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, item.Name); + break; + } + + if (uri.IsFile) + { + // only allow one item of each type + if (itemResult.Images.Any(x => x.Type == imageType)) + { + break; + } + + var fileSystemMetadata = _directoryService.GetFile(val); + // non existing file returns null + if (fileSystemMetadata == null || !fileSystemMetadata.Exists) + { + Logger.LogWarning("Artwork file {Path} specified in nfo file for {ItemName} does not exist.", uri, item.Name); + break; + } + + itemResult.Images.Add(new LocalImageInfo() + { + FileInfo = fileSystemMetadata, + Type = imageType + }); + } + else + { + // only allow one item of each type + if (itemResult.RemoteImages.Any(x => x.type == imageType)) + { + break; + } + + itemResult.RemoteImages.Add((uri.ToString(), imageType)); + } + + break; + } + default: string readerName = reader.Name; if (_validProviderIds.TryGetValue(readerName, out string? providerIdValue)) diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index f0c50d8e51..7ae55c791e 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -25,13 +25,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public EpisodeNfoParser( ILogger logger, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) - : base(logger, config, providerManager, userManager, userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) + : base(logger, config, providerManager, userManager, userDataManager, directoryService) { } diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs index 2d0eb8433d..b466fa25ae 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs @@ -25,13 +25,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public MovieNfoParser( ILogger logger, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) - : base(logger, config, providerManager, userManager, userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) + : base(logger, config, providerManager, userManager, userDataManager, directoryService) { } diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs index bd2607bd8d..2f5fd40e2f 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs @@ -21,13 +21,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public SeasonNfoParser( ILogger logger, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) - : base(logger, config, providerManager, userManager, userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) + : base(logger, config, providerManager, userManager, userDataManager, directoryService) { } diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index fbab8b5214..f6574c365c 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -22,13 +22,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public SeriesNfoParser( ILogger logger, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) - : base(logger, config, providerManager, userManager, userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) + : base(logger, config, providerManager, userManager, userDataManager, directoryService) { } diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs index 24f1274112..bd557d783c 100644 --- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly IProviderManager _providerManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; + private readonly IDirectoryService _directoryService; /// /// Initializes a new instance of the class. @@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public AlbumNfoProvider( ILogger logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) : base(fileSystem) { _logger = logger; @@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers _providerManager = providerManager; _userManager = userManager; _userDataManager = userDataManager; + _directoryService = directoryService; } /// protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new BaseNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken); + new BaseNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken); } /// diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs index fac28ab59c..54bb83114b 100644 --- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly IProviderManager _providerManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; + private readonly IDirectoryService _directoryService; /// /// Initializes a new instance of the class. @@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public ArtistNfoProvider( IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) : base(fileSystem) { _logger = logger; @@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers _providerManager = providerManager; _userManager = userManager; _userDataManager = userDataManager; + _directoryService = directoryService; } /// protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new BaseNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken); + new BaseNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken); } /// diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs index af722748b5..b8f76f31da 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs @@ -21,6 +21,7 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly IProviderManager _providerManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; + private readonly IDirectoryService _directoryService; public BaseVideoNfoProvider( ILogger> logger, @@ -28,7 +29,8 @@ namespace MediaBrowser.XbmcMetadata.Providers IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) : base(fileSystem) { _logger = logger; @@ -36,6 +38,7 @@ namespace MediaBrowser.XbmcMetadata.Providers _providerManager = providerManager; _userManager = userManager; _userDataManager = userDataManager; + _directoryService = directoryService; } /// @@ -45,10 +48,12 @@ namespace MediaBrowser.XbmcMetadata.Providers { Item = result.Item }; - new MovieNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(tmpItem, path, cancellationToken); + new MovieNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(tmpItem, path, cancellationToken); result.Item = (T)tmpItem.Item; result.People = tmpItem.People; + result.Images = tmpItem.Images; + result.RemoteImages = tmpItem.RemoteImages; if (tmpItem.UserDataList != null) { diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs index 7233f99dcb..64b208345e 100644 --- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly IProviderManager _providerManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; + private readonly IDirectoryService _directoryService; /// /// Initializes a new instance of the class. @@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public EpisodeNfoProvider( ILogger logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) : base(fileSystem) { _logger = logger; @@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers _providerManager = providerManager; _userManager = userManager; _userDataManager = userDataManager; + _directoryService = directoryService; } /// protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new EpisodeNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken); + new EpisodeNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken); } /// diff --git a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs index 811d39a9da..cdbc5a9187 100644 --- a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs @@ -21,14 +21,16 @@ namespace MediaBrowser.XbmcMetadata.Providers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public MovieNfoProvider( ILogger logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) - : base(logger, fileSystem, config, providerManager, userManager, userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) + : base(logger, fileSystem, config, providerManager, userManager, userDataManager, directoryService) { } } diff --git a/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs index 09df509eed..9d1f3e61d1 100644 --- a/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs @@ -21,14 +21,16 @@ namespace MediaBrowser.XbmcMetadata.Providers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public MusicVideoNfoProvider( ILogger logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) - : base(logger, fileSystem, config, providerManager, userManager, userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) + : base(logger, fileSystem, config, providerManager, userManager, userDataManager, directoryService) { } } diff --git a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs index 8f0ed6df78..97220cf7ea 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly IProviderManager _providerManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; + private readonly IDirectoryService _directoryService; /// /// Initializes a new instance of the class. @@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public SeasonNfoProvider( ILogger logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) : base(fileSystem) { _logger = logger; @@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers _providerManager = providerManager; _userManager = userManager; _userDataManager = userDataManager; + _directoryService = directoryService; } /// protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new SeasonNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken); + new SeasonNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken); } /// diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs index 3e496dc584..9a9b941236 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly IProviderManager _providerManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; + private readonly IDirectoryService _directoryService; /// /// Initializes a new instance of the class. @@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public SeriesNfoProvider( ILogger logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) : base(fileSystem) { _logger = logger; @@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers _providerManager = providerManager; _userManager = userManager; _userDataManager = userDataManager; + _directoryService = directoryService; } /// protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new SeriesNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken); + new SeriesNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken); } /// diff --git a/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs index 4717d81e6f..93b1be62fc 100644 --- a/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs @@ -21,14 +21,16 @@ namespace MediaBrowser.XbmcMetadata.Providers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public VideoNfoProvider( ILogger logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager, IUserManager userManager, - IUserDataManager userDataManager) - : base(logger, fileSystem, config, providerManager, userManager, userDataManager) + IUserDataManager userDataManager, + IDirectoryService directoryService) + : base(logger, fileSystem, config, providerManager, userManager, userDataManager, directoryService) { } } diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs index d10ef9b478..4386844732 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs @@ -37,8 +37,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers .Returns(new XbmcMetadataOptions()); var user = new Mock(); var userData = new Mock(); - - _parser = new EpisodeNfoParser(new NullLogger(), config.Object, providerManager.Object, user.Object, userData.Object); + var directoryService = new Mock(); + + _parser = new EpisodeNfoParser( + new NullLogger(), + config.Object, + providerManager.Object, + user.Object, + userData.Object, + directoryService.Object); } [Fact] diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index 76231391e2..76e10b4515 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -9,7 +9,9 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; +using MediaBrowser.Model.System; using MediaBrowser.Providers.Plugins.Tmdb.Movies; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging.Abstractions; @@ -23,6 +25,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers private readonly MovieNfoParser _parser; private readonly IUserDataManager _userDataManager; private readonly User _testUser; + private readonly FileSystemMetadata _localImageFileMetadata; public MovieNfoParserTests() { @@ -52,8 +55,36 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers userData.Setup(x => x.GetUserData(_testUser, It.IsAny())) .Returns(new UserItemData()); + var directoryService = new Mock(); + if (MediaBrowser.Common.System.OperatingSystem.Id != OperatingSystemId.Windows) + { + _localImageFileMetadata = new FileSystemMetadata() + { + Exists = true, + FullName = "/media/movies/Justice League (2017).jpg" + }; + directoryService.Setup(x => x.GetFile(_localImageFileMetadata.FullName)) + .Returns(_localImageFileMetadata); + } + else + { + _localImageFileMetadata = new FileSystemMetadata() + { + Exists = true, + FullName = "C:\\media\\movies\\Justice League (2017).jpg" + }; + directoryService.Setup(x => x.GetFile(_localImageFileMetadata.FullName)) + .Returns(_localImageFileMetadata); + } + _userDataManager = userData.Object; - _parser = new MovieNfoParser(new NullLogger(), configManager.Object, providerManager.Object, user.Object, userData.Object); + _parser = new MovieNfoParser( + new NullLogger(), + configManager.Object, + providerManager.Object, + user.Object, + userData.Object, + directoryService.Object); } [Fact] @@ -134,6 +165,37 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers // Movie set Assert.Equal("702342", item.ProviderIds[MetadataProvider.TmdbCollection.ToString()]); Assert.Equal("Justice League Collection", item.CollectionName); + + // Images + Assert.Equal(6, result.RemoteImages.Count); + + var posters = result.RemoteImages.Where(x => x.type == ImageType.Primary).ToList(); + Assert.Single(posters); + Assert.Equal("http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", posters[0].url); + + var logos = result.RemoteImages.Where(x => x.type == ImageType.Logo).ToList(); + Assert.Single(logos); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png", logos[0].url); + + var banners = result.RemoteImages.Where(x => x.type == ImageType.Banner).ToList(); + Assert.Single(banners); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg", banners[0].url); + + var thumbs = result.RemoteImages.Where(x => x.type == ImageType.Thumb).ToList(); + Assert.Single(thumbs); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg", thumbs[0].url); + + var art = result.RemoteImages.Where(x => x.type == ImageType.Art).ToList(); + Assert.Single(art); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png", art[0].url); + + var discArt = result.RemoteImages.Where(x => x.type == ImageType.Disc).ToList(); + Assert.Single(discArt); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].url); + + // Local Image - contains only one item depending on operating system + Assert.Single(result.Images); + Assert.Equal(_localImageFileMetadata.Name, result.Images[0].FileInfo.Name); } [Theory] diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs index 2183d2a2f0..29de2a95e9 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs @@ -1,7 +1,6 @@ #pragma warning disable CA5369 using System; -using System.Linq; using System.Threading; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.Audio; @@ -11,7 +10,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Providers.Music; -using MediaBrowser.Providers.Plugins.MusicBrainz; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -38,8 +36,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers .Returns(new XbmcMetadataOptions()); var user = new Mock(); var userData = new Mock(); + var directoryService = new Mock(); - _parser = new BaseNfoParser(new NullLogger>(), config.Object, providerManager.Object, user.Object, userData.Object); + _parser = new BaseNfoParser( + new NullLogger>(), + config.Object, + providerManager.Object, + user.Object, + userData.Object, + directoryService.Object); } [Fact] diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs index f86b7604e4..e54b35eb5e 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs @@ -35,8 +35,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers .Returns(new XbmcMetadataOptions()); var user = new Mock(); var userData = new Mock(); + var directoryService = new Mock(); - _parser = new BaseNfoParser(new NullLogger>(), config.Object, providerManager.Object, user.Object, userData.Object); + _parser = new BaseNfoParser( + new NullLogger>(), + config.Object, + providerManager.Object, + user.Object, + userData.Object, + directoryService.Object); } [Fact] diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs index 898554936b..bf887cab18 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs @@ -30,8 +30,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers var user = new Mock(); var userData = new Mock(); + var directoryService = new Mock(); - _parser = new MovieNfoParser(new NullLogger>(), config.Object, providerManager.Object, user.Object, userData.Object); + _parser = new MovieNfoParser( + new NullLogger>(), + config.Object, + providerManager.Object, + user.Object, + userData.Object, + directoryService.Object); } [Fact] diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs index 602db7c09a..c497d4fa26 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs @@ -31,8 +31,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers .Returns(new XbmcMetadataOptions()); var user = new Mock(); var userData = new Mock(); + var directoryService = new Mock(); - _parser = new SeasonNfoParser(new NullLogger(), config.Object, providerManager.Object, user.Object, userData.Object); + _parser = new SeasonNfoParser( + new NullLogger(), + config.Object, + providerManager.Object, + user.Object, + userData.Object, + directoryService.Object); } [Fact] diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs index f8eb04b3a9..7c7d698f08 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs @@ -29,8 +29,9 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers .Returns(new XbmcMetadataOptions()); var user = new Mock(); var userData = new Mock(); + var directoryService = new Mock(); - _parser = new SeriesNfoParser(new NullLogger(), config.Object, providerManager.Object, user.Object, userData.Object); + _parser = new SeriesNfoParser(new NullLogger(), config.Object, providerManager.Object, user.Object, userData.Object, directoryService.Object); } [Fact] diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo index 72e27fe506..c35d37b180 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo +++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo @@ -59,6 +59,8 @@ http://image.tmdb.org/t/p/original/exLtrlI7JjKcfQVTccI7XdQRFMz.jpg http://image.tmdb.org/t/p/original/paLcue01KpfQftorfjKqqD4qvlL.jpg http://image.tmdb.org/t/p/original/yVDIfiKIsCbdFcgLXW34bAsnQvy.jpg + C:\media\movies\Justice League (2017).jpg + /media/movies/Justice League (2017).jpg https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-57b476a831d74.png From 4bbfcaef83579239a9bef708c71a889cb3d829b4 Mon Sep 17 00:00:00 2001 From: Moshe Schmidt Date: Wed, 17 Feb 2021 21:18:27 +0100 Subject: [PATCH 018/282] Include specials in the calculation for the "Next Up" episode. Fixes #1479 --- CONTRIBUTORS.md | 1 + .../Sorting/AiredEpisodeOrderComparer.cs | 4 +- .../TV/TVSeriesManager.cs | 46 +++++++++++++++---- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1200275d52..bcc27b4dd8 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -104,6 +104,7 @@ - [shemanaev](https://github.com/shemanaev) - [skaro13](https://github.com/skaro13) - [sl1288](https://github.com/sl1288) + - [Smith00101010](https://github.com/Smith00101010) - [sorinyo2004](https://github.com/sorinyo2004) - [sparky8251](https://github.com/sparky8251) - [spookbits](https://github.com/spookbits) diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 1f68a9c810..60698e803d 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -131,11 +131,11 @@ namespace Emby.Server.Implementations.Sorting return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y)); } - private static int GetSpecialCompareValue(Episode item) + private static long GetSpecialCompareValue(Episode item) { // First sort by season number // Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough) - var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000; + var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000L; // Second sort order is if it airs after the season if (item.AirsAfterSeasonNumber.HasValue) diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 839b62448b..d111e1a1c6 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; @@ -11,7 +10,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Series = MediaBrowser.Controller.Entities.TV.Series; @@ -23,12 +21,14 @@ namespace Emby.Server.Implementations.TV private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _configurationManager; - public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager) + public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) { _userManager = userManager; _userDataManager = userDataManager; _libraryManager = libraryManager; + _configurationManager = configurationManager; } public QueryResult GetNextUp(NextUpQuery request, DtoOptions dtoOptions) @@ -200,13 +200,10 @@ namespace Emby.Server.Implementations.TV ParentIndexNumberNotEquals = 0, DtoOptions = new DtoOptions { - Fields = new ItemFields[] - { - ItemFields.SortName - }, + Fields = new[] { ItemFields.SortName }, EnableImages = false } - }).FirstOrDefault(); + }).Cast().FirstOrDefault(); Func getEpisode = () => { @@ -224,6 +221,39 @@ namespace Emby.Server.Implementations.TV DtoOptions = dtoOptions }).Cast().FirstOrDefault(); + if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons) + { + var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user) + { + AncestorWithPresentationUniqueKey = null, + SeriesPresentationUniqueKey = seriesKey, + ParentIndexNumber = 0, + IncludeItemTypes = new[] { nameof(Episode) }, + IsPlayed = false, + IsVirtualItem = false, + DtoOptions = dtoOptions + }).Cast().Where(episode => episode.AirsBeforeSeasonNumber != null || episode.AirsAfterSeasonNumber != null).ToList(); + + if (lastWatchedEpisode != null) + { + // Last watched episode is added, because there could be specials that aired before the last watched episode + consideredEpisodes.Add(lastWatchedEpisode); + } + + if (nextEpisode != null) + { + consideredEpisodes.Add(nextEpisode); + } + + var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, new[] { (ItemSortBy.AiredEpisodeOrder, SortOrder.Ascending) }).Cast(); + if (lastWatchedEpisode != null) + { + sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => episode.Id != lastWatchedEpisode.Id).Skip(1); + } + + nextEpisode = sortedConsideredEpisodes.FirstOrDefault(); + } + if (nextEpisode != null) { var userData = _userDataManager.GetUserData(user, nextEpisode); From 18cd634ec8316fe5978d7ad3867de66b85cd5fba Mon Sep 17 00:00:00 2001 From: Mister Rajoy Date: Sun, 28 Feb 2021 23:03:28 +0100 Subject: [PATCH 019/282] Fix duplicated movies when group movies into collections is enabled --- .../Collections/CollectionManager.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 1ab2bdfbe8..449f8872fd 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -344,7 +344,20 @@ namespace Emby.Server.Implementations.Collections } else { - results[item.Id] = item; + var alreadyInResults = false; + foreach (var child in item.GetMediaSources(true)) + { + + if (results.ContainsKey(Guid.Parse(child.Id))) + { + alreadyInResults = true; + } + } + if (!alreadyInResults) + { + + results[item.Id] = item; + } } } } From ed0267252f1a2b5922db7a52ad63799a5009a6ce Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Mar 2021 20:00:00 +0100 Subject: [PATCH 020/282] Remove tests that are upstreamed libse (the SSA parser we use) has these same tests now --- .../Subtitles/SsaParserTests.cs | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs index 5033d1de91..5db80c3001 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs @@ -13,38 +13,11 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests { public class SsaParserTests { - // commonly shared invariant value between tests, assumes default format order - private const string InvariantDialoguePrefix = "[Events]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,"; - private readonly SsaParser _parser = new SsaParser(new NullLogger()); - [Theory] - [InlineData("[EvEnTs]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,text", "text")] // label casing insensitivity - [InlineData("[Events]\n,0:00:00.00,0:00:00.01,,,,,,,labelless dialogue", "labelless dialogue")] // no "Dialogue:" label, it is optional - // TODO: Fix upstream - // [InlineData("[Events]\nFormat: Text, Start, End, Layer, Effect, Style\nDialogue: reordered text,0:00:00.00,0:00:00.01", "reordered text")] // reordered formats - [InlineData(InvariantDialoguePrefix + "Cased TEXT", "Cased TEXT")] // preserve text casing - [InlineData(InvariantDialoguePrefix + " text ", " text ")] // do not trim text - [InlineData(InvariantDialoguePrefix + "text, more text", "text, more text")] // append excess dialogue values (> 10) to text - [InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text{\\fn} end", "start text end")] // font name - [InlineData(InvariantDialoguePrefix + "start {\\fs10}text{\\fs} end", "start text end")] // font size - [InlineData(InvariantDialoguePrefix + "start {\\c&H112233}text{\\c} end", "start text end")] // color - // TODO: Fix upstream - // [InlineData(InvariantDialoguePrefix + "start {\\1c&H112233}text{\\1c} end", "start text end")] // primay color - // [InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text1 {\\fs10}text2{\\fs}{\\fn} {\\1c&H112233}text3{\\1c} end", "start text1 text2 text3 end")] // nested formatting - public void Parse(string ssa, string expectedText) - { - using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa))) - { - SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, CancellationToken.None); - SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[0]; - Assert.Equal(expectedText, actual.Text); - } - } - [Theory] [MemberData(nameof(Parse_MultipleDialogues_TestData))] - public void Parse_MultipleDialogues(string ssa, IReadOnlyList expectedSubtitleTrackEvents) + public void Parse_MultipleDialogues_Success(string ssa, IReadOnlyList expectedSubtitleTrackEvents) { using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa))) { From ba62d9d1fe84dfb16c502ab7e105c6c6807770ab Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Mar 2021 20:35:38 +0100 Subject: [PATCH 021/282] Revert breaking change --- .../Entities/ProviderIdsExtensions.cs | 27 +++++++++++++ .../Entities/ProviderIdsExtensionsTests.cs | 38 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 11c3dbe42e..bde5a1da19 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -9,6 +9,33 @@ namespace MediaBrowser.Model.Entities /// public static class ProviderIdsExtensions { + /// + /// Checks if this instance has an id for the given provider. + /// + /// The instance. + /// The of the provider name. + /// true if a provider id with the given name was found; otherwise false. + public static bool HasProviderId(this IHasProviderIds instance, string name) + { + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + return instance.ProviderIds?.ContainsKey(name) ?? false; + } + + /// + /// Checks if this instance has an id for the given provider. + /// + /// The instance. + /// The provider. + /// true if a provider id with the given name was found; otherwise false. + public static bool HasProviderId(this IHasProviderIds instance, MetadataProvider provider) + { + return instance.HasProviderId(provider.ToString()); + } + /// /// Gets a provider id. /// diff --git a/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs index c1a1525bad..2b2414ef1b 100644 --- a/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs +++ b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs @@ -9,6 +9,44 @@ namespace Jellyfin.Model.Tests.Entities { private const string ExampleImdbId = "tt0113375"; + [Fact] + public void HasProviderId_NullInstance_ThrowsArgumentNullException() + { + Assert.Throws(() => ProviderIdsExtensions.HasProviderId(null!, MetadataProvider.Imdb)); + } + + [Fact] + public void HasProviderId_NullProvider_False() + { + var nullProvider = new ProviderIdsExtensionsTestsObject() + { + ProviderIds = null! + }; + + Assert.False(nullProvider.HasProviderId(MetadataProvider.Imdb)); + } + + [Fact] + public void HasProviderId_NullName_ThrowsArgumentNullException() + { + Assert.Throws(() => ProviderIdsExtensionsTestsObject.Empty.HasProviderId(null!)); + } + + [Fact] + public void HasProviderId_NotFoundName_False() + { + Assert.False(ProviderIdsExtensionsTestsObject.Empty.HasProviderId(MetadataProvider.Imdb)); + } + + [Fact] + public void HasProviderId_FoundName_True() + { + var provider = new ProviderIdsExtensionsTestsObject(); + provider.ProviderIds[MetadataProvider.Imdb.ToString()] = ExampleImdbId; + + Assert.True(provider.HasProviderId(MetadataProvider.Imdb)); + } + [Fact] public void GetProviderId_NullInstance_ThrowsArgumentNullException() { From fdd4b6b3f146c4598fb1709f71dd11ffa623db28 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 1 Mar 2021 21:02:20 +0000 Subject: [PATCH 022/282] Changed message --- Jellyfin.Server/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index f05cdfe9bd..461ff163c3 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -198,7 +198,7 @@ namespace Jellyfin.Server } catch { - _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); + _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in network.xml and try again."); throw; } From dedc94ec91d3562a71cc9d7e88c8c23f373d64a2 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 1 Mar 2021 21:32:49 +0000 Subject: [PATCH 023/282] correction of ip6 loopback --- Jellyfin.Networking/Manager/NetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 51fcb6d9a0..9c7d096a76 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -414,7 +414,7 @@ namespace Jellyfin.Networking.Manager } // There isn't any others, so we'll use the loopback. - result = IsIP6Enabled ? "::" : "127.0.0.1"; + result = IsIP6Enabled ? "::1" : "127.0.0.1"; _logger.LogWarning("{Source}: GetBindInterface: Loopback {Result} returned.", source, result); return result; } From 594294871438bd83fee3198c647b2ff30defa8e1 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 1 Mar 2021 23:42:04 +0000 Subject: [PATCH 024/282] Kestrel workaround --- Jellyfin.Networking/Manager/NetworkManager.cs | 19 +++++++++++++++---- Jellyfin.Server/Program.cs | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 9c7d096a76..d2e9dcf9e6 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -285,14 +285,25 @@ namespace Jellyfin.Networking.Manager // No bind address and no exclusions, so listen on all interfaces. Collection result = new Collection(); - if (IsIP4Enabled) + if (IsIP6Enabled && IsIP4Enabled) + { + // Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any + result.AddItem(IPAddress.IPv6Any); + } + else if (IsIP4Enabled) { result.AddItem(IPAddress.Any); } - - if (IsIP6Enabled) + else if (IsIP6Enabled) { - result.AddItem(IPAddress.IPv6Any); + // Cannot use IPv6Any as Kestrel will bind to IPv4 addresses. + foreach (var iface in _interfaceAddresses) + { + if (iface.AddressFamily == AddressFamily.InterNetworkV6) + { + result.AddItem(iface.Address); + } + } } return result; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index f05cdfe9bd..aee53b9868 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -280,7 +280,7 @@ namespace Jellyfin.Server bool flagged = false; foreach (IPObject netAdd in addresses) { - _logger.LogInformation("Kestrel listening on {0}", netAdd); + _logger.LogInformation("Kestrel listening on {0}", netAdd.Address == IPAddress.IPv6Any ? "All Addresses" : netAdd); options.Listen(netAdd.Address, appHost.HttpPort); if (appHost.ListenWithHttps) { From b5e3c02865b4fc4953c317f647b7319db34922c8 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 1 Mar 2021 19:37:46 -0500 Subject: [PATCH 025/282] Move IHasPermissions.cs to correct namespace --- Jellyfin.Data/Interfaces/IHasPermissions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Data/Interfaces/IHasPermissions.cs b/Jellyfin.Data/Interfaces/IHasPermissions.cs index 3be72259ad..85ee12ad74 100644 --- a/Jellyfin.Data/Interfaces/IHasPermissions.cs +++ b/Jellyfin.Data/Interfaces/IHasPermissions.cs @@ -2,7 +2,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; -namespace Jellyfin.Data +namespace Jellyfin.Data.Interfaces { /// /// An abstraction representing an entity that has permissions. From c275c5c1ea12a73c90b0f3599916eb48e21e130c Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 2 Mar 2021 08:57:27 +0000 Subject: [PATCH 026/282] Update Jellyfin.Server/Program.cs Co-authored-by: Claus Vium --- Jellyfin.Server/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index aee53b9868..d2ab11be4f 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -280,7 +280,7 @@ namespace Jellyfin.Server bool flagged = false; foreach (IPObject netAdd in addresses) { - _logger.LogInformation("Kestrel listening on {0}", netAdd.Address == IPAddress.IPv6Any ? "All Addresses" : netAdd); + _logger.LogInformation("Kestrel listening on {Address}", netAdd.Address == IPAddress.IPv6Any ? "All Addresses" : netAdd); options.Listen(netAdd.Address, appHost.HttpPort); if (appHost.ListenWithHttps) { From 8f99bdd07ce037b633993f06f5a68dcb293d6828 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Tue, 2 Mar 2021 21:17:25 +0100 Subject: [PATCH 027/282] Fix TMDb search name containing year (#5349) --- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 16 ++++++---------- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 14 +++++++++----- .../Plugins/Tmdb/TmdbClientManager.cs | 5 +++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index 93998a1102..b455e56349 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -111,10 +111,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var item = new Episode { - Name = info.Name, IndexNumber = info.IndexNumber, ParentIndexNumber = info.ParentIndexNumber, - IndexNumberEnd = info.IndexNumberEnd + IndexNumberEnd = info.IndexNumberEnd, + Name = episodeResult.Name, + PremiereDate = episodeResult.AirDate, + ProductionYear = episodeResult.AirDate?.Year, + Overview = episodeResult.Overview, + CommunityRating = Convert.ToSingle(episodeResult.VoteAverage) }; if (!string.IsNullOrEmpty(episodeResult.ExternalIds?.TvdbId)) @@ -122,14 +126,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV item.SetProviderId(MetadataProvider.Tvdb, episodeResult.ExternalIds.TvdbId); } - item.PremiereDate = episodeResult.AirDate; - item.ProductionYear = episodeResult.AirDate?.Year; - - item.Name = episodeResult.Name; - item.Overview = episodeResult.Overview; - - item.CommunityRating = Convert.ToSingle(episodeResult.VoteAverage); - if (episodeResult.Videos?.Results != null) { foreach (var video in episodeResult.Videos.Results) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 942c85b90d..0238d0574b 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -22,15 +23,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public class TmdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; + private readonly ILibraryManager _libraryManager; private readonly TmdbClientManager _tmdbClientManager; public TmdbSeriesProvider( + ILibraryManager libraryManager, IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { + _libraryManager = libraryManager; _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; - Current = this; } public string Name => TmdbUtils.ProviderName; @@ -38,8 +41,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // After TheTVDB public int Order => 1; - internal static TmdbSeriesProvider Current { get; private set; } - public async Task> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) { var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb); @@ -104,7 +105,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken) + var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken: cancellationToken) .ConfigureAwait(false); var remoteResults = new RemoteSearchResult[tvSearchResults.Count]; @@ -203,7 +204,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (string.IsNullOrEmpty(tmdbId)) { result.QueriedById = false; - var searchResults = await _tmdbClientManager.SearchSeriesAsync(info.Name, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + // ParseName is required here. + // Caller provides the filename with extension stripped and NOT the parsed filename + var parsedName = _libraryManager.ParseName(info.Name); + var searchResults = await _tmdbClientManager.SearchSeriesAsync(parsedName.Name, info.MetadataLanguage, info.Year ?? 0, cancellationToken).ConfigureAwait(false); if (searchResults.Count > 0) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 2dc5cd55da..bf0f027fce 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -278,9 +278,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// /// The name of the tv show. /// The tv show's language. + /// The year the tv show first aired. /// The cancellation token. /// The TMDb tv show information. - public async Task> SearchSeriesAsync(string name, string language, CancellationToken cancellationToken) + public async Task> SearchSeriesAsync(string name, string language, int year = 0, CancellationToken cancellationToken = default) { var key = $"searchseries-{name}-{language}"; if (_memoryCache.TryGetValue(key, out SearchContainer series)) @@ -291,7 +292,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); var searchResults = await _tmDbClient - .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), cancellationToken: cancellationToken) + .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), firstAirDateYear: year, cancellationToken: cancellationToken) .ConfigureAwait(false); if (searchResults.Results.Count > 0) From bfe84affb32f798b6f2b2a6a4306065fc8800b1a Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Tue, 2 Mar 2021 22:20:41 -0500 Subject: [PATCH 028/282] Update README to include ffmpeg --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 29f9923499..5af312ad70 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,8 @@ Before the project can be built, you must first install the [.NET 5.0 SDK](https Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET Core development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2017) and [Visual Studio Code](https://code.visualstudio.com/Download). +[ffmpeg](https://ffmpeg.org/download.html) will also need to be installed. + ### Cloning the Repository After dependencies are installed you will need to clone a local copy of this repository. If you just want to run the server from source you can clone this repository directly, but if you are intending to contribute code changes to the project, you should [set up your own fork](https://jellyfin.org/docs/general/contributing/development.html#set-up-your-copy-of-the-repo) of the repository. The following example shows how you can clone the repository directly over HTTPS. From 664c5da31728e65d0e53ada7c06c918059f73615 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 3 Mar 2021 09:09:57 +0100 Subject: [PATCH 029/282] return false when providerid is null or empty --- MediaBrowser.Model/Entities/ProviderIdsExtensions.cs | 8 +++++--- MediaBrowser.Providers/Manager/ProviderManager.cs | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index bde5a1da19..571bc70062 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -50,13 +50,15 @@ namespace MediaBrowser.Model.Entities throw new ArgumentNullException(nameof(instance)); } - if (instance.ProviderIds == null) + var foundProviderId = instance.ProviderIds.TryGetValue(name, out id); + // This occurs when searching with Identify (and possibly in other places) + if (string.IsNullOrEmpty(id)) { id = null; - return false; + foundProviderId = false; } - return instance.ProviderIds.TryGetValue(name, out id); + return foundProviderId; } /// diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 913f14d9b8..bc16a8abb3 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -869,14 +869,14 @@ namespace MediaBrowser.Providers.Manager } } } - catch (Exception) +#pragma warning disable CA1031 // do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // do not catch general exception types { - // Logged at lower levels + _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name); } } - // _logger.LogDebug("Returning search results {0}", _json.SerializeToString(resultList)); - return resultList; } From a49f5d2a441d53076c0eae27fc115a97691f4856 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 3 Mar 2021 09:37:21 +0100 Subject: [PATCH 030/282] revert removal of null check --- MediaBrowser.Model/Entities/ProviderIdsExtensions.cs | 6 ++++++ .../Entities/ProviderIdsExtensionsTests.cs | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 571bc70062..3086fcefd4 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -50,6 +50,12 @@ namespace MediaBrowser.Model.Entities throw new ArgumentNullException(nameof(instance)); } + if (instance.ProviderIds == null) + { + id = null; + return false; + } + var foundProviderId = instance.ProviderIds.TryGetValue(name, out id); // This occurs when searching with Identify (and possibly in other places) if (string.IsNullOrEmpty(id)) diff --git a/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs index 2b2414ef1b..cf9fb15d70 100644 --- a/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs +++ b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Model.Tests.Entities [Fact] public void HasProviderId_NullProvider_False() { - var nullProvider = new ProviderIdsExtensionsTestsObject() + var nullProvider = new ProviderIdsExtensionsTestsObject { ProviderIds = null! }; @@ -68,7 +68,7 @@ namespace Jellyfin.Model.Tests.Entities [Fact] public void GetProviderId_NullProvider_Null() { - var nullProvider = new ProviderIdsExtensionsTestsObject() + var nullProvider = new ProviderIdsExtensionsTestsObject { ProviderIds = null! }; @@ -85,7 +85,7 @@ namespace Jellyfin.Model.Tests.Entities [Fact] public void TryGetProviderId_NullProvider_False() { - var nullProvider = new ProviderIdsExtensionsTestsObject() + var nullProvider = new ProviderIdsExtensionsTestsObject { ProviderIds = null! }; @@ -146,7 +146,7 @@ namespace Jellyfin.Model.Tests.Entities [Fact] public void SetProviderId_NullProvider_Success() { - var nullProvider = new ProviderIdsExtensionsTestsObject() + var nullProvider = new ProviderIdsExtensionsTestsObject { ProviderIds = null! }; @@ -158,7 +158,7 @@ namespace Jellyfin.Model.Tests.Entities [Fact] public void SetProviderId_NullProviderAndEmptyName_Success() { - var nullProvider = new ProviderIdsExtensionsTestsObject() + var nullProvider = new ProviderIdsExtensionsTestsObject { ProviderIds = null! }; From 8b72b902f53b32c0c0d69eeda3bd32f992ac53ee Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 3 Mar 2021 12:28:40 +0100 Subject: [PATCH 031/282] fix HasProviderId and add tests --- .../Entities/ProviderIdsExtensions.cs | 2 +- .../Entities/ProviderIdsExtensionsTests.cs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 3086fcefd4..09d14dc6a4 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Model.Entities throw new ArgumentNullException(nameof(instance)); } - return instance.ProviderIds?.ContainsKey(name) ?? false; + return instance.TryGetProviderId(name, out _); } /// diff --git a/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs index cf9fb15d70..a1ace84769 100644 --- a/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs +++ b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs @@ -47,6 +47,15 @@ namespace Jellyfin.Model.Tests.Entities Assert.True(provider.HasProviderId(MetadataProvider.Imdb)); } + [Fact] + public void HasProviderId_FoundNameEmptyValue_False() + { + var provider = new ProviderIdsExtensionsTestsObject(); + provider.ProviderIds[MetadataProvider.Imdb.ToString()] = string.Empty; + + Assert.False(provider.HasProviderId(MetadataProvider.Imdb)); + } + [Fact] public void GetProviderId_NullInstance_ThrowsArgumentNullException() { @@ -112,6 +121,16 @@ namespace Jellyfin.Model.Tests.Entities Assert.Equal(ExampleImdbId, id); } + [Fact] + public void TryGetProviderId_FoundNameEmptyValue_False() + { + var provider = new ProviderIdsExtensionsTestsObject(); + provider.ProviderIds[MetadataProvider.Imdb.ToString()] = string.Empty; + + Assert.False(provider.TryGetProviderId(MetadataProvider.Imdb, out var id)); + Assert.Null(id); + } + [Fact] public void SetProviderId_NullInstance_ThrowsArgumentNullException() { From 610a30d791ab11b3d815cbf7e361198f30fdafd5 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 3 Mar 2021 15:13:43 +0100 Subject: [PATCH 032/282] Do nothing in timer callback when device locator is disposed --- RSSDP/SsdpDeviceLocator.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index bfad6de97a..0cdc5ce3db 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -97,6 +97,11 @@ namespace Rssdp.Infrastructure private async void OnBroadcastTimerCallback(object state) { + if (IsDisposed) + { + return; + } + StartListeningForNotifications(); RemoveExpiredDevicesFromCache(); @@ -180,8 +185,6 @@ namespace Rssdp.Infrastructure /// Throw if the ty is true. public void StartListeningForNotifications() { - ThrowIfDisposed(); - _CommunicationsServer.RequestReceived -= CommsServer_RequestReceived; _CommunicationsServer.RequestReceived += CommsServer_RequestReceived; _CommunicationsServer.BeginListeningForBroadcasts(); @@ -353,7 +356,7 @@ namespace Rssdp.Infrastructure { return; } - + var location = GetFirstHeaderUriValue("Location", message); if (location != null) { @@ -515,11 +518,6 @@ namespace Rssdp.Infrastructure private void RemoveExpiredDevicesFromCache() { - if (this.IsDisposed) - { - return; - } - DiscoveredSsdpDevice[] expiredDevices = null; lock (_Devices) { From d819a1d92827f6a8c8b3b5289a4ca306a9f098ed Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 3 Mar 2021 14:41:18 +0000 Subject: [PATCH 033/282] Remove Content-Length header from DLNA HEAD request (#5335) --- Emby.Dlna/PlayTo/PlayToController.cs | 2 +- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index e4923b9eb0..5abc1bc134 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -132,7 +132,7 @@ namespace Emby.Dlna.PlayTo private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e) { - if (_disposed) + if (_disposed || string.IsNullOrEmpty(e.OldMediaInfo.Url)) { return; } diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 89d36ab09f..f828b1d9d6 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -107,7 +107,8 @@ namespace Jellyfin.Api.Helpers // Headers only if (isHeadRequest) { - return new FileContentResult(Array.Empty(), contentType); + httpContext.Response.Headers[HeaderNames.ContentType] = contentType; + return new OkResult(); } var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath); From 631c0a35f61e9c9ce3e6decfe369faf2c73f3f62 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 3 Mar 2021 17:11:19 -0700 Subject: [PATCH 034/282] Always use case insensitive json parsing for api --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 77f6695bb7..1828f1a7eb 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -232,7 +232,6 @@ namespace Jellyfin.Server.Extensions options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented; options.JsonSerializerOptions.DefaultIgnoreCondition = jsonOptions.DefaultIgnoreCondition; options.JsonSerializerOptions.NumberHandling = jsonOptions.NumberHandling; - options.JsonSerializerOptions.PropertyNameCaseInsensitive = jsonOptions.PropertyNameCaseInsensitive; options.JsonSerializerOptions.Converters.Clear(); foreach (var converter in jsonOptions.Converters) From 858c91ab485f79648aa625e89378c6de9e19df4b Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 4 Mar 2021 18:25:52 -0700 Subject: [PATCH 035/282] Specify defaults or set query parameter to nullable --- Jellyfin.Api/Controllers/AudioController.cs | 8 ++-- .../Controllers/DynamicHlsController.cs | 48 +++++++++---------- .../Controllers/HlsSegmentController.cs | 4 +- Jellyfin.Api/Controllers/ImageController.cs | 16 +++---- .../Controllers/ItemUpdateController.cs | 2 +- Jellyfin.Api/Controllers/LibraryController.cs | 2 +- .../Controllers/PlaystateController.cs | 20 ++++---- .../Controllers/UniversalAudioController.cs | 2 +- .../Controllers/VideoHlsController.cs | 8 ++-- Jellyfin.Api/Controllers/VideosController.cs | 12 ++--- 10 files changed, 62 insertions(+), 60 deletions(-) diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 616fe5b91f..8b1813b200 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -174,7 +174,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, @@ -287,7 +287,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -339,7 +339,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index e375645cf0..f6c23c5aad 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -203,7 +203,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -218,7 +218,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? transcodeReasons, [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, - [FromQuery] EncodingContext context, + [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, [FromQuery] bool enableAdaptiveBitrateStreaming = true) { @@ -255,7 +255,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, @@ -270,7 +270,7 @@ namespace Jellyfin.Api.Controllers TranscodeReasons = transcodeReasons, AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, - Context = context, + Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming }; @@ -370,7 +370,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -385,7 +385,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? transcodeReasons, [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, - [FromQuery] EncodingContext context, + [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, [FromQuery] bool enableAdaptiveBitrateStreaming = true) { @@ -422,7 +422,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, @@ -437,7 +437,7 @@ namespace Jellyfin.Api.Controllers TranscodeReasons = transcodeReasons, AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, - Context = context, + Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming }; @@ -533,7 +533,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -548,7 +548,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? transcodeReasons, [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, - [FromQuery] EncodingContext context, + [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { var cancellationTokenSource = new CancellationTokenSource(); @@ -585,7 +585,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, @@ -600,7 +600,7 @@ namespace Jellyfin.Api.Controllers TranscodeReasons = transcodeReasons, AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, - Context = context, + Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions }; @@ -698,7 +698,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -713,7 +713,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? transcodeReasons, [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, - [FromQuery] EncodingContext context, + [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { var cancellationTokenSource = new CancellationTokenSource(); @@ -750,7 +750,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, @@ -765,7 +765,7 @@ namespace Jellyfin.Api.Controllers TranscodeReasons = transcodeReasons, AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, - Context = context, + Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions }; @@ -868,7 +868,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -883,7 +883,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? transcodeReasons, [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, - [FromQuery] EncodingContext context, + [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { var streamingRequest = new VideoRequestDto @@ -920,7 +920,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, @@ -935,7 +935,7 @@ namespace Jellyfin.Api.Controllers TranscodeReasons = transcodeReasons, AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, - Context = context, + Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions }; @@ -1040,7 +1040,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -1055,7 +1055,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? transcodeReasons, [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, - [FromQuery] EncodingContext context, + [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { var streamingRequest = new StreamingRequestDto @@ -1092,7 +1092,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, @@ -1107,7 +1107,7 @@ namespace Jellyfin.Api.Controllers TranscodeReasons = transcodeReasons, AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, - Context = context, + Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions }; diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index 25abe73ed0..d0ed45acb4 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -96,7 +96,9 @@ namespace Jellyfin.Api.Controllers [HttpDelete("Videos/ActiveEncodings")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult StopEncodingProcess([FromQuery] string deviceId, [FromQuery] string playSessionId) + public ActionResult StopEncodingProcess( + [FromQuery, Required] string deviceId, + [FromQuery, Required] string playSessionId) { _transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true); return NoContent(); diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index a50d6e46bf..cfc038f237 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -392,7 +392,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid itemId, [FromRoute, Required] ImageType imageType, [FromRoute, Required] int imageIndex, - [FromQuery] int newIndex) + [FromQuery, Required] int newIndex) { var item = _libraryManager.GetItemById(itemId); if (item == null) @@ -741,7 +741,7 @@ namespace Jellyfin.Api.Controllers public async Task GetArtistImage( [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, - [FromQuery] string tag, + [FromQuery] string? tag, [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, @@ -820,7 +820,7 @@ namespace Jellyfin.Api.Controllers public async Task GetGenreImage( [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, - [FromQuery] string tag, + [FromQuery] string? tag, [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, @@ -900,7 +900,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromRoute, Required] int imageIndex, - [FromQuery] string tag, + [FromQuery] string? tag, [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, @@ -978,7 +978,7 @@ namespace Jellyfin.Api.Controllers public async Task GetMusicGenreImage( [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, - [FromQuery] string tag, + [FromQuery] string? tag, [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, @@ -1058,7 +1058,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromRoute, Required] int imageIndex, - [FromQuery] string tag, + [FromQuery] string? tag, [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, @@ -1136,7 +1136,7 @@ namespace Jellyfin.Api.Controllers public async Task GetPersonImage( [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, - [FromQuery] string tag, + [FromQuery] string? tag, [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, @@ -1216,7 +1216,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromRoute, Required] int imageIndex, - [FromQuery] string tag, + [FromQuery] string? tag, [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 9e1a398538..a9f4a5a58c 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Items/{itemId}/ContentType")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string contentType) + public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType) { var item = _libraryManager.GetItemById(itemId); if (item == null) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index db4aa96681..3443ebd72f 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -777,7 +777,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetLibraryOptionsInfo( [FromQuery] string? libraryContentType, - [FromQuery] bool isNewLibrary) + [FromQuery] bool isNewLibrary = false) { var result = new LibraryOptionsResultDto(); diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index ec7b84ff60..f256c8c25c 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -152,7 +152,7 @@ namespace Jellyfin.Api.Controllers /// A . [HttpPost("Sessions/Playing/Ping")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult PingPlaybackSession([FromQuery] string playSessionId) + public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId) { _transcodingJobHelper.PingTranscodingJob(playSessionId, null); return NoContent(); @@ -202,9 +202,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? mediaSourceId, [FromQuery] int? audioStreamIndex, [FromQuery] int? subtitleStreamIndex, - [FromQuery] PlayMethod playMethod, + [FromQuery] PlayMethod? playMethod, [FromQuery] string? liveStreamId, - [FromQuery] string playSessionId, + [FromQuery] string? playSessionId, [FromQuery] bool canSeek = false) { var playbackStartInfo = new PlaybackStartInfo @@ -214,7 +214,7 @@ namespace Jellyfin.Api.Controllers MediaSourceId = mediaSourceId, AudioStreamIndex = audioStreamIndex, SubtitleStreamIndex = subtitleStreamIndex, - PlayMethod = playMethod, + PlayMethod = playMethod ?? PlayMethod.Transcode, PlaySessionId = playSessionId, LiveStreamId = liveStreamId }; @@ -254,10 +254,10 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? audioStreamIndex, [FromQuery] int? subtitleStreamIndex, [FromQuery] int? volumeLevel, - [FromQuery] PlayMethod playMethod, + [FromQuery] PlayMethod? playMethod, [FromQuery] string? liveStreamId, - [FromQuery] string playSessionId, - [FromQuery] RepeatMode repeatMode, + [FromQuery] string? playSessionId, + [FromQuery] RepeatMode? repeatMode, [FromQuery] bool isPaused = false, [FromQuery] bool isMuted = false) { @@ -271,10 +271,10 @@ namespace Jellyfin.Api.Controllers AudioStreamIndex = audioStreamIndex, SubtitleStreamIndex = subtitleStreamIndex, VolumeLevel = volumeLevel, - PlayMethod = playMethod, + PlayMethod = playMethod ?? PlayMethod.Transcode, PlaySessionId = playSessionId, LiveStreamId = liveStreamId, - RepeatMode = repeatMode + RepeatMode = repeatMode ?? RepeatMode.RepeatNone }; playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId); @@ -352,7 +352,7 @@ namespace Jellyfin.Api.Controllers return _userDataRepository.GetUserDataDto(item, user); } - private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId) + private PlayMethod ValidatePlayMethod(PlayMethod method, string? playSessionId) { if (method == PlayMethod.Transcode) { diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 5aa033ccf4..0c2e6f19f1 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? maxAudioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] bool? enableRemoteMedia, - [FromQuery] bool breakOnNonKeyFrames, + [FromQuery] bool breakOnNonKeyFrames = false, [FromQuery] bool enableRedirection = true) { var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels); diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index ba51aa43e2..620eef5680 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -198,7 +198,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -213,7 +213,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? transcodeReasons, [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, - [FromQuery] EncodingContext context, + [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, @@ -253,7 +253,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, @@ -268,7 +268,7 @@ namespace Jellyfin.Api.Controllers TranscodeReasons = transcodeReasons, AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, - Context = context, + Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, MaxHeight = maxHeight, MaxWidth = maxWidth, diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 44dc639523..8c7aa73257 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -364,7 +364,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -379,7 +379,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? transcodeReasons, [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, - [FromQuery] EncodingContext context, + [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; @@ -418,7 +418,7 @@ namespace Jellyfin.Api.Controllers Height = height, VideoBitRate = videoBitRate, SubtitleStreamIndex = subtitleStreamIndex, - SubtitleMethod = subtitleMethod, + SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, RequireAvc = requireAvc ?? true, @@ -433,7 +433,7 @@ namespace Jellyfin.Api.Controllers TranscodeReasons = transcodeReasons, AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, - Context = context, + Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions }; @@ -620,7 +620,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? videoBitRate, [FromQuery] int? subtitleStreamIndex, - [FromQuery] SubtitleDeliveryMethod subtitleMethod, + [FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] int? maxRefFrames, [FromQuery] int? maxVideoBitDepth, [FromQuery] bool? requireAvc, @@ -635,7 +635,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? transcodeReasons, [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, - [FromQuery] EncodingContext context, + [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { return GetVideoStream( From ba366118f9c9853153a90b7b5b22f0acd40867ed Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 5 Mar 2021 08:18:04 +0100 Subject: [PATCH 036/282] Do not use language or imagelanguages when searching for images TMDb API returns all images if languages are excluded, which is needed for the All Languages toggle in Identify. --- MediaBrowser.Providers/Manager/ProviderManager.cs | 1 + .../Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 3 ++- .../Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs | 3 ++- .../Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs | 3 ++- .../Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs | 3 ++- .../Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs | 3 ++- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index bc16a8abb3..d581dd4341 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -242,6 +242,7 @@ namespace MediaBrowser.Providers.Manager languages.Add(preferredLanguage); } + // TODO include [query.IncludeAllLanguages] as an argument to the providers var tasks = providers.Select(i => GetImages(item, i, languages, cancellationToken, query.ImageType)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index df1e12240d..5ad61c567f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -58,7 +58,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets var language = item.GetPreferredMetadataLanguage(); - var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false); + // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here + var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, null, null, cancellationToken).ConfigureAwait(false); if (collection?.Images == null) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs index dac9e961c6..f34d689c1a 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs @@ -73,8 +73,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies return Enumerable.Empty(); } + // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var movie = await _tmdbClientManager - .GetMovieAsync(movieTmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken) + .GetMovieAsync(movieTmdbId, null, null, cancellationToken) .ConfigureAwait(false); if (movie?.Images == null) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index 3b7a0b254e..d923366242 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -63,8 +63,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var language = item.GetPreferredMetadataLanguage(); + // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, null, null, cancellationToken) .ConfigureAwait(false); var stills = episodeResult?.Images?.Stills; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index f4ed480aef..0d23c7872f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -52,8 +52,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var language = item.GetPreferredMetadataLanguage(); + // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var seasonResult = await _tmdbClientManager - .GetSeasonAsync(seriesTmdbId, season.IndexNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken) + .GetSeasonAsync(seriesTmdbId, season.IndexNumber.Value, null, null, cancellationToken) .ConfigureAwait(false); var posters = seasonResult?.Images?.Posters; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index d0c6b8b886..a96fc8ed63 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -59,8 +59,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var language = item.GetPreferredMetadataLanguage(); + // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var series = await _tmdbClientManager - .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken) + .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), null, null, cancellationToken) .ConfigureAwait(false); if (series?.Images == null) From ed7154db68f089cb8366aedaaeb93f1d5405251a Mon Sep 17 00:00:00 2001 From: Nichgon Date: Fri, 5 Mar 2021 07:42:03 +0000 Subject: [PATCH 037/282] Translated using Weblate (Thai) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/th/ --- Emby.Server.Implementations/Localization/Core/th.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json index 71dd2c7a30..5bf58baf8c 100644 --- a/Emby.Server.Implementations/Localization/Core/th.json +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -50,7 +50,7 @@ "HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ", "HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ", "HeaderContinueWatching": "ดูต่อ", - "HeaderAlbumArtists": "อัลบั้มศิลปิน", + "HeaderAlbumArtists": "ศิลปินอัลบั้ม", "Genres": "ประเภท", "Folders": "โฟลเดอร์", "Favorites": "รายการโปรด", @@ -112,5 +112,6 @@ "System": "ระบบ", "Sync": "ซิงค์", "SubtitleDownloadFailureFromForItem": "ไม่สามารถดาวน์โหลดคำบรรยายจาก {0} สำหรับ {1} ได้", - "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่" + "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่", + "Default": "ค่าเริ่มต้น" } From a6d0db5d0409f72e2abb02291268b6971eaf1fcf Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 5 Mar 2021 11:15:14 +0100 Subject: [PATCH 038/282] 100% branch coverage for DashboardController --- Emby.Server.Implementations/Plugins/PluginManager.cs | 4 ++-- Jellyfin.Api/Controllers/DashboardController.cs | 4 ++-- MediaBrowser.Common/Plugins/IPluginManager.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 7bc9f0a7e2..c579fc8cba 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Plugins private readonly ILogger _logger; private readonly IApplicationHost _appHost; private readonly ServerConfiguration _config; - private readonly IList _plugins; + private readonly List _plugins; private readonly Version _minimumVersion; private IHttpClientFactory? _httpClientFactory; @@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.Plugins /// /// Gets the Plugins. /// - public IList Plugins => _plugins; + public IReadOnlyList Plugins => _plugins; /// /// Returns all the assemblies. diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index a2c2ecd666..445733c24d 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -95,9 +95,9 @@ namespace Jellyfin.Api.Controllers return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin.Instance, i.Item1)); } - private IEnumerable> GetPluginPages(LocalPlugin? plugin) + private IEnumerable> GetPluginPages(LocalPlugin plugin) { - if (plugin?.Instance is not IHasWebPages hasWebPages) + if (plugin.Instance is not IHasWebPages hasWebPages) { return Enumerable.Empty>(); } diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index fc2fcb5179..f9a8fb6f7a 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Common.Plugins /// /// Gets the Plugins. /// - IList Plugins { get; } + IReadOnlyList Plugins { get; } /// /// Creates the plugins. From 37e374d33d73403470d07d814b5ee1367ca12e85 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 5 Mar 2021 14:09:23 +0100 Subject: [PATCH 039/282] make sure network path substitution matches correctly --- .../Library/LibraryManager.cs | 64 ++++----------- .../Library/PathExtensions.cs | 80 +++++++++++++++++++ .../Library/PathExtensionsTests.cs | 23 ++++++ 3 files changed, 117 insertions(+), 50 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index d9ffe64b3b..973b2df8ae 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2788,10 +2788,9 @@ namespace Emby.Server.Implementations.Library continue; } - var substitutionResult = SubstitutePathInternal(path, pathInfo.Path, pathInfo.NetworkPath); - if (substitutionResult.Item2) + if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out var newPath)) { - return substitutionResult.Item1; + return newPath; } } } @@ -2802,22 +2801,22 @@ namespace Emby.Server.Implementations.Library if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath)) { - var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath); - if (metadataSubstitutionResult.Item2) + if (path.TryReplaceSubPath(metadataPath, metadataNetworkPath, out var newPath)) { - return metadataSubstitutionResult.Item1; + return newPath; } } foreach (var map in _configurationManager.Configuration.PathSubstitutions) { - if (!string.IsNullOrWhiteSpace(map.From)) + if (string.IsNullOrWhiteSpace(map.From)) { - var substitutionResult = SubstitutePathInternal(path, map.From, map.To); - if (substitutionResult.Item2) - { - return substitutionResult.Item1; - } + continue; + } + + if (path.TryReplaceSubPath(map.From, map.To, out var newPath)) + { + return newPath; } } @@ -2826,47 +2825,12 @@ namespace Emby.Server.Implementations.Library public string SubstitutePath(string path, string from, string to) { - return SubstitutePathInternal(path, from, to).Item1; - } - - private Tuple SubstitutePathInternal(string path, string from, string to) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - if (string.IsNullOrWhiteSpace(from)) - { - throw new ArgumentNullException(nameof(from)); - } - - if (string.IsNullOrWhiteSpace(to)) + if (path.TryReplaceSubPath(from, to, out var newPath)) { - throw new ArgumentNullException(nameof(to)); + return newPath; } - from = from.Trim(); - to = to.Trim(); - - var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase); - var changed = false; - - if (!string.Equals(newPath, path, StringComparison.Ordinal)) - { - if (to.IndexOf('/', StringComparison.Ordinal) != -1) - { - newPath = newPath.Replace('\\', '/'); - } - else - { - newPath = newPath.Replace('/', '\\'); - } - - changed = true; - } - - return new Tuple(newPath, changed); + return path; } private void SetExtraTypeFromFilename(Video item) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 06ff3e611b..1fc5526ae1 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace Emby.Server.Implementations.Library @@ -47,5 +48,84 @@ namespace Emby.Server.Implementations.Library return null; } + + /// + /// Replaces a sub path with another sub path and normalizes the final path. + /// + /// The original path. + /// The original sub path. + /// The new sub path. + /// The result of the sub path replacement + /// The path after replacing the sub path. + /// , or is empty. + public static bool TryReplaceSubPath(this string path, string subPath, string newSubPath, [NotNullWhen(true)] out string? newPath) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException(nameof(path)); + } + + if (string.IsNullOrWhiteSpace(subPath)) + { + throw new ArgumentNullException(nameof(subPath)); + } + + if (string.IsNullOrWhiteSpace(newSubPath)) + { + throw new ArgumentNullException(nameof(newSubPath)); + } + + char oldDirectorySeparatorChar; + char newDirectorySeparatorChar; + // True normalization is still not possible https://github.com/dotnet/runtime/issues/2162 + // The reasoning behind this is that a forward slash likely means it's a Linux path and + // so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much). + if (newSubPath.Contains('/', StringComparison.Ordinal)) + { + oldDirectorySeparatorChar = '\\'; + newDirectorySeparatorChar = '/'; + } + else + { + oldDirectorySeparatorChar = '/'; + newDirectorySeparatorChar = '\\'; + } + + if (path.Contains(oldDirectorySeparatorChar, StringComparison.Ordinal)) + { + path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); + } + + if (subPath.Contains(oldDirectorySeparatorChar, StringComparison.Ordinal)) + { + subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); + } + + // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results + // when the sub path matches a similar but in-complete subpath + if (!subPath.EndsWith(newDirectorySeparatorChar)) + { + subPath += newDirectorySeparatorChar; + } + + if (newSubPath.Contains(oldDirectorySeparatorChar, StringComparison.Ordinal)) + { + newSubPath = newSubPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); + } + + if (!newSubPath.EndsWith(newDirectorySeparatorChar)) + { + newSubPath += newDirectorySeparatorChar; + } + + if (!path.Contains(subPath, StringComparison.OrdinalIgnoreCase)) + { + newPath = null; + return false; + } + + newPath = path.Replace(subPath, newSubPath, StringComparison.OrdinalIgnoreCase); + return true; + } } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index 6d768af890..a96a053771 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -24,5 +24,28 @@ namespace Jellyfin.Server.Implementations.Tests.Library { Assert.Throws(() => PathExtensions.GetAttributeValue(input, attribute)); } + + [Theory] + [InlineData("C:/Users/jeff/myfile.mkv", "C:/Users/jeff", "/home/jeff", true, "/home/jeff/myfile.mkv")] + [InlineData("C:/Users/jeff/myfile.mkv", "C:/Users/jeff/", "/home/jeff", true, "/home/jeff/myfile.mkv")] + [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/not jeff's band", "/home/not jeff", false, null)] + [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/jeff's band", "/home/not jeff", true, "/home/not jeff/consistently inconsistent.mp3")] + [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff", "/home/jeff", true, "/home/jeff/myfile.mkv")] + public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, bool succeeded, string? expectedResult) + { + var status = PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result); + Assert.Equal(succeeded, status); + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData("", "", "")] + [InlineData("/my/path", "", "")] + [InlineData("", "/another/path", "")] + [InlineData("", "", "/new/subpath")] + public void TryReplaceSubPath_EmptyString_ThrowsArgumentNullException(string path, string subPath, string newSubPath) + { + Assert.Throws(() => PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out _)); + } } } From fd0b3ca5ef82164f5551369b789b9677f30af172 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 5 Mar 2021 07:48:45 -0700 Subject: [PATCH 040/282] Add JsonVersionConverter and tests --- .../Json/Converters/JsonVersionConverter.cs | 23 ++++++++++++ MediaBrowser.Common/Json/JsonDefaults.cs | 1 + .../MediaBrowser.Common.csproj | 2 +- .../Json/JsonVersionConverterTests.cs | 36 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs create mode 100644 tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs diff --git a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs new file mode 100644 index 0000000000..b8aecf8b84 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Converts a Version object or value to/from JSON. + /// + /// + /// Required to send as a string instead of an object. + /// + public class JsonVersionConverter : JsonConverter + { + /// + public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => new Version(reader.GetString()); + + /// + public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString()); + } +} \ No newline at end of file diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index a3999e7e25..2ef24a884a 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -35,6 +35,7 @@ namespace MediaBrowser.Common.Json { new JsonGuidConverter(), new JsonNullableGuidConverter(), + new JsonVersionConverter(), new JsonStringEnumConverter(), new JsonNullableStructConverterFactory(), new JsonBoolNumberConverter(), diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index e469436a93..8bb30c565b 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -14,7 +14,7 @@ - + diff --git a/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs new file mode 100644 index 0000000000..83dfb0a4fb --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs @@ -0,0 +1,36 @@ +using System; +using System.Text.Json; +using MediaBrowser.Common.Json.Converters; +using Xunit; + +namespace Jellyfin.Common.Tests.Json +{ + public class JsonVersionConverterTests + { + private readonly JsonSerializerOptions _options; + + public JsonVersionConverterTests() + { + _options = new JsonSerializerOptions(); + _options.Converters.Add(new JsonVersionConverter()); + } + + [Fact] + public void Deserialize_Version_Success() + { + var input = "\"1.025.222\""; + var output = new Version(1, 25, 222); + var deserializedInput = JsonSerializer.Deserialize(input, _options); + Assert.Equal(output, deserializedInput); + } + + [Fact] + public void Serialize_Version_Success() + { + var input = new Version(1, 09, 59); + var output = "\"1.9.59\""; + var serializedInput = JsonSerializer.Serialize(input, _options); + Assert.Equal(output, serializedInput); + } + } +} \ No newline at end of file From df1951cfe2baae539758b9592af1a16e51b69897 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 5 Mar 2021 08:30:49 -0700 Subject: [PATCH 041/282] Apply suggestions from code review Co-authored-by: dkanada --- MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs | 2 +- tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs index b8aecf8b84..f69e868cc4 100644 --- a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs @@ -20,4 +20,4 @@ namespace MediaBrowser.Common.Json.Converters public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString()); } -} \ No newline at end of file +} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs index 83dfb0a4fb..f2cefdbf8e 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs @@ -33,4 +33,4 @@ namespace Jellyfin.Common.Tests.Json Assert.Equal(output, serializedInput); } } -} \ No newline at end of file +} From d0a2d00b29d87e7f71e85efd38d2b6c8beafe4ea Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 5 Mar 2021 16:56:21 +0100 Subject: [PATCH 042/282] Fix UpdateMediaPath model binding (#5378) --- .../Controllers/LibraryStructureController.cs | 13 ++++------- .../UpdateMediaPathRequestDto.cs | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index 328efea26e..be9127dd39 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -241,23 +241,20 @@ namespace Jellyfin.Api.Controllers /// /// Updates a media path. /// - /// The name of the library. - /// The path info. + /// The name of the library and path infos. /// A . /// Media path updated. /// The name of the library may not be empty. [HttpPost("Paths/Update")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult UpdateMediaPath( - [FromQuery] string? name, - [FromBody] MediaPathInfo? pathInfo) + public ActionResult UpdateMediaPath([FromBody, Required] UpdateMediaPathRequestDto mediaPathRequestDto) { - if (string.IsNullOrWhiteSpace(name)) + if (string.IsNullOrWhiteSpace(mediaPathRequestDto.Name)) { - throw new ArgumentNullException(nameof(name)); + throw new ArgumentNullException(nameof(mediaPathRequestDto), "Name must not be null or empty"); } - _libraryManager.UpdateMediaPath(name, pathInfo); + _libraryManager.UpdateMediaPath(mediaPathRequestDto.Name, mediaPathRequestDto.PathInfo); return NoContent(); } diff --git a/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs b/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs new file mode 100644 index 0000000000..fbd4985f9c --- /dev/null +++ b/Jellyfin.Api/Models/LibraryStructureDto/UpdateMediaPathRequestDto.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using MediaBrowser.Model.Configuration; + +namespace Jellyfin.Api.Models.LibraryStructureDto +{ + /// + /// Update library options dto. + /// + public class UpdateMediaPathRequestDto + { + /// + /// Gets or sets the library name. + /// + [Required] + public string Name { get; set; } = null!; + + /// + /// Gets or sets library folder path information. + /// + [Required] + public MediaPathInfo PathInfo { get; set; } = null!; + } +} From 59814bd55e8c6c5fbd167e90403b9bd2e390c78c Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 5 Mar 2021 19:57:48 +0100 Subject: [PATCH 043/282] do not pick a linked item as primary when merging versions --- Jellyfin.Api/Controllers/VideosController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 44dc639523..195a96a561 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -217,9 +217,7 @@ namespace Jellyfin.Api.Controllers return BadRequest("Please supply at least two videos to merge."); } - var videosWithVersions = items.Where(i => i.MediaSourceCount > 1).ToList(); - - var primaryVersion = videosWithVersions.FirstOrDefault(); + var primaryVersion = items.FirstOrDefault(i => i.MediaSourceCount > 1 && string.IsNullOrEmpty(i.PrimaryVersionId)); if (primaryVersion == null) { primaryVersion = items From 6293629d3263722c49c14f7642fb33b2986b0e19 Mon Sep 17 00:00:00 2001 From: Smith00101010 Date: Fri, 5 Mar 2021 22:51:08 +0100 Subject: [PATCH 044/282] Apply suggested formatting changes Co-authored-by: BaronGreenback --- Emby.Server.Implementations/TV/TVSeriesManager.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index d111e1a1c6..d3f6fa34d8 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -232,7 +232,10 @@ namespace Emby.Server.Implementations.TV IsPlayed = false, IsVirtualItem = false, DtoOptions = dtoOptions - }).Cast().Where(episode => episode.AirsBeforeSeasonNumber != null || episode.AirsAfterSeasonNumber != null).ToList(); + }) + .Cast() + .Where(episode => episode.AirsBeforeSeasonNumber != null || episode.AirsAfterSeasonNumber != null) + .ToList(); if (lastWatchedEpisode != null) { @@ -245,7 +248,8 @@ namespace Emby.Server.Implementations.TV consideredEpisodes.Add(nextEpisode); } - var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, new[] { (ItemSortBy.AiredEpisodeOrder, SortOrder.Ascending) }).Cast(); + var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, new[] { (ItemSortBy.AiredEpisodeOrder, SortOrder.Ascending) }) + .Cast(); if (lastWatchedEpisode != null) { sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => episode.Id != lastWatchedEpisode.Id).Skip(1); From e8413ed8c0cc401447b880dea96e94092bbefe88 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 6 Mar 2021 03:33:52 +0100 Subject: [PATCH 045/282] Use XDocument.LoadAsync instead of XDocument.Parse --- Emby.Dlna/PlayTo/SsdpHttpClient.cs | 16 ++++++++-------- Emby.Dlna/PlayTo/TransportCommands.cs | 6 ++---- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 557bc69a73..b7643fb279 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -45,10 +45,10 @@ namespace Emby.Dlna.PlayTo cancellationToken) .ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - using var reader = new StreamReader(stream, Encoding.UTF8); - return XDocument.Parse( - await reader.ReadToEndAsync().ConfigureAwait(false), - LoadOptions.PreserveWhitespace); + return await XDocument.LoadAsync( + stream, + LoadOptions.PreserveWhitespace, + cancellationToken).ConfigureAwait(false); } private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) @@ -94,10 +94,10 @@ namespace Emby.Dlna.PlayTo options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - using var reader = new StreamReader(stream, Encoding.UTF8); - return XDocument.Parse( - await reader.ReadToEndAsync().ConfigureAwait(false), - LoadOptions.PreserveWhitespace); + return await XDocument.LoadAsync( + stream, + LoadOptions.PreserveWhitespace, + cancellationToken).ConfigureAwait(false); } private async Task PostSoapDataAsync( diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs index 0865968ad1..cbcf66e45c 100644 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ b/Emby.Dlna/PlayTo/TransportCommands.cs @@ -13,12 +13,10 @@ namespace Emby.Dlna.PlayTo public class TransportCommands { private const string CommandBase = "\r\n" + "" + "" + "" + "{2}" + "" + ""; - private List _stateVariables = new List(); - private List _serviceActions = new List(); - public List StateVariables => _stateVariables; + public List StateVariables { get; } = new List(); - public List ServiceActions => _serviceActions; + public List ServiceActions { get; } = new List(); public static TransportCommands Create(XDocument document) { From 10229e1afe7e2403c59f508a667cafbb2c85a9ce Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Fri, 5 Mar 2021 23:09:35 -0500 Subject: [PATCH 046/282] Link to jellyfin-ffmpeg --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5af312ad70..cba88c8d2b 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ Before the project can be built, you must first install the [.NET 5.0 SDK](https Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET Core development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2017) and [Visual Studio Code](https://code.visualstudio.com/Download). -[ffmpeg](https://ffmpeg.org/download.html) will also need to be installed. +[ffmpeg](https://github.com/jellyfin/jellyfin-ffmpeg) will also need to be installed. ### Cloning the Repository From bc661c16e19413cbe6a94832280e3a24b6cf3c20 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 6 Mar 2021 14:01:37 +0100 Subject: [PATCH 047/282] simplify --- .../Library/PathExtensions.cs | 26 ++++++------------- .../Library/PathExtensionsTests.cs | 23 +++++++++------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 1fc5526ae1..41e64abf32 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -103,28 +103,18 @@ namespace Emby.Server.Implementations.Library // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results // when the sub path matches a similar but in-complete subpath - if (!subPath.EndsWith(newDirectorySeparatorChar)) + var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar; + if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase) + || (!oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar)) { - subPath += newDirectorySeparatorChar; - } - - if (newSubPath.Contains(oldDirectorySeparatorChar, StringComparison.Ordinal)) - { - newSubPath = newSubPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); - } - - if (!newSubPath.EndsWith(newDirectorySeparatorChar)) - { - newSubPath += newDirectorySeparatorChar; - } - - if (!path.Contains(subPath, StringComparison.OrdinalIgnoreCase)) - { - newPath = null; return false; } - newPath = path.Replace(subPath, newSubPath, StringComparison.OrdinalIgnoreCase); + var newSubPathTrimmed = newSubPath.AsSpan().TrimEnd(newDirectorySeparatorChar); + // Ensure that the path with the old subpath removed starts with a leading dir separator + int idx = oldSubPathEndsWithSeparator ? subPath.Length - 1 : subPath.Length; + newPath = string.Concat(newSubPathTrimmed, path.AsSpan(idx)); + return true; } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index a96a053771..a6fe905668 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -26,15 +26,16 @@ namespace Jellyfin.Server.Implementations.Tests.Library } [Theory] - [InlineData("C:/Users/jeff/myfile.mkv", "C:/Users/jeff", "/home/jeff", true, "/home/jeff/myfile.mkv")] - [InlineData("C:/Users/jeff/myfile.mkv", "C:/Users/jeff/", "/home/jeff", true, "/home/jeff/myfile.mkv")] - [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/not jeff's band", "/home/not jeff", false, null)] - [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/jeff's band", "/home/not jeff", true, "/home/not jeff/consistently inconsistent.mp3")] - [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff", "/home/jeff", true, "/home/jeff/myfile.mkv")] - public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, bool succeeded, string? expectedResult) + [InlineData("C:/Users/jeff/myfile.mkv", "C:/Users/jeff", "/home/jeff", "/home/jeff/myfile.mkv")] + [InlineData("C:/Users/jeff/myfile.mkv", "C:/Users/jeff/", "/home/jeff", "/home/jeff/myfile.mkv")] + [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/jeff's band", "/home/not jeff", "/home/not jeff/consistently inconsistent.mp3")] + [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff", "/home/jeff", "/home/jeff/myfile.mkv")] + [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff", "/home/jeff/", "/home/jeff/myfile.mkv")] + [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/home/jeff/", "/home/jeff/myfile.mkv")] + [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/", "/myfile.mkv")] + public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, string? expectedResult) { - var status = PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result); - Assert.Equal(succeeded, status); + Assert.True(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result)); Assert.Equal(expectedResult, result); } @@ -43,9 +44,11 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("/my/path", "", "")] [InlineData("", "/another/path", "")] [InlineData("", "", "/new/subpath")] - public void TryReplaceSubPath_EmptyString_ThrowsArgumentNullException(string path, string subPath, string newSubPath) + [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/not jeff's band", "/home/not jeff")] + public void TryReplaceSubPath_InvalidInput_ReturnsFalseAndNull(string path, string subPath, string newSubPath) { - Assert.Throws(() => PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out _)); + Assert.False(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result)); + Assert.Null(result); } } } From 6f898145afe013d7dfcd47fbc6adab9316e2dd63 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 6 Mar 2021 14:50:27 +0100 Subject: [PATCH 048/282] Use Uri.TryCreate and ImageType helper method --- .../Parsers/BaseNfoParser.cs | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index aa89936d0c..2e222c0a40 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -803,25 +803,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers break; } - ImageType imageType = artType switch - { - "banner" => ImageType.Banner, - "clearlogo" => ImageType.Logo, - "discart" => ImageType.Disc, - "landscape" => ImageType.Thumb, - "clearart" => ImageType.Art, - // unknown type (including "poster") --> primary - _ => ImageType.Primary, - }; - - Uri uri; - try - { - uri = new Uri(val); - } - catch (UriFormatException ex) + ImageType imageType = GetImageType(artType); + + if (!Uri.TryCreate(val, UriKind.Absolute, out var uri) || uri == null) { - Logger.LogError(ex, "Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, item.Name); + Logger.LogError("Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, item.Name); break; } @@ -1256,5 +1242,24 @@ namespace MediaBrowser.XbmcMetadata.Parsers return string.IsNullOrWhiteSpace(value) ? Array.Empty() : value.Split(separator, StringSplitOptions.RemoveEmptyEntries); } + + /// + /// Parses the ImageType from the nfo aspect property. + /// + /// The nfo aspect property. + /// The image type. + private static ImageType GetImageType(string aspect) + { + return aspect switch + { + "banner" => ImageType.Banner, + "clearlogo" => ImageType.Logo, + "discart" => ImageType.Disc, + "landscape" => ImageType.Thumb, + "clearart" => ImageType.Art, + // unknown type (including "poster") --> primary + _ => ImageType.Primary, + }; + } } } From b8d52dafa9047403cb7bde221baf9d39baf24e18 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Sat, 6 Mar 2021 16:02:52 +0100 Subject: [PATCH 049/282] Update MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs Co-authored-by: Bond-009 --- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 2e222c0a40..18b4ffac99 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -805,7 +805,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers ImageType imageType = GetImageType(artType); - if (!Uri.TryCreate(val, UriKind.Absolute, out var uri) || uri == null) + if (!Uri.TryCreate(val, UriKind.Absolute, out var uri)) { Logger.LogError("Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, item.Name); break; From 54211b921c86a4aa69e7390a662a48bf99011de1 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 6 Mar 2021 19:07:02 +0100 Subject: [PATCH 050/282] rider is a prick --- .../Library/PathExtensions.cs | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 41e64abf32..d9e20e19ac 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Text.RegularExpressions; namespace Emby.Server.Implementations.Library @@ -60,19 +61,11 @@ namespace Emby.Server.Implementations.Library /// , or is empty. public static bool TryReplaceSubPath(this string path, string subPath, string newSubPath, [NotNullWhen(true)] out string? newPath) { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - if (string.IsNullOrWhiteSpace(subPath)) - { - throw new ArgumentNullException(nameof(subPath)); - } + newPath = null; - if (string.IsNullOrWhiteSpace(newSubPath)) + if (path.Length == 0 || subPath.Length == 0 || newSubPath.Length == 0 || subPath.Length > path.Length) { - throw new ArgumentNullException(nameof(newSubPath)); + return false; } char oldDirectorySeparatorChar; @@ -91,15 +84,8 @@ namespace Emby.Server.Implementations.Library newDirectorySeparatorChar = '\\'; } - if (path.Contains(oldDirectorySeparatorChar, StringComparison.Ordinal)) - { - path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); - } - - if (subPath.Contains(oldDirectorySeparatorChar, StringComparison.Ordinal)) - { - subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); - } + path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); + subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar); // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results // when the sub path matches a similar but in-complete subpath From 67af30d1ff41734215db4c64f777568c647c2ad3 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 6 Mar 2021 20:53:50 +0100 Subject: [PATCH 051/282] Remove redundant checks --- Emby.Server.Implementations/Library/LibraryManager.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 973b2df8ae..9a43405f45 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2783,11 +2783,6 @@ namespace Emby.Server.Implementations.Library { foreach (var pathInfo in libraryOptions.PathInfos) { - if (string.IsNullOrWhiteSpace(pathInfo.Path) || string.IsNullOrWhiteSpace(pathInfo.NetworkPath)) - { - continue; - } - if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out var newPath)) { return newPath; @@ -2809,11 +2804,6 @@ namespace Emby.Server.Implementations.Library foreach (var map in _configurationManager.Configuration.PathSubstitutions) { - if (string.IsNullOrWhiteSpace(map.From)) - { - continue; - } - if (path.TryReplaceSubPath(map.From, map.To, out var newPath)) { return newPath; From 946411be8e85344ad4f33fb7ffb9a1666cf8e6be Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 6 Mar 2021 21:18:20 +0100 Subject: [PATCH 052/282] Remove redundant check --- .../Library/LibraryManager.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 9a43405f45..0957b8cf7a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2776,6 +2776,7 @@ namespace Emby.Server.Implementations.Library public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem) { + string newPath; if (ownerItem != null) { var libraryOptions = GetLibraryOptions(ownerItem); @@ -2783,7 +2784,7 @@ namespace Emby.Server.Implementations.Library { foreach (var pathInfo in libraryOptions.PathInfos) { - if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out var newPath)) + if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out newPath)) { return newPath; } @@ -2794,17 +2795,14 @@ namespace Emby.Server.Implementations.Library var metadataPath = _configurationManager.Configuration.MetadataPath; var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath; - if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath)) + if (path.TryReplaceSubPath(metadataPath, metadataNetworkPath, out newPath)) { - if (path.TryReplaceSubPath(metadataPath, metadataNetworkPath, out var newPath)) - { - return newPath; - } + return newPath; } foreach (var map in _configurationManager.Configuration.PathSubstitutions) { - if (path.TryReplaceSubPath(map.From, map.To, out var newPath)) + if (path.TryReplaceSubPath(map.From, map.To, out newPath)) { return newPath; } From 287dab4655176b09b0cd5b7607bfaff74e48d20d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 6 Mar 2021 16:17:19 -0500 Subject: [PATCH 053/282] Remove constructor side effects and remove unneeded parameterless constructors --- Jellyfin.Data/Entities/AccessSchedule.cs | 9 ----- Jellyfin.Data/Entities/ActivityLog.cs | 13 +------ .../Entities/CustomItemDisplayPreferences.cs | 17 +++----- Jellyfin.Data/Entities/DisplayPreferences.cs | 7 ---- Jellyfin.Data/Entities/Group.cs | 10 ----- Jellyfin.Data/Entities/ImageInfo.cs | 10 ----- .../Entities/ItemDisplayPreferences.cs | 7 ---- Jellyfin.Data/Entities/Libraries/Artwork.cs | 15 +------ Jellyfin.Data/Entities/Libraries/Book.cs | 3 +- .../Entities/Libraries/BookMetadata.cs | 21 +--------- Jellyfin.Data/Entities/Libraries/Chapter.cs | 20 +--------- .../Entities/Libraries/CollectionItem.cs | 39 ------------------- Jellyfin.Data/Entities/Libraries/Company.cs | 15 +------ .../Entities/Libraries/CompanyMetadata.cs | 17 +------- .../Entities/Libraries/CustomItem.cs | 3 +- .../Entities/Libraries/CustomItemMetadata.cs | 19 +-------- Jellyfin.Data/Entities/Libraries/Episode.cs | 22 +---------- .../Entities/Libraries/EpisodeMetadata.cs | 20 +--------- Jellyfin.Data/Entities/Libraries/Genre.cs | 26 +------------ .../Entities/Libraries/ItemMetadata.cs | 10 ----- Jellyfin.Data/Entities/Libraries/Library.cs | 10 ----- .../Entities/Libraries/LibraryItem.cs | 7 ---- Jellyfin.Data/Entities/Libraries/MediaFile.cs | 20 +--------- .../Entities/Libraries/MediaFileStream.cs | 21 +--------- .../Entities/Libraries/MetadataProvider.cs | 10 ----- .../Entities/Libraries/MetadataProviderId.cs | 20 +--------- Jellyfin.Data/Entities/Libraries/Movie.cs | 3 +- .../Entities/Libraries/MovieMetadata.cs | 15 +------ .../Entities/Libraries/MusicAlbum.cs | 3 +- .../Entities/Libraries/MusicAlbumMetadata.cs | 15 +------ Jellyfin.Data/Entities/Libraries/Person.cs | 10 ----- .../Entities/Libraries/PersonRole.cs | 22 +---------- Jellyfin.Data/Entities/Libraries/Photo.cs | 3 +- .../Entities/Libraries/PhotoMetadata.cs | 21 +--------- Jellyfin.Data/Entities/Libraries/Rating.cs | 21 +--------- .../Entities/Libraries/RatingSource.cs | 21 +--------- Jellyfin.Data/Entities/Libraries/Release.cs | 15 +------ Jellyfin.Data/Entities/Libraries/Season.cs | 22 +---------- .../Entities/Libraries/SeasonMetadata.cs | 20 +--------- Jellyfin.Data/Entities/Libraries/Series.cs | 3 +- .../Entities/Libraries/SeriesMetadata.cs | 21 +--------- Jellyfin.Data/Entities/Libraries/Track.cs | 22 +---------- .../Entities/Libraries/TrackMetadata.cs | 21 +--------- Jellyfin.Data/Entities/Permission.cs | 8 ---- Jellyfin.Data/Entities/Preference.cs | 8 ---- Jellyfin.Data/Entities/User.cs | 8 ---- 46 files changed, 46 insertions(+), 627 deletions(-) diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs index 7d1b76a3f8..72bca061da 100644 --- a/Jellyfin.Data/Entities/AccessSchedule.cs +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -1,7 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; using System.Xml.Serialization; using Jellyfin.Data.Enums; @@ -27,14 +26,6 @@ namespace Jellyfin.Data.Entities EndHour = endHour; } - /// - /// Initializes a new instance of the class. - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected AccessSchedule() - { - } - /// /// Gets or sets the id of this instance. /// diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs index e2d5c71879..80e32db30e 100644 --- a/Jellyfin.Data/Entities/ActivityLog.cs +++ b/Jellyfin.Data/Entities/ActivityLog.cs @@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities /// The name. /// The type. /// The user id. - /// The log level. - public ActivityLog(string name, string type, Guid userId, LogLevel logLevel = LogLevel.Information) + public ActivityLog(string name, string type, Guid userId) { if (string.IsNullOrEmpty(name)) { @@ -35,15 +34,7 @@ namespace Jellyfin.Data.Entities Type = type; UserId = userId; DateCreated = DateTime.UtcNow; - LogSeverity = logLevel; - } - - /// - /// Initializes a new instance of the class. - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected ActivityLog() - { + LogSeverity = LogLevel.Information; } /// diff --git a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs index 511e3b281a..d407180d46 100644 --- a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs @@ -15,22 +15,15 @@ namespace Jellyfin.Data.Entities /// The user id. /// The item id. /// The client. - /// The preference key. - /// The preference value. - public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string preferenceKey, string preferenceValue) + /// The preference key. + /// The preference value. + public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string key, string value) { UserId = userId; ItemId = itemId; Client = client; - Key = preferenceKey; - Value = preferenceValue; - } - - /// - /// Initializes a new instance of the class. - /// - protected CustomItemDisplayPreferences() - { + Key = key; + Value = value; } /// diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index 1a8ca1da37..d186deb293 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -36,13 +36,6 @@ namespace Jellyfin.Data.Entities HomeSections = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - protected DisplayPreferences() - { - } - /// /// Gets or sets the Id. /// diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index 878811e59c..8c45dde928 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -32,16 +32,6 @@ namespace Jellyfin.Data.Entities Preferences = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Group() - { - } - /// /// Gets or sets the id of this group. /// diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs index ab8452e62c..f9ae1a955f 100644 --- a/Jellyfin.Data/Entities/ImageInfo.cs +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -19,16 +19,6 @@ namespace Jellyfin.Data.Entities LastModified = DateTime.UtcNow; } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected ImageInfo() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs index 2b25bb25f2..f0a04f8ea6 100644 --- a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs @@ -28,13 +28,6 @@ namespace Jellyfin.Data.Entities RememberIndexing = false; } - /// - /// Initializes a new instance of the class. - /// - protected ItemDisplayPreferences() - { - } - /// /// Gets or sets the Id. /// diff --git a/Jellyfin.Data/Entities/Libraries/Artwork.cs b/Jellyfin.Data/Entities/Libraries/Artwork.cs index 06cd333301..df28ce7374 100644 --- a/Jellyfin.Data/Entities/Libraries/Artwork.cs +++ b/Jellyfin.Data/Entities/Libraries/Artwork.cs @@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The path. /// The kind of art. - /// The owner. - public Artwork(string path, ArtKind kind, IHasArtwork owner) + public Artwork(string path, ArtKind kind) { if (string.IsNullOrEmpty(path)) { @@ -28,18 +27,6 @@ namespace Jellyfin.Data.Entities.Libraries Path = path; Kind = kind; - - owner?.Artwork.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Artwork() - { } /// diff --git a/Jellyfin.Data/Entities/Libraries/Book.cs b/Jellyfin.Data/Entities/Libraries/Book.cs index 2e63f75bd9..aea3d58d58 100644 --- a/Jellyfin.Data/Entities/Libraries/Book.cs +++ b/Jellyfin.Data/Entities/Libraries/Book.cs @@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - public Book() + /// The library. + public Book(Library library) : base(library) { BookMetadata = new HashSet(); Releases = new HashSet(); diff --git a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs index 4a3d290f01..8b0c965306 100644 --- a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs @@ -1,6 +1,5 @@ #pragma warning disable CA2227 -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; @@ -17,29 +16,11 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the object. /// ISO-639-3 3-character language codes. - /// The book. - public BookMetadata(string title, string language, Book book) : base(title, language) + public BookMetadata(string title, string language) : base(title, language) { - if (book == null) - { - throw new ArgumentNullException(nameof(book)); - } - - book.BookMetadata.Add(this); - Publishers = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected BookMetadata() - { - } - /// /// Gets or sets the ISBN. /// diff --git a/Jellyfin.Data/Entities/Libraries/Chapter.cs b/Jellyfin.Data/Entities/Libraries/Chapter.cs index f503de3794..f253143d74 100644 --- a/Jellyfin.Data/Entities/Libraries/Chapter.cs +++ b/Jellyfin.Data/Entities/Libraries/Chapter.cs @@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// ISO-639-3 3-character language codes. /// The start time for this chapter. - /// The release. - public Chapter(string language, long startTime, Release release) + public Chapter(string language, long startTime) { if (string.IsNullOrEmpty(language)) { @@ -27,23 +26,6 @@ namespace Jellyfin.Data.Entities.Libraries Language = language; StartTime = startTime; - - if (release == null) - { - throw new ArgumentNullException(nameof(release)); - } - - release.Chapters.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Chapter() - { } /// diff --git a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs index f9539964d0..1157de442b 100644 --- a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs +++ b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; @@ -10,44 +9,6 @@ namespace Jellyfin.Data.Entities.Libraries /// public class CollectionItem : IHasConcurrencyToken { - /// - /// Initializes a new instance of the class. - /// - /// The collection. - /// The previous item. - /// The next item. - public CollectionItem(Collection collection, CollectionItem previous, CollectionItem next) - { - if (collection == null) - { - throw new ArgumentNullException(nameof(collection)); - } - - collection.Items.Add(this); - - if (next != null) - { - Next = next; - next.Previous = this; - } - - if (previous != null) - { - Previous = previous; - previous.Next = this; - } - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected CollectionItem() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/Company.cs b/Jellyfin.Data/Entities/Libraries/Company.cs index 3b6ed3309a..499ba3800a 100644 --- a/Jellyfin.Data/Entities/Libraries/Company.cs +++ b/Jellyfin.Data/Entities/Libraries/Company.cs @@ -15,24 +15,11 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - /// The owner of this company. - public Company(IHasCompanies owner) + public Company() { - owner?.Companies.Add(this); - CompanyMetadata = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Company() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs index 8aa0486afa..86642b38a5 100644 --- a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; namespace Jellyfin.Data.Entities.Libraries @@ -13,21 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the object. /// ISO-639-3 3-character language codes. - /// The company. - public CompanyMetadata(string title, string language, Company company) : base(title, language) - { - if (company == null) - { - throw new ArgumentNullException(nameof(company)); - } - - company.CompanyMetadata.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - protected CompanyMetadata() + public CompanyMetadata(string title, string language) : base(title, language) { } diff --git a/Jellyfin.Data/Entities/Libraries/CustomItem.cs b/Jellyfin.Data/Entities/Libraries/CustomItem.cs index 115489c786..88d1a0c258 100644 --- a/Jellyfin.Data/Entities/Libraries/CustomItem.cs +++ b/Jellyfin.Data/Entities/Libraries/CustomItem.cs @@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - public CustomItem() + /// The library. + public CustomItem(Library library) : base(library) { CustomItemMetadata = new HashSet(); Releases = new HashSet(); diff --git a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs index f0daedfbe8..a69a8eafa4 100644 --- a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs @@ -12,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the object. /// ISO-639-3 3-character language codes. - /// The item. - public CustomItemMetadata(string title, string language, CustomItem item) : base(title, language) - { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - item.CustomItemMetadata.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected CustomItemMetadata() + public CustomItemMetadata(string title, string language) : base(title, language) { } } diff --git a/Jellyfin.Data/Entities/Libraries/Episode.cs b/Jellyfin.Data/Entities/Libraries/Episode.cs index 0bdc2d7642..458c7d9f5a 100644 --- a/Jellyfin.Data/Entities/Libraries/Episode.cs +++ b/Jellyfin.Data/Entities/Libraries/Episode.cs @@ -1,6 +1,5 @@ #pragma warning disable CA2227 -using System; using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -14,30 +13,13 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - /// The season. - public Episode(Season season) + /// The library. + public Episode(Library library) : base(library) { - if (season == null) - { - throw new ArgumentNullException(nameof(season)); - } - - season.Episodes.Add(this); - Releases = new HashSet(); EpisodeMetadata = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Episode() - { - } - /// /// Gets or sets the episode number. /// diff --git a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs index 7efb840f0b..5662decdb8 100644 --- a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; namespace Jellyfin.Data.Entities.Libraries @@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the object. /// ISO-639-3 3-character language codes. - /// The episode. - public EpisodeMetadata(string title, string language, Episode episode) : base(title, language) - { - if (episode == null) - { - throw new ArgumentNullException(nameof(episode)); - } - - episode.EpisodeMetadata.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected EpisodeMetadata() + public EpisodeMetadata(string title, string language) : base(title, language) { } diff --git a/Jellyfin.Data/Entities/Libraries/Genre.cs b/Jellyfin.Data/Entities/Libraries/Genre.cs index 2a2dbd1a5d..befa75550a 100644 --- a/Jellyfin.Data/Entities/Libraries/Genre.cs +++ b/Jellyfin.Data/Entities/Libraries/Genre.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; @@ -14,32 +13,9 @@ namespace Jellyfin.Data.Entities.Libraries /// Initializes a new instance of the class. /// /// The name. - /// The metadata. - public Genre(string name, ItemMetadata itemMetadata) + public Genre(string name) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException(nameof(name)); - } - Name = name; - - if (itemMetadata == null) - { - throw new ArgumentNullException(nameof(itemMetadata)); - } - - itemMetadata.Genres.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Genre() - { } /// diff --git a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs index d74330c051..a0efa66e46 100644 --- a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs @@ -42,16 +42,6 @@ namespace Jellyfin.Data.Entities.Libraries Sources = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to being abstract. - /// - protected ItemMetadata() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/Library.cs b/Jellyfin.Data/Entities/Libraries/Library.cs index 4f82a2e2a7..3ec4341a48 100644 --- a/Jellyfin.Data/Entities/Libraries/Library.cs +++ b/Jellyfin.Data/Entities/Libraries/Library.cs @@ -24,16 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries Name = name; } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Library() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs index a9167aa7fd..504b9c853b 100644 --- a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs +++ b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs @@ -20,13 +20,6 @@ namespace Jellyfin.Data.Entities.Libraries Library = library; } - /// - /// Initializes a new instance of the class. - /// - protected LibraryItem() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/MediaFile.cs b/Jellyfin.Data/Entities/Libraries/MediaFile.cs index 9924d5728d..7f64978e24 100644 --- a/Jellyfin.Data/Entities/Libraries/MediaFile.cs +++ b/Jellyfin.Data/Entities/Libraries/MediaFile.cs @@ -19,8 +19,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The path relative to the LibraryRoot. /// The file kind. - /// The release. - public MediaFile(string path, MediaFileKind kind, Release release) + public MediaFile(string path, MediaFileKind kind) { if (string.IsNullOrEmpty(path)) { @@ -30,26 +29,9 @@ namespace Jellyfin.Data.Entities.Libraries Path = path; Kind = kind; - if (release == null) - { - throw new ArgumentNullException(nameof(release)); - } - - release.MediaFiles.Add(this); - MediaFileStreams = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MediaFile() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs index 5b03e260e2..c4468766f9 100644 --- a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs +++ b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; @@ -14,27 +13,9 @@ namespace Jellyfin.Data.Entities.Libraries /// Initializes a new instance of the class. /// /// The number of this stream. - /// The media file. - public MediaFileStream(int streamNumber, MediaFile mediaFile) + public MediaFileStream(int streamNumber) { StreamNumber = streamNumber; - - if (mediaFile == null) - { - throw new ArgumentNullException(nameof(mediaFile)); - } - - mediaFile.MediaFileStreams.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MediaFileStream() - { } /// diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs index a18a612bcd..20de5bf4bb 100644 --- a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs +++ b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs @@ -24,16 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries Name = name; } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MetadataProvider() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs index fcfb35bfac..12672dd25b 100644 --- a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs +++ b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs @@ -14,8 +14,7 @@ namespace Jellyfin.Data.Entities.Libraries /// Initializes a new instance of the class. /// /// The provider id. - /// The metadata entity. - public MetadataProviderId(string providerId, ItemMetadata itemMetadata) + public MetadataProviderId(string providerId) { if (string.IsNullOrEmpty(providerId)) { @@ -23,23 +22,6 @@ namespace Jellyfin.Data.Entities.Libraries } ProviderId = providerId; - - if (itemMetadata == null) - { - throw new ArgumentNullException(nameof(itemMetadata)); - } - - itemMetadata.Sources.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MetadataProviderId() - { } /// diff --git a/Jellyfin.Data/Entities/Libraries/Movie.cs b/Jellyfin.Data/Entities/Libraries/Movie.cs index 08db904fa8..f89cacff44 100644 --- a/Jellyfin.Data/Entities/Libraries/Movie.cs +++ b/Jellyfin.Data/Entities/Libraries/Movie.cs @@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - public Movie() + /// The library. + public Movie(Library library) : base(library) { Releases = new HashSet(); MovieMetadata = new HashSet(); diff --git a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs index aa1501a5cc..8cf7ca6a7f 100644 --- a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs @@ -17,22 +17,9 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the movie. /// ISO-639-3 3-character language codes. - /// The movie. - public MovieMetadata(string title, string language, Movie movie) : base(title, language) + public MovieMetadata(string title, string language) : base(title, language) { Studios = new HashSet(); - - movie.MovieMetadata.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MovieMetadata() - { } /// diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs index 06aff6f457..4049cdac8e 100644 --- a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs +++ b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs @@ -12,7 +12,8 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - public MusicAlbum() + /// The library. + public MusicAlbum(Library library) : base(library) { MusicAlbumMetadata = new HashSet(); Tracks = new HashSet(); diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs index 05c0b0374b..9e44e550af 100644 --- a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs @@ -15,22 +15,9 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the album. /// ISO-639-3 3-character language codes. - /// The music album. - public MusicAlbumMetadata(string title, string language, MusicAlbum album) : base(title, language) + public MusicAlbumMetadata(string title, string language) : base(title, language) { Labels = new HashSet(); - - album.MusicAlbumMetadata.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MusicAlbumMetadata() - { } /// diff --git a/Jellyfin.Data/Entities/Libraries/Person.cs b/Jellyfin.Data/Entities/Libraries/Person.cs index af4c87b73c..cc4b9e0f9f 100644 --- a/Jellyfin.Data/Entities/Libraries/Person.cs +++ b/Jellyfin.Data/Entities/Libraries/Person.cs @@ -31,16 +31,6 @@ namespace Jellyfin.Data.Entities.Libraries Sources = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Person() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/PersonRole.cs b/Jellyfin.Data/Entities/Libraries/PersonRole.cs index cd38ee83d0..3ae2e4a685 100644 --- a/Jellyfin.Data/Entities/Libraries/PersonRole.cs +++ b/Jellyfin.Data/Entities/Libraries/PersonRole.cs @@ -1,6 +1,5 @@ #pragma warning disable CA2227 -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -18,31 +17,12 @@ namespace Jellyfin.Data.Entities.Libraries /// Initializes a new instance of the class. /// /// The role type. - /// The metadata. - public PersonRole(PersonRoleType type, ItemMetadata itemMetadata) + public PersonRole(PersonRoleType type) { Type = type; - - if (itemMetadata == null) - { - throw new ArgumentNullException(nameof(itemMetadata)); - } - - itemMetadata.PersonRoles.Add(this); - Sources = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected PersonRole() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/Photo.cs b/Jellyfin.Data/Entities/Libraries/Photo.cs index 25562ec96f..eb5c962675 100644 --- a/Jellyfin.Data/Entities/Libraries/Photo.cs +++ b/Jellyfin.Data/Entities/Libraries/Photo.cs @@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - public Photo() + /// The library. + public Photo(Library library) : base(library) { PhotoMetadata = new HashSet(); Releases = new HashSet(); diff --git a/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs b/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs index ffc790b574..6c284307d7 100644 --- a/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs @@ -1,5 +1,3 @@ -using System; - namespace Jellyfin.Data.Entities.Libraries { /// @@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the photo. /// ISO-639-3 3-character language codes. - /// The photo. - public PhotoMetadata(string title, string language, Photo photo) : base(title, language) - { - if (photo == null) - { - throw new ArgumentNullException(nameof(photo)); - } - - photo.PhotoMetadata.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected PhotoMetadata() + public PhotoMetadata(string title, string language) : base(title, language) { } } diff --git a/Jellyfin.Data/Entities/Libraries/Rating.cs b/Jellyfin.Data/Entities/Libraries/Rating.cs index 98226cd802..0ea933fd79 100644 --- a/Jellyfin.Data/Entities/Libraries/Rating.cs +++ b/Jellyfin.Data/Entities/Libraries/Rating.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; @@ -14,27 +13,9 @@ namespace Jellyfin.Data.Entities.Libraries /// Initializes a new instance of the class. /// /// The value. - /// The metadata. - public Rating(double value, ItemMetadata itemMetadata) + public Rating(double value) { Value = value; - - if (itemMetadata == null) - { - throw new ArgumentNullException(nameof(itemMetadata)); - } - - itemMetadata.Ratings.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Rating() - { } /// diff --git a/Jellyfin.Data/Entities/Libraries/RatingSource.cs b/Jellyfin.Data/Entities/Libraries/RatingSource.cs index 549f418042..7e1a5a8f46 100644 --- a/Jellyfin.Data/Entities/Libraries/RatingSource.cs +++ b/Jellyfin.Data/Entities/Libraries/RatingSource.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; @@ -15,28 +14,10 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The minimum value. /// The maximum value. - /// The rating. - public RatingSource(double minimumValue, double maximumValue, Rating rating) + public RatingSource(double minimumValue, double maximumValue) { MinimumValue = minimumValue; MaximumValue = maximumValue; - - if (rating == null) - { - throw new ArgumentNullException(nameof(rating)); - } - - rating.RatingType = this; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected RatingSource() - { } /// diff --git a/Jellyfin.Data/Entities/Libraries/Release.cs b/Jellyfin.Data/Entities/Libraries/Release.cs index b633e08fb3..1871e0f10b 100644 --- a/Jellyfin.Data/Entities/Libraries/Release.cs +++ b/Jellyfin.Data/Entities/Libraries/Release.cs @@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries /// Initializes a new instance of the class. /// /// The name of this release. - /// The owner of this release. - public Release(string name, IHasReleases owner) + public Release(string name) { if (string.IsNullOrEmpty(name)) { @@ -27,22 +26,10 @@ namespace Jellyfin.Data.Entities.Libraries Name = name; - owner?.Releases.Add(this); - MediaFiles = new HashSet(); Chapters = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Release() - { - } - /// /// Gets or sets the id. /// diff --git a/Jellyfin.Data/Entities/Libraries/Season.cs b/Jellyfin.Data/Entities/Libraries/Season.cs index eb6674dbc3..04f723a1d1 100644 --- a/Jellyfin.Data/Entities/Libraries/Season.cs +++ b/Jellyfin.Data/Entities/Libraries/Season.cs @@ -1,6 +1,5 @@ #pragma warning disable CA2227 -using System; using System.Collections.Generic; namespace Jellyfin.Data.Entities.Libraries @@ -13,30 +12,13 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - /// The series. - public Season(Series series) + /// The library. + public Season(Library library) : base(library) { - if (series == null) - { - throw new ArgumentNullException(nameof(series)); - } - - series.Seasons.Add(this); - Episodes = new HashSet(); SeasonMetadata = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Season() - { - } - /// /// Gets or sets the season number. /// diff --git a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs index 7ce79756b2..61714f9095 100644 --- a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; namespace Jellyfin.Data.Entities.Libraries @@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the object. /// ISO-639-3 3-character language codes. - /// The season. - public SeasonMetadata(string title, string language, Season season) : base(title, language) - { - if (season == null) - { - throw new ArgumentNullException(nameof(season)); - } - - season.SeasonMetadata.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected SeasonMetadata() + public SeasonMetadata(string title, string language) : base(title, language) { } diff --git a/Jellyfin.Data/Entities/Libraries/Series.cs b/Jellyfin.Data/Entities/Libraries/Series.cs index 8c8317d14b..59508831e4 100644 --- a/Jellyfin.Data/Entities/Libraries/Series.cs +++ b/Jellyfin.Data/Entities/Libraries/Series.cs @@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - public Series() + /// The library. + public Series(Library library) : base(library) { DateAdded = DateTime.UtcNow; Seasons = new HashSet(); diff --git a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs index 877dbfc69c..e1acd2d457 100644 --- a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs @@ -1,6 +1,5 @@ #pragma warning disable CA2227 -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -18,29 +17,11 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the object. /// ISO-639-3 3-character language codes. - /// The series. - public SeriesMetadata(string title, string language, Series series) : base(title, language) + public SeriesMetadata(string title, string language) : base(title, language) { - if (series == null) - { - throw new ArgumentNullException(nameof(series)); - } - - series.SeriesMetadata.Add(this); - Networks = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected SeriesMetadata() - { - } - /// /// Gets or sets the outline. /// diff --git a/Jellyfin.Data/Entities/Libraries/Track.cs b/Jellyfin.Data/Entities/Libraries/Track.cs index 782bfb5ce7..86a3edff83 100644 --- a/Jellyfin.Data/Entities/Libraries/Track.cs +++ b/Jellyfin.Data/Entities/Libraries/Track.cs @@ -1,6 +1,5 @@ #pragma warning disable CA2227 -using System; using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -14,30 +13,13 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Initializes a new instance of the class. /// - /// The album. - public Track(MusicAlbum album) + /// The library. + public Track(Library library) : base(library) { - if (album == null) - { - throw new ArgumentNullException(nameof(album)); - } - - album.Tracks.Add(this); - Releases = new HashSet(); TrackMetadata = new HashSet(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Track() - { - } - /// /// Gets or sets the track number. /// diff --git a/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs b/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs index 321f93bf2e..042d2b90db 100644 --- a/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs @@ -1,5 +1,3 @@ -using System; - namespace Jellyfin.Data.Entities.Libraries { /// @@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// The title or name of the object. /// ISO-639-3 3-character language codes. - /// The track. - public TrackMetadata(string title, string language, Track track) : base(title, language) - { - if (track == null) - { - throw new ArgumentNullException(nameof(track)); - } - - track.TrackMetadata.Add(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected TrackMetadata() + public TrackMetadata(string title, string language) : base(title, language) { } } diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index d92e5d9d25..17e3bf50e1 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -22,14 +22,6 @@ namespace Jellyfin.Data.Entities Value = value; } - /// - /// Initializes a new instance of the class. - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Permission() - { - } - /// /// Gets or sets the id of this permission. /// diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index 4efddf2a41..40f2f8ede9 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -23,14 +23,6 @@ namespace Jellyfin.Data.Entities Value = value ?? throw new ArgumentNullException(nameof(value)); } - /// - /// Initializes a new instance of the class. - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Preference() - { - } - /// /// Gets or sets the id of this preference. /// diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 362f3b4ebb..28e12adde6 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -77,14 +77,6 @@ namespace Jellyfin.Data.Entities AddDefaultPreferences(); } - /// - /// Initializes a new instance of the class. - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected User() - { - } - /// /// Gets or sets the Id of the user. /// From f638ee6b0918c2ef05ec11cfa43584d3efad68d1 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 6 Mar 2021 17:43:01 -0500 Subject: [PATCH 054/282] Enable nullable for Jellyfin.Data and remove unnecessary attributes --- Emby.Drawing/ImageProcessor.cs | 7 ++++++- Jellyfin.Api/Controllers/ImageController.cs | 12 +++++++++++- Jellyfin.Api/Controllers/StartupController.cs | 5 ++++- Jellyfin.Data/Entities/AccessSchedule.cs | 7 ------- Jellyfin.Data/Entities/ActivityLog.cs | 8 +++----- .../Entities/CustomItemDisplayPreferences.cs | 3 --- Jellyfin.Data/Entities/DisplayPreferences.cs | 7 ++----- Jellyfin.Data/Entities/Group.cs | 1 - Jellyfin.Data/Entities/HomeSection.cs | 1 - Jellyfin.Data/Entities/ImageInfo.cs | 1 - .../Entities/ItemDisplayPreferences.cs | 2 -- Jellyfin.Data/Entities/Libraries/Artwork.cs | 1 - .../Entities/Libraries/BookMetadata.cs | 2 -- Jellyfin.Data/Entities/Libraries/Chapter.cs | 3 +-- Jellyfin.Data/Entities/Libraries/Collection.cs | 2 +- .../Entities/Libraries/CollectionItem.cs | 13 +++++++++++-- Jellyfin.Data/Entities/Libraries/Company.cs | 2 +- .../Entities/Libraries/CompanyMetadata.cs | 8 ++++---- .../Entities/Libraries/CustomItemMetadata.cs | 2 -- .../Entities/Libraries/EpisodeMetadata.cs | 6 +++--- Jellyfin.Data/Entities/Libraries/Genre.cs | 1 - .../Entities/Libraries/ItemMetadata.cs | 6 ++---- Jellyfin.Data/Entities/Libraries/Library.cs | 12 +++--------- Jellyfin.Data/Entities/Libraries/LibraryItem.cs | 1 - Jellyfin.Data/Entities/Libraries/MediaFile.cs | 1 - .../Entities/Libraries/MetadataProvider.cs | 1 - .../Entities/Libraries/MetadataProviderId.cs | 5 +++-- .../Entities/Libraries/MovieMetadata.cs | 10 ++++------ .../Entities/Libraries/MusicAlbumMetadata.cs | 6 +++--- Jellyfin.Data/Entities/Libraries/Person.cs | 3 +-- Jellyfin.Data/Entities/Libraries/PersonRole.cs | 8 +++++--- Jellyfin.Data/Entities/Libraries/Rating.cs | 2 +- .../Entities/Libraries/RatingSource.cs | 4 ++-- Jellyfin.Data/Entities/Libraries/Release.cs | 1 - .../Entities/Libraries/SeasonMetadata.cs | 2 +- .../Entities/Libraries/SeriesMetadata.cs | 9 ++++----- Jellyfin.Data/Entities/Preference.cs | 1 - Jellyfin.Data/Entities/User.cs | 17 +++++++---------- Jellyfin.Data/Jellyfin.Data.csproj | 1 + .../Consumers/Session/PlaybackStartLogger.cs | 4 ++-- .../Users/UserManager.cs | 6 +++--- .../Migrations/Routines/MigrateUserDb.cs | 4 +--- .../Drawing/IImageProcessor.cs | 2 +- tests/Jellyfin.Api.Tests/TestHelpers.cs | 4 ++-- 44 files changed, 93 insertions(+), 111 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 8a2301d2d6..aa8a3d212a 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -352,8 +352,13 @@ namespace Emby.Drawing } /// - public string GetImageCacheTag(User user) + public string? GetImageCacheTag(User user) { + if (user.ProfileImage == null) + { + return null; + } + return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5() .ToString("N", CultureInfo.InvariantCulture); } diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index a50d6e46bf..b016f24500 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -196,6 +196,11 @@ namespace Jellyfin.Api.Controllers } var user = _userManager.GetUserById(userId); + if (user?.ProfileImage == null) + { + return NoContent(); + } + try { System.IO.File.Delete(user.ProfileImage.Path); @@ -235,6 +240,11 @@ namespace Jellyfin.Api.Controllers } var user = _userManager.GetUserById(userId); + if (user?.ProfileImage == null) + { + return NoContent(); + } + try { System.IO.File.Delete(user.ProfileImage.Path); @@ -1469,7 +1479,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? imageIndex) { var user = _userManager.GetUserById(userId); - if (user == null) + if (user?.ProfileImage == null) { return NotFound(); } diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index d9cb34557b..a01a617fc0 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -132,7 +132,10 @@ namespace Jellyfin.Api.Controllers { var user = _userManager.Users.First(); - user.Username = startupUserDto.Name; + if (startupUserDto.Name != null) + { + user.Username = startupUserDto.Name; + } await _userManager.UpdateUserAsync(user).ConfigureAwait(false); diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs index 72bca061da..9ac0666ac3 100644 --- a/Jellyfin.Data/Entities/AccessSchedule.cs +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Xml.Serialization; using Jellyfin.Data.Enums; @@ -33,8 +32,6 @@ namespace Jellyfin.Data.Entities /// Identity, Indexed, Required. /// [XmlIgnore] - [Key] - [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } @@ -42,28 +39,24 @@ namespace Jellyfin.Data.Entities /// Gets or sets the id of the associated user. /// [XmlIgnore] - [Required] public Guid UserId { get; protected set; } /// /// Gets or sets the day of week. /// /// The day of week. - [Required] public DynamicDayOfWeek DayOfWeek { get; set; } /// /// Gets or sets the start hour. /// /// The start hour. - [Required] public double StartHour { get; set; } /// /// Gets or sets the end hour. /// /// The end hour. - [Required] public double EndHour { get; set; } /// diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs index 80e32db30e..e4534e8b5e 100644 --- a/Jellyfin.Data/Entities/ActivityLog.cs +++ b/Jellyfin.Data/Entities/ActivityLog.cs @@ -50,7 +50,6 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 512. /// - [Required] [MaxLength(512)] [StringLength(512)] public string Name { get; set; } @@ -63,7 +62,7 @@ namespace Jellyfin.Data.Entities /// [MaxLength(512)] [StringLength(512)] - public string Overview { get; set; } + public string? Overview { get; set; } /// /// Gets or sets the short overview. @@ -73,7 +72,7 @@ namespace Jellyfin.Data.Entities /// [MaxLength(512)] [StringLength(512)] - public string ShortOverview { get; set; } + public string? ShortOverview { get; set; } /// /// Gets or sets the type. @@ -81,7 +80,6 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 256. /// - [Required] [MaxLength(256)] [StringLength(256)] public string Type { get; set; } @@ -102,7 +100,7 @@ namespace Jellyfin.Data.Entities /// [MaxLength(256)] [StringLength(256)] - public string ItemId { get; set; } + public string? ItemId { get; set; } /// /// Gets or sets the date created. This should be in UTC. diff --git a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs index d407180d46..cc46248c75 100644 --- a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs @@ -57,7 +57,6 @@ namespace Jellyfin.Data.Entities /// /// Required. Max Length = 32. /// - [Required] [MaxLength(32)] [StringLength(32)] public string Client { get; set; } @@ -68,7 +67,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] public string Key { get; set; } /// @@ -77,7 +75,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] public string Value { get; set; } } } diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index d186deb293..64cd6812a4 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -30,8 +30,6 @@ namespace Jellyfin.Data.Entities SkipBackwardLength = 10000; ScrollDirection = ScrollDirection.Horizontal; ChromecastVersion = ChromecastVersion.Stable; - DashboardTheme = string.Empty; - TvHome = string.Empty; HomeSections = new HashSet(); } @@ -67,7 +65,6 @@ namespace Jellyfin.Data.Entities /// /// Required. Max Length = 32. /// - [Required] [MaxLength(32)] [StringLength(32)] public string Client { get; set; } @@ -138,14 +135,14 @@ namespace Jellyfin.Data.Entities /// [MaxLength(32)] [StringLength(32)] - public string DashboardTheme { get; set; } + public string? DashboardTheme { get; set; } /// /// Gets or sets the tv home screen. /// [MaxLength(32)] [StringLength(32)] - public string TvHome { get; set; } + public string? TvHome { get; set; } /// /// Gets or sets the home sections. diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index 8c45dde928..b14e22b7b3 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -46,7 +46,6 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 255. /// - [Required] [MaxLength(255)] [StringLength(255)] public string Name { get; set; } diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs index 0620462602..5adc52491c 100644 --- a/Jellyfin.Data/Entities/HomeSection.cs +++ b/Jellyfin.Data/Entities/HomeSection.cs @@ -15,7 +15,6 @@ namespace Jellyfin.Data.Entities /// /// Identity. Required. /// - [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs index f9ae1a955f..e0c37047d8 100644 --- a/Jellyfin.Data/Entities/ImageInfo.cs +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -39,7 +39,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] [MaxLength(512)] [StringLength(512)] public string Path { get; set; } diff --git a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs index f0a04f8ea6..4bfeb2fa30 100644 --- a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs @@ -59,7 +59,6 @@ namespace Jellyfin.Data.Entities /// /// Required. Max Length = 32. /// - [Required] [MaxLength(32)] [StringLength(32)] public string Client { get; set; } @@ -99,7 +98,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] [MaxLength(64)] [StringLength(64)] public string SortBy { get; set; } diff --git a/Jellyfin.Data/Entities/Libraries/Artwork.cs b/Jellyfin.Data/Entities/Libraries/Artwork.cs index df28ce7374..84a524de2c 100644 --- a/Jellyfin.Data/Entities/Libraries/Artwork.cs +++ b/Jellyfin.Data/Entities/Libraries/Artwork.cs @@ -44,7 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required, Max length = 65535. /// - [Required] [MaxLength(65535)] [StringLength(65535)] public string Path { get; set; } diff --git a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs index 8b0c965306..1ff4327b01 100644 --- a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs @@ -1,7 +1,6 @@ #pragma warning disable CA2227 using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; namespace Jellyfin.Data.Entities.Libraries @@ -32,7 +31,6 @@ namespace Jellyfin.Data.Entities.Libraries public virtual ICollection Publishers { get; protected set; } /// - [NotMapped] public ICollection Companies => Publishers; } } diff --git a/Jellyfin.Data/Entities/Libraries/Chapter.cs b/Jellyfin.Data/Entities/Libraries/Chapter.cs index f253143d74..11f53ae20a 100644 --- a/Jellyfin.Data/Entities/Libraries/Chapter.cs +++ b/Jellyfin.Data/Entities/Libraries/Chapter.cs @@ -45,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the language. @@ -54,7 +54,6 @@ namespace Jellyfin.Data.Entities.Libraries /// Required, Min length = 3, Max length = 3 /// ISO-639-3 3-character language codes. /// - [Required] [MinLength(3)] [MaxLength(3)] [StringLength(3)] diff --git a/Jellyfin.Data/Entities/Libraries/Collection.cs b/Jellyfin.Data/Entities/Libraries/Collection.cs index 39eded752d..d230eeb2fe 100644 --- a/Jellyfin.Data/Entities/Libraries/Collection.cs +++ b/Jellyfin.Data/Entities/Libraries/Collection.cs @@ -37,7 +37,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Name { get; set; } + public string? Name { get; set; } /// [ConcurrencyCheck] diff --git a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs index 1157de442b..e19362bdf1 100644 --- a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs +++ b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs @@ -9,6 +9,15 @@ namespace Jellyfin.Data.Entities.Libraries /// public class CollectionItem : IHasConcurrencyToken { + /// + /// Initializes a new instance of the class. + /// + /// The library item. + public CollectionItem(LibraryItem libraryItem) + { + LibraryItem = libraryItem; + } + /// /// Gets or sets the id. /// @@ -36,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// TODO check if this properly updated Dependant and has the proper principal relationship. /// - public virtual CollectionItem Next { get; set; } + public virtual CollectionItem? Next { get; set; } /// /// Gets or sets the previous item in the collection. @@ -44,7 +53,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// TODO check if this properly updated Dependant and has the proper principal relationship. /// - public virtual CollectionItem Previous { get; set; } + public virtual CollectionItem? Previous { get; set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Company.cs b/Jellyfin.Data/Entities/Libraries/Company.cs index 499ba3800a..09050bb522 100644 --- a/Jellyfin.Data/Entities/Libraries/Company.cs +++ b/Jellyfin.Data/Entities/Libraries/Company.cs @@ -18,6 +18,7 @@ namespace Jellyfin.Data.Entities.Libraries public Company() { CompanyMetadata = new HashSet(); + ChildCompanies = new HashSet(); } /// @@ -44,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries public virtual ICollection ChildCompanies { get; protected set; } /// - [NotMapped] public ICollection Companies => ChildCompanies; /// diff --git a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs index 86642b38a5..a29f08c7f6 100644 --- a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(65535)] [StringLength(65535)] - public string Description { get; set; } + public string? Description { get; set; } /// /// Gets or sets the headquarters. @@ -34,7 +34,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(255)] [StringLength(255)] - public string Headquarters { get; set; } + public string? Headquarters { get; set; } /// /// Gets or sets the country code. @@ -44,7 +44,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(2)] [StringLength(2)] - public string Country { get; set; } + public string? Country { get; set; } /// /// Gets or sets the homepage. @@ -54,6 +54,6 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Homepage { get; set; } + public string? Homepage { get; set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs index a69a8eafa4..af2393870f 100644 --- a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs @@ -1,5 +1,3 @@ -using System; - namespace Jellyfin.Data.Entities.Libraries { /// diff --git a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs index 5662decdb8..b0ef11e0f2 100644 --- a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Outline { get; set; } + public string? Outline { get; set; } /// /// Gets or sets the plot. @@ -34,7 +34,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(65535)] [StringLength(65535)] - public string Plot { get; set; } + public string? Plot { get; set; } /// /// Gets or sets the tagline. @@ -44,6 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Tagline { get; set; } + public string? Tagline { get; set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Genre.cs b/Jellyfin.Data/Entities/Libraries/Genre.cs index befa75550a..9f3d650283 100644 --- a/Jellyfin.Data/Entities/Libraries/Genre.cs +++ b/Jellyfin.Data/Entities/Libraries/Genre.cs @@ -33,7 +33,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Indexed, Required, Max length = 255. /// - [Required] [MaxLength(255)] [StringLength(255)] public string Name { get; set; } diff --git a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs index a0efa66e46..d12e011a8b 100644 --- a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs @@ -57,7 +57,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required, Max length = 1024. /// - [Required] [MaxLength(1024)] [StringLength(1024)] public string Title { get; set; } @@ -70,7 +69,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string OriginalTitle { get; set; } + public string? OriginalTitle { get; set; } /// /// Gets or sets the sort title. @@ -80,7 +79,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string SortTitle { get; set; } + public string? SortTitle { get; set; } /// /// Gets or sets the language. @@ -89,7 +88,6 @@ namespace Jellyfin.Data.Entities.Libraries /// Required, Min length = 3, Max length = 3. /// ISO-639-3 3-character language codes. /// - [Required] [MinLength(3)] [MaxLength(3)] [StringLength(3)] diff --git a/Jellyfin.Data/Entities/Libraries/Library.cs b/Jellyfin.Data/Entities/Libraries/Library.cs index 3ec4341a48..e453849020 100644 --- a/Jellyfin.Data/Entities/Libraries/Library.cs +++ b/Jellyfin.Data/Entities/Libraries/Library.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; @@ -14,14 +13,11 @@ namespace Jellyfin.Data.Entities.Libraries /// Initializes a new instance of the class. /// /// The name of the library. - public Library(string name) + /// The path of the library. + public Library(string name, string path) { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - Name = name; + Path = path; } /// @@ -39,7 +35,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required, Max length = 128. /// - [Required] [MaxLength(128)] [StringLength(128)] public string Name { get; set; } @@ -50,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required. /// - [Required] public string Path { get; set; } /// diff --git a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs index 504b9c853b..67ffad944b 100644 --- a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs +++ b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs @@ -44,7 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required. /// - [Required] public virtual Library Library { get; set; } /// diff --git a/Jellyfin.Data/Entities/Libraries/MediaFile.cs b/Jellyfin.Data/Entities/Libraries/MediaFile.cs index 7f64978e24..f3e2fe6539 100644 --- a/Jellyfin.Data/Entities/Libraries/MediaFile.cs +++ b/Jellyfin.Data/Entities/Libraries/MediaFile.cs @@ -47,7 +47,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required, Max length = 65535. /// - [Required] [MaxLength(65535)] [StringLength(65535)] public string Path { get; set; } diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs index 20de5bf4bb..fb2587882d 100644 --- a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs +++ b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs @@ -39,7 +39,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required, Max length = 1024. /// - [Required] [MaxLength(1024)] [StringLength(1024)] public string Name { get; set; } diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs index 12672dd25b..2a9c904c88 100644 --- a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs +++ b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs @@ -14,7 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries /// Initializes a new instance of the class. /// /// The provider id. - public MetadataProviderId(string providerId) + /// The metadata provider. + public MetadataProviderId(string providerId, MetadataProvider metadataProvider) { if (string.IsNullOrEmpty(providerId)) { @@ -22,6 +23,7 @@ namespace Jellyfin.Data.Entities.Libraries } ProviderId = providerId; + MetadataProvider = metadataProvider; } /// @@ -39,7 +41,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required, Max length = 255. /// - [Required] [MaxLength(255)] [StringLength(255)] public string ProviderId { get; set; } diff --git a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs index 8cf7ca6a7f..fb181dea60 100644 --- a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; namespace Jellyfin.Data.Entities.Libraries @@ -30,7 +29,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Outline { get; set; } + public string? Outline { get; set; } /// /// Gets or sets the tagline. @@ -40,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Tagline { get; set; } + public string? Tagline { get; set; } /// /// Gets or sets the plot. @@ -50,7 +49,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(65535)] [StringLength(65535)] - public string Plot { get; set; } + public string? Plot { get; set; } /// /// Gets or sets the country code. @@ -60,7 +59,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(2)] [StringLength(2)] - public string Country { get; set; } + public string? Country { get; set; } /// /// Gets or sets the studios that produced this movie. @@ -68,7 +67,6 @@ namespace Jellyfin.Data.Entities.Libraries public virtual ICollection Studios { get; protected set; } /// - [NotMapped] public ICollection Companies => Studios; } } diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs index 9e44e550af..3080bd692d 100644 --- a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(255)] [StringLength(255)] - public string Barcode { get; set; } + public string? Barcode { get; set; } /// /// Gets or sets the label number. @@ -38,7 +38,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(255)] [StringLength(255)] - public string LabelNumber { get; set; } + public string? LabelNumber { get; set; } /// /// Gets or sets the country code. @@ -48,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(2)] [StringLength(2)] - public string Country { get; set; } + public string? Country { get; set; } /// /// Gets or sets a collection containing the labels. diff --git a/Jellyfin.Data/Entities/Libraries/Person.cs b/Jellyfin.Data/Entities/Libraries/Person.cs index cc4b9e0f9f..159bd47be7 100644 --- a/Jellyfin.Data/Entities/Libraries/Person.cs +++ b/Jellyfin.Data/Entities/Libraries/Person.cs @@ -46,7 +46,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required, Max length = 1024. /// - [Required] [MaxLength(1024)] [StringLength(1024)] public string Name { get; set; } @@ -59,7 +58,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(256)] [StringLength(256)] - public string SourceId { get; set; } + public string? SourceId { get; set; } /// /// Gets or sets the date added. diff --git a/Jellyfin.Data/Entities/Libraries/PersonRole.cs b/Jellyfin.Data/Entities/Libraries/PersonRole.cs index 3ae2e4a685..988aa84bae 100644 --- a/Jellyfin.Data/Entities/Libraries/PersonRole.cs +++ b/Jellyfin.Data/Entities/Libraries/PersonRole.cs @@ -17,9 +17,12 @@ namespace Jellyfin.Data.Entities.Libraries /// Initializes a new instance of the class. /// /// The role type. - public PersonRole(PersonRoleType type) + /// The person. + public PersonRole(PersonRoleType type, Person person) { Type = type; + Person = person; + Artwork = new HashSet(); Sources = new HashSet(); } @@ -40,7 +43,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Role { get; set; } + public string? Role { get; set; } /// /// Gets or sets the person's role type. @@ -60,7 +63,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required. /// - [Required] public virtual Person Person { get; set; } /// diff --git a/Jellyfin.Data/Entities/Libraries/Rating.cs b/Jellyfin.Data/Entities/Libraries/Rating.cs index 0ea933fd79..6862012a80 100644 --- a/Jellyfin.Data/Entities/Libraries/Rating.cs +++ b/Jellyfin.Data/Entities/Libraries/Rating.cs @@ -48,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries /// Gets or sets the rating type. /// If this is null it's the internal user rating. /// - public virtual RatingSource RatingType { get; set; } + public virtual RatingSource? RatingType { get; set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/RatingSource.cs b/Jellyfin.Data/Entities/Libraries/RatingSource.cs index 7e1a5a8f46..ae0d806ff0 100644 --- a/Jellyfin.Data/Entities/Libraries/RatingSource.cs +++ b/Jellyfin.Data/Entities/Libraries/RatingSource.cs @@ -37,7 +37,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the minimum value. @@ -62,7 +62,7 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Gets or sets the metadata source. /// - public virtual MetadataProviderId Source { get; set; } + public virtual MetadataProviderId? Source { get; set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Release.cs b/Jellyfin.Data/Entities/Libraries/Release.cs index 1871e0f10b..21d4039793 100644 --- a/Jellyfin.Data/Entities/Libraries/Release.cs +++ b/Jellyfin.Data/Entities/Libraries/Release.cs @@ -45,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries /// /// Required, Max length = 1024. /// - [Required] [MaxLength(1024)] [StringLength(1024)] public string Name { get; set; } diff --git a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs index 61714f9095..da40a075f5 100644 --- a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs @@ -24,6 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Outline { get; set; } + public string? Outline { get; set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs index e1acd2d457..730deccaed 100644 --- a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs @@ -30,7 +30,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Outline { get; set; } + public string? Outline { get; set; } /// /// Gets or sets the plot. @@ -40,7 +40,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(65535)] [StringLength(65535)] - public string Plot { get; set; } + public string? Plot { get; set; } /// /// Gets or sets the tagline. @@ -50,7 +50,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(1024)] [StringLength(1024)] - public string Tagline { get; set; } + public string? Tagline { get; set; } /// /// Gets or sets the country code. @@ -60,7 +60,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [MaxLength(2)] [StringLength(2)] - public string Country { get; set; } + public string? Country { get; set; } /// /// Gets or sets a collection containing the networks. @@ -68,7 +68,6 @@ namespace Jellyfin.Data.Entities.Libraries public virtual ICollection Networks { get; protected set; } /// - [NotMapped] public ICollection Companies => Networks; } } diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index 40f2f8ede9..a8813ab88d 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -46,7 +46,6 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 65535. /// - [Required] [MaxLength(65535)] [StringLength(65535)] public string Value { get; set; } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 28e12adde6..9aa8091640 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -51,6 +51,7 @@ namespace Jellyfin.Data.Entities PasswordResetProviderId = passwordResetProviderId; AccessSchedules = new HashSet(); + DisplayPreferences = new HashSet(); ItemDisplayPreferences = new HashSet(); // Groups = new HashSet(); Permissions = new HashSet(); @@ -92,7 +93,6 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 255. /// - [Required] [MaxLength(255)] [StringLength(255)] public string Username { get; set; } @@ -105,7 +105,7 @@ namespace Jellyfin.Data.Entities /// [MaxLength(65535)] [StringLength(65535)] - public string Password { get; set; } + public string? Password { get; set; } /// /// Gets or sets the user's easy password, or null if none is set. @@ -115,7 +115,7 @@ namespace Jellyfin.Data.Entities /// [MaxLength(65535)] [StringLength(65535)] - public string EasyPassword { get; set; } + public string? EasyPassword { get; set; } /// /// Gets or sets a value indicating whether the user must update their password. @@ -133,7 +133,7 @@ namespace Jellyfin.Data.Entities /// [MaxLength(255)] [StringLength(255)] - public string AudioLanguagePreference { get; set; } + public string? AudioLanguagePreference { get; set; } /// /// Gets or sets the authentication provider id. @@ -141,7 +141,6 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 255. /// - [Required] [MaxLength(255)] [StringLength(255)] public string AuthenticationProviderId { get; set; } @@ -152,7 +151,6 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 255. /// - [Required] [MaxLength(255)] [StringLength(255)] public string PasswordResetProviderId { get; set; } @@ -209,7 +207,7 @@ namespace Jellyfin.Data.Entities /// [MaxLength(255)] [StringLength(255)] - public string SubtitleLanguagePreference { get; set; } + public string? SubtitleLanguagePreference { get; set; } /// /// Gets or sets a value indicating whether missing episodes should be displayed. @@ -304,7 +302,7 @@ namespace Jellyfin.Data.Entities /// Gets or sets the user's profile image. Can be null. /// // [ForeignKey("UserId")] - public virtual ImageInfo ProfileImage { get; set; } + public virtual ImageInfo? ProfileImage { get; set; } /// /// Gets or sets the user's display preferences. @@ -312,8 +310,7 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] - public virtual DisplayPreferences DisplayPreferences { get; set; } + public virtual ICollection DisplayPreferences { get; set; } /// /// Gets or sets the level of sync play permissions this user has. diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index a8ac45645f..a2b6f074e4 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -5,6 +5,7 @@ false true true + enable true true true diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs index 0340248bbd..aa6015caae 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs @@ -86,7 +86,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session return name; } - private static string? GetPlaybackNotificationType(string mediaType) + private static string GetPlaybackNotificationType(string mediaType) { if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { @@ -98,7 +98,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session return NotificationType.VideoPlayback.ToString(); } - return null; + return "Playback"; } } } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 76d1389caf..b400a0dd1b 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -184,8 +184,8 @@ namespace Jellyfin.Server.Implementations.Users var user = new User( name, - _defaultAuthenticationProvider.GetType().FullName, - _defaultPasswordResetProvider.GetType().FullName) + _defaultAuthenticationProvider.GetType().FullName!, + _defaultPasswordResetProvider.GetType().FullName!) { InternalId = max + 1 }; @@ -444,7 +444,7 @@ namespace Jellyfin.Server.Implementations.Users { var providerId = authenticationProvider.GetType().FullName; - if (!string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + if (providerId != null && !string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) { user.AuthenticationProviderId = providerId; await UpdateUserAsync(user).ConfigureAwait(false); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 33f039c394..6d318d2329 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -1,7 +1,5 @@ using System; -using System.Globalization; using System.IO; -using System.Linq; using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Serialization; using Jellyfin.Data.Entities; @@ -104,7 +102,7 @@ namespace Jellyfin.Server.Migrations.Routines _ => policy.LoginAttemptsBeforeLockout }; - var user = new User(mockup.Name, policy.AuthenticationProviderId, policy.PasswordResetProviderId) + var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!) { Id = entry[1].ReadGuidFromBlob(), InternalId = entry[0].ToInt64(), diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 935a790312..142cebd0c0 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Controller.Drawing string GetImageCacheTag(BaseItem item, ChapterInfo info); - string GetImageCacheTag(User user); + string? GetImageCacheTag(User user); /// /// Processes the image. diff --git a/tests/Jellyfin.Api.Tests/TestHelpers.cs b/tests/Jellyfin.Api.Tests/TestHelpers.cs index f27cdf7b63..c1549561db 100644 --- a/tests/Jellyfin.Api.Tests/TestHelpers.cs +++ b/tests/Jellyfin.Api.Tests/TestHelpers.cs @@ -26,8 +26,8 @@ namespace Jellyfin.Api.Tests { var user = new User( "jellyfin", - typeof(DefaultAuthenticationProvider).FullName, - typeof(DefaultPasswordResetProvider).FullName); + typeof(DefaultAuthenticationProvider).FullName!, + typeof(DefaultPasswordResetProvider).FullName!); // Set administrator flag. user.SetPermission(PermissionKind.IsAdministrator, role.Equals(UserRoles.Administrator, StringComparison.OrdinalIgnoreCase)); From 7b37ae94f7773069b1406728e297a012e3f726cc Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 6 Mar 2021 20:02:42 -0500 Subject: [PATCH 055/282] Remove unused factory method --- Jellyfin.Data/Entities/AccessSchedule.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs index 9ac0666ac3..7974d3addb 100644 --- a/Jellyfin.Data/Entities/AccessSchedule.cs +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -58,18 +58,5 @@ namespace Jellyfin.Data.Entities /// /// The end hour. public double EndHour { get; set; } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The day of the week. - /// The start hour. - /// The end hour. - /// The associated user's id. - /// The newly created instance. - public static AccessSchedule Create(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId) - { - return new AccessSchedule(dayOfWeek, startHour, endHour, userId); - } } } From 7c413a323b0d22a59532687b854ea228d544ecb7 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 6 Mar 2021 20:07:55 -0500 Subject: [PATCH 056/282] Move EF Core dependency out of Jellyfin.Data --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 1 + Jellyfin.Data/Jellyfin.Data.csproj | 3 +-- .../Jellyfin.Server.Implementations.csproj | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index f03f04e021..93a4c3a7d8 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -27,6 +27,7 @@ + diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index a2b6f074e4..8651dee252 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -42,8 +42,7 @@ - - + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 4f24da0ee4..e3278cfd02 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -26,6 +26,8 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 60ffa6f514656ed7256b555d483771c36164fce7 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 7 Mar 2021 14:43:28 +0100 Subject: [PATCH 057/282] Use FileShare.None when creating files --- Emby.Dlna/DlnaManager.cs | 3 ++- Emby.Server.Implementations/AppBase/ConfigurationHelper.cs | 3 ++- Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs | 6 ++++-- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 6 ++++-- .../LiveTv/EmbyTV/EncodedRecorder.cs | 3 ++- .../LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 3 ++- .../LiveTv/TunerHosts/SharedHttpStream.cs | 3 ++- Jellyfin.Api/Controllers/ItemLookupController.cs | 3 ++- Jellyfin.Api/Controllers/RemoteImageController.cs | 3 ++- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 3 ++- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 3 ++- MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 3 ++- MediaBrowser.Providers/Manager/ImageSaver.cs | 3 ++- MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs | 3 ++- MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs | 3 ++- MediaBrowser.Providers/Subtitles/SubtitleManager.cs | 3 ++- 16 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 9ab3240388..d7b75f9796 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -395,7 +395,8 @@ namespace Emby.Dlna { Directory.CreateDirectory(systemProfilesPath); - using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 77819c7649..3f70763830 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -53,7 +53,8 @@ namespace Emby.Server.Implementations.AppBase Directory.CreateDirectory(directory); // Save it after load in case we got new items - using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) { fs.Write(newBytes, 0, newBytesLen); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 341194f239..7a6b1d8b61 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -45,7 +45,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); - using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None)) { onStarted(); @@ -70,7 +71,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); - await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read); + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None); onStarted(); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 13b5a1c552..91a21db60a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1856,7 +1856,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None)) { var settings = new XmlWriterSettings { @@ -1920,7 +1921,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None)) { var settings = new XmlWriterSettings { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 78a82118ed..40b934d32b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -91,7 +91,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false); await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index b16ccc561a..08832bae14 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -193,7 +193,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var resolved = false; - using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)) { while (true) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index eeb2426f4f..233c3d83a3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -136,7 +136,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath); using var message = response; await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read); + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.None); await StreamHelper.CopyToAsync( stream, fileStream, diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index dfc68ffcec..dabd4deb79 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -344,11 +344,12 @@ namespace Jellyfin.Api.Controllers Directory.CreateDirectory(directory); using (var stream = result.Content) { + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . await using var fileStream = new FileStream( fullCachePath, FileMode.Create, FileAccess.Write, - FileShare.Read, + FileShare.None, IODefaults.FileStreamBufferSize, true); diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index 5284888d82..e226adc646 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -259,7 +259,8 @@ namespace Jellyfin.Api.Controllers var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); Directory.CreateDirectory(fullCacheDirectory); - await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); await response.Content.CopyToAsync(fileStream).ConfigureAwait(false); var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath)); diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 240d132b15..7cd9024b08 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -553,7 +553,8 @@ namespace Jellyfin.Api.Helpers $"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log"); // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index a337521c62..e59fcb965b 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -133,7 +133,8 @@ namespace MediaBrowser.LocalMetadata.Savers // On Windows, savint the file will fail if the file is hidden or readonly FileSystem.SetAttributes(path, false, false); - using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) { stream.CopyTo(filestream); } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index d195387301..fbb1563bbf 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -677,7 +677,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (!string.Equals(text, newText, StringComparison.Ordinal)) { - using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)) using (var writer = new StreamWriter(fileStream, encoding)) { await writer.WriteAsync(newText.AsMemory(), cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 9dd87aef53..5bb85be7bf 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -261,7 +261,8 @@ namespace MediaBrowser.Providers.Manager _fileSystem.SetAttributes(path, false, false); - await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index f463a3566b..0a79f5bb59 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -171,7 +171,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index 7a15adb8e8..4b1d915671 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -155,7 +155,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb Directory.CreateDirectory(Path.GetDirectoryName(path)); - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 47e9d5ee8c..d4d79d27b5 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -228,7 +228,8 @@ namespace MediaBrowser.Providers.Subtitles { Directory.CreateDirectory(Path.GetDirectoryName(savePath)); - using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, FileStreamBufferSize, true)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true)) { await stream.CopyToAsync(fs).ConfigureAwait(false); } From 75c9659e05363f2e29e01757dc66f14f3f20f318 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 7 Mar 2021 14:17:32 +0000 Subject: [PATCH 058/282] Fix unreachable code & assign id to each profile. --- Emby.Dlna/Profiles/DefaultProfile.cs | 3 +++ Jellyfin.Api/Helpers/StreamingHelpers.cs | 17 +++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs index d4af72b626..8eaf12ba9a 100644 --- a/Emby.Dlna/Profiles/DefaultProfile.cs +++ b/Emby.Dlna/Profiles/DefaultProfile.cs @@ -1,5 +1,7 @@ #pragma warning disable CS1591 +using System; +using System.Globalization; using System.Linq; using MediaBrowser.Model.Dlna; @@ -10,6 +12,7 @@ namespace Emby.Dlna.Profiles { public DefaultProfile() { + Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); Name = "Generic Device"; ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*"; diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index d20a02cf5d..6bc5524cba 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -508,17 +508,18 @@ namespace Jellyfin.Api.Helpers private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static) { - var headers = request.Headers; - if (!string.IsNullOrWhiteSpace(deviceProfileId)) { - state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId); - } - else if (!string.IsNullOrWhiteSpace(deviceProfileId)) - { - var caps = deviceManager.GetCapabilities(deviceProfileId); + if (state.DeviceProfile == null) + { + state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId); + } - state.DeviceProfile = caps == null ? dlnaManager.GetProfile(headers) : caps.DeviceProfile; + if (state.DeviceProfile == null) + { + var caps = deviceManager.GetCapabilities(deviceProfileId); + state.DeviceProfile = caps == null ? dlnaManager.GetProfile(request.Headers) : caps.DeviceProfile; + } } var profile = state.DeviceProfile; From e0db17a9354ff511c31c7bcf02c41d868ed91c5a Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 7 Mar 2021 22:49:31 +0100 Subject: [PATCH 059/282] do not throw ArgumentNullException in TryCleanString --- Emby.Naming/Video/CleanStringParser.cs | 11 +++++++++-- Emby.Naming/Video/VideoResolver.cs | 3 ++- tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index 09a0cd1893..deeea4ddac 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace Emby.Naming.Video @@ -16,7 +17,7 @@ namespace Emby.Naming.Video /// List of regex to parse name and year from. /// Parsing result string. /// True if parsing was successful. - public static bool TryClean(string name, IReadOnlyList expressions, out ReadOnlySpan newName) + public static bool TryClean(string name, IReadOnlyList expressions, [NotNullWhen(true)] out ReadOnlySpan newName) { var len = expressions.Count; for (int i = 0; i < len; i++) @@ -31,8 +32,14 @@ namespace Emby.Naming.Video return false; } - private static bool TryClean(string name, Regex expression, out ReadOnlySpan newName) + private static bool TryClean(string name, Regex expression, [NotNullWhen(true)] out ReadOnlySpan newName) { + if (string.IsNullOrEmpty(name)) + { + newName = null; + return false; + } + var match = expression.Match(name); int index = match.Index; if (match.Success && index != 0) diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 619d1520e4..d845d2ca6e 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Emby.Naming.Common; @@ -146,7 +147,7 @@ namespace Emby.Naming.Video /// Raw name. /// Clean name. /// True if cleaning of name was successful. - public bool TryCleanString(string name, out ReadOnlySpan newName) + public bool TryCleanString(string name, [NotNullWhen(true)] out ReadOnlySpan newName) { return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName); } diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs index fde06c5a17..4b363843ac 100644 --- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs @@ -28,6 +28,7 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")] + [InlineData(null, null)] // FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")] public void CleanStringTest(string input, string expectedName) { From fcacae8cdeb3be15b27952d873ae08e29b6c7f94 Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 7 Mar 2021 22:59:08 +0100 Subject: [PATCH 060/282] return empty span instead of null for backwards compat --- Emby.Naming/Video/CleanStringParser.cs | 9 ++++----- Emby.Naming/Video/VideoResolver.cs | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index deeea4ddac..bd7553a91c 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace Emby.Naming.Video @@ -17,7 +16,7 @@ namespace Emby.Naming.Video /// List of regex to parse name and year from. /// Parsing result string. /// True if parsing was successful. - public static bool TryClean(string name, IReadOnlyList expressions, [NotNullWhen(true)] out ReadOnlySpan newName) + public static bool TryClean(string name, IReadOnlyList expressions, out ReadOnlySpan newName) { var len = expressions.Count; for (int i = 0; i < len; i++) @@ -32,11 +31,11 @@ namespace Emby.Naming.Video return false; } - private static bool TryClean(string name, Regex expression, [NotNullWhen(true)] out ReadOnlySpan newName) + private static bool TryClean(string name, Regex expression, out ReadOnlySpan newName) { if (string.IsNullOrEmpty(name)) { - newName = null; + newName = ReadOnlySpan.Empty; return false; } @@ -48,7 +47,7 @@ namespace Emby.Naming.Video return true; } - newName = string.Empty; + newName = ReadOnlySpan.Empty; return false; } } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index d845d2ca6e..619d1520e4 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Emby.Naming.Common; @@ -147,7 +146,7 @@ namespace Emby.Naming.Video /// Raw name. /// Clean name. /// True if cleaning of name was successful. - public bool TryCleanString(string name, [NotNullWhen(true)] out ReadOnlySpan newName) + public bool TryCleanString(string name, out ReadOnlySpan newName) { return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName); } From bc95268bd6a81db3f8e1c192aad48df5b879d231 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Sun, 7 Mar 2021 21:40:56 +0000 Subject: [PATCH 061/282] Translated using Weblate (Kazakh) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/ --- Emby.Server.Implementations/Localization/Core/kk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index a321e35d03..bdfb786c94 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -109,7 +109,7 @@ "TasksMaintenanceCategory": "Qyzmet körsetu", "Undefined": "Anyqtalmağan", "Forced": "Mäjbürlı", - "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı Internetten ızdeidı.", + "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.", "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.", "TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.", "TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.", From 2e62c09f2e55b33c2ba8eeb97157495e2f8ab5a9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 8 Mar 2021 02:16:35 +0100 Subject: [PATCH 062/282] Fix casing CollectionType --- .../Entities/JsonLowerCaseConverter.cs | 29 ++++++++ .../Entities/VirtualFolderInfo.cs | 2 + .../Entities/JsonLowerCaseConverterTests.cs | 70 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 MediaBrowser.Model/Entities/JsonLowerCaseConverter.cs create mode 100644 tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs diff --git a/MediaBrowser.Model/Entities/JsonLowerCaseConverter.cs b/MediaBrowser.Model/Entities/JsonLowerCaseConverter.cs new file mode 100644 index 0000000000..7c627f0e39 --- /dev/null +++ b/MediaBrowser.Model/Entities/JsonLowerCaseConverter.cs @@ -0,0 +1,29 @@ +#nullable disable +// THIS IS A HACK +// TODO: @bond Move to separate project + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Model.Entities +{ + /// + /// Converts an object to a lowercase string. + /// + /// The object type. + public class JsonLowerCaseConverter : JsonConverter + { + /// + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize(ref reader, options); + } + + /// + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + writer.WriteStringValue(value?.ToString().ToLowerInvariant()); + } + } +} diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs index ea3df37265..8fed392b9d 100644 --- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs +++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs @@ -2,6 +2,7 @@ #pragma warning disable CS1591 using System; +using System.Text.Json.Serialization; using MediaBrowser.Model.Configuration; namespace MediaBrowser.Model.Entities @@ -35,6 +36,7 @@ namespace MediaBrowser.Model.Entities /// Gets or sets the type of the collection. /// /// The type of the collection. + [JsonConverter(typeof(JsonLowerCaseConverter))] public CollectionTypeOptions? CollectionType { get; set; } public LibraryOptions LibraryOptions { get; set; } diff --git a/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs b/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs new file mode 100644 index 0000000000..955d296cc8 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs @@ -0,0 +1,70 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using MediaBrowser.Model.Entities; +using Xunit; + +namespace Jellyfin.Model.Tests.Entities +{ + public class JsonLowerCaseConverterTests + { + private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() + { + Converters = + { + new JsonStringEnumConverter() + } + }; + + [Theory] + [InlineData(null, "{\"CollectionType\":null}")] + [InlineData(CollectionTypeOptions.Movies, "{\"CollectionType\":\"movies\"}")] + [InlineData(CollectionTypeOptions.MusicVideos, "{\"CollectionType\":\"musicvideos\"}")] + public void Serialize_CollectionTypeOptions_Correct(CollectionTypeOptions? collectionType, string expected) + { + Assert.Equal(expected, JsonSerializer.Serialize(new TestContainer(collectionType), _jsonOptions)); + } + + [Theory] + [InlineData("{\"CollectionType\":null}", null)] + [InlineData("{\"CollectionType\":\"movies\"}", CollectionTypeOptions.Movies)] + [InlineData("{\"CollectionType\":\"musicvideos\"}", CollectionTypeOptions.MusicVideos)] + public void Deserialize_CollectionTypeOptions_Correct(string json, CollectionTypeOptions? result) + { + var res = JsonSerializer.Deserialize(json, _jsonOptions); + Assert.NotNull(res); + Assert.Equal(result, res!.CollectionType); + } + + [Theory] + [InlineData(null)] + [InlineData(CollectionTypeOptions.Movies)] + [InlineData(CollectionTypeOptions.MusicVideos)] + public void RoundTrip_CollectionTypeOptions_Correct(CollectionTypeOptions? value) + { + var res = JsonSerializer.Deserialize(JsonSerializer.Serialize(new TestContainer(value), _jsonOptions), _jsonOptions); + Assert.NotNull(res); + Assert.Equal(value, res!.CollectionType); + } + + [Theory] + [InlineData("{\"CollectionType\":null}")] + [InlineData("{\"CollectionType\":\"movies\"}")] + [InlineData("{\"CollectionType\":\"musicvideos\"}")] + public void RoundTrip_String_Correct(string json) + { + var res = JsonSerializer.Serialize(JsonSerializer.Deserialize(json, _jsonOptions), _jsonOptions); + Assert.Equal(json, res); + } + + private class TestContainer + { + public TestContainer(CollectionTypeOptions? collectionType) + { + CollectionType = collectionType; + } + + [JsonConverter(typeof(JsonLowerCaseConverter))] + public CollectionTypeOptions? CollectionType { get; set; } + } + } +} From d4201f812cac5d1b5f4cdd618770df421b3fad70 Mon Sep 17 00:00:00 2001 From: Ikomhoog Date: Mon, 8 Mar 2021 11:02:51 +0100 Subject: [PATCH 063/282] Changed string.Length == 0 to string.IsNullOrEmpty in case of null --- Emby.Server.Implementations/Library/PathExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index d9e20e19ac..7dcc925c2f 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Library { newPath = null; - if (path.Length == 0 || subPath.Length == 0 || newSubPath.Length == 0 || subPath.Length > path.Length) + if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(subPath) || string.IsNullOrEmpty(newSubPath) || subPath.Length > path.Length) { return false; } From 02122f28cc7dce1758f4a8e714dc8a8e7f4bb21f Mon Sep 17 00:00:00 2001 From: Ikomhoog Date: Mon, 8 Mar 2021 11:14:01 +0100 Subject: [PATCH 064/282] Update contributors --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1200275d52..954315f0eb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -49,6 +49,7 @@ - [h1nk](https://github.com/h1nk) - [hawken93](https://github.com/hawken93) - [HelloWorld017](https://github.com/HelloWorld017) + - [ikomhoog](https://github.com/ikomhoog) - [jftuga](https://github.com/jftuga) - [joern-h](https://github.com/joern-h) - [joshuaboniface](https://github.com/joshuaboniface) From 54f81c4da484e3bb3d62669f0216c2a3a239166d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 8 Mar 2021 12:08:17 +0100 Subject: [PATCH 065/282] Call ToLower on CollectionTypeOptions.ToString --- Emby.Server.Implementations/Library/LibraryManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index d9ffe64b3b..6d92e12b3a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1247,7 +1247,7 @@ namespace Emby.Server.Implementations.Library { // TODO: @bond use a ReadOnlySpan here when Enum.TryParse supports it // https://github.com/dotnet/runtime/issues/20008 - if (Enum.TryParse(Path.GetExtension(file), true, out var res)) + if (Enum.TryParse(Path.GetFileNameWithoutExtension(file), true, out var res)) { return res; } @@ -3001,7 +3001,7 @@ namespace Emby.Server.Implementations.Library if (collectionType != null) { - var path = Path.Combine(virtualFolderPath, collectionType.ToString() + ".collection"); + var path = Path.Combine(virtualFolderPath, collectionType.ToString().ToLowerInvariant() + ".collection"); File.WriteAllBytes(path, Array.Empty()); } From d3390302f9c3d62bb9c878f52cf11f9f00438cb1 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 8 Mar 2021 11:43:59 +0000 Subject: [PATCH 066/282] Update ApplicationHost.cs --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 37047a4f72..49febdb963 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -43,6 +43,7 @@ using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SyncPlay; using Emby.Server.Implementations.TV; +using Emby.Server.Implementations.Udp; using Emby.Server.Implementations.Updates; using Jellyfin.Api.Helpers; using Jellyfin.Networking.Configuration; @@ -233,7 +234,7 @@ namespace Emby.Server.Implementations /// /// Gets the value of the PublishedServerUrl setting. /// - public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig["PublishedServerUrl"]; + public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey]; /// /// Gets the server configuration manager. From a031f7e410f7b599e1a50b2c54a7bd78f4187301 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 9 Mar 2021 00:07:21 +0000 Subject: [PATCH 067/282] Fix for multiple ip's in the same subnet per interface. --- Jellyfin.Networking/Manager/NetworkManager.cs | 37 ++++++++++--------- MediaBrowser.Common/Net/NetworkExtensions.cs | 5 ++- .../NetworkTesting/NetworkParseTests.cs | 2 + 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 51fcb6d9a0..17bbf9633a 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -157,15 +157,16 @@ namespace Jellyfin.Networking.Manager /// Creates a new network collection. /// /// Items to assign the collection, or null. + /// True if subnets that overlap should be merged (default). /// The collection created. - public static Collection CreateCollection(IEnumerable? source = null) + public static Collection CreateCollection(IEnumerable? source = null, bool unique = true) { var result = new Collection(); if (source != null) { foreach (var item in source) { - result.AddItem(item); + result.AddItem(item, unique); } } @@ -386,10 +387,12 @@ namespace Jellyfin.Networking.Manager } // Get the first LAN interface address that isn't a loopback. - var interfaces = CreateCollection(_interfaceAddresses - .Exclude(_bindExclusions) - .Where(IsInLocalNetwork) - .OrderBy(p => p.Tag)); + var interfaces = CreateCollection( + _interfaceAddresses + .Exclude(_bindExclusions) + .Where(IsInLocalNetwork) + .OrderBy(p => p.Tag), + false); if (interfaces.Count > 0) { @@ -429,11 +432,11 @@ namespace Jellyfin.Networking.Manager if (_bindExclusions.Count > 0) { // Return all the internal interfaces except the ones excluded. - return CreateCollection(_internalInterfaces.Where(p => !_bindExclusions.ContainsAddress(p))); + return CreateCollection(_internalInterfaces.Where(p => !_bindExclusions.ContainsAddress(p)), false); } // No bind address, so return all internal interfaces. - return CreateCollection(_internalInterfaces.Where(p => !p.IsLoopback())); + return CreateCollection(_internalInterfaces.Where(p => !p.IsLoopback()), false); } return new Collection(_bindAddresses); @@ -555,7 +558,7 @@ namespace Jellyfin.Networking.Manager && ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork) || (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6))) { - result.AddItem(iface); + result.AddItem(iface, false); } } @@ -599,8 +602,8 @@ namespace Jellyfin.Networking.Manager var address = IPNetAddress.Parse(parts[0]); var index = int.Parse(parts[1], CultureInfo.InvariantCulture); address.Tag = index; - _interfaceAddresses.AddItem(address); - _interfaceNames.Add(parts[2], Math.Abs(index)); + _interfaceAddresses.AddItem(address, false); + _interfaceNames[parts[2]] = Math.Abs(index); } } @@ -977,7 +980,7 @@ namespace Jellyfin.Networking.Manager { _logger.LogDebug("Using LAN interface addresses as user provided no LAN details."); // Internal interfaces must be private and not excluded. - _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsPrivateAddressRange(i) && !_excludedSubnets.ContainsAddress(i))); + _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsPrivateAddressRange(i) && !_excludedSubnets.ContainsAddress(i)), false); // Subnets are the same as the calculated internal interface. _lanSubnets = new Collection(); @@ -1012,7 +1015,7 @@ namespace Jellyfin.Networking.Manager } // Internal interfaces must be private, not excluded and part of the LocalNetworkSubnet. - _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i))); + _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i)), false); } _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString()); @@ -1071,7 +1074,7 @@ namespace Jellyfin.Networking.Manager nw.Tag *= -1; } - _interfaceAddresses.AddItem(nw); + _interfaceAddresses.AddItem(nw, false); // Store interface name so we can use the name in Collections. _interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag; @@ -1092,7 +1095,7 @@ namespace Jellyfin.Networking.Manager nw.Tag *= -1; } - _interfaceAddresses.AddItem(nw); + _interfaceAddresses.AddItem(nw, false); // Store interface name so we can use the name in Collections. _interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag; @@ -1126,10 +1129,10 @@ namespace Jellyfin.Networking.Manager { _logger.LogWarning("No interfaces information available. Using loopback."); // Last ditch attempt - use loopback address. - _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback); + _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback, false); if (IsIP6Enabled) { - _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback); + _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback, false); } } } diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs index 9c1a0cf495..cd0c2ea242 100644 --- a/MediaBrowser.Common/Net/NetworkExtensions.cs +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -27,9 +27,10 @@ namespace MediaBrowser.Common.Net /// /// The . /// Item to add. - public static void AddItem(this Collection source, IPObject item) + /// True if subnets that overlap should be merged (default). + public static void AddItem(this Collection source, IPObject item, bool unique = true) { - if (!source.ContainsAddress(item)) + if (!source.ContainsAddress(item) || !unique) { source.Add(item); } diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs index 9f928ded1b..a13822fcbd 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs @@ -38,6 +38,8 @@ namespace Jellyfin.Networking.Tests [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")] // vEthernet1 and vEthernet212 should be excluded. [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")] + // Overlapping interface, + [InlineData("192.168.1.110/24,-20,br0|192.168.1.10/24,-16,br0|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.110/24,192.168.1.10/24]")] public void IgnoreVirtualInterfaces(string interfaces, string lan, string value) { var conf = new NetworkConfiguration() From 5241bd41ef4917e0a3071f961f08dd2eeec5a5dd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 01:28:21 +0100 Subject: [PATCH 068/282] Add code analysis attributes where appropriate --- Emby.Naming/Video/CleanStringParser.cs | 15 ++++---- Emby.Naming/Video/VideoResolver.cs | 3 +- .../Library/PathExtensions.cs | 11 ++++-- .../Video/CleanStringTests.cs | 36 ++++++++++--------- .../Library/PathExtensionsTests.cs | 6 +++- 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index bd7553a91c..4eef3ebc5e 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace Emby.Naming.Video @@ -16,8 +17,14 @@ namespace Emby.Naming.Video /// List of regex to parse name and year from. /// Parsing result string. /// True if parsing was successful. - public static bool TryClean(string name, IReadOnlyList expressions, out ReadOnlySpan newName) + public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList expressions, out ReadOnlySpan newName) { + if (string.IsNullOrEmpty(name)) + { + newName = ReadOnlySpan.Empty; + return false; + } + var len = expressions.Count; for (int i = 0; i < len; i++) { @@ -33,12 +40,6 @@ namespace Emby.Naming.Video private static bool TryClean(string name, Regex expression, out ReadOnlySpan newName) { - if (string.IsNullOrEmpty(name)) - { - newName = ReadOnlySpan.Empty; - return false; - } - var match = expression.Match(name); int index = match.Index; if (match.Success && index != 0) diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 619d1520e4..79a6da8f7b 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Emby.Naming.Common; @@ -146,7 +147,7 @@ namespace Emby.Naming.Video /// Raw name. /// Clean name. /// True if cleaning of name was successful. - public bool TryCleanString(string name, out ReadOnlySpan newName) + public bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan newName) { return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName); } diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 7dcc925c2f..57d0c26b9c 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -59,11 +59,18 @@ namespace Emby.Server.Implementations.Library /// The result of the sub path replacement /// The path after replacing the sub path. /// , or is empty. - public static bool TryReplaceSubPath(this string path, string subPath, string newSubPath, [NotNullWhen(true)] out string? newPath) + public static bool TryReplaceSubPath( + [NotNullWhen(true)] this string? path, + [NotNullWhen(true)] string? subPath, + [NotNullWhen(true)] string? newSubPath, + [NotNullWhen(true)] out string? newPath) { newPath = null; - if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(subPath) || string.IsNullOrEmpty(newSubPath) || subPath.Length > path.Length) + if (string.IsNullOrEmpty(path) + || string.IsNullOrEmpty(subPath) + || string.IsNullOrEmpty(newSubPath) + || subPath.Length > path.Length) { return false; } diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs index 4b363843ac..a720bdadeb 100644 --- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs @@ -7,18 +7,13 @@ namespace Jellyfin.Naming.Tests.Video { public sealed class CleanStringTests { - private readonly NamingOptions _namingOptions = new NamingOptions(); + private readonly VideoResolver _videoResolver = new VideoResolver(new NamingOptions()); [Theory] [InlineData("Super movie 480p.mp4", "Super movie")] [InlineData("Super movie 480p 2001.mp4", "Super movie")] [InlineData("Super movie [480p].mp4", "Super movie")] [InlineData("480 Super movie [tmdbid=12345].mp4", "480 Super movie")] - [InlineData("Super movie(2009).mp4", "Super movie(2009).mp4")] - [InlineData("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4")] - [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv")] - [InlineData(@"American Psycho.mkv", "American Psycho.mkv")] - [InlineData(@"[rec].mkv", "[rec].mkv")] [InlineData("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon")] @@ -28,19 +23,26 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")] - [InlineData(null, null)] // FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")] - public void CleanStringTest(string input, string expectedName) + public void CleanStringTest_NeedsCleaning_Success(string input, string expectedName) { - if (new VideoResolver(_namingOptions).TryCleanString(input, out ReadOnlySpan newName)) - { - // TODO: compare spans when XUnit supports it - Assert.Equal(expectedName, newName.ToString()); - } - else - { - Assert.Equal(expectedName, input); - } + Assert.True(_videoResolver.TryCleanString(input, out ReadOnlySpan newName)); + // TODO: compare spans when XUnit supports it + Assert.Equal(expectedName, newName.ToString()); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("Super movie(2009).mp4")] + [InlineData("[rec].mkv")] + [InlineData("American.Psycho.mkv")] + [InlineData("American Psycho.mkv")] + [InlineData("Run lola run (lola rennt) (2009).mp4")] + public void CleanStringTest_DoesntNeedCleaning_False(string? input) + { + Assert.False(_videoResolver.TryCleanString(input, out ReadOnlySpan newName)); + Assert.True(newName.IsEmpty); } } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index a6fe905668..e5508243f0 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -40,12 +40,16 @@ namespace Jellyfin.Server.Implementations.Tests.Library } [Theory] + [InlineData(null, null, null)] + [InlineData(null, "/my/path", "/another/path")] + [InlineData("/my/path", null, "/another/path")] + [InlineData("/my/path", "/another/path", null)] [InlineData("", "", "")] [InlineData("/my/path", "", "")] [InlineData("", "/another/path", "")] [InlineData("", "", "/new/subpath")] [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/not jeff's band", "/home/not jeff")] - public void TryReplaceSubPath_InvalidInput_ReturnsFalseAndNull(string path, string subPath, string newSubPath) + public void TryReplaceSubPath_InvalidInput_ReturnsFalseAndNull(string? path, string? subPath, string? newSubPath) { Assert.False(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result)); Assert.Null(result); From 9ed7f429c01c3f54a154442250d3447fd66d1b02 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 03:04:47 +0100 Subject: [PATCH 069/282] FxCop -> Net Analyzers (part 1) --- Emby.Dlna/Emby.Dlna.csproj | 1 - Emby.Drawing/Emby.Drawing.csproj | 1 - Emby.Naming/Emby.Naming.csproj | 1 - Emby.Notifications/Emby.Notifications.csproj | 1 - Emby.Photos/Emby.Photos.csproj | 1 - .../Emby.Server.Implementations.csproj | 1 - Jellyfin.Api/Jellyfin.Api.csproj | 1 - Jellyfin.Data/Jellyfin.Data.csproj | 1 - .../Jellyfin.Drawing.Skia.csproj | 1 - .../Jellyfin.Networking.csproj | 1 - .../Jellyfin.Server.Implementations.csproj | 1 - Jellyfin.Server/Jellyfin.Server.csproj | 1 - Jellyfin.sln | 14 +-- .../MediaBrowser.Common.csproj | 1 - .../MediaBrowser.Controller.csproj | 1 - .../MediaBrowser.LocalMetadata.csproj | 1 - .../BdInfo/BdInfoDirectoryInfo.cs | 2 +- .../Encoder/MediaEncoder.cs | 92 +++---------------- MediaBrowser.MediaEncoding/FfmpegException.cs | 39 ++++++++ .../MediaBrowser.MediaEncoding.csproj | 8 +- .../Probing/ProbeResultNormalizer.cs | 18 +++- .../Subtitles/SubtitleEncoder.cs | 6 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 9 +- .../MediaBrowser.Providers.csproj | 8 +- .../MediaBrowser.XbmcMetadata.csproj | 8 +- .../Parsers/BaseNfoParser.cs | 4 +- .../Providers/BaseVideoNfoProvider.cs | 2 +- .../Savers/AlbumNfoSaver.cs | 18 ++-- .../Savers/ArtistNfoSaver.cs | 13 ++- .../Savers/BaseNfoSaver.cs | 16 +--- .../Savers/EpisodeNfoSaver.cs | 31 +++---- .../Savers/MovieNfoSaver.cs | 17 ++-- .../Savers/SeasonNfoSaver.cs | 11 +-- .../Savers/SeriesNfoSaver.cs | 21 ++--- jellyfin.ruleset | 8 +- .../Jellyfin.Api.Tests.csproj | 8 +- .../Jellyfin.Common.Tests.csproj | 8 +- .../Jellyfin.Controller.Tests.csproj | 8 +- .../Jellyfin.Dlna.Tests.csproj | 8 +- .../Jellyfin.MediaEncoding.Tests.csproj | 8 +- .../Jellyfin.Model.Tests.csproj | 8 +- .../AudioBook/AudioBookResolverTests.cs | 4 +- .../Jellyfin.Naming.Tests.csproj | 10 +- .../Video/VideoResolverTests.cs | 4 +- .../Jellyfin.Networking.Tests.csproj | 17 ++-- .../{NetworkTesting => }/NetworkParseTests.cs | 29 +++--- ...llyfin.Server.Implementations.Tests.csproj | 8 +- .../Jellyfin.XbmcMetadata.Tests.csproj | 8 +- 48 files changed, 214 insertions(+), 274 deletions(-) create mode 100644 MediaBrowser.MediaEncoding/FfmpegException.cs rename tests/Jellyfin.Networking.Tests/{NetworkTesting => }/Jellyfin.Networking.Tests.csproj (73%) rename tests/Jellyfin.Networking.Tests/{NetworkTesting => }/NetworkParseTests.cs (98%) diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 8b057a0950..480621dd70 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -25,7 +25,6 @@ - diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index 7d479a5c65..5c5afe1c6e 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -25,7 +25,6 @@ - diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index b43203e9dd..63116f3680 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -44,7 +44,6 @@ - diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj index 16ee918c46..526a27229f 100644 --- a/Emby.Notifications/Emby.Notifications.csproj +++ b/Emby.Notifications/Emby.Notifications.csproj @@ -25,7 +25,6 @@ - diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index 62e33e6c44..e64a658c51 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -28,7 +28,6 @@ - diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index f03f04e021..5a9792b518 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -49,7 +49,6 @@ - diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 67d0a3b5ab..d5372d7526 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -28,7 +28,6 @@ - diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index a8ac45645f..42731bb119 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -34,7 +34,6 @@ - diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 466a12e676..1a8415ae0e 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -32,7 +32,6 @@ - diff --git a/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj index cbda74361f..f89a184263 100644 --- a/Jellyfin.Networking/Jellyfin.Networking.csproj +++ b/Jellyfin.Networking/Jellyfin.Networking.csproj @@ -13,7 +13,6 @@ - diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 4f24da0ee4..19c7ac5672 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -14,7 +14,6 @@ - diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index bf4f806693..6bfb5b8786 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -26,7 +26,6 @@ - diff --git a/Jellyfin.sln b/Jellyfin.sln index d83013dab0..02ac1c7e98 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -68,14 +68,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jellyfin.Networking\Jellyfin.Networking.csproj", "{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\NetworkTesting\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -190,10 +190,6 @@ Global {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.Build.0 = Release|Any CPU - {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -206,6 +202,10 @@ Global {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.Build.0 = Release|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -217,10 +217,10 @@ Global {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 8bb30c565b..34e1934e25 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -46,7 +46,6 @@ - diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 6b1c096acb..d487a324f4 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -47,7 +47,6 @@ - diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 3ce9ff4cc4..1792f1d9b6 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -24,7 +24,6 @@ - diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs index 4a54b677dd..ef9943722d 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs @@ -71,7 +71,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo _impl.FullName, new[] { searchPattern }, false, - searchOption.HasFlag(System.IO.SearchOption.AllDirectories)).ToArray(), + (searchOption & System.IO.SearchOption.AllDirectories) == System.IO.SearchOption.AllDirectories).ToArray(), x => new BdInfoFileInfo(x)); } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a520193844..8a25a64c74 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -448,7 +448,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (result == null || (result.Streams == null && result.Format == null)) { - throw new Exception("ffprobe failed - streams and format are both null."); + throw new FfmpegException("ffprobe failed - streams and format are both null."); } if (result.Streams != null) @@ -571,32 +571,18 @@ namespace MediaBrowser.MediaEncoding.Encoder // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar. // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar - var vf = string.Empty; - - if (threedFormat.HasValue) + var vf = threedFormat switch { - switch (threedFormat.Value) - { - case Video3DFormat.HalfSideBySide: - vf = "-vf crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; - // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not. - break; - case Video3DFormat.FullSideBySide: - vf = "-vf crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; - // fsbs crop width in half,set the display aspect,crop out any black bars we may have made - break; - case Video3DFormat.HalfTopAndBottom: - vf = "-vf crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; - // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made - break; - case Video3DFormat.FullTopAndBottom: - vf = "-vf crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; - // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made - break; - default: - break; - } - } + // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not. + Video3DFormat.HalfSideBySide => "-vf crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", + // fsbs crop width in half,set the display aspect,crop out any black bars we may have made + Video3DFormat.FullSideBySide => "-vf crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", + // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made + Video3DFormat.HalfTopAndBottom => "-vf crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", + // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made + Video3DFormat.FullTopAndBottom => "-vf crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1", + _ => string.Empty + }; var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; @@ -604,7 +590,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (enableHdrExtraction) { string tonemapFilters = "zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p"; - if (string.IsNullOrEmpty(vf)) + if (vf.Length == 0) { vf = "-vf " + tonemapFilters; } @@ -633,35 +619,11 @@ namespace MediaBrowser.MediaEncoding.Encoder var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads); - var probeSizeArgument = string.Empty; - var analyzeDurationArgument = string.Empty; - - if (!string.IsNullOrWhiteSpace(probeSizeArgument)) - { - args = probeSizeArgument + " " + args; - } - - if (!string.IsNullOrWhiteSpace(analyzeDurationArgument)) - { - args = analyzeDurationArgument + " " + args; - } - if (offset.HasValue) { args = string.Format(CultureInfo.InvariantCulture, "-ss {0} ", GetTimeParameter(offset.Value)) + args; } - if (videoStream != null) - { - /* fix - var decoder = encodinghelper.GetHardwareAcceleratedVideoDecoder(VideoType.VideoFile, videoStream, GetEncodingOptions()); - if (!string.IsNullOrWhiteSpace(decoder)) - { - args = decoder + " " + args; - } - */ - } - if (!string.IsNullOrWhiteSpace(container)) { var inputFormat = EncodingHelper.GetInputFormat(container); @@ -723,7 +685,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogError(msg); - throw new Exception(msg); + throw new FfmpegException(msg); } return tempExtractPath; @@ -770,30 +732,6 @@ namespace MediaBrowser.MediaEncoding.Encoder var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, threads); - var probeSizeArgument = string.Empty; - var analyzeDurationArgument = string.Empty; - - if (!string.IsNullOrWhiteSpace(probeSizeArgument)) - { - args = probeSizeArgument + " " + args; - } - - if (!string.IsNullOrWhiteSpace(analyzeDurationArgument)) - { - args = analyzeDurationArgument + " " + args; - } - - if (videoStream != null) - { - /* fix - var decoder = encodinghelper.GetHardwareAcceleratedVideoDecoder(VideoType.VideoFile, videoStream, GetEncodingOptions()); - if (!string.IsNullOrWhiteSpace(decoder)) - { - args = decoder + " " + args; - } - */ - } - if (!string.IsNullOrWhiteSpace(container)) { var inputFormat = EncodingHelper.GetInputFormat(container); @@ -872,7 +810,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogError(msg); - throw new Exception(msg); + throw new FfmpegException(msg); } } } diff --git a/MediaBrowser.MediaEncoding/FfmpegException.cs b/MediaBrowser.MediaEncoding/FfmpegException.cs new file mode 100644 index 0000000000..1697fd33a1 --- /dev/null +++ b/MediaBrowser.MediaEncoding/FfmpegException.cs @@ -0,0 +1,39 @@ +using System; + +namespace MediaBrowser.MediaEncoding +{ + /// + /// Represents errors that occur during interaction with FFmpeg. + /// + public class FfmpegException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public FfmpegException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public FfmpegException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a + /// reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if + /// no inner exception is specified. + /// + public FfmpegException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 3d6b4f98a7..de00920bab 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -10,6 +10,9 @@ false true true + true + AllEnabledByDefault + ../jellyfin.ruleset @@ -30,13 +33,8 @@ - - ../jellyfin.ruleset - - - diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index b9cb49cf2f..75067315f9 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -640,7 +640,7 @@ namespace MediaBrowser.MediaEncoding.Probing } // Filter out junk - if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString) && streamInfo.CodecTagString.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) + if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString) && !streamInfo.CodecTagString.Contains("[0]", StringComparison.OrdinalIgnoreCase)) { stream.CodecTag = streamInfo.CodecTagString; } @@ -1500,11 +1500,23 @@ namespace MediaBrowser.MediaEncoding.Probing } else { - throw new Exception(); // Switch to default parsing + // Switch to default parsing + if (subtitle.Contains('.', StringComparison.Ordinal)) + { + // skip the comment, keep the subtitle + description = string.Join('.', subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first + } + else + { + description = subtitle.Trim(); // Clean up whitespaces and save it + } } } - catch // Default parsing + catch (Exception ex) { + _logger.LogError(ex, "Error while parsing subtitle field"); + + // Default parsing if (subtitle.Contains('.', StringComparison.Ordinal)) { // skip the comment, keep the subtitle diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index fbb1563bbf..39bec8da1c 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -25,7 +25,7 @@ using UtfUnknown; namespace MediaBrowser.MediaEncoding.Subtitles { - public class SubtitleEncoder : ISubtitleEncoder + public sealed class SubtitleEncoder : ISubtitleEncoder { private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; @@ -484,7 +484,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { _logger.LogError("ffmpeg subtitle conversion failed for {Path}", inputPath); - throw new Exception( + throw new FfmpegException( string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle conversion failed for {0}", inputPath)); } @@ -637,7 +637,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.LogError(msg); - throw new Exception(msg); + throw new FfmpegException(msg); } else { diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b6d9169139..30403219f5 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -19,7 +19,9 @@ true true enable - latest + true + + ../jellyfin.ruleset true true true @@ -44,7 +46,6 @@ - @@ -53,8 +54,4 @@ - - ../jellyfin.ruleset - - diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 071a149db9..5e7b8043f1 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -30,20 +30,18 @@ false true true + true + AllEnabledByDefault + ../jellyfin.ruleset - - - ../jellyfin.ruleset - - diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index 40f06c731f..95327d3ae8 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -20,18 +20,16 @@ true true enable + true + AllEnabledByDefault + ../jellyfin.ruleset - - - ../jellyfin.ruleset - - diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 6f164caf37..c4bbaf301c 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -1168,11 +1168,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// IEnumerable{System.String}. private IEnumerable SplitNames(string value) { - value = value ?? string.Empty; - // Only split by comma if there is no pipe in the string // We have to be careful to not split names like Matthew, Jr. - var separator = value.IndexOf('|', StringComparison.Ordinal) == -1 && value.IndexOf(';', StringComparison.Ordinal) == -1 + var separator = !value.Contains('|', StringComparison.Ordinal) && !value.Contains(';', StringComparison.Ordinal) ? new[] { ',' } : new[] { '|', ';' }; diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs index af722748b5..64cfc098f9 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; - public BaseVideoNfoProvider( + protected BaseVideoNfoProvider( ILogger> logger, IFileSystem fileSystem, IConfigurationManager config, diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs index c22f77dcd5..2385e70485 100644 --- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs @@ -96,18 +96,16 @@ namespace MediaBrowser.XbmcMetadata.Savers } /// - protected override List GetTagsUsed(BaseItem item) + protected override IEnumerable GetTagsUsed(BaseItem item) { - var list = base.GetTagsUsed(item); - list.AddRange( - new string[] - { - "track", - "artist", - "albumartist" - }); + foreach (var tag in base.GetTagsUsed(item)) + { + yield return tag; + } - return list; + yield return "track"; + yield return "artist"; + yield return "albumartist"; } } } diff --git a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs index 6365cdecb4..71b58cddb9 100644 --- a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs @@ -88,16 +88,15 @@ namespace MediaBrowser.XbmcMetadata.Savers } /// - protected override List GetTagsUsed(BaseItem item) + protected override IEnumerable GetTagsUsed(BaseItem item) { - var list = base.GetTagsUsed(item); - list.AddRange(new string[] + foreach (var tag in base.GetTagsUsed(item)) { - "album", - "disbanded" - }); + yield return tag; + } - return list; + yield return "album"; + yield return "disbanded"; } } } diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 0edab37878..3be35e2d9b 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -166,19 +166,16 @@ namespace MediaBrowser.XbmcMetadata.Savers /// public abstract bool IsEnabledFor(BaseItem item, ItemUpdateType updateType); - protected virtual List GetTagsUsed(BaseItem item) + protected virtual IEnumerable GetTagsUsed(BaseItem item) { - var list = new List(); foreach (var providerKey in item.ProviderIds.Keys) { var providerIdTagName = GetTagForProviderKey(providerKey); if (!_commonTags.Contains(providerIdTagName)) { - list.Add(providerIdTagName); + yield return providerIdTagName; } } - - return list; } /// @@ -261,7 +258,7 @@ namespace MediaBrowser.XbmcMetadata.Savers AddMediaInfo(hasMediaSources, writer); } - var tagsUsed = GetTagsUsed(item); + var tagsUsed = GetTagsUsed(item).ToList(); try { @@ -351,10 +348,7 @@ namespace MediaBrowser.XbmcMetadata.Savers } var scanType = stream.IsInterlaced ? "interlaced" : "progressive"; - if (!string.IsNullOrEmpty(scanType)) - { - writer.WriteElementString("scantype", scanType); - } + writer.WriteElementString("scantype", scanType); if (stream.Channels.HasValue) { @@ -968,7 +962,7 @@ namespace MediaBrowser.XbmcMetadata.Savers => string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase); - private void AddCustomTags(string path, List xmlTagsUsed, XmlWriter writer, ILogger logger) + private void AddCustomTags(string path, IReadOnlyCollection xmlTagsUsed, XmlWriter writer, ILogger logger) { var settings = new XmlReaderSettings() { diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs index 5d3d17893a..62f80e81bd 100644 --- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs @@ -111,24 +111,23 @@ namespace MediaBrowser.XbmcMetadata.Savers } /// - protected override List GetTagsUsed(BaseItem item) + protected override IEnumerable GetTagsUsed(BaseItem item) { - var list = base.GetTagsUsed(item); - list.AddRange(new string[] + foreach (var tag in base.GetTagsUsed(item)) { - "aired", - "season", - "episode", - "episodenumberend", - "airsafter_season", - "airsbefore_episode", - "airsbefore_season", - "displayseason", - "displayepisode", - "showtitle" - }); - - return list; + yield return tag; + } + + yield return "aired"; + yield return "season"; + yield return "episode"; + yield return "episodenumberend"; + yield return "airsafter_season"; + yield return "airsbefore_episode"; + yield return "airsbefore_season"; + yield return "displayseason"; + yield return "displayepisode"; + yield return "showtitle"; } } } diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index 8411217352..412e8031b2 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs @@ -123,18 +123,17 @@ namespace MediaBrowser.XbmcMetadata.Savers } /// - protected override List GetTagsUsed(BaseItem item) + protected override IEnumerable GetTagsUsed(BaseItem item) { - var list = base.GetTagsUsed(item); - list.AddRange(new string[] + foreach (var tag in base.GetTagsUsed(item)) { - "album", - "artist", - "set", - "id" - }); + yield return tag; + } - return list; + yield return "album"; + yield return "artist"; + yield return "set"; + yield return "id"; } } } diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs index 925a230bdb..b9d73ba822 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs @@ -72,15 +72,14 @@ namespace MediaBrowser.XbmcMetadata.Savers } /// - protected override List GetTagsUsed(BaseItem item) + protected override IEnumerable GetTagsUsed(BaseItem item) { - var list = base.GetTagsUsed(item); - list.AddRange(new string[] + foreach (var tag in base.GetTagsUsed(item)) { - "seasonnumber" - }); + yield return tag; + } - return list; + yield return "seasonnumber"; } } } diff --git a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs index 42285db76d..083f22e5d2 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs @@ -90,20 +90,19 @@ namespace MediaBrowser.XbmcMetadata.Savers } /// - protected override List GetTagsUsed(BaseItem item) + protected override IEnumerable GetTagsUsed(BaseItem item) { - var list = base.GetTagsUsed(item); - list.AddRange(new string[] + foreach (var tag in base.GetTagsUsed(item)) { - "id", - "episodeguide", - "season", - "episode", - "status", - "displayorder" - }); + yield return tag; + } - return list; + yield return "id"; + yield return "episodeguide"; + yield return "season"; + yield return "episode"; + yield return "status"; + yield return "displayorder"; } } } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 81337390cc..b012d2b003 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -38,7 +38,7 @@ - + @@ -53,6 +53,8 @@ + + @@ -61,7 +63,11 @@ + + + + diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 873ff0ab4a..0d8176bb2a 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -10,6 +10,9 @@ false true enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -27,7 +30,6 @@ - @@ -37,10 +39,6 @@ - - ../jellyfin-tests.ruleset - - diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 278f341099..78e3061f76 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -10,6 +10,9 @@ false true enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -21,7 +24,6 @@ - @@ -32,8 +34,4 @@ - - ../jellyfin-tests.ruleset - - diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index b02a68a3d9..df1eb86172 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -10,6 +10,9 @@ false true enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -21,7 +24,6 @@ - @@ -31,8 +33,4 @@ - - ../jellyfin-tests.ruleset - - diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index 850db1c75b..d173d5c93e 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -5,6 +5,9 @@ false true enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -16,7 +19,6 @@ - @@ -26,8 +28,4 @@ - - ../jellyfin-tests.ruleset - - diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index e729dbb09e..84306e0f73 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -10,6 +10,9 @@ false true enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -27,7 +30,6 @@ - @@ -37,8 +39,4 @@ - - ../jellyfin-tests.ruleset - - diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index b6d2c63bd8..b458c06fff 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -5,6 +5,9 @@ false true enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -16,7 +19,6 @@ - @@ -26,8 +28,4 @@ - - ../jellyfin-tests.ruleset - - diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs index b3257ace3b..ad63adadc3 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs @@ -10,7 +10,7 @@ namespace Jellyfin.Naming.Tests.AudioBook { private readonly NamingOptions _namingOptions = new NamingOptions(); - public static IEnumerable GetResolveFileTestData() + public static IEnumerable Resolve_ValidFileNameTestData() { yield return new object[] { @@ -36,7 +36,7 @@ namespace Jellyfin.Naming.Tests.AudioBook } [Theory] - [MemberData(nameof(GetResolveFileTestData))] + [MemberData(nameof(Resolve_ValidFileNameTestData))] public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult) { var result = new AudioBookResolver(_namingOptions).Resolve(expectedResult.Path); diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 99185c975d..0f8a0333a9 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -8,8 +8,11 @@ net5.0 false - enable true + enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -25,14 +28,9 @@ - - - ../jellyfin-tests.ruleset - - diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs index ba5eaf1aff..9bbbe29709 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Naming.Tests.Video { private readonly VideoResolver _videoResolver = new VideoResolver(new NamingOptions()); - public static IEnumerable GetResolveFileTestData() + public static IEnumerable ResolveFile_ValidFileNameTestData() { yield return new object[] { @@ -156,7 +156,7 @@ namespace Jellyfin.Naming.Tests.Video } [Theory] - [MemberData(nameof(GetResolveFileTestData))] + [MemberData(nameof(ResolveFile_ValidFileNameTestData))] public void ResolveFile_ValidFileName_Success(VideoFileInfo expectedResult) { var result = _videoResolver.ResolveFile(expectedResult.Path); diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj similarity index 73% rename from tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj rename to tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index fd77397bae..61eead0e95 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -8,8 +8,11 @@ net5.0 false - enable true + enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -22,18 +25,18 @@ - + + - - + + - - ../../jellyfin-tests.ruleset - + DEBUG + diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs similarity index 98% rename from tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs rename to tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 9f928ded1b..c3469035eb 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -1,13 +1,13 @@ using System; +using System.Collections.ObjectModel; using System.Net; using Jellyfin.Networking.Configuration; using Jellyfin.Networking.Manager; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; -using Moq; using Microsoft.Extensions.Logging.Abstractions; +using Moq; using Xunit; -using System.Collections.ObjectModel; namespace Jellyfin.Networking.Tests { @@ -124,7 +124,6 @@ namespace Jellyfin.Networking.Tests Assert.True(IPNetAddress.TryParse(address, out _)); } - /// /// All should be invalid address strings. /// @@ -141,7 +140,6 @@ namespace Jellyfin.Networking.Tests Assert.False(IPHost.TryParse(address, out _)); } - /// /// Test collection parsing. /// @@ -152,19 +150,22 @@ namespace Jellyfin.Networking.Tests /// Excluded IP4 addresses from the collection. /// Network addresses of the collection. [Theory] - [InlineData("127.0.0.1#", + [InlineData( + "127.0.0.1#", "[]", "[]", "[]", "[]", "[]")] - [InlineData("!127.0.0.1", + [InlineData( + "!127.0.0.1", "[]", "[]", "[127.0.0.1/32]", "[127.0.0.1/32]", "[]")] - [InlineData("", + [InlineData( + "", "[]", "[]", "[]", @@ -177,7 +178,8 @@ namespace Jellyfin.Networking.Tests "[10.10.10.10/32]", "[10.10.10.10/32]", "[192.158.0.0/16,127.0.0.1/32,::1/128,fd23:184f:2029:0:3139:7386:67d7:d517/128]")] - [InlineData("192.158.1.2/255.255.0.0,192.169.1.2/8", + [InlineData( + "192.158.1.2/255.255.0.0,192.169.1.2/8", "[192.158.1.2/16,192.169.1.2/8]", "[192.158.1.2/16,192.169.1.2/8]", "[]", @@ -194,12 +196,12 @@ namespace Jellyfin.Networking.Tests { EnableIPV6 = true, EnableIPV4 = true, - }; + }; using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); // Test included. - Collection nc = nm.CreateIPCollection(settings.Split(","), false); + Collection nc = nm.CreateIPCollection(settings.Split(","), false); Assert.Equal(nc.AsString(), result1); // Test excluded. @@ -208,7 +210,7 @@ namespace Jellyfin.Networking.Tests conf.EnableIPV6 = false; nm.UpdateSettings(conf); - + // Test IP4 included. nc = nm.CreateIPCollection(settings.Split(","), false); Assert.Equal(nc.AsString(), result2); @@ -252,7 +254,6 @@ namespace Jellyfin.Networking.Tests throw new ArgumentNullException(nameof(result)); } - var conf = new NetworkConfiguration() { EnableIPV6 = true, @@ -378,7 +379,6 @@ namespace Jellyfin.Networking.Tests Assert.True(ncResult.Compare(resultCollection)); } - [Theory] [InlineData("10.1.1.1/32", "10.1.1.1")] [InlineData("192.168.1.254/32", "192.168.1.254/255.255.255.255")] @@ -455,7 +455,7 @@ namespace Jellyfin.Networking.Tests // On my system eth16 is internal, eth11 external (Windows defines the indexes). // // This test is to replicate how subnet bound ServerPublisherUri work throughout the system. - + // User on internal network, we're bound internal and external - so result is internal override. [InlineData("192.168.1.1", "192.168.1.0/24", "eth16,eth11", false, "192.168.1.0/24=internal.jellyfin", "internal.jellyfin")] @@ -479,7 +479,6 @@ namespace Jellyfin.Networking.Tests // User is internal, no binding - so result is the 1st internal, which is then overridden. [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "eth16=http://helloworld.com", "http://helloworld.com")] - public void TestBindInterfaceOverrides(string source, string lan, string bindAddresses, bool ipv6enabled, string publishedServers, string result) { if (lan == null) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 1ad8171bee..8debb08c52 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -10,6 +10,9 @@ false true enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset Jellyfin.Server.Implementations.Tests @@ -31,7 +34,6 @@ - @@ -42,8 +44,4 @@ - - ../jellyfin-tests.ruleset - - diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index d6aab3f85e..2bb94c81f3 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -5,6 +5,9 @@ false true enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -23,7 +26,6 @@ - @@ -34,8 +36,4 @@ - - ../jellyfin-tests.ruleset - - From d202df6e8a1f36a253e9780e7ec1521bc0c4b75e Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 03:22:51 +0100 Subject: [PATCH 070/282] Remove useless line --- MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj | 1 - MediaBrowser.Model/MediaBrowser.Model.csproj | 1 - MediaBrowser.Providers/MediaBrowser.Providers.csproj | 1 - MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj | 1 - tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 1 - tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj | 1 - tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj | 1 - tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj | 1 - .../Jellyfin.MediaEncoding.Tests.csproj | 1 - tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj | 1 - tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj | 1 - tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj | 1 - .../Jellyfin.Server.Implementations.Tests.csproj | 1 - .../Jellyfin.XbmcMetadata.Tests.csproj | 1 - 14 files changed, 14 deletions(-) diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index de00920bab..39fb0b47c1 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -10,7 +10,6 @@ false true true - true AllEnabledByDefault ../jellyfin.ruleset diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 30403219f5..f622a042ac 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -19,7 +19,6 @@ true true enable - true ../jellyfin.ruleset true diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 5e7b8043f1..152ea664a1 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -30,7 +30,6 @@ false true true - true AllEnabledByDefault ../jellyfin.ruleset diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index 95327d3ae8..2904b40ecf 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -20,7 +20,6 @@ true true enable - true AllEnabledByDefault ../jellyfin.ruleset diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 0d8176bb2a..a336d2aee8 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -10,7 +10,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 78e3061f76..017a67e9f9 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -10,7 +10,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index df1eb86172..6dec25aa4e 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -10,7 +10,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index d173d5c93e..5d52f94c02 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -5,7 +5,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index 84306e0f73..4cc1d37ee9 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -10,7 +10,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index b458c06fff..0c7e262f59 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -5,7 +5,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 0f8a0333a9..cc12a99a61 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -10,7 +10,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index 61eead0e95..a76c0e9a06 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -10,7 +10,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 8debb08c52..c3c258b680 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -10,7 +10,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset Jellyfin.Server.Implementations.Tests diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index 2bb94c81f3..9380fe2af4 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -5,7 +5,6 @@ false true enable - true AllEnabledByDefault ../jellyfin-tests.ruleset From e89bd8ba02cd5d5a98bba1bd8b1562af8fdaf5cf Mon Sep 17 00:00:00 2001 From: pkreuzt Date: Tue, 9 Mar 2021 01:58:03 +0000 Subject: [PATCH 071/282] Translated using Weblate (Galician) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gl/ --- .../Localization/Core/gl.json | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json index 12bcd793e3..11139d32a8 100644 --- a/Emby.Server.Implementations/Localization/Core/gl.json +++ b/Emby.Server.Implementations/Localization/Core/gl.json @@ -57,5 +57,27 @@ "DeviceOnlineWithName": "{0} conectouse", "DeviceOfflineWithName": "{0} desconectouse", "Default": "Por defecto", - "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}" + "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", + "TaskCleanLogs": "Limpar Carpeta de Rexistros", + "TaskCleanActivityLog": "Limpar Rexistro de Actividade", + "TasksChannelsCategory": "Canáis de Internet", + "TaskUpdatePlugins": "Actualizar Plugins", + "User": "Usuario", + "Undefined": "Sen definir", + "TvShows": "Programas de TV", + "System": "Sistema", + "Sync": "Sincronizar", + "SubtitleDownloadFailureFromForItem": "Fallou a descarga de subtítulos para {1} dende {0}", + "StartupEmbyServerIsLoading": "O Servidor Jellyfin está cargando. Por favor, reinténteo en breve.", + "Songs": "Cancións", + "Shows": "Programas", + "ServerNameNeedsToBeRestarted": "{0} precisa ser reiniciado", + "ScheduledTaskStartedWithName": "{0} comezou", + "ScheduledTaskFailedWithName": "{0} fallou", + "ProviderValue": "Provedor: {0}", + "PluginUpdatedWithName": "{0} foi actualizado", + "PluginUninstalledWithName": "{0} foi desinstalado", + "PluginInstalledWithName": "{0} foi instalado", + "Playlists": "Listas de reproducción", + "Photos": "Fotos" } From 80fb52e64f9da4884eb9de5ff52865a3b7af6fc0 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 9 Mar 2021 10:46:54 +0100 Subject: [PATCH 072/282] Default to the searchinfo year, fallback to parsed year --- MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 2 +- MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 9a3e3d5fad..14be428ce0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies // ParseName is required here. // Caller provides the filename with extension stripped and NOT the parsed filename var parsedName = _libraryManager.ParseName(info.Name); - var searchResults = await _tmdbClientManager.SearchMovieAsync(parsedName.Name, parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var searchResults = await _tmdbClientManager.SearchMovieAsync(parsedName.Name, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); if (searchResults.Count > 0) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 0238d0574b..e18614524c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -207,7 +207,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // ParseName is required here. // Caller provides the filename with extension stripped and NOT the parsed filename var parsedName = _libraryManager.ParseName(info.Name); - var searchResults = await _tmdbClientManager.SearchSeriesAsync(parsedName.Name, info.MetadataLanguage, info.Year ?? 0, cancellationToken).ConfigureAwait(false); + var searchResults = await _tmdbClientManager.SearchSeriesAsync(parsedName.Name, info.MetadataLanguage, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false); if (searchResults.Count > 0) { From 880c8636bcfcbc14f60f8247105b3aa41de55c91 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 9 Mar 2021 11:44:39 +0100 Subject: [PATCH 073/282] Use imdbid as fallback in movie provider Includes post-ProviderIdExtensions cleanup --- .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 9 +++ .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 74 ++++++++----------- 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 9a3e3d5fad..8b5e2ddcaa 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -148,6 +148,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies } } + if (string.IsNullOrEmpty(tmdbId) && !string.IsNullOrEmpty(imdbId)) + { + var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + if (movieResultFromImdbId?.MovieResults.Count > 0) + { + tmdbId = movieResultFromImdbId.MovieResults[0].Id.ToString(); + } + } + if (string.IsNullOrEmpty(tmdbId)) { return new MetadataResult(); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 0238d0574b..5d3c94d714 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -43,9 +43,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public async Task> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) { - var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb); - - if (!string.IsNullOrEmpty(tmdbId)) + if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var tmdbId)) { var series = await _tmdbClientManager .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, cancellationToken) @@ -59,9 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb); - - if (!string.IsNullOrEmpty(imdbId)) + if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out var imdbId)) { var findResult = await _tmdbClientManager .FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, searchInfo.MetadataLanguage, cancellationToken) @@ -82,9 +78,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - var tvdbId = searchInfo.GetProviderId(MetadataProvider.Tvdb); - - if (!string.IsNullOrEmpty(tvdbId)) + if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId)) { var findResult = await _tmdbClientManager .FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, searchInfo.MetadataLanguage, cancellationToken) @@ -171,33 +165,21 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var tmdbId = info.GetProviderId(MetadataProvider.Tmdb); - if (string.IsNullOrEmpty(tmdbId)) + if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Imdb, out var imdbId)) { - var imdbId = info.GetProviderId(MetadataProvider.Imdb); - - if (!string.IsNullOrEmpty(imdbId)) + var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + if (searchResult?.TvResults.Count > 0) { - var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); - - if (searchResult != null) - { - tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture); - } + tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture); } } - if (string.IsNullOrEmpty(tmdbId)) + if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId)) { - var tvdbId = info.GetProviderId(MetadataProvider.Tvdb); - - if (!string.IsNullOrEmpty(tvdbId)) + var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + if (searchResult?.TvResults.Count > 0) { - var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); - - if (searchResult != null) - { - tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture); - } + tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture); } } @@ -215,32 +197,34 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - if (!string.IsNullOrEmpty(tmdbId)) + if (string.IsNullOrEmpty(tmdbId)) { - cancellationToken.ThrowIfCancellationRequested(); + return result; + } - var tvShow = await _tmdbClientManager - .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) - .ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); - result = new MetadataResult - { - Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode), - ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage - }; + var tvShow = await _tmdbClientManager + .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) + .ConfigureAwait(false); - foreach (var person in GetPersons(tvShow)) - { - result.AddPerson(person); - } + result = new MetadataResult + { + Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode), + ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage + }; - result.HasMetadata = result.Item != null; + foreach (var person in GetPersons(tvShow)) + { + result.AddPerson(person); } + result.HasMetadata = result.Item != null; + return result; } - private Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode) + private static Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode) { var series = new Series { From fa8bfece4e72c32f8350aaa947c81b2494f6bb77 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Mar 2021 19:35:58 +0100 Subject: [PATCH 074/282] Split integration tests from unit tests --- Jellyfin.Server/Properties/AssemblyInfo.cs | 2 +- Jellyfin.sln | 2 + .../Jellyfin.Api.Tests.csproj | 7 +--- .../Controllers/BrandingControllerTests.cs | 2 +- .../Controllers/DashboardControllerTests.cs | 4 +- .../Jellyfin.Server.Integration.Tests.csproj | 40 +++++++++++++++++++ .../JellyfinApplicationFactory.cs | 2 +- .../OpenApiSpecTests.cs | 2 +- .../TestAppHost.cs | 2 +- .../TestPage.html | 0 .../TestPlugin.cs | 2 +- .../Jellyfin.Server.Tests.csproj | 39 ++++++++++++++++++ .../ParseNetworkTests.cs | 2 +- 13 files changed, 92 insertions(+), 14 deletions(-) rename tests/{Jellyfin.Api.Tests => Jellyfin.Server.Integration.Tests}/Controllers/BrandingControllerTests.cs (97%) rename tests/{Jellyfin.Api.Tests => Jellyfin.Server.Integration.Tests}/Controllers/DashboardControllerTests.cs (95%) create mode 100644 tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj rename tests/{Jellyfin.Api.Tests => Jellyfin.Server.Integration.Tests}/JellyfinApplicationFactory.cs (99%) rename tests/{Jellyfin.Api.Tests => Jellyfin.Server.Integration.Tests}/OpenApiSpecTests.cs (97%) rename tests/{Jellyfin.Api.Tests => Jellyfin.Server.Integration.Tests}/TestAppHost.cs (98%) rename tests/{Jellyfin.Api.Tests => Jellyfin.Server.Integration.Tests}/TestPage.html (100%) rename tests/{Jellyfin.Api.Tests => Jellyfin.Server.Integration.Tests}/TestPlugin.cs (96%) create mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj rename tests/{Jellyfin.Api.Tests => Jellyfin.Server.Tests}/ParseNetworkTests.cs (98%) diff --git a/Jellyfin.Server/Properties/AssemblyInfo.cs b/Jellyfin.Server/Properties/AssemblyInfo.cs index 7abf298b12..fe2d5c5f97 100644 --- a/Jellyfin.Server/Properties/AssemblyInfo.cs +++ b/Jellyfin.Server/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] -[assembly: InternalsVisibleTo("Jellyfin.Api.Tests")] +[assembly: InternalsVisibleTo("Jellyfin.Server.Tests")] diff --git a/Jellyfin.sln b/Jellyfin.sln index 02ac1c7e98..c749c51e69 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -76,6 +76,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index a336d2aee8..577b61d02a 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -35,11 +35,8 @@ - - - - - + + diff --git a/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs similarity index 97% rename from tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs rename to tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs index 40933562db..fdd93f20ef 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using MediaBrowser.Model.Branding; using Xunit; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { public sealed class BrandingControllerTests : IClassFixture { diff --git a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs similarity index 95% rename from tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs rename to tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs index 300b2697f2..88905fc6d0 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs @@ -8,7 +8,7 @@ using Jellyfin.Api.Models; using MediaBrowser.Common.Json; using Xunit; -namespace Jellyfin.Api.Tests.Controllers +namespace Jellyfin.Server.Integration.Tests.Controllers { public sealed class DashboardControllerTests : IClassFixture { @@ -39,7 +39,7 @@ namespace Jellyfin.Api.Tests.Controllers Assert.True(response.IsSuccessStatusCode); Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType); - StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Api.Tests.TestPage.html")!); + StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Server.Integration.Tests.TestPage.html")!); Assert.Equal(await response.Content.ReadAsStringAsync(), reader.ReadToEnd()); } diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj new file mode 100644 index 0000000000..49004966b9 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -0,0 +1,40 @@ + + + net5.0 + false + true + enable + AllEnabledByDefault + ../jellyfin-tests.ruleset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs similarity index 99% rename from tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs rename to tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 92c5495c83..94e6181023 100644 --- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { /// /// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests. diff --git a/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs similarity index 97% rename from tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs rename to tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs index 03ab56d1f4..3cbd638f90 100644 --- a/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Branding; using Xunit; using Xunit.Abstractions; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { public sealed class OpenApiSpecTests : IClassFixture { diff --git a/tests/Jellyfin.Api.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs similarity index 98% rename from tests/Jellyfin.Api.Tests/TestAppHost.cs rename to tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs index eb4c9b3055..4e5d0fcb63 100644 --- a/tests/Jellyfin.Api.Tests/TestAppHost.cs +++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { /// /// Implementation of the abstract class. diff --git a/tests/Jellyfin.Api.Tests/TestPage.html b/tests/Jellyfin.Server.Integration.Tests/TestPage.html similarity index 100% rename from tests/Jellyfin.Api.Tests/TestPage.html rename to tests/Jellyfin.Server.Integration.Tests/TestPage.html diff --git a/tests/Jellyfin.Api.Tests/TestPlugin.cs b/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs similarity index 96% rename from tests/Jellyfin.Api.Tests/TestPlugin.cs rename to tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs index a3b4b6994f..1d67ac4870 100644 --- a/tests/Jellyfin.Api.Tests/TestPlugin.cs +++ b/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs @@ -7,7 +7,7 @@ using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { public class TestPlugin : BasePlugin, IHasWebPages { diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj new file mode 100644 index 0000000000..65ea28e94d --- /dev/null +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -0,0 +1,39 @@ + + + + net5.0 + false + true + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../jellyfin-tests.ruleset + + + diff --git a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs similarity index 98% rename from tests/Jellyfin.Api.Tests/ParseNetworkTests.cs rename to tests/Jellyfin.Server.Tests/ParseNetworkTests.cs index 3984407ee9..0b714e80a2 100644 --- a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs +++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Tests { public class ParseNetworkTests { From afc70b28d4e1b13e75d12a40eab9455460be1c47 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Mar 2021 19:42:22 +0100 Subject: [PATCH 075/282] Add Jellyfin.Server.Tests to solution --- Jellyfin.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Jellyfin.sln b/Jellyfin.sln index c749c51e69..415d024901 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -78,6 +78,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -208,6 +210,10 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -223,6 +229,7 @@ Global {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} From bbbb811e72fef85f0ae95f195da42a924ef1f642 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Mar 2021 20:03:15 +0100 Subject: [PATCH 076/282] Fix azure --- .ci/azure-pipelines-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml index 95e0d8c58d..7838b3b026 100644 --- a/.ci/azure-pipelines-test.yml +++ b/.ci/azure-pipelines-test.yml @@ -94,5 +94,5 @@ jobs: displayName: 'Publish OpenAPI Artifact' condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) inputs: - targetPath: "tests/Jellyfin.Api.Tests/bin/Release/net5.0/openapi.json" + targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json" artifactName: 'OpenAPI Spec' From 3471ddfc84b409a876ebdc71582195de8ad6934f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 2 Mar 2021 02:36:50 +0100 Subject: [PATCH 077/282] Fix duplicate project id in sln --- Jellyfin.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index 415d024901..7b81f43462 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -76,7 +76,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" EndProject From b9577d0fd986fcadd656a9438eba81f49f375ad8 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 2 Mar 2021 02:57:36 +0100 Subject: [PATCH 078/282] Check for specific status code instead of success --- .../Controllers/BrandingControllerTests.cs | 3 ++- .../Controllers/DashboardControllerTests.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs index fdd93f20ef..87136dfc8a 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs @@ -1,3 +1,4 @@ +using System.Net; using System.Net.Mime; using System.Text; using System.Text.Json; @@ -26,7 +27,7 @@ namespace Jellyfin.Server.Integration.Tests var response = await client.GetAsync("/Branding/Configuration"); // Assert - Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); var responseBody = await response.Content.ReadAsStreamAsync(); diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs index 88905fc6d0..86d6326d84 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs @@ -37,7 +37,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin").ConfigureAwait(false); - Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType); StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Server.Integration.Tests.TestPage.html")!); Assert.Equal(await response.Content.ReadAsStringAsync(), reader.ReadToEnd()); @@ -60,7 +60,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false); - Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); var res = await response.Content.ReadAsStreamAsync(); _ = await JsonSerializer.DeserializeAsync(res, _jsonOpions); @@ -74,7 +74,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false); - Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); From 37eb7d6d49b35067c6711ba6f74a64242da419b0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 5 Mar 2021 12:34:22 +0100 Subject: [PATCH 079/282] Perform static initialization only once --- .../JellyfinApplicationFactory.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 94e6181023..d9ec81a271 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -4,7 +4,6 @@ using System.IO; using System.Threading; using Emby.Server.Implementations; using Emby.Server.Implementations.IO; -using Jellyfin.Server; using MediaBrowser.Common; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -26,9 +25,9 @@ namespace Jellyfin.Server.Integration.Tests private readonly ConcurrentBag _disposableComponents = new ConcurrentBag(); /// - /// Initializes a new instance of the class. + /// Initializes static members of the class. /// - public JellyfinApplicationFactory() + static JellyfinApplicationFactory() { // Perform static initialization that only needs to happen once per test-run Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger(); From a618d6053f78664ecb27cb01216ddb21833f5f89 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 14:09:28 +0100 Subject: [PATCH 080/282] Move TestPluginWithoutPages to the correct project --- .../TestPluginWithoutPages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/{Jellyfin.Api.Tests => Jellyfin.Server.Integration.Tests}/TestPluginWithoutPages.cs (94%) diff --git a/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs b/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs similarity index 94% rename from tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs rename to tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs index 2d2f78a981..ac10c47845 100644 --- a/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs +++ b/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs @@ -6,7 +6,7 @@ using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { public class TestPluginWithoutPages : BasePlugin { From eca3b37d6eae727bd46b7a719cbf5b70a84627f8 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 9 Mar 2021 17:01:05 +0100 Subject: [PATCH 081/282] Use FileShare.Read to fix HdHomeRun --- .../LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 3 +-- .../LiveTv/TunerHosts/SharedHttpStream.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 08832bae14..b16ccc561a 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -193,8 +193,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var resolved = false; - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)) + using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read)) { while (true) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 233c3d83a3..eeb2426f4f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -136,8 +136,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath); using var message = response; await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.None); + await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read); await StreamHelper.CopyToAsync( stream, fileStream, From ece0d67f99240324aafdcde85def3e933e413b69 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 9 Mar 2021 17:31:31 +0100 Subject: [PATCH 082/282] Use FileShare.Read for log files --- Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs | 3 +-- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 40b934d32b..78a82118ed 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -91,8 +91,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); + _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false); await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 7cd9024b08..240d132b15 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -553,8 +553,7 @@ namespace Jellyfin.Api.Helpers $"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log"); // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); + Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); From 235b36a4c7f5e2dbb682bb7e6588b8f06219ce6c Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Tue, 9 Mar 2021 12:41:51 -0500 Subject: [PATCH 083/282] Remove Microsoft repo from install step This was breaking Fedora builds due to a mismatch. We can use the .NET SDK 5.0 from the Fedora 33 repos instead and this seems to work. --- deployment/Dockerfile.fedora.amd64 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64 index 2549f25ee7..137e56ecf2 100644 --- a/deployment/Dockerfile.fedora.amd64 +++ b/deployment/Dockerfile.fedora.amd64 @@ -13,9 +13,7 @@ RUN dnf update -y \ && dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd # Install DotNET SDK -RUN rpm --import https://packages.microsoft.com/keys/microsoft.asc \ - && curl -o /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/$(rpm -E %fedora)/prod.repo \ - && dnf install -y dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION} +RUN dnf install -y dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION} # Create symlinks and directories RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.amd64 /build.sh \ From c5a870051a097e346360b66d063d2f3492f25b0b Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 10 Mar 2021 08:20:02 +0100 Subject: [PATCH 084/282] Use distinct for artists to avoid double refreshing --- .../Entities/Audio/IHasAlbumArtist.cs | 12 +++--------- MediaBrowser.Controller/Library/NameExtensions.cs | 8 ++++---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs index f6d3cd6cc2..20fad4cb0e 100644 --- a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs @@ -1,6 +1,8 @@ #pragma warning disable CS1591 using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.Entities.Audio { @@ -23,15 +25,7 @@ namespace MediaBrowser.Controller.Entities.Audio public static IEnumerable GetAllArtists(this T item) where T : IHasArtist, IHasAlbumArtist { - foreach (var i in item.AlbumArtists) - { - yield return i; - } - - foreach (var i in item.Artists) - { - yield return i; - } + return item.AlbumArtists.Concat(item.Artists).DistinctNames(); } } } diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs index 1c90bb4e02..6e79dc8ddf 100644 --- a/MediaBrowser.Controller/Library/NameExtensions.cs +++ b/MediaBrowser.Controller/Library/NameExtensions.cs @@ -10,6 +10,10 @@ namespace MediaBrowser.Controller.Library { public static class NameExtensions { + public static IEnumerable DistinctNames(this IEnumerable names) + => names.GroupBy(RemoveDiacritics, StringComparer.OrdinalIgnoreCase) + .Select(x => x.First()); + private static string RemoveDiacritics(string? name) { if (name == null) @@ -19,9 +23,5 @@ namespace MediaBrowser.Controller.Library return name.RemoveDiacritics(); } - - public static IEnumerable DistinctNames(this IEnumerable names) - => names.GroupBy(RemoveDiacritics, StringComparer.OrdinalIgnoreCase) - .Select(x => x.First()); } } From 84da57cd4811d3bda6b3341c0a8a3b45b3aa72de Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 10 Mar 2021 09:07:11 +0000 Subject: [PATCH 085/282] Update StreamingHelpers.cs Updated condition --- Jellyfin.Api/Helpers/StreamingHelpers.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 6bc5524cba..e2306aa272 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -510,10 +510,7 @@ namespace Jellyfin.Api.Helpers { if (!string.IsNullOrWhiteSpace(deviceProfileId)) { - if (state.DeviceProfile == null) - { - state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId); - } + state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId); if (state.DeviceProfile == null) { From 3824c09e774f35df18f37b56e2fc6101dc2022fe Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 10 Mar 2021 10:47:35 +0100 Subject: [PATCH 086/282] fix multiversion eligibility check for complex folder names --- Emby.Naming/Video/VideoListResolver.cs | 17 +++++++++------- .../Video/MultiVersionTests.cs | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 09a030d2de..0b44763a2f 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -221,20 +221,23 @@ namespace Emby.Naming.Video string testFilename = Path.GetFileNameWithoutExtension(testFilePath); if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) { - if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName)) + // Remove the folder name before cleaning as we don't care about cleaning that part + if (folderName.Length <= testFilename.Length) { - testFilename = cleanName.ToString(); + testFilename = testFilename.Substring(folderName.Length).Trim(); } - if (folderName.Length <= testFilename.Length) + if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName)) { - testFilename = testFilename.Substring(folderName.Length).Trim(); + testFilename = cleanName.ToString(); } + // The CleanStringParser should have removed common keywords etc., so if it starts with -, _ or [ it's eligible. return string.IsNullOrEmpty(testFilename) - || testFilename[0] == '-' - || testFilename[0] == '_' - || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); + || testFilename[0] == '-' + || testFilename[0] == '_' + || testFilename[0] == '[' + || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); } return false; diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index bc5e6fa631..a46caeca0a 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -368,6 +368,26 @@ namespace Jellyfin.Naming.Tests.Video Assert.Single(result[0].AlternateVersions); } + [Fact] + public void Resolve_GivenFolderNameWithBracketsAndHyphens_GroupsBasedOnFolderName() + { + var files = new[] + { + @"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 1.mkv", + @"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 2.mkv" + }; + + var result = _videoListResolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Empty(result[0].Extras); + Assert.Single(result[0].AlternateVersions); + } + [Fact] public void TestEmptyList() { From 2fe26ef136fb00a59362bec33f0a4c31335a330a Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 10 Mar 2021 13:28:18 +0000 Subject: [PATCH 087/282] removed parameter preset --- Jellyfin.Networking/Manager/NetworkManager.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 17bbf9633a..bf121737b6 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -157,16 +157,15 @@ namespace Jellyfin.Networking.Manager /// Creates a new network collection. /// /// Items to assign the collection, or null. - /// True if subnets that overlap should be merged (default). /// The collection created. - public static Collection CreateCollection(IEnumerable? source = null, bool unique = true) + public static Collection CreateCollection(IEnumerable? source = null) { var result = new Collection(); if (source != null) { foreach (var item in source) { - result.AddItem(item, unique); + result.AddItem(item, false); } } @@ -391,8 +390,7 @@ namespace Jellyfin.Networking.Manager _interfaceAddresses .Exclude(_bindExclusions) .Where(IsInLocalNetwork) - .OrderBy(p => p.Tag), - false); + .OrderBy(p => p.Tag)); if (interfaces.Count > 0) { @@ -432,11 +430,11 @@ namespace Jellyfin.Networking.Manager if (_bindExclusions.Count > 0) { // Return all the internal interfaces except the ones excluded. - return CreateCollection(_internalInterfaces.Where(p => !_bindExclusions.ContainsAddress(p)), false); + return CreateCollection(_internalInterfaces.Where(p => !_bindExclusions.ContainsAddress(p))); } // No bind address, so return all internal interfaces. - return CreateCollection(_internalInterfaces.Where(p => !p.IsLoopback()), false); + return CreateCollection(_internalInterfaces.Where(p => !p.IsLoopback())); } return new Collection(_bindAddresses); @@ -980,7 +978,7 @@ namespace Jellyfin.Networking.Manager { _logger.LogDebug("Using LAN interface addresses as user provided no LAN details."); // Internal interfaces must be private and not excluded. - _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsPrivateAddressRange(i) && !_excludedSubnets.ContainsAddress(i)), false); + _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsPrivateAddressRange(i) && !_excludedSubnets.ContainsAddress(i))); // Subnets are the same as the calculated internal interface. _lanSubnets = new Collection(); @@ -1015,7 +1013,7 @@ namespace Jellyfin.Networking.Manager } // Internal interfaces must be private, not excluded and part of the LocalNetworkSubnet. - _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i)), false); + _internalInterfaces = CreateCollection(_interfaceAddresses.Where(i => IsInLocalNetwork(i))); } _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString()); From 8a74d7659819be55fd60dc8d272b0cca2f5178e2 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 10 Mar 2021 17:03:19 +0100 Subject: [PATCH 088/282] Fix id tag setting IMDb id when it is TMDb id --- MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs index 2d0eb8433d..d30190a7e8 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs @@ -47,12 +47,19 @@ namespace MediaBrowser.XbmcMetadata.Parsers { case "id": { + // get ids from attributes string? imdbId = reader.GetAttribute("IMDB"); string? tmdbId = reader.GetAttribute("TMDB"); - if (string.IsNullOrWhiteSpace(imdbId)) + // read id from content + var contentId = reader.ReadElementContentAsString(); + if (contentId.Contains("tt", StringComparison.Ordinal) && string.IsNullOrEmpty(imdbId)) { - imdbId = reader.ReadElementContentAsString(); + imdbId = contentId; + } + else if (string.IsNullOrEmpty(tmdbId)) + { + tmdbId = contentId; } if (!string.IsNullOrWhiteSpace(imdbId)) From 954148eb6de1f276fb584aa70b384babea0e58ce Mon Sep 17 00:00:00 2001 From: David Date: Wed, 10 Mar 2021 17:04:15 +0100 Subject: [PATCH 089/282] Fix Radarr url nfo files --- .../Parsers/BaseNfoParser.cs | 56 ++++++------------- .../Parsers/SeriesNfoParser.cs | 2 +- .../Parsers/MovieNfoParserTests.cs | 15 +++++ .../Test Data/Justice League.nfo | 3 +- .../Test Data/Radarr.nfo | 2 + 5 files changed, 38 insertions(+), 40 deletions(-) create mode 100644 tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index c4bbaf301c..3129c131d0 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers protected virtual bool SupportsUrlAfterClosingXmlTag => false; - protected virtual string MovieDbParserSearchString => "themoviedb.org/movie/"; + protected virtual string TmdbRegex => "themoviedb\\.org\\/movie\\/([0-9]+)"; /// /// Fetches metadata for an item from one xml file. @@ -181,8 +181,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers } else { - // If the file is just an Imdb url, handle that - + // If the file is just provider urls, handle that ParseProviderLinks(item.Item, xml); return; @@ -221,50 +220,31 @@ namespace MediaBrowser.XbmcMetadata.Parsers protected void ParseProviderLinks(T item, string xml) { - // Look for a match for the Regex pattern "tt" followed by 7 or 8 digits - var m = Regex.Match(xml, "tt([0-9]{7,8})", RegexOptions.IgnoreCase); - if (m.Success) + // IMDB: + // https://www.imdb.com/title/tt4154796 + var imdbRegex = Regex.Match(xml, "tt([0-9]{7,8})", RegexOptions.Compiled); + if (imdbRegex.Success) { - item.SetProviderId(MetadataProvider.Imdb, m.Value); + item.SetProviderId(MetadataProvider.Imdb, imdbRegex.Value); } - // Support Tmdb - // https://www.themoviedb.org/movie/30287-fallo - var srch = MovieDbParserSearchString; - var index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - - if (index != -1) + // TMDB: + // https://www.themoviedb.org/movie/30287-fallo (movie) + // https://www.themoviedb.org/tv/1668-friends (tv) + var tmdbRegex = Regex.Match(xml, TmdbRegex, RegexOptions.Compiled); + if (tmdbRegex.Success) { - var tmdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/'); - index = tmdbId.IndexOf('-'); - if (index != -1) - { - tmdbId = tmdbId.Slice(0, index); - } - - if (!tmdbId.IsEmpty - && !tmdbId.IsWhiteSpace() - && int.TryParse(tmdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) - { - item.SetProviderId(MetadataProvider.Tmdb, value.ToString(UsCulture)); - } + item.SetProviderId(MetadataProvider.Tmdb, tmdbRegex.Groups[1].Value); } + // TVDB: + // https://www.thetvdb.com/?tab=series&id=121361 if (item is Series) { - srch = "thetvdb.com/?tab=series&id="; - - index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - - if (index != -1) + var tvdbRegex = Regex.Match(xml, "thetvdb\\.com\\/\\?tab=series\\&id=([0-9]+)", RegexOptions.Compiled); + if (tvdbRegex.Success) { - var tvdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/'); - if (!tvdbId.IsEmpty - && !tvdbId.IsWhiteSpace() - && int.TryParse(tvdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) - { - item.SetProviderId(MetadataProvider.Tvdb, value.ToString(UsCulture)); - } + item.SetProviderId(MetadataProvider.Tvdb, tvdbRegex.Groups[1].Value); } } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index fbab8b5214..c09781b1ae 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers protected override bool SupportsUrlAfterClosingXmlTag => true; /// - protected override string MovieDbParserSearchString => "themoviedb.org/tv/"; + protected override string TmdbRegex => "themoviedb\\.org\\/tv\\/([0-9]+)"; /// protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index ff4795569c..7496784eb9 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -152,6 +152,21 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers Assert.Equal(id, item.ProviderIds[provider]); } + [Fact] + public void Parse_RadarrUrlFile_Success() + { + var result = new MetadataResult public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer) { - if (communicationsServer == null) + _CommunicationsServer = communicationsServer; + + if (communicationsServer != null) { - throw new ArgumentNullException(nameof(communicationsServer)); + // This can occur is dlna is enabled, but defined to run over https. + _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; } - _CommunicationsServer = communicationsServer; - _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; - _Devices = new List(); } From 2f843b3b4885aa6c0397c560bf11cada7d17c5aa Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 10 Mar 2021 19:32:13 +0000 Subject: [PATCH 093/282] Hide msg if dlna disabled --- Emby.Dlna/Main/DlnaEntryPoint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 9a2d524d16..d3e9a41ec4 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -128,7 +128,8 @@ namespace Emby.Dlna.Main _netConfig = config.GetConfiguration("network"); _disabled = appHost.ListenWithHttps && _netConfig.RequireHttps; - if (_disabled) + + if (_disabled && _config.GetDlnaConfiguration().EnableServer) { _logger.LogError("The DLNA specification does not support HTTPS."); } From 1dd6036765f08892a39530ad73f5b046afbdaaaf Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 10 Mar 2021 19:56:33 +0000 Subject: [PATCH 094/282] Fixed false starts --- Emby.Dlna/Ssdp/DeviceDiscovery.cs | 2 +- RSSDP/SsdpDeviceLocator.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index 8c7d961f3e..d13871add8 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -69,7 +69,7 @@ namespace Emby.Dlna.Ssdp { lock (_syncLock) { - if (_listenerCount > 0 && _deviceLocator == null) + if (_listenerCount > 0 && _deviceLocator == null && _commsServer != null) { _deviceLocator = new SsdpDeviceLocator(_commsServer); diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index e2b524cf46..9a110683e5 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -27,14 +27,15 @@ namespace Rssdp.Infrastructure /// public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer) { - _CommunicationsServer = communicationsServer; - - if (communicationsServer != null) + if (communicationsServer == null) { - // This can occur is dlna is enabled, but defined to run over https. - _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; + throw new ArgumentNullException(nameof(communicationsServer)); } + + _CommunicationsServer = communicationsServer; + // This can occur is dlna is enabled, but defined to run over https. + _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; _Devices = new List(); } From a324d8a9c5351ec2f101f66e2f511eaf208f1254 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 10 Mar 2021 19:57:32 +0000 Subject: [PATCH 095/282] removed space --- Jellyfin.sln | 15 ++++++++++----- RSSDP/SsdpDeviceLocator.cs | 5 ++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index 7b81f43462..3f8d8ce85b 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30503.244 MinimumVisualStudioVersion = 10.0.40219.1 @@ -70,15 +71,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jell EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -210,6 +211,10 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 9a110683e5..0cdc5ce3db 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -31,11 +31,10 @@ namespace Rssdp.Infrastructure { throw new ArgumentNullException(nameof(communicationsServer)); } - - _CommunicationsServer = communicationsServer; - // This can occur is dlna is enabled, but defined to run over https. + _CommunicationsServer = communicationsServer; _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; + _Devices = new List(); } From 0960c945d04249e7ec4af2d7f74f53f1cfdd5116 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 10 Mar 2021 19:58:45 +0000 Subject: [PATCH 096/282] Revert "removed space" This reverts commit a324d8a9c5351ec2f101f66e2f511eaf208f1254. --- Jellyfin.sln | 15 +++++---------- RSSDP/SsdpDeviceLocator.cs | 5 +++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index 3f8d8ce85b..7b81f43462 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -1,5 +1,4 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30503.244 MinimumVisualStudioVersion = 10.0.40219.1 @@ -71,15 +70,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jell EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -211,10 +210,6 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 0cdc5ce3db..9a110683e5 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -31,10 +31,11 @@ namespace Rssdp.Infrastructure { throw new ArgumentNullException(nameof(communicationsServer)); } - + _CommunicationsServer = communicationsServer; - _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; + // This can occur is dlna is enabled, but defined to run over https. + _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; _Devices = new List(); } From 5db998e1350ddd984c72624f882413c676bee6ef Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 10 Mar 2021 19:59:18 +0000 Subject: [PATCH 097/282] removed space. --- RSSDP/SsdpDeviceLocator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 9a110683e5..31d520ff7f 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -33,9 +33,8 @@ namespace Rssdp.Infrastructure } _CommunicationsServer = communicationsServer; - - // This can occur is dlna is enabled, but defined to run over https. _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; + _Devices = new List(); } From 1723ed1604a5ab6ae4d8e4dc243118c3267d879d Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 10 Mar 2021 19:59:58 +0000 Subject: [PATCH 098/282] removed the space AGAIN!!! --- RSSDP/SsdpDeviceLocator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 31d520ff7f..0cdc5ce3db 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -31,7 +31,7 @@ namespace Rssdp.Infrastructure { throw new ArgumentNullException(nameof(communicationsServer)); } - + _CommunicationsServer = communicationsServer; _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; From e6f49f50090f91c2c910066b3dbfe1ce1d2c0585 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Wed, 10 Mar 2021 21:00:33 +0100 Subject: [PATCH 099/282] Remove BuildPackage dependeny for PublishNuget in CI --- .ci/azure-pipelines-package.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 20f4dfe33b..62018fd24b 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -186,9 +186,6 @@ jobs: - job: PublishNuget displayName: 'Publish NuGet packages' - dependsOn: - - BuildPackage - condition: succeeded('BuildPackage') pool: vmImage: 'ubuntu-latest' From b827913fcf298bac28e5ced081dca5e4ded3f3ac Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Wed, 10 Mar 2021 21:09:45 +0100 Subject: [PATCH 100/282] Run CollectArtifacts taks regardless of result from BuildPackage and BuildDocker --- .ci/azure-pipelines-package.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 62018fd24b..543fd7fc6d 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -160,7 +160,6 @@ jobs: dependsOn: - BuildPackage - BuildDocker - condition: and(succeeded('BuildPackage'), succeeded('BuildDocker')) pool: vmImage: 'ubuntu-latest' From 8e09276d7d83304792dcbfc07ae511d42b90936d Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 10 Mar 2021 16:33:46 -0700 Subject: [PATCH 101/282] Add websocket session message type to generated openapi.json --- Jellyfin.Server/Filters/WebsocketModelFilter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/WebsocketModelFilter.cs index 2488028576..38afb201db 100644 --- a/Jellyfin.Server/Filters/WebsocketModelFilter.cs +++ b/Jellyfin.Server/Filters/WebsocketModelFilter.cs @@ -25,6 +25,8 @@ namespace Jellyfin.Server.Filters context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + + context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository); } } } From e6407b414ba264ce277f3cf813fe5c2ca11c18ce Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Thu, 11 Mar 2021 09:34:10 -0500 Subject: [PATCH 102/282] Remove forum badge --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index cba88c8d2b..6859a8a76f 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,6 @@ Submit Feature Requests - -Discuss on our Forum - Chat on Matrix From 3fdf0de6e3b3600e2825b6ee75634e906d102ce2 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 11 Mar 2021 21:36:58 +0000 Subject: [PATCH 103/282] Fix remote access --- Jellyfin.Networking/Manager/NetworkManager.cs | 40 +++++++++++++++++++ .../IpBasedAccessValidationMiddleware.cs | 26 +----------- Jellyfin.sln | 17 +++++--- MediaBrowser.Common/Net/INetworkManager.cs | 7 ++++ .../NetworkParseTests.cs | 20 ++++++++++ 5 files changed, 79 insertions(+), 31 deletions(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index d2e9dcf9e6..b874f5984b 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -576,6 +576,46 @@ namespace Jellyfin.Networking.Manager return false; } + /// + /// Checks to see if has access. + /// + /// IP Address of client. + /// True if has access, otherwise false. + public bool HasRemoteAccess(IPAddress remoteIp) + { + var config = _configurationManager.GetNetworkConfiguration(); + if (config.EnableRemoteAccess) + { + // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. + // If left blank, all remote addresses will be allowed. + var remoteAddressFilter = RemoteAddressFilter; + + if (RemoteAddressFilter.Count > 0 && !IsInLocalNetwork(remoteIp)) + { + // remoteAddressFilter is a whitelist or blacklist. + bool isListed = RemoteAddressFilter.ContainsAddress(remoteIp); + if (config.IsRemoteIPFilterBlacklist) + { + // Black list, so flip over. + isListed = !isListed; + } + + if (!isListed) + { + // If your name isn't on the list, you arn't coming in. + return false; + } + } + } + else if (!IsInLocalNetwork(remoteIp)) + { + // Remote not enabled. So everyone should be LAN. + return false; + } + + return true; + } + /// /// Reloads all settings and re-initialises the instance. /// diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs index 525cd9ffe2..96e6fd8340 100644 --- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs +++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs @@ -42,32 +42,8 @@ namespace Jellyfin.Server.Middleware var remoteIp = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback; - if (serverConfigurationManager.GetNetworkConfiguration().EnableRemoteAccess) + if (!networkManager.HasRemoteAccess(remoteIp)) { - // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. - // If left blank, all remote addresses will be allowed. - var remoteAddressFilter = networkManager.RemoteAddressFilter; - - if (remoteAddressFilter.Count > 0 && !networkManager.IsInLocalNetwork(remoteIp)) - { - // remoteAddressFilter is a whitelist or blacklist. - bool isListed = remoteAddressFilter.ContainsAddress(remoteIp); - if (!serverConfigurationManager.GetNetworkConfiguration().IsRemoteIPFilterBlacklist) - { - // Black list, so flip over. - isListed = !isListed; - } - - if (!isListed) - { - // If your name isn't on the list, you arn't coming in. - return; - } - } - } - else if (!networkManager.IsInLocalNetwork(remoteIp)) - { - // Remote not enabled. So everyone should be LAN. return; } diff --git a/Jellyfin.sln b/Jellyfin.sln index 7b81f43462..9d5cd98e26 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30503.244 MinimumVisualStudioVersion = 10.0.40219.1 @@ -70,15 +71,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jell EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -210,6 +211,10 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index b6c390d239..012824f652 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -229,5 +229,12 @@ namespace MediaBrowser.Common.Net /// Optional filter for the list. /// Returns a filtered list of LAN addresses. Collection GetFilteredLANSubnets(Collection? filter = null); + + /// + /// Checks to see if has access. + /// + /// IP Address of client. + /// True if has access, otherwise false. + bool HasRemoteAccess(IPAddress remoteIp); } } diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index c3469035eb..96b9f5e765 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -514,5 +514,25 @@ namespace Jellyfin.Networking.Tests Assert.Equal(intf, result); } + + [Theory] + [InlineData("185.10.10.10,200.200.200.200", "79.2.3.4", false, true)] // whitelist + [InlineData("185.10.10.10", "185.10.10.10", false, false)] // whitelist + [InlineData("185.10.10.10", "79.2.3.4", true, false)] // blacklist + public void TestRemoteAccess(string addresses, string remoteIp, bool blacklist, bool denied) + { + // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. + // If left blank, all remote addresses will be allowed. + var conf = new NetworkConfiguration() + { + EnableIPV4 = true, + RemoteIPFilter = addresses.Split(","), + IsRemoteIPFilterBlacklist = blacklist + }; + + using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied); + } } } From 034ee38583e613e7dd24282d02051f599b15cedc Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 11 Mar 2021 21:42:01 +0000 Subject: [PATCH 104/282] Update Jellyfin.sln reverted --- Jellyfin.sln | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index 9d5cd98e26..7b81f43462 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -1,5 +1,4 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30503.244 MinimumVisualStudioVersion = 10.0.40219.1 @@ -71,15 +70,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jell EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -211,10 +210,6 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU From f66cb9777ddaf42f19ea452f85c5fa0b7e907391 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 11 Mar 2021 22:46:07 +0000 Subject: [PATCH 105/282] Update Jellyfin.Networking/Manager/NetworkManager.cs Co-authored-by: Claus Vium --- Jellyfin.Networking/Manager/NetworkManager.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index b874f5984b..f42c0aeeb0 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -593,18 +593,7 @@ namespace Jellyfin.Networking.Manager if (RemoteAddressFilter.Count > 0 && !IsInLocalNetwork(remoteIp)) { // remoteAddressFilter is a whitelist or blacklist. - bool isListed = RemoteAddressFilter.ContainsAddress(remoteIp); - if (config.IsRemoteIPFilterBlacklist) - { - // Black list, so flip over. - isListed = !isListed; - } - - if (!isListed) - { - // If your name isn't on the list, you arn't coming in. - return false; - } + return RemoteAddressFilter.ContainsAddress(remoteIp) == !config.IsRemoteIPFilterBlacklist; } } else if (!IsInLocalNetwork(remoteIp)) From 3fa84500cf7430e3f56f04303e7e150f10e88dfb Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 11 Mar 2021 22:46:24 +0000 Subject: [PATCH 106/282] Update Jellyfin.Networking/Manager/NetworkManager.cs Co-authored-by: Claus Vium --- Jellyfin.Networking/Manager/NetworkManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index f42c0aeeb0..b0046af481 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -588,8 +588,6 @@ namespace Jellyfin.Networking.Manager { // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // If left blank, all remote addresses will be allowed. - var remoteAddressFilter = RemoteAddressFilter; - if (RemoteAddressFilter.Count > 0 && !IsInLocalNetwork(remoteIp)) { // remoteAddressFilter is a whitelist or blacklist. From e5914fd28c9f5073eeadff6fc6097297837b2544 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 11 Mar 2021 22:47:30 +0000 Subject: [PATCH 107/282] split tests --- .../NetworkParseTests.cs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 96b9f5e765..8993d487aa 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -516,10 +516,9 @@ namespace Jellyfin.Networking.Tests } [Theory] - [InlineData("185.10.10.10,200.200.200.200", "79.2.3.4", false, true)] // whitelist - [InlineData("185.10.10.10", "185.10.10.10", false, false)] // whitelist - [InlineData("185.10.10.10", "79.2.3.4", true, false)] // blacklist - public void TestRemoteAccess(string addresses, string remoteIp, bool blacklist, bool denied) + [InlineData("185.10.10.10,200.200.200.200", "79.2.3.4", true)] + [InlineData("185.10.10.10", "185.10.10.10", false)] + public void HasRemoteAccess_GivenNonEmptyWhitelist_AllowsOnlyIpsInWhitelist(string addresses, string remoteIp, bool denied) { // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // If left blank, all remote addresses will be allowed. @@ -527,7 +526,25 @@ namespace Jellyfin.Networking.Tests { EnableIPV4 = true, RemoteIPFilter = addresses.Split(","), - IsRemoteIPFilterBlacklist = blacklist + IsRemoteIPFilterBlacklist = false + }; + using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied); + } + + [Theory] + [InlineData("185.10.10.10", "79.2.3.4", false)] // blacklist + [InlineData("185.10.10.10", "185.10.10.10", true)] // blacklist + public void HasRemoteAccess_GivenNonEmptBlacklist_BlacklistTheIps(string addresses, string remoteIp, bool denied) + { + // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. + // If left blank, all remote addresses will be allowed. + var conf = new NetworkConfiguration() + { + EnableIPV4 = true, + RemoteIPFilter = addresses.Split(","), + IsRemoteIPFilterBlacklist = true }; using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); From eef15dc7ac23f026f11334e2a262a572df126e48 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 11 Mar 2021 22:45:58 -0700 Subject: [PATCH 108/282] Fix third part integration --- Jellyfin.Api/Controllers/LibraryController.cs | 6 ++-- .../Controllers/NotificationsController.cs | 21 +++++-------- .../Models/LibraryDtos/MediaUpdateInfoDto.cs | 15 ++++------ .../LibraryDtos/MediaUpdateInfoPathDto.cs | 19 ++++++++++++ .../NotificationDtos/AdminNotificationDto.cs | 30 +++++++++++++++++++ 5 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs create mode 100644 Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 3443ebd72f..2bd2d4933d 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -590,15 +590,15 @@ namespace Jellyfin.Api.Controllers /// /// Reports that new movies have been added by an external source. /// - /// A list of updated media paths. + /// The update paths. /// Report success. /// A . [HttpPost("Library/Media/Updated")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates) + public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto) { - foreach (var item in updates) + foreach (var item in dto.Updates) { _libraryMonitor.ReportFileSystemChanged(item.Path); } diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 0ceda6815c..420630cdf4 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using Jellyfin.Api.Constants; @@ -86,26 +87,19 @@ namespace Jellyfin.Api.Controllers /// /// Sends a notification to all admins. /// - /// The URL of the notification. - /// The level of the notification. - /// The name of the notification. - /// The description of the notification. + /// The notification request. /// Notification sent. /// A . [HttpPost("Admin")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult CreateAdminNotification( - [FromQuery] string? url, - [FromQuery] NotificationLevel? level, - [FromQuery] string name = "", - [FromQuery] string description = "") + public ActionResult CreateAdminNotification([FromBody, Required] AdminNotificationDto notificationDto) { var notification = new NotificationRequest { - Name = name, - Description = description, - Url = url, - Level = level ?? NotificationLevel.Normal, + Name = notificationDto.Name, + Description = notificationDto.Description, + Url = notificationDto.Url, + Level = notificationDto.NotificationLevel ?? NotificationLevel.Normal, UserIds = _userManager.Users .Where(user => user.HasPermission(PermissionKind.IsAdministrator)) .Select(user => user.Id) @@ -114,7 +108,6 @@ namespace Jellyfin.Api.Controllers }; _notificationManager.SendNotification(notification, CancellationToken.None); - return NoContent(); } diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs index 991dbfc502..f936388980 100644 --- a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs +++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs @@ -1,4 +1,7 @@ -namespace Jellyfin.Api.Models.LibraryDtos +using System; +using System.Collections.Generic; + +namespace Jellyfin.Api.Models.LibraryDtos { /// /// Media Update Info Dto. @@ -6,14 +9,8 @@ public class MediaUpdateInfoDto { /// - /// Gets or sets media path. - /// - public string? Path { get; set; } - - /// - /// Gets or sets media update type. - /// Created, Modified, Deleted. + /// Gets or sets the list of updates. /// - public string? UpdateType { get; set; } + public IReadOnlyList Updates { get; set; } = Array.Empty(); } } diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs new file mode 100644 index 0000000000..852315b92d --- /dev/null +++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs @@ -0,0 +1,19 @@ +namespace Jellyfin.Api.Models.LibraryDtos +{ + /// + /// The media update info path. + /// + public class MediaUpdateInfoPathDto + { + /// + /// Gets or sets media path. + /// + public string? Path { get; set; } + + /// + /// Gets or sets media update type. + /// Created, Modified, Deleted. + /// + public string? UpdateType { get; set; } + } +} diff --git a/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs b/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs new file mode 100644 index 0000000000..2c3a6282f2 --- /dev/null +++ b/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs @@ -0,0 +1,30 @@ +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Api.Models.NotificationDtos +{ + /// + /// The admin notification dto. + /// + public class AdminNotificationDto + { + /// + /// Gets or sets the notification name. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the notification description. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the notification level. + /// + public NotificationLevel? NotificationLevel { get; set; } + + /// + /// Gets or sets the notification url. + /// + public string? Url { get; set; } + } +} From 80846a1c66eb7f669d95d021786b5273ba3b15f6 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Fri, 12 Mar 2021 12:58:05 +0000 Subject: [PATCH 109/282] Translated using Weblate (Kazakh) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/ --- Emby.Server.Implementations/Localization/Core/kk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index bdfb786c94..829a29ad42 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -116,7 +116,7 @@ "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.", "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.", "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.", - "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşіn nobailar jasaidy.", + "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşın nobailar jasaidy.", "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.", "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady." } From e814d8e2cfe41acb9269c048bbbd5f133dfcae7b Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 12 Mar 2021 06:43:57 -0700 Subject: [PATCH 110/282] Add JsonStringConverter --- .../Json/Converters/JsonStringConverter.cs | 38 ++++++++++++++++++ MediaBrowser.Common/Json/JsonDefaults.cs | 3 +- .../Json/JsonStringConverterTests.cs | 39 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 MediaBrowser.Common/Json/Converters/JsonStringConverter.cs create mode 100644 tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs diff --git a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs new file mode 100644 index 0000000000..585f52b8f1 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs @@ -0,0 +1,38 @@ +using System; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Converter to allow the serializer to read strings. + /// + public class JsonStringConverter : JsonConverter + { + /// + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.Null => null, + JsonTokenType.String => reader.GetString(), + _ => GetRawValue(reader) + }; + } + + /// + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WriteStringValue(value); + } + + private static string GetRawValue(Utf8JsonReader reader) + { + var utf8Bytes = reader.HasValueSequence + ? reader.ValueSequence.FirstSpan + : reader.ValueSpan; + return Encoding.UTF8.GetString(utf8Bytes); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 2ef24a884a..61938fdf29 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -39,7 +39,8 @@ namespace MediaBrowser.Common.Json new JsonStringEnumConverter(), new JsonNullableStructConverterFactory(), new JsonBoolNumberConverter(), - new JsonDateTimeConverter() + new JsonDateTimeConverter(), + new JsonStringConverter() } }; diff --git a/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs new file mode 100644 index 0000000000..fd77694b30 --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json; +using MediaBrowser.Common.Json.Converters; +using Xunit; + +namespace Jellyfin.Common.Tests.Json +{ + public class JsonStringConverterTests + { + private readonly JsonSerializerOptions _jsonSerializerOptions + = new () + { + Converters = + { + new JsonStringConverter() + } + }; + + [Theory] + [InlineData("\"test\"", "test")] + [InlineData("123", "123")] + [InlineData("123.45", "123.45")] + [InlineData("true", "true")] + [InlineData("false", "false")] + public void Deserialize_String_Valid_Success(string input, string output) + { + var deserialized = JsonSerializer.Deserialize(input, _jsonSerializerOptions); + Assert.Equal(deserialized, output); + } + + [Fact] + public void Deserialize_Int32asInt32_Valid_Success() + { + const string? input = "123"; + const int output = 123; + var deserialized = JsonSerializer.Deserialize(input, _jsonSerializerOptions); + Assert.Equal(deserialized, output); + } + } +} \ No newline at end of file From b7fb152faf6c01e20d9b8c46be13fc7cc5e0edbc Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 12 Mar 2021 14:44:05 +0000 Subject: [PATCH 111/282] Update tests/Jellyfin.Networking.Tests/NetworkParseTests.cs Co-authored-by: Claus Vium --- tests/Jellyfin.Networking.Tests/NetworkParseTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 8993d487aa..19f0faa8fe 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -536,7 +536,7 @@ namespace Jellyfin.Networking.Tests [Theory] [InlineData("185.10.10.10", "79.2.3.4", false)] // blacklist [InlineData("185.10.10.10", "185.10.10.10", true)] // blacklist - public void HasRemoteAccess_GivenNonEmptBlacklist_BlacklistTheIps(string addresses, string remoteIp, bool denied) + public void HasRemoteAccess_GivenNonEmptyBlacklist_BlacklistTheIps(string addresses, string remoteIp, bool denied) { // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // If left blank, all remote addresses will be allowed. From 4cf88f67baf8f76ce4ed630cc73ef73405c0212b Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 12 Mar 2021 14:58:04 +0000 Subject: [PATCH 112/282] Update NetworkManager.cs --- Jellyfin.Networking/Manager/NetworkManager.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index b0046af481..785a058d3a 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -576,11 +576,7 @@ namespace Jellyfin.Networking.Manager return false; } - /// - /// Checks to see if has access. - /// - /// IP Address of client. - /// True if has access, otherwise false. + /// public bool HasRemoteAccess(IPAddress remoteIp) { var config = _configurationManager.GetNetworkConfiguration(); From 1d5b7b61fb002dd4b2e3fe4cf8dd02df590f64e7 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 12 Mar 2021 22:20:13 +0000 Subject: [PATCH 113/282] Change First to FirstOrDefault --- Emby.Dlna/DlnaManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index d7b75f9796..3f49256609 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -333,7 +333,12 @@ namespace Emby.Dlna throw new ArgumentNullException(nameof(id)); } - var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase)); + var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase)); + + if (info == null) + { + return null; + } return ParseProfileFile(info.Path, info.Info.Type); } From 500832bdfd3dbbb5367484561079b579cb853318 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 12 Mar 2021 17:11:43 -0700 Subject: [PATCH 114/282] Set openapi version to server version --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 1828f1a7eb..9670a2b2c7 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -260,15 +260,16 @@ namespace Jellyfin.Server.Extensions { return serviceCollection.AddSwaggerGen(c => { + var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString(); c.SwaggerDoc("api-docs", new OpenApiInfo { Title = "Jellyfin API", - Version = "v1", + Version = version, Extensions = new Dictionary { { "x-jellyfin-version", - new OpenApiString(typeof(ApplicationHost).Assembly.GetName().Version?.ToString()) + new OpenApiString(version) } } }); From fe2a310fe28bb893a855d1278845ce6539c39656 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 13 Mar 2021 09:13:45 +0100 Subject: [PATCH 115/282] fix refresh endpoint It was originally a POST https://github.com/jellyfin/jellyfin/blob/9af6eda0b495649e3a77694b2bb30abad1a26484/MediaBrowser.Api/Library/LibraryService.cs#L155 --- Jellyfin.Api/Controllers/LibraryController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 3443ebd72f..76f882b437 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -303,7 +303,7 @@ namespace Jellyfin.Api.Controllers /// /// Library scan started. /// A . - [HttpGet("Library/Refresh")] + [HttpPost("Library/Refresh")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task RefreshLibrary() From 37b1b31a46e818cb5d229ebf11d7db7895c3b964 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 13 Mar 2021 08:41:16 -0700 Subject: [PATCH 116/282] Convert full ValueSequence --- MediaBrowser.Common/Json/Converters/JsonStringConverter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs index 585f52b8f1..669b3cd077 100644 --- a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -30,7 +31,7 @@ namespace MediaBrowser.Common.Json.Converters private static string GetRawValue(Utf8JsonReader reader) { var utf8Bytes = reader.HasValueSequence - ? reader.ValueSequence.FirstSpan + ? reader.ValueSequence.ToArray() : reader.ValueSpan; return Encoding.UTF8.GetString(utf8Bytes); } From 1169a0214b3ed166e53b9d6a80c8cbf707518e7b Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 13 Mar 2021 08:43:14 -0700 Subject: [PATCH 117/282] Set default version --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 9670a2b2c7..c2b3d9f5ef 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -260,7 +260,7 @@ namespace Jellyfin.Server.Extensions { return serviceCollection.AddSwaggerGen(c => { - var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString(); + var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString() ?? "0.0.0.1"; c.SwaggerDoc("api-docs", new OpenApiInfo { Title = "Jellyfin API", From 9ac9543ee2b69f423f0102f80671fb09893534ae Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 13 Mar 2021 09:09:22 -0700 Subject: [PATCH 118/282] Add missing InstantMix endpoints --- .../Controllers/InstantMixController.cs | 90 +++++++++++++++++-- 1 file changed, 82 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index f061755c30..f232dffaa5 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Creates an instant playlist based on a given song. + /// Creates an instant playlist based on a given album. /// /// The item id. /// Optional. Filter by user id, and attach user data. @@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Creates an instant playlist based on a given song. + /// Creates an instant playlist based on a given playlist. /// /// The item id. /// Optional. Filter by user id, and attach user data. @@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Creates an instant playlist based on a given song. + /// Creates an instant playlist based on a given genre. /// /// The genre name. /// Optional. Filter by user id, and attach user data. @@ -172,7 +172,7 @@ namespace Jellyfin.Api.Controllers /// A with the playlist items. [HttpGet("MusicGenres/{name}/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetInstantMixFromMusicGenre( + public ActionResult> GetInstantMixFromMusicGenreByName( [FromRoute, Required] string name, [FromQuery] Guid? userId, [FromQuery] int? limit, @@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Creates an instant playlist based on a given song. + /// Creates an instant playlist based on a given artist. /// /// The item id. /// Optional. Filter by user id, and attach user data. @@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Creates an instant playlist based on a given song. + /// Creates an instant playlist based on a given genre. /// /// The item id. /// Optional. Filter by user id, and attach user data. @@ -243,7 +243,7 @@ namespace Jellyfin.Api.Controllers /// A with the playlist items. [HttpGet("MusicGenres/{id}/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetInstantMixFromMusicGenres( + public ActionResult> GetInstantMixFromMusicGenreById( [FromRoute, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, @@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Creates an instant playlist based on a given song. + /// Creates an instant playlist based on a given item. /// /// The item id. /// Optional. Filter by user id, and attach user data. @@ -300,6 +300,80 @@ namespace Jellyfin.Api.Controllers return GetResult(items, user, limit, dtoOptions); } + /// + /// Creates an instant playlist based on a given artist. + /// + /// The item id. + /// Optional. Filter by user id, and attach user data. + /// Optional. The maximum number of records to return. + /// Optional. Specify additional fields of information to return in the output. + /// Optional. Include image information in output. + /// Optional. Include user data. + /// Optional. The max number of images to return, per image type. + /// Optional. The image types to include in the output. + /// Instant playlist returned. + /// A with the playlist items. + [HttpGet("Artists/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Use GetInstantMixFromArtists")] + public ActionResult> GetInstantMixFromArtists2( + [FromQuery, Required] Guid id, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + { + return GetInstantMixFromArtists( + id, + userId, + limit, + fields, + enableImages, + enableUserData, + imageTypeLimit, + enableImageTypes); + } + + /// + /// Creates an instant playlist based on a given genre. + /// + /// The item id. + /// Optional. Filter by user id, and attach user data. + /// Optional. The maximum number of records to return. + /// Optional. Specify additional fields of information to return in the output. + /// Optional. Include image information in output. + /// Optional. Include user data. + /// Optional. The max number of images to return, per image type. + /// Optional. The image types to include in the output. + /// Instant playlist returned. + /// A with the playlist items. + [HttpGet("MusicGenres/InstantMix")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Use GetInstantMixFromMusicGenres instead")] + public ActionResult> GetInstantMixFromMusicGenreById2( + [FromQuery, Required] Guid id, + [FromQuery] Guid? userId, + [FromQuery] int? limit, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery] bool? enableImages, + [FromQuery] bool? enableUserData, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + { + return GetInstantMixFromMusicGenreById( + id, + userId, + limit, + fields, + enableImages, + enableUserData, + imageTypeLimit, + enableImageTypes); + } + private QueryResult GetResult(List items, User? user, int? limit, DtoOptions dtoOptions) { var list = items; From f5789483fd5288fe735057bbf6e38a260e6915d2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 13 Mar 2021 19:18:11 +0100 Subject: [PATCH 119/282] Add test for HdHomerunManager.WriteSetMessage --- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 2 +- .../LiveTv/HdHomerunManagerTests.cs | 37 ++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 20a4d87fba..579333254e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -292,7 +292,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return FinishPacket(buffer, offset); } - private static int WriteSetMessage(Span buffer, int tuner, string name, string value, uint? lockkey) + internal static int WriteSetMessage(Span buffer, int tuner, string name, string value, uint? lockkey) { var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name); int offset = WriteHeaderAndPayload(buffer, byteName); diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs index 7e04a1ec12..e8634be4c2 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs @@ -17,8 +17,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Span buffer = stackalloc byte[128]; int len = HdHomerunManager.WriteNullTerminatedString(buffer, string.Empty); - Assert.Equal(expected.Length, len); - Assert.True(expected.SequenceEqual(buffer.Slice(0, len))); + Assert.Equal( + Convert.ToHexString(expected), + Convert.ToHexString(buffer.Slice(0, len))); } [Fact] @@ -32,8 +33,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Span buffer = stackalloc byte[128]; int len = HdHomerunManager.WriteNullTerminatedString(buffer, "The quick"); - Assert.Equal(expected.Length, len); - Assert.True(expected.SequenceEqual(buffer.Slice(0, len))); + Assert.Equal( + Convert.ToHexString(expected), + Convert.ToHexString(buffer.Slice(0, len))); } [Fact] @@ -51,8 +53,31 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Span buffer = stackalloc byte[128]; int len = HdHomerunManager.WriteGetMessage(buffer, 0, "N"); - Assert.Equal(expected.Length, len); - Assert.True(expected.SequenceEqual(buffer.Slice(0, len))); + Assert.Equal( + Convert.ToHexString(expected), + Convert.ToHexString(buffer.Slice(0, len))); + } + + [Fact] + public void WriteSetMessage_NoLockKey_Success() + { + ReadOnlySpan expected = stackalloc byte[] + { + 0, 4, + 0, 20, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0xa9, 0x49, 0xd0, 0x68 + }; + + Span buffer = stackalloc byte[128]; + int len = HdHomerunManager.WriteSetMessage(buffer, 0, "N", "value", null); + + Assert.Equal( + Convert.ToHexString(expected), + Convert.ToHexString(buffer.Slice(0, len))); } } } From 4b17648df348ac17b359e2dcfe3e361798d45f2b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 13 Mar 2021 19:23:52 +0100 Subject: [PATCH 120/282] Remove empty line Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Collections/CollectionManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 449f8872fd..907b58886f 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -347,7 +347,6 @@ namespace Emby.Server.Implementations.Collections var alreadyInResults = false; foreach (var child in item.GetMediaSources(true)) { - if (results.ContainsKey(Guid.Parse(child.Id))) { alreadyInResults = true; From 7a3109104bf4154d285c94a48504083368556c55 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 13 Mar 2021 19:24:02 +0100 Subject: [PATCH 121/282] Remove empty line Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Collections/CollectionManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 907b58886f..151e869487 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -354,7 +354,6 @@ namespace Emby.Server.Implementations.Collections } if (!alreadyInResults) { - results[item.Id] = item; } } From 7fb3a354fd6dd8fa83832566f76bcf0a46a5e82f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 13 Mar 2021 19:24:47 +0100 Subject: [PATCH 122/282] Add test for HdHomerunManager.WriteSetMessage with lockkey --- .../LiveTv/HdHomerunManagerTests.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs index e8634be4c2..2991fe1ed6 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs @@ -79,5 +79,29 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Convert.ToHexString(expected), Convert.ToHexString(buffer.Slice(0, len))); } + + [Fact] + public void WriteSetMessage_LockKey_Success() + { + ReadOnlySpan expected = stackalloc byte[] + { + 0, 4, + 0, 26, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 21, + 4, 0x00, 0x01, 0x38, 0xd5, + 0x8e, 0xb6, 0x06, 0x82 + }; + + Span buffer = stackalloc byte[128]; + int len = HdHomerunManager.WriteSetMessage(buffer, 0, "N", "value", 80085); + + Assert.Equal( + Convert.ToHexString(expected), + Convert.ToHexString(buffer.Slice(0, len))); + } } } From e8b18e5f8fe87f981b11237d24032ab11589bcd2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 13 Mar 2021 19:32:40 +0100 Subject: [PATCH 123/282] Add test for HdHomerunManager.ParseReturnMessage --- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 2 +- .../LiveTv/HdHomerunManagerTests.cs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 579333254e..9c113bc7c4 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -355,7 +355,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return offset + 4; } - private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal) + internal static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal) { returnVal = string.Empty; diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs index 2991fe1ed6..355cfa6412 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs @@ -103,5 +103,23 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Convert.ToHexString(expected), Convert.ToHexString(buffer.Slice(0, len))); } + + [Fact] + public void ParseReturnMessage_Valid_Success() + { + ReadOnlySpan packet = stackalloc byte[] + { + 0, 5, + 0, 20, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0x7d, 0xa3, 0xa3, 0xf3 + }; + + Assert.True(HdHomerunManager.ParseReturnMessage(packet.ToArray(), packet.Length, out var value)); + Assert.Equal("value", value); + } } } From 4cc3b938fa7913bccf7229dafce4b9f1d369dd16 Mon Sep 17 00:00:00 2001 From: Mister Rajoy Date: Sat, 13 Mar 2021 20:33:05 +0100 Subject: [PATCH 124/282] Change Guid.Parse to Guid.TryParse --- Emby.Server.Implementations/Collections/CollectionManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 449f8872fd..b5d95134bd 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -348,11 +348,13 @@ namespace Emby.Server.Implementations.Collections foreach (var child in item.GetMediaSources(true)) { - if (results.ContainsKey(Guid.Parse(child.Id))) + if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id)) { alreadyInResults = true; + break; } } + if (!alreadyInResults) { From f9640f43663fdf443c130f61cab3521fcb1c7823 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 13 Mar 2021 21:12:11 +0100 Subject: [PATCH 125/282] Rewrite HdHomerunManager.ParseReturnMessage --- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 75 ++++--- .../LiveTv/HdHomerunManagerTests.cs | 209 +++++++++++++++++- 2 files changed, 244 insertions(+), 40 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 9c113bc7c4..a7fda1d72e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -130,9 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - ParseReturnMessage(buffer, receivedBytes, out string returnVal); - - return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase); + return VerifyReturnValueOfGetSet(buffer.AsSpan(receivedBytes), "none"); } finally { @@ -173,7 +171,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked - if (!ParseReturnMessage(buffer, receivedBytes, out _)) + if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _)) { continue; } @@ -185,7 +183,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked - if (!ParseReturnMessage(buffer, receivedBytes, out _)) + if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _)) { await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false); continue; @@ -199,7 +197,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked - if (!ParseReturnMessage(buffer, receivedBytes, out _)) + if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _)) { await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false); continue; @@ -239,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked - if (!ParseReturnMessage(buffer, receivedBytes, out _)) + if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _)) { return; } @@ -355,60 +353,65 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return offset + 4; } - internal static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal) + internal static bool VerifyReturnValueOfGetSet(ReadOnlySpan buffer, string expected) { - returnVal = string.Empty; + return TryGetReturnValueOfGetSet(buffer, out var value) + && string.Equals(Encoding.UTF8.GetString(value), expected, StringComparison.OrdinalIgnoreCase); + } - if (numBytes < 4) + internal static bool TryGetReturnValueOfGetSet(ReadOnlySpan buffer, out ReadOnlySpan value) + { + value = ReadOnlySpan.Empty; + + if (buffer.Length < 8) { return false; } - var flipEndian = BitConverter.IsLittleEndian; - int offset = 0; - byte[] msgTypeBytes = new byte[2]; - Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length); - - if (flipEndian) + uint crc = BinaryPrimitives.ReadUInt32LittleEndian(buffer[^4..]); + if (crc != Crc32.Compute(buffer[..^4])) { - Array.Reverse(msgTypeBytes); + return false; } - var msgType = BitConverter.ToUInt16(msgTypeBytes, 0); - offset += 2; - - if (msgType != GetSetReply) + if (BinaryPrimitives.ReadUInt16BigEndian(buffer) != GetSetReply) { return false; } - byte[] msgLengthBytes = new byte[2]; - Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length); - if (flipEndian) + var msgLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2)); + if (buffer.Length != 2 + 2 + 4 + msgLength) { - Array.Reverse(msgLengthBytes); + return false; } - var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0); - offset += 2; - - if (numBytes < msgLength + 8) + var offset = 4; + if (buffer[offset++] != GetSetName) { return false; } - offset++; // Name Tag - - var nameLength = buf[offset++]; + var nameLength = buffer[offset++]; + if (buffer.Length < 4 + 1 + offset + nameLength) + { + return false; + } - // skip the name field to get to value for return offset += nameLength; - offset++; // Value Tag + if (buffer[offset++] != GetSetValue) + { + return false; + } - var valueLength = buf[offset++]; + var valueLength = buffer[offset++]; + if (buffer.Length < 4 + offset + valueLength) + { + return false; + } - returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator + // remove null terminator + value = buffer.Slice(offset, valueLength - 1); return true; } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs index 355cfa6412..c404e6ed5c 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun; using Xunit; @@ -105,9 +106,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv } [Fact] - public void ParseReturnMessage_Valid_Success() + public void TryGetReturnValueOfGetSet_Valid_Success() { - ReadOnlySpan packet = stackalloc byte[] + ReadOnlySpan packet = new byte[] { 0, 5, 0, 20, @@ -118,8 +119,208 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv 0x7d, 0xa3, 0xa3, 0xf3 }; - Assert.True(HdHomerunManager.ParseReturnMessage(packet.ToArray(), packet.Length, out var value)); - Assert.Equal("value", value); + Assert.True(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out var value)); + Assert.Equal("value", Encoding.UTF8.GetString(value)); + } + + [Fact] + public void TryGetReturnValueOfGetSet_InvalidPacketType_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 4, + 0, 20, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0x7d, 0xa3, 0xa3, 0xf3 + }; + + Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); + } + + [Fact] + public void TryGetReturnValueOfGetSet_InvalidCrc_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 20, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0x7d, 0xa3, 0xa3, 0xf4 + }; + + Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); + } + + [Fact] + public void TryGetReturnValueOfGetSet_InvalidPacket_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 20, + 0x7d, 0xa3, 0xa3 + }; + + Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); + } + + [Fact] + public void TryGetReturnValueOfGetSet_TooSmallMessageLength_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 19, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0x25, 0x25, 0x44, 0x9a + }; + + Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); + } + + [Fact] + public void TryGetReturnValueOfGetSet_TooLargeMessageLength_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 21, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0xe3, 0x20, 0x79, 0x6c + }; + + Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); + } + + [Fact] + public void TryGetReturnValueOfGetSet_TooLargeNameLength_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 20, + 3, + 20, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0xe1, 0x8e, 0x9c, 0x74 + }; + + Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); + } + + [Fact] + public void TryGetReturnValueOfGetSet_InvalidGetSetNameTag_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 20, + 4, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0xee, 0x05, 0xe7, 0x12 + }; + + Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); + } + + [Fact] + public void TryGetReturnValueOfGetSet_InvalidGetSetValueTag_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 20, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 3, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0x64, 0xaa, 0x66, 0xf9 + }; + + Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); + } + + [Fact] + public void TryGetReturnValueOfGetSet_TooLargeValueLength_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 20, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 7, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0xc9, 0xa8, 0xd4, 0x55 + }; + + Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); + } + + [Fact] + public void VerifyReturnValueOfGetSet_Valid_True() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 20, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0x7d, 0xa3, 0xa3, 0xf3 + }; + + Assert.True(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "value")); + } + + [Fact] + public void VerifyReturnValueOfGetSet_WrongValue_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 5, + 0, 20, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0x7d, 0xa3, 0xa3, 0xf3 + }; + + Assert.False(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "none")); + } + + [Fact] + public void VerifyReturnValueOfGetSet_InvalidPacket_False() + { + ReadOnlySpan packet = new byte[] + { + 0, 4, + 0, 20, + 3, + 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, + 4, + 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, + 0x7d, 0xa3, 0xa3, 0xf3 + }; + + Assert.False(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "value")); } } } From d9eb7ae6dc69aa4d0b77e7a2ae1c9ccac78d0db9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 13 Mar 2021 21:28:04 +0100 Subject: [PATCH 126/282] Fix invalid crc in TryGetReturnValueOfGetSet_InvalidPacketType test --- .../LiveTv/HdHomerunManagerTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs index c404e6ed5c..fd499d9cf9 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs @@ -124,34 +124,34 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv } [Fact] - public void TryGetReturnValueOfGetSet_InvalidPacketType_False() + public void TryGetReturnValueOfGetSet_InvalidCrc_False() { ReadOnlySpan packet = new byte[] { - 0, 4, + 0, 5, 0, 20, 3, 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, 4, 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, - 0x7d, 0xa3, 0xa3, 0xf3 + 0x7d, 0xa3, 0xa3, 0xf4 }; Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); } [Fact] - public void TryGetReturnValueOfGetSet_InvalidCrc_False() + public void TryGetReturnValueOfGetSet_InvalidPacketType_False() { ReadOnlySpan packet = new byte[] { - 0, 5, + 0, 4, 0, 20, 3, 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0, 4, 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0, - 0x7d, 0xa3, 0xa3, 0xf4 + 0xa9, 0x49, 0xd0, 0x68 }; Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _)); From a8ed753f6c890f74d3a70c2653ac5548d2399737 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 05:57:38 +0100 Subject: [PATCH 127/282] FxCop -> Net Analyzers (part 2) --- Emby.Dlna/DlnaManager.cs | 2 +- .../ApplicationHost.cs | 5 +- .../Channels/ChannelManager.cs | 2 +- .../Data/SqliteItemRepository.cs | 2 +- .../HttpServer/WebSocketConnection.cs | 2 +- .../Library/LiveStreamHelper.cs | 2 +- .../Library/MediaSourceManager.cs | 2 +- .../LiveTv/EmbyTV/EncodedRecorder.cs | 2 +- .../LiveTv/EmbyTV/ItemDataProvider.cs | 2 +- .../LiveTv/Listings/SchedulesDirect.cs | 2 +- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 2 +- .../Localization/LocalizationManager.cs | 2 +- .../Plugins/PluginManager.cs | 4 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 2 +- .../Updates/InstallationManager.cs | 2 +- .../Controllers/ConfigurationController.cs | 2 +- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- Jellyfin.Data/DayOfWeekHelper.cs | 60 +++---------------- .../Entities/Libraries/Collection.cs | 1 + .../Entities/Libraries/MediaFileStream.cs | 2 + Jellyfin.Data/Entities/Permission.cs | 2 + Jellyfin.Data/Jellyfin.Data.csproj | 6 +- .../Jellyfin.Drawing.Skia.csproj | 11 ++-- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 13 ++-- .../Jellyfin.Networking.csproj | 6 +- .../Security/AuthenticationSucceededLogger.cs | 8 +-- .../Consumers/System/TaskCompletedLogger.cs | 14 ++--- .../Updates/PluginUninstalledLogger.cs | 4 +- .../Consumers/Users/UserUpdatedNotifier.cs | 6 +- .../Jellyfin.Server.Implementations.csproj | 2 + .../Users/DefaultPasswordResetProvider.cs | 2 +- .../Users/DisplayPreferencesManager.cs | 3 +- .../ApiServiceCollectionExtensions.cs | 2 +- .../CamelCaseJsonProfileFormatter.cs | 2 +- .../PascalCaseJsonProfileFormatter.cs | 2 +- Jellyfin.Server/Jellyfin.Server.csproj | 8 +-- .../Migrations/MigrationOptions.cs | 3 + .../Migrations/Routines/MigrateUserDb.cs | 2 +- Jellyfin.Server/Program.cs | 4 +- MediaBrowser.Common/IApplicationHost.cs | 4 +- MediaBrowser.Common/Json/JsonDefaults.cs | 6 +- .../MediaBrowser.Common.csproj | 6 +- MediaBrowser.Common/Net/IPHost.cs | 2 +- MediaBrowser.Common/Net/IPNetAddress.cs | 6 +- MediaBrowser.Common/Plugins/BasePlugin.cs | 2 +- MediaBrowser.Common/Plugins/BasePluginOfT.cs | 36 ++++++----- MediaBrowser.Common/Plugins/LocalPlugin.cs | 6 +- .../Progress/ActionableProgress.cs | 1 + .../Progress/SimpleProgress.cs | 1 + .../Entities/CollectionFolder.cs | 2 +- .../MediaBrowser.Controller.csproj | 6 +- .../Providers/ILocalImageProvider.cs | 2 +- .../CollectionFolderLocalImageProvider.cs | 2 +- .../Images/EpisodeLocalImageProvider.cs | 2 +- .../InternalMetadataFolderImageProvider.cs | 7 ++- .../Images/LocalImageProvider.cs | 14 ++--- .../MediaBrowser.LocalMetadata.csproj | 6 +- .../Parsers/BaseItemXmlParser.cs | 4 +- .../Parsers/BoxSetXmlParser.cs | 6 +- .../Parsers/PlaylistXmlParser.cs | 6 +- .../Savers/BaseXmlSaver.cs | 17 ++---- .../Encoder/MediaEncoder.cs | 2 +- .../Plugins/AudioDb/AlbumImageProvider.cs | 2 +- .../Plugins/AudioDb/AlbumProvider.cs | 2 +- .../Plugins/AudioDb/ArtistImageProvider.cs | 2 +- .../Plugins/AudioDb/ArtistProvider.cs | 2 +- .../Plugins/Omdb/OmdbItemProvider.cs | 2 +- .../Plugins/Omdb/OmdbProvider.cs | 2 +- .../FFprobeParserTests.cs | 2 +- .../Controllers/DashboardControllerTests.cs | 2 +- 70 files changed, 151 insertions(+), 213 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index d7b75f9796..552cd501b7 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -36,7 +36,7 @@ namespace Emby.Dlna private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly Dictionary> _profiles = new Dictionary>(StringComparer.Ordinal); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 835dc33b0f..164e6d49de 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -10,8 +10,6 @@ using System.Net; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Emby.Dlna; @@ -51,7 +49,6 @@ using Jellyfin.Networking.Manager; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; -using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; @@ -470,7 +467,7 @@ namespace Emby.Server.Implementations } /// - public IReadOnlyCollection GetExports(CreationDelegate defaultFunc, bool manageLifetime = true) + public IReadOnlyCollection GetExports(CreationDelegateFactory defaultFunc, bool manageLifetime = true) { // Convert to list so this isn't executed for each iteration var parts = GetExportTypes() diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 8c5fa09f69..87ebe960ab 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Channels private readonly IProviderManager _providerManager; private readonly IMemoryCache _memoryCache; private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index d78b93bd78..2ae8054478 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Data _imageProcessor = imageProcessor; _typeMapper = new TypeMapper(); - _jsonOptions = JsonDefaults.GetOptions(); + _jsonOptions = JsonDefaults.Options; DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); } diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 7e0c2c1da2..06acb56061 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer RemoteEndPoint = remoteEndPoint; QueryString = query; - _jsonOptions = JsonDefaults.GetOptions(); + _jsonOptions = JsonDefaults.Options; LastActivityDate = DateTime.Now; } diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 2070df31e4..c2951dd155 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Library private readonly IMediaEncoder _mediaEncoder; private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths) { diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index c63eb70179..b2943020c5 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Library private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private IMediaSourceProvider[] _providers; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 78a82118ed..44a8cdee49 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly IServerApplicationPaths _appPaths; private readonly TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly IServerConfigurationManager _serverConfigurationManager; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private bool _hasExited; private Stream _logFileStream; private string _targetPath; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 57424f0435..c20b080883 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { private readonly string _dataPath; private readonly object _fileDataLock = new object(); - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private T[] _items; public ItemDataProvider( diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 6d7c5ac6ee..1926e738f2 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings private readonly ICryptoProvider _cryptoProvider; private readonly ConcurrentDictionary _tokens = new ConcurrentDictionary(); - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private DateTime _lastErrorResponse; public SchedulesDirect( diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 0760e81274..68173a0ef7 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _networkManager = networkManager; _streamHelper = streamHelper; - _jsonOptions = JsonDefaults.GetOptions(); + _jsonOptions = JsonDefaults.Options; } public string Name => "HD Homerun"; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 3f9e221066..98de848bca 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Localization private List _cultures; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index c579fc8cba..700396c4cf 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.Plugins _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _pluginsPath = pluginsPath; _appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion)); - _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions()) + _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options) { WriteIndented = true }; @@ -678,7 +678,7 @@ namespace Emby.Server.Implementations.Plugins var entry = versions[x]; if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase)) { - entry.DllFiles.AddRange(Directory.EnumerateFiles(entry.Path, "*.dll", SearchOption.AllDirectories)); + entry.DllFiles = Directory.GetFiles(entry.Path, "*.dll", SearchOption.AllDirectories); if (entry.IsEnabledAndSupported) { lastName = entry.Name; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index b302303f88..a145a84237 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The options for the json Serializer. /// - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 7af52ea652..fc34f93cdd 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Updates _httpClientFactory = httpClientFactory; _config = config; _zipClient = zipClient; - _jsonSerializerOptions = JsonDefaults.GetOptions(); + _jsonSerializerOptions = JsonDefaults.Options; _pluginManager = pluginManager; } diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs index e1c9f69f61..049a4bed7b 100644 --- a/Jellyfin.Api/Controllers/ConfigurationController.cs +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Api.Controllers private readonly IServerConfigurationManager _configurationManager; private readonly IMediaEncoder _mediaEncoder; - private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.Options; /// /// Initializes a new instance of the class. diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index a5aa9bfcae..24285bfb98 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -45,7 +45,7 @@ namespace Jellyfin.Api.Controllers { _installationManager = installationManager; _pluginManager = pluginManager; - _serializerOptions = JsonDefaults.GetOptions(); + _serializerOptions = JsonDefaults.Options; _config = config; } diff --git a/Jellyfin.Data/DayOfWeekHelper.cs b/Jellyfin.Data/DayOfWeekHelper.cs index 4e75f4cfd1..8d760a1556 100644 --- a/Jellyfin.Data/DayOfWeekHelper.cs +++ b/Jellyfin.Data/DayOfWeekHelper.cs @@ -1,67 +1,21 @@ #pragma warning disable CS1591 using System; -using System.Collections.Generic; using Jellyfin.Data.Enums; namespace Jellyfin.Data { public static class DayOfWeekHelper { - public static List GetDaysOfWeek(DynamicDayOfWeek day) + public static DayOfWeek[] GetDaysOfWeek(DynamicDayOfWeek day) { - var days = new List(7); - - if (day == DynamicDayOfWeek.Sunday - || day == DynamicDayOfWeek.Weekend - || day == DynamicDayOfWeek.Everyday) - { - days.Add(DayOfWeek.Sunday); - } - - if (day == DynamicDayOfWeek.Monday - || day == DynamicDayOfWeek.Weekday - || day == DynamicDayOfWeek.Everyday) - { - days.Add(DayOfWeek.Monday); - } - - if (day == DynamicDayOfWeek.Tuesday - || day == DynamicDayOfWeek.Weekday - || day == DynamicDayOfWeek.Everyday) - { - days.Add(DayOfWeek.Tuesday); - } - - if (day == DynamicDayOfWeek.Wednesday - || day == DynamicDayOfWeek.Weekday - || day == DynamicDayOfWeek.Everyday) + return day switch { - days.Add(DayOfWeek.Wednesday); - } - - if (day == DynamicDayOfWeek.Thursday - || day == DynamicDayOfWeek.Weekday - || day == DynamicDayOfWeek.Everyday) - { - days.Add(DayOfWeek.Thursday); - } - - if (day == DynamicDayOfWeek.Friday - || day == DynamicDayOfWeek.Weekday - || day == DynamicDayOfWeek.Everyday) - { - days.Add(DayOfWeek.Friday); - } - - if (day == DynamicDayOfWeek.Saturday - || day == DynamicDayOfWeek.Weekend - || day == DynamicDayOfWeek.Everyday) - { - days.Add(DayOfWeek.Saturday); - } - - return days; + DynamicDayOfWeek.Everyday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday }, + DynamicDayOfWeek.Weekday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }, + DynamicDayOfWeek.Weekend => new[] { DayOfWeek.Saturday, DayOfWeek.Sunday }, + _ => new[] { (DayOfWeek)day } + }; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Collection.cs b/Jellyfin.Data/Entities/Libraries/Collection.cs index 39eded752d..854f17f802 100644 --- a/Jellyfin.Data/Entities/Libraries/Collection.cs +++ b/Jellyfin.Data/Entities/Libraries/Collection.cs @@ -1,3 +1,4 @@ +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix #pragma warning disable CA2227 using System.Collections.Generic; diff --git a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs index 5b03e260e2..5e27156a45 100644 --- a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs +++ b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix + using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index d92e5d9d25..0162e1acf8 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix + using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Enums; diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 42731bb119..0340cda013 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -5,6 +5,8 @@ false true true + AllEnabledByDefault + ../jellyfin.ruleset true true true @@ -24,10 +26,6 @@ GPL-3.0-only - - ../jellyfin.ruleset - - diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 1a8415ae0e..3fc44640b0 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -11,6 +11,8 @@ true true enable + AllEnabledByDefault + ../jellyfin.ruleset @@ -30,6 +32,11 @@ + + + + + @@ -37,8 +44,4 @@ - - ../jellyfin.ruleset - - diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index eab5777d5d..fd7cb5ec58 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -274,8 +274,8 @@ namespace Jellyfin.Drawing.Skia if (requiresTransparencyHack || forceCleanBitmap) { - using var codec = SKCodec.Create(NormalizePath(path)); - if (codec == null) + using SKCodec codec = SKCodec.Create(NormalizePath(path), out SKCodecResult res); + if (res != SKCodecResult.Success) { origin = GetSKEncodedOrigin(orientation); return null; @@ -345,11 +345,6 @@ namespace Jellyfin.Drawing.Skia private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin) { - if (origin == SKEncodedOrigin.Default) - { - return bitmap; - } - var needsFlip = origin == SKEncodedOrigin.LeftBottom || origin == SKEncodedOrigin.LeftTop || origin == SKEncodedOrigin.RightBottom @@ -447,7 +442,7 @@ namespace Jellyfin.Drawing.Skia } /// - public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) + public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat) { if (inputPath.Length == 0) { @@ -459,7 +454,7 @@ namespace Jellyfin.Drawing.Skia throw new ArgumentException("String can't be empty.", nameof(outputPath)); } - var skiaOutputFormat = GetImageFormat(selectedOutputFormat); + var skiaOutputFormat = GetImageFormat(outputFormat); var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor); var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer); diff --git a/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj index f89a184263..63557e91f0 100644 --- a/Jellyfin.Networking/Jellyfin.Networking.csproj +++ b/Jellyfin.Networking/Jellyfin.Networking.csproj @@ -5,6 +5,8 @@ true true enable + AllEnabledByDefault + ../jellyfin.ruleset @@ -18,10 +20,6 @@ - - ../jellyfin.ruleset - - diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs index 2f9f44ed67..8b0bd84c66 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs @@ -29,20 +29,20 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security } /// - public async Task OnEvent(GenericEventArgs e) + public async Task OnEvent(GenericEventArgs eventArgs) { await _activityManager.CreateAsync(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("AuthenticationSucceededWithUserName"), - e.Argument.User.Name), + eventArgs.Argument.User.Name), "AuthenticationSucceeded", - e.Argument.User.Id) + eventArgs.Argument.User.Id) { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("LabelIpAddressValue"), - e.Argument.SessionInfo.RemoteEndPoint), + eventArgs.Argument.SessionInfo.RemoteEndPoint), }).ConfigureAwait(false); } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs index 05201a3469..cbc9f30173 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs @@ -33,10 +33,10 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System } /// - public async Task OnEvent(TaskCompletionEventArgs e) + public async Task OnEvent(TaskCompletionEventArgs eventArgs) { - var result = e.Result; - var task = e.Task; + var result = eventArgs.Result; + var task = eventArgs.Task; if (task.ScheduledTask is IConfigurableScheduledTask activityTask && !activityTask.IsLogged) @@ -54,14 +54,14 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System { var vals = new List(); - if (!string.IsNullOrEmpty(e.Result.ErrorMessage)) + if (!string.IsNullOrEmpty(eventArgs.Result.ErrorMessage)) { - vals.Add(e.Result.ErrorMessage); + vals.Add(eventArgs.Result.ErrorMessage); } - if (!string.IsNullOrEmpty(e.Result.LongErrorMessage)) + if (!string.IsNullOrEmpty(eventArgs.Result.LongErrorMessage)) { - vals.Add(e.Result.LongErrorMessage); + vals.Add(eventArgs.Result.LongErrorMessage); } await _activityManager.CreateAsync(new ActivityLog( diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs index 91a30069e8..eb7572ac66 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs @@ -30,13 +30,13 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates } /// - public async Task OnEvent(PluginUninstalledEventArgs e) + public async Task OnEvent(PluginUninstalledEventArgs eventArgs) { await _activityManager.CreateAsync(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("PluginUninstalledWithName"), - e.Argument.Name), + eventArgs.Argument.Name), NotificationType.PluginUninstalled.ToString(), Guid.Empty)) .ConfigureAwait(false); diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs index a14911b94a..9beb6f2f25 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs @@ -30,12 +30,12 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users } /// - public async Task OnEvent(UserUpdatedEventArgs e) + public async Task OnEvent(UserUpdatedEventArgs eventArgs) { await _sessionManager.SendMessageToUserSessions( - new List { e.Argument.Id }, + new List { eventArgs.Argument.Id }, SessionMessageType.UserUpdated, - _userManager.GetUserDto(e.Argument), + _userManager.GetUserDto(eventArgs.Argument), CancellationToken.None).ConfigureAwait(false); } } diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 19c7ac5672..5a5992bd65 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -6,6 +6,8 @@ true true enable + AllEnabledByDefault + ../jellyfin.ruleset diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 9cc1c3e5e5..c99c5e4efc 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -66,7 +66,7 @@ namespace Jellyfin.Server.Implementations.Users else if (string.Equals( spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal), pin.Replace("-", string.Empty, StringComparison.Ordinal), - StringComparison.InvariantCultureIgnoreCase)) + StringComparison.OrdinalIgnoreCase)) { var resetUser = userManager.GetUserByName(spr.UserName) ?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index c8a589cabc..a3e9516b9f 100644 --- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CA1307 +#pragma warning disable CA1309 using System; using System.Collections.Generic; @@ -35,7 +36,7 @@ namespace Jellyfin.Server.Implementations.Users if (prefs == null) { - prefs = new DisplayPreferences(userId, itemId, client); + prefs = new DisplayPreferences(userId, itemId, client); _dbContext.DisplayPreferences.Add(prefs); } diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 1828f1a7eb..a3f49e6cb1 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -225,7 +225,7 @@ namespace Jellyfin.Server.Extensions .AddJsonOptions(options => { // Update all properties that are set in JsonDefaults - var jsonOptions = JsonDefaults.GetPascalCaseOptions(); + var jsonOptions = JsonDefaults.PascalCaseOptions; // From JsonDefaults options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling; diff --git a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs index 8043989b1e..c349e3dca2 100644 --- a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs +++ b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs @@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters /// /// Initializes a new instance of the class. /// - public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions()) + public CamelCaseJsonProfileFormatter() : base(JsonDefaults.CamelCaseOptions) { SupportedMediaTypes.Clear(); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType)); diff --git a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs index d0110b125c..0480f5e0ec 100644 --- a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs +++ b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.Formatters /// /// Initializes a new instance of the class. /// - public PascalCaseJsonProfileFormatter() : base(JsonDefaults.GetPascalCaseOptions()) + public PascalCaseJsonProfileFormatter() : base(JsonDefaults.PascalCaseOptions) { SupportedMediaTypes.Clear(); // Add application/json for default formatter diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 6bfb5b8786..09799307b7 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -13,7 +13,9 @@ true true enable - true + AllEnabledByDefault + ../jellyfin.ruleset + @@ -31,10 +33,6 @@ - - ../jellyfin.ruleset - - diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs index 816dd9ee74..c9710f1fd1 100644 --- a/Jellyfin.Server/Migrations/MigrationOptions.cs +++ b/Jellyfin.Server/Migrations/MigrationOptions.cs @@ -16,9 +16,12 @@ namespace Jellyfin.Server.Migrations Applied = new List<(Guid Id, string Name)>(); } +// .Net xml serializer can't handle interfaces +#pragma warning disable CA1002 // Do not expose generic lists /// /// Gets the list of applied migration routine names. /// public List<(Guid Id, string Name)> Applied { get; } +#pragma warning restore CA1002 } } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 33f039c394..d61c044478 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -76,7 +76,7 @@ namespace Jellyfin.Server.Migrations.Routines foreach (var entry in queryResult) { - UserMockup? mockup = JsonSerializer.Deserialize(entry[2].ToBlob(), JsonDefaults.GetOptions()); + UserMockup? mockup = JsonSerializer.Deserialize(entry[2].ToBlob(), JsonDefaults.Options); if (mockup == null) { continue; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 6ae0542c03..4f203b7a9d 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -222,7 +222,7 @@ namespace Jellyfin.Server } finally { - appHost?.Dispose(); + appHost.Dispose(); } if (_restartOnShutdown) @@ -623,7 +623,7 @@ namespace Jellyfin.Server string commandLineArgsString; if (options.RestartArgs != null) { - commandLineArgsString = options.RestartArgs ?? string.Empty; + commandLineArgsString = options.RestartArgs; } else { diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index ddcf2ac171..c3e4ed6db8 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Common /// /// Type to create. /// New instance of type type. - public delegate object CreationDelegate(Type type); + public delegate object CreationDelegateFactory(Type type); /// /// An interface to be implemented by the applications hosting a kernel. @@ -112,7 +112,7 @@ namespace MediaBrowser.Common /// Delegate function that gets called to create the object. /// If set to true [manage lifetime]. /// . - IReadOnlyCollection GetExports(CreationDelegate defaultFunc, bool manageLifetime = true); + IReadOnlyCollection GetExports(CreationDelegateFactory defaultFunc, bool manageLifetime = true); /// /// Gets the export types. diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 2ef24a884a..177ad39fa8 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Common.Json /// If the defaults must be modified the author must use the copy constructor. /// /// The default options. - public static JsonSerializerOptions GetOptions() + public static JsonSerializerOptions Options => _jsonSerializerOptions; /// @@ -72,7 +72,7 @@ namespace MediaBrowser.Common.Json /// If the defaults must be modified the author must use the copy constructor. /// /// The camelCase options. - public static JsonSerializerOptions GetCamelCaseOptions() + public static JsonSerializerOptions CamelCaseOptions => _camelCaseJsonSerializerOptions; /// @@ -83,7 +83,7 @@ namespace MediaBrowser.Common.Json /// If the defaults must be modified the author must use the copy constructor. /// /// The PascalCase options. - public static JsonSerializerOptions GetPascalCaseOptions() + public static JsonSerializerOptions PascalCaseOptions => _pascalCaseJsonSerializerOptions; } } diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 34e1934e25..0d9f78704e 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -33,6 +33,8 @@ false true true + AllEnabledByDefault + ../jellyfin.ruleset true true true @@ -51,10 +53,6 @@ - - ../jellyfin.ruleset - - <_Parameter1>Jellyfin.Common.Tests diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index 4a7c701907..d67b6b8e15 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -406,7 +406,7 @@ namespace MediaBrowser.Common.Net } // If we haven't resolved before, or our timer has run out... - if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved?.AddMinutes(Timeout))) + if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout))) { _lastResolved = DateTime.UtcNow; ResolveHostInternal().GetAwaiter().GetResult(); diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs index 5fab52eac7..59e37a5c6c 100644 --- a/MediaBrowser.Common/Net/IPNetAddress.cs +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -216,11 +216,11 @@ namespace MediaBrowser.Common.Net } /// - public override bool Equals(IPAddress address) + public override bool Equals(IPAddress ip) { - if (address != null && !address.Equals(IPAddress.None) && !Address.Equals(IPAddress.None)) + if (ip != null && !ip.Equals(IPAddress.None) && !Address.Equals(IPAddress.None)) { - return address.Equals(Address); + return ip.Equals(Address); } return false; diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 7b162c0e1c..ad5a7338dd 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Common.Plugins /// Gets a value indicating whether the plugin can be uninstalled. /// public bool CanUninstall => !Path.GetDirectoryName(AssemblyFilePath) - .Equals(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), StringComparison.InvariantCulture); + .Equals(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), StringComparison.Ordinal); /// /// Gets the plugin info. diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index d5c7808512..99c226f509 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -39,29 +39,27 @@ namespace MediaBrowser.Common.Plugins { ApplicationPaths = applicationPaths; XmlSerializer = xmlSerializer; - if (this is IPluginAssembly assemblyPlugin) - { - var assembly = GetType().Assembly; - var assemblyName = assembly.GetName(); - var assemblyFilePath = assembly.Location; - var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - if (!Directory.Exists(dataFolderPath) && Version != null) - { - // Try again with the version number appended to the folder name. - dataFolderPath = dataFolderPath + "_" + Version.ToString(); - } + var assembly = GetType().Assembly; + var assemblyName = assembly.GetName(); + var assemblyFilePath = assembly.Location; - assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + if (!Directory.Exists(dataFolderPath) && Version != null) + { + // Try again with the version number appended to the folder name. + dataFolderPath = dataFolderPath + "_" + Version.ToString(); + } - var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); - if (idAttributes.Length > 0) - { - var attribute = (GuidAttribute)idAttributes[0]; - var assemblyId = new Guid(attribute.Value); + SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); - assemblyPlugin.SetId(assemblyId); - } + var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); + if (idAttributes.Length > 0) + { + var attribute = (GuidAttribute)idAttributes[0]; + var assemblyId = new Guid(attribute.Value); + + SetId(assemblyId); } } diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index 23b6cfa81a..12a1ad1ec8 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Common.Plugins public LocalPlugin(string path, bool isSupported, PluginManifest manifest) { Path = path; - DllFiles = new List(); + DllFiles = Array.Empty(); _supported = isSupported; Manifest = manifest; } @@ -59,9 +59,9 @@ namespace MediaBrowser.Common.Plugins public string Path { get; } /// - /// Gets the list of dll files for this plugin. + /// Gets or sets the list of dll files for this plugin. /// - public List DllFiles { get; } + public IReadOnlyList DllFiles { get; set; } /// /// Gets or sets the instance of this plugin. diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs index d5bcd5be96..fe7cb10788 100644 --- a/MediaBrowser.Common/Progress/ActionableProgress.cs +++ b/MediaBrowser.Common/Progress/ActionableProgress.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable CA1003 using System; diff --git a/MediaBrowser.Common/Progress/SimpleProgress.cs b/MediaBrowser.Common/Progress/SimpleProgress.cs index d75675bf17..988d8ad34a 100644 --- a/MediaBrowser.Common/Progress/SimpleProgress.cs +++ b/MediaBrowser.Common/Progress/SimpleProgress.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable CA1003 using System; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 65fd1654c3..76b6d39a93 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Entities /// public class CollectionFolder : Folder, ICollectionFolder { - private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; public static IXmlSerializer XmlSerializer { get; set; } public static IServerApplicationHost ApplicationHost { get; set; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index d487a324f4..8c68b47dd7 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -34,6 +34,8 @@ false true true + AllEnabledByDefault + ../jellyfin.ruleset true true true @@ -52,8 +54,4 @@ - - ../jellyfin.ruleset - - diff --git a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs index c129eddb3a..f78bd6ddf4 100644 --- a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs +++ b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs @@ -10,6 +10,6 @@ namespace MediaBrowser.Controller.Providers /// public interface ILocalImageProvider : IImageProvider { - List GetImages(BaseItem item, IDirectoryService directoryService); + IEnumerable GetImages(BaseItem item, IDirectoryService directoryService); } } diff --git a/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs index 556bb6a0e4..b6189bcdd6 100644 --- a/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.LocalMetadata.Images } /// - public List GetImages(BaseItem item, IDirectoryService directoryService) + public IEnumerable GetImages(BaseItem item, IDirectoryService directoryService) { var collectionFolder = (CollectionFolder)item; diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs index 393ad2efb5..2d3b2d889c 100644 --- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.LocalMetadata.Images } /// - public List GetImages(BaseItem item, IDirectoryService directoryService) + public IEnumerable GetImages(BaseItem item, IDirectoryService directoryService) { var parentPath = Path.GetDirectoryName(item.Path); diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs index 509b5d700d..10d691b3e9 100644 --- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -69,13 +70,13 @@ namespace MediaBrowser.LocalMetadata.Images } /// - public List GetImages(BaseItem item, IDirectoryService directoryService) + public IEnumerable GetImages(BaseItem item, IDirectoryService directoryService) { var path = item.GetInternalMetadataPath(); if (!Directory.Exists(path)) { - return new List(); + return Enumerable.Empty(); } try @@ -85,7 +86,7 @@ namespace MediaBrowser.LocalMetadata.Images catch (IOException ex) { _logger.LogError(ex, "Error while getting images for {Library}", item.Name); - return new List(); + return Enumerable.Empty(); } } } diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 84c3ed8b0b..7ad8c24e89 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -108,7 +108,7 @@ namespace MediaBrowser.LocalMetadata.Images { if (!item.IsFileProtocol) { - return new List(); + return Enumerable.Empty(); } var path = item.ContainingFolderPath; @@ -116,7 +116,7 @@ namespace MediaBrowser.LocalMetadata.Images // Exit if the cache dir does not exist, alternative solution is to create it, but that's a lot of empty dirs... if (!Directory.Exists(path)) { - return Array.Empty(); + return Enumerable.Empty(); } if (includeDirectories) @@ -133,7 +133,7 @@ namespace MediaBrowser.LocalMetadata.Images } /// - public List GetImages(BaseItem item, IDirectoryService directoryService) + public IEnumerable GetImages(BaseItem item, IDirectoryService directoryService) { var files = GetFiles(item, true, directoryService).ToList(); @@ -151,7 +151,7 @@ namespace MediaBrowser.LocalMetadata.Images /// The images path. /// Instance of the interface. /// The local image info. - public List GetImages(BaseItem item, string path, IDirectoryService directoryService) + public IEnumerable GetImages(BaseItem item, string path, IDirectoryService directoryService) { return GetImages(item, new[] { path }, directoryService); } @@ -163,7 +163,7 @@ namespace MediaBrowser.LocalMetadata.Images /// The image paths. /// Instance of the interface. /// The local image info. - public List GetImages(BaseItem item, IEnumerable paths, IDirectoryService directoryService) + public IEnumerable GetImages(BaseItem item, IEnumerable paths, IDirectoryService directoryService) { IEnumerable files = paths.SelectMany(i => _fileSystem.GetFiles(i, BaseItem.SupportedImageExtensions, true, false)); @@ -181,9 +181,7 @@ namespace MediaBrowser.LocalMetadata.Images { if (supportParentSeriesFiles) { - var season = item as Season; - - if (season != null) + if (item is Season season) { PopulateSeasonImagesFromSeriesFolder(season, images, directoryService); } diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 1792f1d9b6..eb2077a5ff 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -16,6 +16,8 @@ true true enable + AllEnabledByDefault + ../jellyfin.ruleset @@ -29,8 +31,4 @@ - - ../jellyfin.ruleset - - diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index b0afb834b1..5f620634fb 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -1275,8 +1275,8 @@ namespace MediaBrowser.LocalMetadata.Parsers // Only split by comma if there is no pipe in the string // We have to be careful to not split names like Matthew, Jr. - var separator = value.IndexOf('|', StringComparison.Ordinal) == -1 - && value.IndexOf(';', StringComparison.Ordinal) == -1 ? new[] { ',' } : new[] { '|', ';' }; + var separator = !value.Contains('|', StringComparison.Ordinal) + && !value.Contains(';', StringComparison.Ordinal) ? new[] { ',' } : new[] { '|', ';' }; value = value.Trim().Trim(separator); diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs index ff846830bf..7df800971a 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.LocalMetadata.Parsers } /// - protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult item) + protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) { switch (reader.Name) { @@ -33,7 +33,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { using (var subReader = reader.ReadSubtree()) { - FetchFromCollectionItemsNode(subReader, item); + FetchFromCollectionItemsNode(subReader, itemResult); } } else @@ -44,7 +44,7 @@ namespace MediaBrowser.LocalMetadata.Parsers break; default: - base.FetchDataFromXmlNode(reader, item); + base.FetchDataFromXmlNode(reader, itemResult); break; } } diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs index 78c0fa8ad3..b84307cb20 100644 --- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs @@ -23,9 +23,9 @@ namespace MediaBrowser.LocalMetadata.Parsers } /// - protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult result) + protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) { - var item = result.Item; + var item = itemResult.Item; switch (reader.Name) { @@ -53,7 +53,7 @@ namespace MediaBrowser.LocalMetadata.Parsers break; default: - base.FetchDataFromXmlNode(reader, result); + base.FetchDataFromXmlNode(reader, itemResult); break; } } diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index e59fcb965b..dfbce5f49a 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text; using System.Threading; using System.Xml; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -37,7 +36,7 @@ namespace MediaBrowser.LocalMetadata.Savers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) + protected BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) { FileSystem = fileSystem; ConfigurationManager = configurationManager; @@ -421,20 +420,17 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteEndElement(); } - var boxset = item as BoxSet; - if (boxset != null) + if (item is BoxSet boxset) { AddLinkedChildren(boxset, writer, "CollectionItems", "CollectionItem"); } - var playlist = item as Playlist; - if (playlist != null && !Playlist.IsPlaylistFile(playlist.Path)) + if (item is Playlist playlist && !Playlist.IsPlaylistFile(playlist.Path)) { AddLinkedChildren(playlist, writer, "PlaylistItems", "PlaylistItem"); } - var hasShares = item as IHasShares; - if (hasShares != null) + if (item is IHasShares hasShares) { AddShares(hasShares, writer); } @@ -542,10 +538,5 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteEndElement(); } - - private bool IsPersonType(PersonInfo person, string type) - { - return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 8a25a64c74..47cf020b4e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -86,7 +86,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _localization = localization; _encodingHelperFactory = encodingHelperFactory; _startupOptionFFmpegPath = config.GetValue(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty; - _jsonSerializerOptions = JsonDefaults.GetOptions(); + _jsonSerializerOptions = JsonDefaults.Options; } /// diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs index cd9e477432..2adb119087 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb { private readonly IServerConfigurationManager _config; private readonly IHttpClientFactory _httpClientFactory; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory) { diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index 0a79f5bb59..00feeec1f9 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; private readonly IHttpClientFactory _httpClientFactory; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; public static AudioDbAlbumProvider Current; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs index 36700d1917..b8095ff04c 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb { private readonly IServerConfigurationManager _config; private readonly IHttpClientFactory _httpClientFactory; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; public AudioDbArtistImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory) { diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index 4b1d915671..59ecbc0171 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; private readonly IHttpClientFactory _httpClientFactory; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory) { diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 97fcbfb6fe..428b0ded11 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb _configurationManager = configurationManager; _appHost = appHost; - _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions()); + _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options); _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter()); _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter()); } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index e3301ff329..d35805a84b 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb _configurationManager = configurationManager; _appHost = appHost; - _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions()); + _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options); _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter()); _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter()); } diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs index c39ef0ce99..415682e855 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs @@ -16,7 +16,7 @@ namespace Jellyfin.MediaEncoding.Tests var path = Path.Join("Test Data", fileName); using (var stream = File.OpenRead(path)) { - await JsonSerializer.DeserializeAsync(stream, JsonDefaults.GetOptions()).ConfigureAwait(false); + await JsonSerializer.DeserializeAsync(stream, JsonDefaults.Options).ConfigureAwait(false); } } } diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs index 86d6326d84..f5411dcb8d 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers public sealed class DashboardControllerTests : IClassFixture { private readonly JellyfinApplicationFactory _factory; - private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.Options; public DashboardControllerTests(JellyfinApplicationFactory factory) { From 46a41ecba66f8055a9e7de4d0dd4a1e07ea371a3 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 17:01:14 +0100 Subject: [PATCH 128/282] Sunday isn't a weekend --- Jellyfin.Data/DayOfWeekHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Data/DayOfWeekHelper.cs b/Jellyfin.Data/DayOfWeekHelper.cs index 8d760a1556..1bfd4a0c69 100644 --- a/Jellyfin.Data/DayOfWeekHelper.cs +++ b/Jellyfin.Data/DayOfWeekHelper.cs @@ -12,8 +12,8 @@ namespace Jellyfin.Data return day switch { DynamicDayOfWeek.Everyday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday }, - DynamicDayOfWeek.Weekday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }, - DynamicDayOfWeek.Weekend => new[] { DayOfWeek.Saturday, DayOfWeek.Sunday }, + DynamicDayOfWeek.Weekday => new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }, + DynamicDayOfWeek.Weekend => new[] { DayOfWeek.Sunday, DayOfWeek.Saturday }, _ => new[] { (DayOfWeek)day } }; } From 01dfa8801a74f8c6802aea33d9bce0e421601faa Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 13 Mar 2021 22:40:23 +0100 Subject: [PATCH 129/282] Fix GetDaysOfWeek behavior --- Jellyfin.Data/DayOfWeekHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Data/DayOfWeekHelper.cs b/Jellyfin.Data/DayOfWeekHelper.cs index 1bfd4a0c69..b7ba30180e 100644 --- a/Jellyfin.Data/DayOfWeekHelper.cs +++ b/Jellyfin.Data/DayOfWeekHelper.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Data { return day switch { - DynamicDayOfWeek.Everyday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday }, + DynamicDayOfWeek.Everyday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday }, DynamicDayOfWeek.Weekday => new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }, DynamicDayOfWeek.Weekend => new[] { DayOfWeek.Sunday, DayOfWeek.Saturday }, _ => new[] { (DayOfWeek)day } From b1f0c5eb4935904bea3a784e147d403b9d43097d Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 13 Mar 2021 22:16:12 +0000 Subject: [PATCH 130/282] Update NetworkExtensions.cs changed description --- MediaBrowser.Common/Net/NetworkExtensions.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs index cd0c2ea242..26f21b5f14 100644 --- a/MediaBrowser.Common/Net/NetworkExtensions.cs +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -27,10 +27,11 @@ namespace MediaBrowser.Common.Net /// /// The . /// Item to add. - /// True if subnets that overlap should be merged (default). - public static void AddItem(this Collection source, IPObject item, bool unique = true) + /// If true the values are treated as subnets. + /// If false items are addresses. + public static void AddItem(this Collection source, IPObject item, bool itemsAreNetworks = true) { - if (!source.ContainsAddress(item) || !unique) + if (!source.ContainsAddress(item) || !itemsAreNetworks) { source.Add(item); } From 72d3eed15c0a24612cc8ff90475a9e19ff8c7558 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 14 Mar 2021 01:07:53 +0100 Subject: [PATCH 131/282] Fix integration test project --- Jellyfin.sln | 9 +++++++-- .../Jellyfin.Server.Integration.Tests.csproj | 1 - tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 7 ++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index 7b81f43462..0f36e283c6 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -76,10 +76,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -214,6 +214,10 @@ Global {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.Build.0 = Release|Any CPU + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -230,6 +234,7 @@ Global {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 49004966b9..b0a38736ae 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -23,7 +23,6 @@ - diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 65ea28e94d..a310b0ea96 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -5,6 +5,8 @@ false true enable + AllEnabledByDefault + ../jellyfin-tests.ruleset @@ -22,7 +24,6 @@ - @@ -32,8 +33,4 @@ - - ../jellyfin-tests.ruleset - - From 6087831aa65398a6305570c4dc84f0bc2613b842 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 14 Mar 2021 17:30:25 +0000 Subject: [PATCH 132/282] Fixed selection of correct interface ip --- Jellyfin.Networking/Manager/NetworkManager.cs | 26 +++++++++++++------ MediaBrowser.Common/Net/NetworkExtensions.cs | 7 ++--- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index bf121737b6..9ce3b7e49a 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -274,7 +274,7 @@ namespace Jellyfin.Networking.Manager if (_bindExclusions.Count > 0) { // Return all the interfaces except the ones specifically excluded. - return _interfaceAddresses.Exclude(_bindExclusions); + return _interfaceAddresses.Exclude(_bindExclusions, false); } if (individualInterfaces) @@ -299,7 +299,7 @@ namespace Jellyfin.Networking.Manager } // Remove any excluded bind interfaces. - return _bindAddresses.Exclude(_bindExclusions); + return _bindAddresses.Exclude(_bindExclusions, false); } /// @@ -388,7 +388,7 @@ namespace Jellyfin.Networking.Manager // Get the first LAN interface address that isn't a loopback. var interfaces = CreateCollection( _interfaceAddresses - .Exclude(_bindExclusions) + .Exclude(_bindExclusions, false) .Where(IsInLocalNetwork) .OrderBy(p => p.Tag)); @@ -396,6 +396,16 @@ namespace Jellyfin.Networking.Manager { if (haveSource) { + foreach (var intf in interfaces) + { + if (intf.Address.Equals(source.Address)) + { + result = FormatIP6String(intf.Address); + _logger.LogDebug("{Source}: GetBindInterface: Has found matching interface. {Result}", source, result); + return result; + } + } + // Does the request originate in one of the interface subnets? // (For systems with multiple internal network cards, and multiple subnets) foreach (var intf in interfaces) @@ -522,10 +532,10 @@ namespace Jellyfin.Networking.Manager { if (filter == null) { - return _lanSubnets.Exclude(_excludedSubnets).AsNetworks(); + return _lanSubnets.Exclude(_excludedSubnets, true).AsNetworks(); } - return _lanSubnets.Exclude(filter); + return _lanSubnets.Exclude(filter, true); } /// @@ -1018,7 +1028,7 @@ namespace Jellyfin.Networking.Manager _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString()); _logger.LogInformation("Defined LAN exclusions : {0}", _excludedSubnets.AsString()); - _logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets).AsNetworks().AsString()); + _logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets, true).AsNetworks().AsString()); } } @@ -1207,7 +1217,7 @@ namespace Jellyfin.Networking.Manager private bool MatchesBindInterface(IPObject source, bool isInExternalSubnet, out string result) { result = string.Empty; - var addresses = _bindAddresses.Exclude(_bindExclusions); + var addresses = _bindAddresses.Exclude(_bindExclusions, false); int count = addresses.Count; if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any))) @@ -1292,7 +1302,7 @@ namespace Jellyfin.Networking.Manager result = string.Empty; // Get the first WAN interface address that isn't a loopback. var extResult = _interfaceAddresses - .Exclude(_bindExclusions) + .Exclude(_bindExclusions, false) .Where(p => !IsInLocalNetwork(p)) .OrderBy(p => p.Tag); diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs index 26f21b5f14..93cfb4817b 100644 --- a/MediaBrowser.Common/Net/NetworkExtensions.cs +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.Common.Net /// /// The . /// Item to add. - /// If true the values are treated as subnets. + /// If true the values are treated as subnets. /// If false items are addresses. public static void AddItem(this Collection source, IPObject item, bool itemsAreNetworks = true) { @@ -192,8 +192,9 @@ namespace MediaBrowser.Common.Net /// /// The . /// Items to exclude. + /// Collection is a network collection. /// A new collection, with the items excluded. - public static Collection Exclude(this Collection source, Collection excludeList) + public static Collection Exclude(this Collection source, Collection excludeList, bool isNetwork) { if (source.Count == 0 || excludeList == null) { @@ -218,7 +219,7 @@ namespace MediaBrowser.Common.Net if (!found) { - results.AddItem(outer); + results.AddItem(outer, isNetwork); } } From ab0cff8556403e123642dc9717ba778329554634 Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 14 Mar 2021 19:56:45 +0100 Subject: [PATCH 133/282] do not resolve episode-like files if they are in extras folders --- .../Library/Resolvers/BaseVideoResolver.cs | 2 +- .../Library/Resolvers/Books/BookResolver.cs | 2 +- .../Library/Resolvers/Movies/MovieResolver.cs | 208 +++++++++--------- .../Library/Resolvers/TV/EpisodeResolver.cs | 12 +- .../Resolvers/BaseItemResolver.cs | 2 +- 5 files changed, 114 insertions(+), 112 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 2f5e46038d..dd92e252fa 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library.Resolvers /// /// The args. /// `0. - protected override T Resolve(ItemResolveArgs args) + public override T Resolve(ItemResolveArgs args) { return ResolveVideo(args, false); } diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 86242d1379..0525c7e307 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books { private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" }; - protected override Book Resolve(ItemResolveArgs args) + public override Book Resolve(ItemResolveArgs args) { var collectionType = args.GetCollectionType(); diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 8ef7172de1..714bc3a84d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -69,6 +69,110 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return result; } + /// + /// Resolves the specified args. + /// + /// The args. + /// Video. + public override Video Resolve(ItemResolveArgs args) + { + var collectionType = args.GetCollectionType(); + + // Find movies with their own folders + if (args.IsDirectory) + { + if (IsInvalid(args.Parent, collectionType)) + { + return null; + } + + var files = args.FileSystemChildren + .Where(i => !LibraryManager.IgnoreFile(i, args.Parent)) + .ToList(); + + if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) + { + return FindMovie(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); + } + + if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) + { + return FindMovie /// The args. /// Episode. - protected override Episode Resolve(ItemResolveArgs args) + public override Episode Resolve(ItemResolveArgs args) { var parent = args.Parent; @@ -34,11 +35,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV season = parent.GetParents().OfType().FirstOrDefault(); } - // If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something + // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // Also handle flat tv folders - if (season != null || - string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || - args.HasParent()) + if ((season != null || + string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || + args.HasParent()) + && !BaseItem.AllExtrasTypesFolderNames.Contains(parent.Name, StringComparer.OrdinalIgnoreCase)) { var episode = ResolveVideo(args, false); diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs index 67acdd9a3c..25128a5cd0 100644 --- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Resolvers /// /// The args. /// `0. - protected virtual T Resolve(ItemResolveArgs args) + public virtual T Resolve(ItemResolveArgs args) { return null; } From 025e351f619137426a0a64074ca2ada863ccc493 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 15 Mar 2021 08:25:20 +0100 Subject: [PATCH 134/282] add unit tests --- .../Library/Resolvers/BaseVideoResolver.cs | 2 +- .../Library/Resolvers/TV/EpisodeResolver.cs | 20 +++--- .../Library/EpisodeResolverTest.cs | 65 +++++++++++++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index dd92e252fa..6e688693be 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Library.Resolvers /// The args. /// if set to true [parse name]. /// ``0. - protected TVideoType ResolveVideo(ItemResolveArgs args, bool parseName) + protected virtual TVideoType ResolveVideo(ItemResolveArgs args, bool parseName) where TVideoType : Video, new() { var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index da65c746d9..9b4cd7a3df 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -12,6 +12,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// public class EpisodeResolver : BaseVideoResolver { + /// + /// Initializes a new instance of the class. + /// + /// The library manager. + public EpisodeResolver(ILibraryManager libraryManager) + : base(libraryManager) + { + } + /// /// Resolves the specified args. /// @@ -40,7 +49,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV if ((season != null || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || args.HasParent()) - && !BaseItem.AllExtrasTypesFolderNames.Contains(parent.Name, StringComparer.OrdinalIgnoreCase)) + && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.Contains(parent.Name, StringComparer.OrdinalIgnoreCase))) { var episode = ResolveVideo(args, false); @@ -76,14 +85,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return null; } - - /// - /// Initializes a new instance of the class. - /// - /// The library manager. - public EpisodeResolver(ILibraryManager libraryManager) - : base(libraryManager) - { - } } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs new file mode 100644 index 0000000000..8765192150 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs @@ -0,0 +1,65 @@ +using System; +using Emby.Server.Implementations.Library.Resolvers.TV; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Library +{ + public class EpisodeResolverTest + { + [Fact] + public void Resolve_GivenVideoInExtrasFolder_DoesNotResolveToEpisode() + { + var season = new Season { Name = "Season 1" }; + var parent = new Folder { Name = "extras" }; + var libraryManagerMock = new Mock(); + libraryManagerMock.Setup(x => x.GetItemById(It.IsAny())).Returns(season); + + var episodeResolver = new EpisodeResolver(libraryManagerMock.Object); + var itemResolveArgs = new ItemResolveArgs( + Mock.Of(), + Mock.Of()) + { + Parent = parent, + CollectionType = CollectionType.TvShows, + Path = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv" + }; + + Assert.Null(episodeResolver.Resolve(itemResolveArgs)); + } + + [Fact] + public void Resolve_GivenVideoInExtrasSeriesFolder_ResolvesToEpisode() + { + var series = new Series { Name = "Extras" }; + + // Have to create a mock because of moq proxies not being castable to a concrete implementation + // https://github.com/jellyfin/jellyfin/blob/ab0cff8556403e123642dc9717ba778329554634/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs#L48 + var episodeResolver = new EpisodeResolverMock(Mock.Of()); + var itemResolveArgs = new ItemResolveArgs( + Mock.Of(), + Mock.Of()) + { + Parent = series, + CollectionType = CollectionType.TvShows, + Path = "Extras/Extras S01E01.mkv" + }; + Assert.NotNull(episodeResolver.Resolve(itemResolveArgs)); + } + + private class EpisodeResolverMock : EpisodeResolver + { + public EpisodeResolverMock(ILibraryManager libraryManager) : base(libraryManager) + { + } + + protected override TVideoType ResolveVideo(ItemResolveArgs args, bool parseName) => new (); + } + } +} From 23c3188501447db9def5cd5345112a76cfe04b83 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 15 Mar 2021 23:24:59 +0100 Subject: [PATCH 135/282] revert underscore as a multiversion separator --- Emby.Naming/Video/VideoListResolver.cs | 1 - tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index d71d609540..7b6a1705ba 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -235,7 +235,6 @@ namespace Emby.Naming.Video // The CleanStringParser should have removed common keywords etc. return string.IsNullOrEmpty(testFilename) || testFilename[0] == '-' - || testFilename[0] == '_' || Regex.IsMatch(testFilename, @"^\[([^]]*)\]"); } diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index 2af6667595..6e803593e8 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -295,12 +295,9 @@ namespace Jellyfin.Naming.Tests.Video FullName = i }).ToList()).ToList(); - Assert.Single(result); + Assert.Equal(7, result.Count); Assert.Empty(result[0].Extras); - Assert.Equal(6, result[0].AlternateVersions.Count); - Assert.False(result[0].AlternateVersions[2].Is3D); - Assert.True(result[0].AlternateVersions[3].Is3D); - Assert.True(result[0].AlternateVersions[4].Is3D); + Assert.Empty(result[0].AlternateVersions); } [Fact] From e14311ca8b86fa14033d2c02f9b003f235fa7e6a Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 16 Mar 2021 17:40:56 +0000 Subject: [PATCH 136/282] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- .../Localization/Core/eo.json | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index 3ff7eddae3..ca615cc8cf 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -22,5 +22,26 @@ "Artists": "Artistoj", "Application": "Aplikaĵo", "AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}", - "Albums": "Albumoj" + "Albums": "Albumoj", + "TasksLibraryCategory": "Libraro", + "VersionNumber": "Versio {0}", + "UserDownloadingItemWithValues": "{0} elŝutas {1}", + "UserCreatedWithName": "Uzanto {0} kreiĝis", + "User": "Uzanto", + "System": "Sistemo", + "Songs": "Kantoj", + "ScheduledTaskStartedWithName": "{0} komencis", + "ScheduledTaskFailedWithName": "{0} malsukcesis", + "PluginUninstalledWithName": "{0} malinstaliĝis", + "PluginInstalledWithName": "{0} instaliĝis", + "Plugin": "Kromprogramo", + "Playlists": "Ludlistoj", + "Photos": "Fotoj", + "NotificationOptionPluginUninstalled": "Kromprogramo malinstaliĝis", + "NotificationOptionNewLibraryContent": "Nova enhavo aldoniĝis", + "NotificationOptionPluginInstalled": "Kromprogramo instaliĝis", + "MusicVideos": "Muzikvideoj", + "LabelIpAddressValue": "IP-adreso: {0}", + "Genres": "Ĝenroj", + "DeviceOfflineWithName": "{0} malkonektis" } From 14cbd22fbe0a1989f70895edbd0a0f52d850d55e Mon Sep 17 00:00:00 2001 From: David Date: Tue, 16 Mar 2021 21:11:34 +0100 Subject: [PATCH 137/282] Use Helper Methods for provider url parsing --- .../Library/PathExtensions.cs | 5 +- .../Providers/ProviderIdParsers.cs | 119 ++++++++++++++++++ .../Parsers/BaseNfoParser.cs | 35 +++--- .../Parsers/SeriesNfoParser.cs | 3 - .../Providers/ProviderIdParserTests.cs | 63 ++++++++++ 5 files changed, 201 insertions(+), 24 deletions(-) create mode 100644 MediaBrowser.Common/Providers/ProviderIdParsers.cs create mode 100644 tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 7dcc925c2f..72fe5a854a 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text.RegularExpressions; +using MediaBrowser.Common.Providers; namespace Emby.Server.Implementations.Library { @@ -43,8 +44,8 @@ namespace Emby.Server.Implementations.Library // for imdbid we also accept pattern matching if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase)) { - var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase); - return m.Success ? m.Value : null; + var match = ProviderIdParsers.TryParseImdbId(str, out var imdbId); + return match ? imdbId : null; } return null; diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs new file mode 100644 index 0000000000..56e0112ddc --- /dev/null +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -0,0 +1,119 @@ +#nullable enable + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace MediaBrowser.Common.Providers +{ + /// + /// Parsers for provider ids. + /// + public static class ProviderIdParsers + { + /// + /// Parses an IMDb id from a string. + /// + /// The text to parse. + /// The parsed IMDb id. + /// True if parsing was successful, false otherwise. + public static bool TryParseImdbId(string text, [NotNullWhen(true)] out string? imdbId) + { + var span = text.AsSpan(); + var tt = "tt".AsSpan(); + + while (true) + { + var ttPos = span.IndexOf(tt); + if (ttPos == -1) + { + imdbId = default; + return false; + } + + span = span.Slice(ttPos + tt.Length); + + int i = 0; + // IMDb id has a maximum of 8 digits + int max = span.Length > 8 ? 8 : span.Length; + for (; i < max; i++) + { + var c = span[i]; + + if (c < '0' || c > '9') + { + break; + } + } + + // IMDb id has a minimum of 7 digits + if (i >= 7) + { + imdbId = string.Concat(tt, span.Slice(0, i)); + return true; + } + } + } + + /// + /// Parses an TMDb id from a movie url. + /// + /// The text with the url to parse. + /// The parsed TMDb id. + /// True if parsing was successful, false otherwise. + public static bool TryParseTmdbMovieId(string text, [NotNullWhen(true)] out string? tmdbId) + => TryParseProviderId(text, "themoviedb.org/movie/", out tmdbId); + + /// + /// Parses an TMDb id from a series url. + /// + /// The text with the url to parse. + /// The parsed TMDb id. + /// True if parsing was successful, false otherwise. + public static bool TryParseTmdbSeriesId(string text, [NotNullWhen(true)] out string? tmdbId) + => TryParseProviderId(text, "themoviedb.org/tv/", out tmdbId); + + /// + /// Parses an TVDb id from a url. + /// + /// The text with the url to parse. + /// The parsed TVDb id. + /// True if parsing was successful, false otherwise. + public static bool TryParseTvdbId(string text, [NotNullWhen(true)] out string? tvdbId) + => TryParseProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId); + + private static bool TryParseProviderId(string text, string searchString, [NotNullWhen(true)] out string? providerId) + { + var span = text.AsSpan(); + var searchSpan = searchString.AsSpan(); + + while (true) + { + var searchPos = span.IndexOf(searchSpan); + if (searchPos == -1) + { + providerId = default; + return false; + } + + span = span.Slice(searchPos + searchSpan.Length); + + int i = 0; + for (; i < span.Length; i++) + { + var c = span[i]; + + if (c < '0' || c > '9') + { + break; + } + } + + if (i >= 1) + { + providerId = span.Slice(0, i).ToString(); + return true; + } + } + } + } +} diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 3129c131d0..d2fa120e8c 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -6,11 +6,12 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Xml; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Providers; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -63,8 +64,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers protected virtual bool SupportsUrlAfterClosingXmlTag => false; - protected virtual string TmdbRegex => "themoviedb\\.org\\/movie\\/([0-9]+)"; - /// /// Fetches metadata for an item from one xml file. /// @@ -220,31 +219,29 @@ namespace MediaBrowser.XbmcMetadata.Parsers protected void ParseProviderLinks(T item, string xml) { - // IMDB: - // https://www.imdb.com/title/tt4154796 - var imdbRegex = Regex.Match(xml, "tt([0-9]{7,8})", RegexOptions.Compiled); - if (imdbRegex.Success) + if (ProviderIdParsers.TryParseImdbId(xml, out var imdbId)) { - item.SetProviderId(MetadataProvider.Imdb, imdbRegex.Value); + item.SetProviderId(MetadataProvider.Imdb, imdbId); } - // TMDB: - // https://www.themoviedb.org/movie/30287-fallo (movie) - // https://www.themoviedb.org/tv/1668-friends (tv) - var tmdbRegex = Regex.Match(xml, TmdbRegex, RegexOptions.Compiled); - if (tmdbRegex.Success) + if (item is Movie) { - item.SetProviderId(MetadataProvider.Tmdb, tmdbRegex.Groups[1].Value); + if (ProviderIdParsers.TryParseTmdbMovieId(xml, out var tmdbId)) + { + item.SetProviderId(MetadataProvider.Tmdb, tmdbId); + } } - // TVDB: - // https://www.thetvdb.com/?tab=series&id=121361 if (item is Series) { - var tvdbRegex = Regex.Match(xml, "thetvdb\\.com\\/\\?tab=series\\&id=([0-9]+)", RegexOptions.Compiled); - if (tvdbRegex.Success) + if (ProviderIdParsers.TryParseTmdbSeriesId(xml, out var tmdbId)) + { + item.SetProviderId(MetadataProvider.Tmdb, tmdbId); + } + + if (ProviderIdParsers.TryParseTvdbId(xml, out var tvdbId)) { - item.SetProviderId(MetadataProvider.Tvdb, tvdbRegex.Groups[1].Value); + item.SetProviderId(MetadataProvider.Tvdb, tvdbId); } } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index c09781b1ae..1dce378dca 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -35,9 +35,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// protected override bool SupportsUrlAfterClosingXmlTag => true; - /// - protected override string TmdbRegex => "themoviedb\\.org\\/tv\\/([0-9]+)"; - /// protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) { diff --git a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs new file mode 100644 index 0000000000..a493dda64f --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs @@ -0,0 +1,63 @@ +using MediaBrowser.Common.Providers; +using Xunit; + +namespace Jellyfin.Common.Tests.Providers +{ + public class ProviderIdParserTests + { + [Theory] + [InlineData("tt123456", false, null)] + [InlineData("tt1234567", true, "tt1234567")] + [InlineData("tt12345678", true, "tt12345678")] + [InlineData("https://www.imdb.com/title/tt123456", false, null)] + [InlineData("https://www.imdb.com/title/tt1234567", true, "tt1234567")] + [InlineData("https://www.imdb.com/title/tt12345678", true, "tt12345678")] + [InlineData(@"multiline\nhttps://www.imdb.com/title/tt1234567", true, "tt1234567")] + [InlineData(@"multiline\nhttps://www.imdb.com/title/tt12345678", true, "tt12345678")] + [InlineData("Jellyfin", false, null)] + [InlineData("tt1234567tt7654321", true, "tt1234567")] + [InlineData("tt12345678tt7654321", true, "tt12345678")] + public void Parse_Imdb(string text, bool shouldSucceed, string? imdbId) + { + var succeeded = ProviderIdParsers.TryParseImdbId(text, out string? parsedId); + Assert.Equal(shouldSucceed, succeeded); + Assert.Equal(imdbId, parsedId); + } + + [Theory] + [InlineData("https://www.themoviedb.org/movie/30287-fallo", true, "30287")] + [InlineData("themoviedb.org/movie/30287", true, "30287")] + [InlineData("https://www.themoviedb.org/movie/fallo-30287", false, null)] + [InlineData("https://www.themoviedb.org/tv/1668-friends", false, null)] + public void Parse_TmdbMovie(string text, bool shouldSucceed, string? tmdbId) + { + var succeeded = ProviderIdParsers.TryParseTmdbMovieId(text, out string? parsedId); + Assert.Equal(shouldSucceed, succeeded); + Assert.Equal(tmdbId, parsedId); + } + + [Theory] + [InlineData("https://www.themoviedb.org/tv/1668-friends", true, "1668")] + [InlineData("themoviedb.org/tv/1668", true, "1668")] + [InlineData("https://www.themoviedb.org/tv/friends-1668", false, null)] + [InlineData("https://www.themoviedb.org/movie/30287-fallo", false, null)] + public void Parse_TmdbSeries(string text, bool shouldSucceed, string? tmdbId) + { + var succeeded = ProviderIdParsers.TryParseTmdbSeriesId(text, out string? parsedId); + Assert.Equal(shouldSucceed, succeeded); + Assert.Equal(tmdbId, parsedId); + } + + [Theory] + [InlineData("https://www.thetvdb.com/?tab=series&id=121361", true, "121361")] + [InlineData("thetvdb.com/?tab=series&id=121361", true, "121361")] + [InlineData("thetvdb.com/?tab=series&id=Jellyfin121361", false, null)] + [InlineData("https://www.themoviedb.org/tv/1668-friends", false, null)] + public void Parse_Tvdb(string text, bool shouldSucceed, string? tvdbId) + { + var succeeded = ProviderIdParsers.TryParseTvdbId(text, out string? parsedId); + Assert.Equal(shouldSucceed, succeeded); + Assert.Equal(tvdbId, parsedId); + } + } +} From 37aa3e8735bf0089333cd19d6a0d03ed496e9f17 Mon Sep 17 00:00:00 2001 From: Vitorvlv Date: Wed, 17 Mar 2021 01:13:53 +0000 Subject: [PATCH 138/282] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 5ec8f1e88a..323dcced07 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -16,7 +16,7 @@ "Folders": "Pastas", "Genres": "Gêneros", "HeaderAlbumArtists": "Artistas do Álbum", - "HeaderContinueWatching": "Continuar Assistindo", + "HeaderContinueWatching": "Continuar assistindo", "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteEpisodes": "Episódios favoritos", From 151156f2271104277a575054aea9e5b0f92d6591 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 17 Mar 2021 10:29:45 +0100 Subject: [PATCH 139/282] Clean the entity name for non-words before searching --- .../Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs | 11 +++++++++-- .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 3 ++- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 3 ++- MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs | 14 ++++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs index fcd8e614c1..ca1af6c499 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; @@ -19,11 +20,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + private readonly ILibraryManager _libraryManager; - public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) + public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager, ILibraryManager libraryManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; + _libraryManager = libraryManager; } public string Name => TmdbUtils.ProviderName; @@ -83,7 +86,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets // We don't already have an Id, need to fetch it if (tmdbId <= 0) { - var searchResults = await _tmdbClientManager.SearchCollectionAsync(id.Name, language, cancellationToken).ConfigureAwait(false); + // ParseName is required here. + // Caller provides the filename with extension stripped and NOT the parsed filename + var parsedName = _libraryManager.ParseName(id.Name); + var cleanedName = TmdbUtils.CleanName(parsedName.Name); + var searchResults = await _tmdbClientManager.SearchCollectionAsync(cleanedName, language, cancellationToken).ConfigureAwait(false); if (searchResults != null && searchResults.Count > 0) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 2cd1ee717a..4963777bc4 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -140,7 +140,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies // ParseName is required here. // Caller provides the filename with extension stripped and NOT the parsed filename var parsedName = _libraryManager.ParseName(info.Name); - var searchResults = await _tmdbClientManager.SearchMovieAsync(parsedName.Name, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var cleanedName = TmdbUtils.CleanName(parsedName.Name); + var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); if (searchResults.Count > 0) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 74c2acf473..496e1ae256 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -189,7 +189,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // ParseName is required here. // Caller provides the filename with extension stripped and NOT the parsed filename var parsedName = _libraryManager.ParseName(info.Name); - var searchResults = await _tmdbClientManager.SearchSeriesAsync(parsedName.Name, info.MetadataLanguage, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false); + var cleanedName = TmdbUtils.CleanName(parsedName.Name); + var searchResults = await _tmdbClientManager.SearchSeriesAsync(cleanedName, info.MetadataLanguage, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false); if (searchResults.Count > 0) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 0e8a5baab6..15a44c7ed3 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; using MediaBrowser.Model.Entities; using TMDbLib.Objects.General; @@ -12,6 +13,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// public static class TmdbUtils { + private static readonly Regex _nonWords = new (@"[\W_]+", RegexOptions.Compiled); + /// /// URL of the TMDB instance to use. /// @@ -42,6 +45,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb PersonType.Producer }; + /// + /// Cleans the name according to TMDb requirements. + /// + /// The name of the entity. + /// The cleaned name. + public static string CleanName(string name) + { + // TMDb expects a space separated list of words make sure that is the case + return _nonWords.Replace(name, " "); + } + /// /// Maps the TMDB provided roles for crew members to Jellyfin roles. /// From 12b8e29aefad3ad202769cdab9c6afc17c5687d9 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Mar 2021 17:42:45 -0400 Subject: [PATCH 140/282] Fix duplicate permissions --- Jellyfin.Data/Entities/User.cs | 33 ++++++++++--------- .../Users/UserManager.cs | 3 ++ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 9aa8091640..74331726cc 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -73,9 +73,6 @@ namespace Jellyfin.Data.Entities PlayDefaultAudioTrack = true; SubtitleMode = SubtitlePlaybackMode.Default; SyncPlayAccess = SyncPlayUserAccessType.CreateAndJoinGroups; - - AddDefaultPermissions(); - AddDefaultPreferences(); } /// @@ -483,18 +480,11 @@ namespace Jellyfin.Data.Entities return Array.IndexOf(GetPreferenceValues(PreferenceKind.GroupedFolders), id) != -1; } - private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) - { - var localTime = date.ToLocalTime(); - var hour = localTime.TimeOfDay.TotalHours; - - return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek) - && hour >= schedule.StartHour - && hour <= schedule.EndHour; - } - + /// + /// Initializes the default permissions for a user. Should only be called on user creation. + /// // TODO: make these user configurable? - private void AddDefaultPermissions() + public void AddDefaultPermissions() { Permissions.Add(new Permission(PermissionKind.IsAdministrator, false)); Permissions.Add(new Permission(PermissionKind.IsDisabled, false)); @@ -519,12 +509,25 @@ namespace Jellyfin.Data.Entities Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false)); } - private void AddDefaultPreferences() + /// + /// Initializes the default preferences. Should only be called on user creation. + /// + public void AddDefaultPreferences() { foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast()) { Preferences.Add(new Preference(val, string.Empty)); } } + + private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) + { + var localTime = date.ToLocalTime(); + var hour = localTime.TimeOfDay.TotalHours; + + return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek) + && hour >= schedule.StartHour + && hour <= schedule.EndHour; + } } } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index b400a0dd1b..50d7612f27 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -190,6 +190,9 @@ namespace Jellyfin.Server.Implementations.Users InternalId = max + 1 }; + user.AddDefaultPermissions(); + user.AddDefaultPreferences(); + _users.Add(user.Id, user); return user; From 85da0b50e2e9686a0d660e2c2138709937c2fee2 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Mar 2021 21:24:14 -0400 Subject: [PATCH 141/282] Fix user mocking --- .../Auth/CustomAuthenticationHandlerTests.cs | 2 ++ tests/Jellyfin.Api.Tests/TestHelpers.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs index ee20cc5738..de03aa5f5b 100644 --- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs @@ -128,6 +128,8 @@ namespace Jellyfin.Api.Tests.Auth { var authorizationInfo = _fixture.Create(); authorizationInfo.User = _fixture.Create(); + authorizationInfo.User.AddDefaultPermissions(); + authorizationInfo.User.AddDefaultPreferences(); authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin); authorizationInfo.IsApiKey = false; diff --git a/tests/Jellyfin.Api.Tests/TestHelpers.cs b/tests/Jellyfin.Api.Tests/TestHelpers.cs index c1549561db..f9bca41465 100644 --- a/tests/Jellyfin.Api.Tests/TestHelpers.cs +++ b/tests/Jellyfin.Api.Tests/TestHelpers.cs @@ -29,6 +29,9 @@ namespace Jellyfin.Api.Tests typeof(DefaultAuthenticationProvider).FullName!, typeof(DefaultPasswordResetProvider).FullName!); + user.AddDefaultPermissions(); + user.AddDefaultPreferences(); + // Set administrator flag. user.SetPermission(PermissionKind.IsAdministrator, role.Equals(UserRoles.Administrator, StringComparison.OrdinalIgnoreCase)); From 840eeff2afb0faca7e65057276a1a3c7787102bf Mon Sep 17 00:00:00 2001 From: David Date: Thu, 18 Mar 2021 11:25:58 +0100 Subject: [PATCH 142/282] Apply suggestions from code review --- .../Providers/ProviderIdParsers.cs | 72 +++++++++++-------- .../Providers/ProviderIdParserTests.cs | 1 + 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs index 56e0112ddc..bfe61a3f8f 100644 --- a/MediaBrowser.Common/Providers/ProviderIdParsers.cs +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -10,6 +10,9 @@ namespace MediaBrowser.Common.Providers /// public static class ProviderIdParsers { + private const int ImdbMinNumbers = 7; + private const int ImdbMaxNumbers = 8; + /// /// Parses an IMDb id from a string. /// @@ -21,7 +24,8 @@ namespace MediaBrowser.Common.Providers var span = text.AsSpan(); var tt = "tt".AsSpan(); - while (true) + // imdb id is at least 9 chars (tt + 7 numbers) + while (span.Length >= 2 + ImdbMinNumbers) { var ttPos = span.IndexOf(tt); if (ttPos == -1) @@ -31,27 +35,28 @@ namespace MediaBrowser.Common.Providers } span = span.Slice(ttPos + tt.Length); - - int i = 0; - // IMDb id has a maximum of 8 digits - int max = span.Length > 8 ? 8 : span.Length; - for (; i < max; i++) + var i = 0; + for (; i < Math.Min(span.Length, ImdbMaxNumbers); i++) { var c = span[i]; - - if (c < '0' || c > '9') + if (!IsDigit(c)) { break; } } - // IMDb id has a minimum of 7 digits - if (i >= 7) + // skip if more than 8 digits + if (i <= ImdbMaxNumbers && i >= ImdbMinNumbers) { imdbId = string.Concat(tt, span.Slice(0, i)); return true; } + + span = span.Slice(i); } + + imdbId = default; + return false; } /// @@ -86,34 +91,39 @@ namespace MediaBrowser.Common.Providers var span = text.AsSpan(); var searchSpan = searchString.AsSpan(); - while (true) + var searchPos = span.IndexOf(searchSpan); + if (searchPos == -1) { - var searchPos = span.IndexOf(searchSpan); - if (searchPos == -1) - { - providerId = default; - return false; - } - - span = span.Slice(searchPos + searchSpan.Length); + providerId = default; + return false; + } - int i = 0; - for (; i < span.Length; i++) - { - var c = span[i]; + span = span.Slice(searchPos + searchSpan.Length); - if (c < '0' || c > '9') - { - break; - } - } + int i = 0; + for (; i < span.Length; i++) + { + var c = span[i]; - if (i >= 1) + if (!IsDigit(c)) { - providerId = span.Slice(0, i).ToString(); - return true; + break; } } + + if (i >= 1) + { + providerId = span.Slice(0, i).ToString(); + return true; + } + + providerId = default; + return false; + } + + private static bool IsDigit(char c) + { + return c >= '0' && c <= '9'; } } } diff --git a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs index a493dda64f..cfe1ea86bf 100644 --- a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs +++ b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs @@ -17,6 +17,7 @@ namespace Jellyfin.Common.Tests.Providers [InlineData("Jellyfin", false, null)] [InlineData("tt1234567tt7654321", true, "tt1234567")] [InlineData("tt12345678tt7654321", true, "tt12345678")] + [InlineData("tt123456789", true, "tt12345678")] public void Parse_Imdb(string text, bool shouldSucceed, string? imdbId) { var succeeded = ProviderIdParsers.TryParseImdbId(text, out string? parsedId); From 59641e5c766d8d4fc756ef327642ac04be24968a Mon Sep 17 00:00:00 2001 From: David Date: Thu, 18 Mar 2021 20:52:56 +0100 Subject: [PATCH 143/282] Use ReadOnlySpan and char.IsDigit --- .../Providers/ProviderIdParsers.cs | 47 ++++++++----------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs index bfe61a3f8f..7986675054 100644 --- a/MediaBrowser.Common/Providers/ProviderIdParsers.cs +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -19,27 +19,26 @@ namespace MediaBrowser.Common.Providers /// The text to parse. /// The parsed IMDb id. /// True if parsing was successful, false otherwise. - public static bool TryParseImdbId(string text, [NotNullWhen(true)] out string? imdbId) + public static bool TryParseImdbId(ReadOnlySpan text, [NotNullWhen(true)] out string? imdbId) { - var span = text.AsSpan(); var tt = "tt".AsSpan(); // imdb id is at least 9 chars (tt + 7 numbers) - while (span.Length >= 2 + ImdbMinNumbers) + while (text.Length >= 2 + ImdbMinNumbers) { - var ttPos = span.IndexOf(tt); + var ttPos = text.IndexOf(tt); if (ttPos == -1) { imdbId = default; return false; } - span = span.Slice(ttPos + tt.Length); + text = text.Slice(ttPos + tt.Length); var i = 0; - for (; i < Math.Min(span.Length, ImdbMaxNumbers); i++) + for (; i < Math.Min(text.Length, ImdbMaxNumbers); i++) { - var c = span[i]; - if (!IsDigit(c)) + var c = text[i]; + if (!char.IsDigit(c)) { break; } @@ -48,11 +47,11 @@ namespace MediaBrowser.Common.Providers // skip if more than 8 digits if (i <= ImdbMaxNumbers && i >= ImdbMinNumbers) { - imdbId = string.Concat(tt, span.Slice(0, i)); + imdbId = string.Concat(tt, text.Slice(0, i)); return true; } - span = span.Slice(i); + text = text.Slice(i); } imdbId = default; @@ -65,7 +64,7 @@ namespace MediaBrowser.Common.Providers /// The text with the url to parse. /// The parsed TMDb id. /// True if parsing was successful, false otherwise. - public static bool TryParseTmdbMovieId(string text, [NotNullWhen(true)] out string? tmdbId) + public static bool TryParseTmdbMovieId(ReadOnlySpan text, [NotNullWhen(true)] out string? tmdbId) => TryParseProviderId(text, "themoviedb.org/movie/", out tmdbId); /// @@ -74,7 +73,7 @@ namespace MediaBrowser.Common.Providers /// The text with the url to parse. /// The parsed TMDb id. /// True if parsing was successful, false otherwise. - public static bool TryParseTmdbSeriesId(string text, [NotNullWhen(true)] out string? tmdbId) + public static bool TryParseTmdbSeriesId(ReadOnlySpan text, [NotNullWhen(true)] out string? tmdbId) => TryParseProviderId(text, "themoviedb.org/tv/", out tmdbId); /// @@ -83,29 +82,26 @@ namespace MediaBrowser.Common.Providers /// The text with the url to parse. /// The parsed TVDb id. /// True if parsing was successful, false otherwise. - public static bool TryParseTvdbId(string text, [NotNullWhen(true)] out string? tvdbId) + public static bool TryParseTvdbId(ReadOnlySpan text, [NotNullWhen(true)] out string? tvdbId) => TryParseProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId); - private static bool TryParseProviderId(string text, string searchString, [NotNullWhen(true)] out string? providerId) + private static bool TryParseProviderId(ReadOnlySpan text, ReadOnlySpan searchString, [NotNullWhen(true)] out string? providerId) { - var span = text.AsSpan(); - var searchSpan = searchString.AsSpan(); - - var searchPos = span.IndexOf(searchSpan); + var searchPos = text.IndexOf(searchString); if (searchPos == -1) { providerId = default; return false; } - span = span.Slice(searchPos + searchSpan.Length); + text = text.Slice(searchPos + searchString.Length); int i = 0; - for (; i < span.Length; i++) + for (; i < text.Length; i++) { - var c = span[i]; + var c = text[i]; - if (!IsDigit(c)) + if (!char.IsDigit(c)) { break; } @@ -113,17 +109,12 @@ namespace MediaBrowser.Common.Providers if (i >= 1) { - providerId = span.Slice(0, i).ToString(); + providerId = text.Slice(0, i).ToString(); return true; } providerId = default; return false; } - - private static bool IsDigit(char c) - { - return c >= '0' && c <= '9'; - } } } From 9857c21717380261e11ec948c3520a673ffd83ae Mon Sep 17 00:00:00 2001 From: andrewthemeow Date: Fri, 19 Mar 2021 06:29:39 +0000 Subject: [PATCH 144/282] Translated using Weblate (Lithuanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/ --- Emby.Server.Implementations/Localization/Core/lt-LT.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index d4cb592efc..9920ef4d51 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -113,5 +113,9 @@ "TasksChannelsCategory": "Internetiniai Kanalai", "TasksApplicationCategory": "Programa", "TasksLibraryCategory": "Mediateka", - "TasksMaintenanceCategory": "Priežiūra" + "TasksMaintenanceCategory": "Priežiūra", + "TaskCleanActivityLog": "Švarus veiklos žurnalas", + "Undefined": "Neapibrėžtas", + "Forced": "Priverstas", + "Default": "Numatytas" } From 7685569480409f2703fc6ead32d093a08d783312 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 19 Mar 2021 12:34:21 +0100 Subject: [PATCH 145/282] Rollback char.IsDigit --- MediaBrowser.Common/Providers/ProviderIdParsers.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs index 7986675054..7744124eaa 100644 --- a/MediaBrowser.Common/Providers/ProviderIdParsers.cs +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Common.Providers for (; i < Math.Min(text.Length, ImdbMaxNumbers); i++) { var c = text[i]; - if (!char.IsDigit(c)) + if (!IsDigit(c)) { break; } @@ -101,7 +101,7 @@ namespace MediaBrowser.Common.Providers { var c = text[i]; - if (!char.IsDigit(c)) + if (!IsDigit(c)) { break; } @@ -116,5 +116,10 @@ namespace MediaBrowser.Common.Providers providerId = default; return false; } + + private static bool IsDigit(char c) + { + return c >= '0' && c <= '9'; + } } } From f61d18612b2e6c6e9a5dd4510331ac8d89a337d5 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Sat, 20 Mar 2021 00:38:58 +0100 Subject: [PATCH 146/282] Fix directory traversal in the HlsSegmentController in a fairly rudimentary but working way. GHSL-2021-050: Issue 1,2,3 Arbitrary file read and directory traversal. The segment id's can probably just be verified to be an actual ID or to not contain any forward or backward slashes --- .../Controllers/HlsSegmentController.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index d0ed45acb4..5a8542048a 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -62,6 +62,13 @@ namespace Jellyfin.Api.Controllers // TODO: Deprecate with new iOS app var file = segmentId + Path.GetExtension(Request.Path); file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); + var transcodePath = _serverConfigurationManager.GetTranscodePath(); + file = Path.GetFullPath(Path.Combine(transcodePath, file)); + var fileDir = Path.GetDirectoryName(file); + if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath)) + { + return BadRequest("Invalid segment."); + } return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, HttpContext); } @@ -82,6 +89,13 @@ namespace Jellyfin.Api.Controllers { var file = playlistId + Path.GetExtension(Request.Path); file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); + var transcodePath = _serverConfigurationManager.GetTranscodePath(); + file = Path.GetFullPath(Path.Combine(transcodePath, file)); + var fileDir = Path.GetDirectoryName(file); + if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath) || Path.GetExtension(file) != ".m3u8") + { + return BadRequest("Invalid segment."); + } return GetFileResult(file, file); } @@ -131,6 +145,12 @@ namespace Jellyfin.Api.Controllers var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath(); file = Path.Combine(transcodeFolderPath, file); + file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file)); + var fileDir = Path.GetDirectoryName(file); + if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath)) + { + return BadRequest("Invalid segment."); + } var normalizedPlaylistId = playlistId; From 239a7156cc9c2c383aca1e7265ae4679666d5c85 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Sat, 20 Mar 2021 00:46:59 +0100 Subject: [PATCH 147/282] Fix arbitrary image file reads in ImageByNameController GHSL-2021-050: Issue 4 Arbitrary image file read and directory traversal. --- .../Controllers/ImageByNameController.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/ImageByNameController.cs b/Jellyfin.Api/Controllers/ImageByNameController.cs index 198dbc51fc..e1b8080984 100644 --- a/Jellyfin.Api/Controllers/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/ImageByNameController.cs @@ -74,7 +74,7 @@ namespace Jellyfin.Api.Controllers : type; var path = BaseItem.SupportedImageExtensions - .Select(i => Path.Combine(_applicationPaths.GeneralPath, name, filename + i)) + .Select(i => Path.GetFullPath(Path.Combine(_applicationPaths.GeneralPath, name, filename + i))) .FirstOrDefault(System.IO.File.Exists); if (path == null) @@ -82,6 +82,11 @@ namespace Jellyfin.Api.Controllers return NotFound(); } + if (!path.StartsWith(_applicationPaths.GeneralPath)) + { + return BadRequest("Invalid image path."); + } + var contentType = MimeTypes.GetMimeType(path); return File(System.IO.File.OpenRead(path), contentType); } @@ -163,7 +168,8 @@ namespace Jellyfin.Api.Controllers /// A containing the image contents on success, or a if the image could not be found. private ActionResult GetImageFile(string basePath, string theme, string? name) { - var themeFolder = Path.Combine(basePath, theme); + var themeFolder = Path.GetFullPath(Path.Combine(basePath, theme)); + if (Directory.Exists(themeFolder)) { var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, name + i)) @@ -171,12 +177,18 @@ namespace Jellyfin.Api.Controllers if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) { + if (!path.StartsWith(basePath)) + { + return BadRequest("Invalid image path."); + } + var contentType = MimeTypes.GetMimeType(path); + return PhysicalFile(path, contentType); } } - var allFolder = Path.Combine(basePath, "all"); + var allFolder = Path.GetFullPath(Path.Combine(basePath, "all")); if (Directory.Exists(allFolder)) { var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, name + i)) @@ -184,6 +196,11 @@ namespace Jellyfin.Api.Controllers if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) { + if (!path.StartsWith(basePath)) + { + return BadRequest("Invalid image path."); + } + var contentType = MimeTypes.GetMimeType(path); return PhysicalFile(path, contentType); } From 470305f75edc037653b68dd0614f73009219bdbd Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Sat, 20 Mar 2021 01:07:09 +0100 Subject: [PATCH 148/282] Authenticated arbitrary file overwrite in SubtitleController -> SubtitleManager GHSL-2021-050: Issue 5 Arbitrary file overwrite. --- .../Subtitles/SubtitleManager.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index d4d79d27b5..1f3d9acff2 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -205,12 +205,30 @@ namespace MediaBrowser.Providers.Subtitles if (saveInMediaFolder) { - savePaths.Add(Path.Combine(video.ContainingFolderPath, saveFileName)); + var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName)); + // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path."); + if (mediaFolderPath.StartsWith(video.ContainingFolderPath)) + { + savePaths.Add(mediaFolderPath); + } } - savePaths.Add(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); + var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); + + // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path."); + if (internalPath.StartsWith(video.GetInternalMetadataPath())) + { + savePaths.Add(internalPath); + } - await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); + if (savePaths.Count > 0) + { + await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); + } + else + { + _logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid."); + } } } From 1f3aa3fe6f72f9b745dac7de5b6f8b877590017c Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Sat, 20 Mar 2021 01:28:14 +0100 Subject: [PATCH 149/282] Apply review suggestions --- Jellyfin.Api/Controllers/HlsSegmentController.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index 5a8542048a..473bdc523c 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -61,7 +61,6 @@ namespace Jellyfin.Api.Controllers { // TODO: Deprecate with new iOS app var file = segmentId + Path.GetExtension(Request.Path); - file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); var transcodePath = _serverConfigurationManager.GetTranscodePath(); file = Path.GetFullPath(Path.Combine(transcodePath, file)); var fileDir = Path.GetDirectoryName(file); @@ -88,7 +87,6 @@ namespace Jellyfin.Api.Controllers public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId) { var file = playlistId + Path.GetExtension(Request.Path); - file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); var transcodePath = _serverConfigurationManager.GetTranscodePath(); file = Path.GetFullPath(Path.Combine(transcodePath, file)); var fileDir = Path.GetDirectoryName(file); @@ -144,7 +142,6 @@ namespace Jellyfin.Api.Controllers var file = segmentId + Path.GetExtension(Request.Path); var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath(); - file = Path.Combine(transcodeFolderPath, file); file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file)); var fileDir = Path.GetDirectoryName(file); if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath)) From 32853ca2441d4b730258e6911faa56eba608dc1d Mon Sep 17 00:00:00 2001 From: LIAUD Date: Sat, 20 Mar 2021 20:15:19 +0100 Subject: [PATCH 150/282] Add 'group-title' channel parsing --- Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs | 5 +++++ MediaBrowser.Controller/LiveTv/ChannelInfo.cs | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index c4f173c7ac..2af635492e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -133,6 +133,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts channel.ImageUrl = value; } + if (attributes.TryGetValue("group-title", out string groupTitle)) + { + channel.ChannelGroup = groupTitle; + } + channel.Name = GetChannelName(extInf, attributes); channel.Number = GetChannelNumber(extInf, attributes, mediaUrl); diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index 44bd38b54f..0f3f878475 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -45,6 +45,12 @@ namespace MediaBrowser.Controller.LiveTv /// The type of the channel. public ChannelType ChannelType { get; set; } + /// + /// Gets or sets the group of the channel + /// + /// The group of the channel + public string ChannelGroup { get; set; } + /// /// Supply the image path if it can be accessed directly from the file system. /// From 72db3df605b7061fef28c99e42b7c11a06e8d995 Mon Sep 17 00:00:00 2001 From: LIAUD Date: Sat, 20 Mar 2021 20:31:38 +0100 Subject: [PATCH 151/282] Changed CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 954315f0eb..9b1ac0673a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -17,6 +17,7 @@ - [bugfixin](https://github.com/bugfixin) - [chaosinnovator](https://github.com/chaosinnovator) - [ckcr4lyf](https://github.com/ckcr4lyf) + - [cocool97](https://github.com/cocool97) - [ConfusedPolarBear](https://github.com/ConfusedPolarBear) - [crankdoofus](https://github.com/crankdoofus) - [crobibero](https://github.com/crobibero) From 849ced470adf3ae122713e870e476b835c57d5ad Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 21 Mar 2021 03:26:51 +0100 Subject: [PATCH 152/282] Add StartupControllerTests --- .../Controllers/StartupControllerTests.cs | 61 +++++++++++++++++++ .../Jellyfin.Server.Integration.Tests.csproj | 1 + 2 files changed, 62 insertions(+) create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs new file mode 100644 index 0000000000..cf4b9dd712 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs @@ -0,0 +1,61 @@ +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Models.StartupDtos; +using MediaBrowser.Common.Json; +using Xunit; +using Xunit.Priority; + +namespace Jellyfin.Server.Integration.Tests.Controllers +{ + public sealed class StartupControllerTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.Options; + + public StartupControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + [Priority(0)] + public async Task GetStartupConfiguration_EditConfig_Success() + { + var client = _factory.CreateClient(); + + using var res0 = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, res0.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, res0.Content.Headers.ContentType?.MediaType); + + var content0 = await res0.Content.ReadAsStreamAsync().ConfigureAwait(false); + _ = await JsonSerializer.DeserializeAsync(content0, _jsonOpions).ConfigureAwait(false); + + var newConfig = new StartupConfigurationDto() + { + UICulture = "NewCulture", + MetadataCountryCode = "be", + PreferredMetadataLanguage = "nl" + }; + + var req1 = JsonSerializer.SerializeToUtf8Bytes(newConfig, _jsonOpions); + using var reqContent1 = new ByteArrayContent(req1); + reqContent1.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + var res1 = await client.PostAsync("/Startup/Configuration", reqContent1).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NoContent, res1.StatusCode); + + var res2 = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, res2.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, res2.Content.Headers.ContentType?.MediaType); + + var content2 = await res2.Content.ReadAsStreamAsync().ConfigureAwait(false); + var config2 = await JsonSerializer.DeserializeAsync(content2, _jsonOpions).ConfigureAwait(false); + Assert.Equal(newConfig.UICulture, config2!.UICulture); + Assert.Equal(newConfig.MetadataCountryCode, config2.MetadataCountryCode); + Assert.Equal(newConfig.PreferredMetadataLanguage, config2.PreferredMetadataLanguage); + } + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index b0a38736ae..34cef90050 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -17,6 +17,7 @@ + From c5079ebed5187c40e19284772c710c41c4252cc9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 21 Mar 2021 03:59:31 +0100 Subject: [PATCH 153/282] Add tests for GetFirstUser, UpdateStartupUser and CompleteWizard --- .../Controllers/StartupControllerTests.cs | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs index cf4b9dd712..bb38db1be4 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs @@ -1,3 +1,4 @@ +using System; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -11,6 +12,7 @@ using Xunit.Priority; namespace Jellyfin.Server.Integration.Tests.Controllers { + [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] public sealed class StartupControllerTests : IClassFixture { private readonly JellyfinApplicationFactory _factory; @@ -23,7 +25,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers [Fact] [Priority(0)] - public async Task GetStartupConfiguration_EditConfig_Success() + public async Task Configuration_EditConfig_Success() { var client = _factory.CreateClient(); @@ -57,5 +59,58 @@ namespace Jellyfin.Server.Integration.Tests.Controllers Assert.Equal(newConfig.MetadataCountryCode, config2.MetadataCountryCode); Assert.Equal(newConfig.PreferredMetadataLanguage, config2.PreferredMetadataLanguage); } + + [Fact] + [Priority(0)] + public async Task User_EditUser_Success() + { + var client = _factory.CreateClient(); + + using var res0 = await client.GetAsync("/Startup/User").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, res0.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, res0.Content.Headers.ContentType?.MediaType); + + var content0 = await res0.Content.ReadAsStreamAsync().ConfigureAwait(false); + var user = await JsonSerializer.DeserializeAsync(content0, _jsonOpions).ConfigureAwait(false); + + user!.Name = "NewName"; + user.Password = "NewPassword"; + + var req1 = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOpions); + using var reqContent1 = new ByteArrayContent(req1); + reqContent1.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + var res1 = await client.PostAsync("/Startup/User", reqContent1).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NoContent, res1.StatusCode); + + var res2 = await client.GetAsync("/Startup/User").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, res2.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, res2.Content.Headers.ContentType?.MediaType); + + var content2 = await res2.Content.ReadAsStreamAsync().ConfigureAwait(false); + var user2 = await JsonSerializer.DeserializeAsync(content2, _jsonOpions).ConfigureAwait(false); + Assert.Equal(user.Name, user2!.Name); + Assert.NotEmpty(user2.Password); + Assert.NotEqual(user.Password, user2.Password); + } + + [Fact] + [Priority(1)] + public async Task CompleteWizard_Success() + { + var client = _factory.CreateClient(); + + var res = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty())).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NoContent, res.StatusCode); + } + + [Fact] + [Priority(2)] + public async Task GetFirstUser_CompleteWizard_Unauthorized() + { + var client = _factory.CreateClient(); + + using var res0 = await client.GetAsync("/Startup/User").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.Unauthorized, res0.StatusCode); + } } } From fcb070abf702c587fc98183253d56ec7f501eaa6 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Mon, 22 Mar 2021 18:36:04 +0800 Subject: [PATCH 154/282] disable auto rotation for some HWA methods --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e5877a484a..b0cf47c7a3 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -541,6 +541,8 @@ namespace MediaBrowser.Controller.MediaEncoding .Append(encodingOptions.VaapiDevice) .Append(' '); } + + arg.Append("-autorotate 0"); } if (state.IsVideoRequest @@ -585,6 +587,8 @@ namespace MediaBrowser.Controller.MediaEncoding .Append("-init_hw_device qsv@va ") .Append("-hwaccel_output_format vaapi "); } + + arg.Append("-autorotate 0 "); } } @@ -592,7 +596,8 @@ namespace MediaBrowser.Controller.MediaEncoding && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecDecoder) { - arg.Append("-hwaccel_output_format cuda "); + arg.Append("-hwaccel_output_format cuda ") + .Append("-autorotate 0 "); } if (state.IsVideoRequest From a6bc191607675cd5676c6d1dceaf0ad37ac7e508 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Mon, 22 Mar 2021 19:25:41 +0800 Subject: [PATCH 155/282] Apply suggestions from code review Co-authored-by: Claus Vium --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b0cf47c7a3..1df040a266 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -596,8 +596,7 @@ namespace MediaBrowser.Controller.MediaEncoding && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecDecoder) { - arg.Append("-hwaccel_output_format cuda ") - .Append("-autorotate 0 "); + arg.Append("-hwaccel_output_format cuda -autorotate 0 "); } if (state.IsVideoRequest From fab4bf184e27eba59566702d20ed352f75f0418b Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Mon, 22 Mar 2021 19:47:05 +0800 Subject: [PATCH 156/282] Apply suggestions from code review Co-authored-by: Claus Vium --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 1df040a266..09080e7b2c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -542,7 +542,7 @@ namespace MediaBrowser.Controller.MediaEncoding .Append(' '); } - arg.Append("-autorotate 0"); + arg.Append("-autorotate 0 "); } if (state.IsVideoRequest From 5253483ce4213a5f32fc15210ef61b2df9915185 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 22 Mar 2021 13:49:00 +0100 Subject: [PATCH 157/282] Improve naming --- .../Controllers/StartupControllerTests.cs | 109 +++++++++--------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs index bb38db1be4..169a5a6c52 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs @@ -16,7 +16,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers public sealed class StartupControllerTests : IClassFixture { private readonly JellyfinApplicationFactory _factory; - private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.Options; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; public StartupControllerTests(JellyfinApplicationFactory factory) { @@ -24,93 +24,96 @@ namespace Jellyfin.Server.Integration.Tests.Controllers } [Fact] - [Priority(0)] + [Priority(-2)] public async Task Configuration_EditConfig_Success() { var client = _factory.CreateClient(); - using var res0 = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false); - Assert.Equal(HttpStatusCode.OK, res0.StatusCode); - Assert.Equal(MediaTypeNames.Application.Json, res0.Content.Headers.ContentType?.MediaType); - - var content0 = await res0.Content.ReadAsStreamAsync().ConfigureAwait(false); - _ = await JsonSerializer.DeserializeAsync(content0, _jsonOpions).ConfigureAwait(false); - - var newConfig = new StartupConfigurationDto() + var config = new StartupConfigurationDto() { UICulture = "NewCulture", MetadataCountryCode = "be", PreferredMetadataLanguage = "nl" }; - var req1 = JsonSerializer.SerializeToUtf8Bytes(newConfig, _jsonOpions); - using var reqContent1 = new ByteArrayContent(req1); - reqContent1.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); - var res1 = await client.PostAsync("/Startup/Configuration", reqContent1).ConfigureAwait(false); - Assert.Equal(HttpStatusCode.NoContent, res1.StatusCode); - - var res2 = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false); - Assert.Equal(HttpStatusCode.OK, res2.StatusCode); - Assert.Equal(MediaTypeNames.Application.Json, res2.Content.Headers.ContentType?.MediaType); - - var content2 = await res2.Content.ReadAsStreamAsync().ConfigureAwait(false); - var config2 = await JsonSerializer.DeserializeAsync(content2, _jsonOpions).ConfigureAwait(false); - Assert.Equal(newConfig.UICulture, config2!.UICulture); - Assert.Equal(newConfig.MetadataCountryCode, config2.MetadataCountryCode); - Assert.Equal(newConfig.PreferredMetadataLanguage, config2.PreferredMetadataLanguage); + using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(config, _jsonOptions)); + postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + using var postResponse = await client.PostAsync("/Startup/Configuration", postContent).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode); + + using var getResponse = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType); + + using var responseStream = await getResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + var newConfig = await JsonSerializer.DeserializeAsync(responseStream, _jsonOptions).ConfigureAwait(false); + Assert.Equal(config.UICulture, newConfig!.UICulture); + Assert.Equal(config.MetadataCountryCode, newConfig.MetadataCountryCode); + Assert.Equal(config.PreferredMetadataLanguage, newConfig.PreferredMetadataLanguage); } [Fact] - [Priority(0)] - public async Task User_EditUser_Success() + [Priority(-2)] + public async Task User_DefaultUser_NameWithoutPassword() { var client = _factory.CreateClient(); - using var res0 = await client.GetAsync("/Startup/User").ConfigureAwait(false); - Assert.Equal(HttpStatusCode.OK, res0.StatusCode); - Assert.Equal(MediaTypeNames.Application.Json, res0.Content.Headers.ContentType?.MediaType); + using var response = await client.GetAsync("/Startup/User").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); + + using var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + var user = await JsonSerializer.DeserializeAsync(contentStream, _jsonOptions).ConfigureAwait(false); + Assert.NotEmpty(user!.Name); + Assert.Null(user.Password); + } - var content0 = await res0.Content.ReadAsStreamAsync().ConfigureAwait(false); - var user = await JsonSerializer.DeserializeAsync(content0, _jsonOpions).ConfigureAwait(false); + [Fact] + [Priority(-1)] + public async Task User_EditUser_Success() + { + var client = _factory.CreateClient(); - user!.Name = "NewName"; - user.Password = "NewPassword"; + var user = new StartupUserDto() + { + Name = "NewName", + Password = "NewPassword" + }; - var req1 = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOpions); - using var reqContent1 = new ByteArrayContent(req1); - reqContent1.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); - var res1 = await client.PostAsync("/Startup/User", reqContent1).ConfigureAwait(false); - Assert.Equal(HttpStatusCode.NoContent, res1.StatusCode); + using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions)); + postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + var postResponse = await client.PostAsync("/Startup/User", postContent).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode); - var res2 = await client.GetAsync("/Startup/User").ConfigureAwait(false); - Assert.Equal(HttpStatusCode.OK, res2.StatusCode); - Assert.Equal(MediaTypeNames.Application.Json, res2.Content.Headers.ContentType?.MediaType); + var getResponse = await client.GetAsync("/Startup/User").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType); - var content2 = await res2.Content.ReadAsStreamAsync().ConfigureAwait(false); - var user2 = await JsonSerializer.DeserializeAsync(content2, _jsonOpions).ConfigureAwait(false); - Assert.Equal(user.Name, user2!.Name); - Assert.NotEmpty(user2.Password); - Assert.NotEqual(user.Password, user2.Password); + var contentStream = await getResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); + var newUser = await JsonSerializer.DeserializeAsync(contentStream, _jsonOptions).ConfigureAwait(false); + Assert.Equal(user.Name, newUser!.Name); + Assert.NotEmpty(newUser.Password); + Assert.NotEqual(user.Password, newUser.Password); } [Fact] - [Priority(1)] + [Priority(0)] public async Task CompleteWizard_Success() { var client = _factory.CreateClient(); - var res = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty())).ConfigureAwait(false); - Assert.Equal(HttpStatusCode.NoContent, res.StatusCode); + var response = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty())).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); } [Fact] - [Priority(2)] + [Priority(1)] public async Task GetFirstUser_CompleteWizard_Unauthorized() { var client = _factory.CreateClient(); - using var res0 = await client.GetAsync("/Startup/User").ConfigureAwait(false); - Assert.Equal(HttpStatusCode.Unauthorized, res0.StatusCode); + using var response = await client.GetAsync("/Startup/User").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } } } From 0853d1265c99a2e8614aa0c7a584dff541484e19 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Tue, 23 Mar 2021 00:59:57 +0800 Subject: [PATCH 158/282] Disable auto rotation for some HWA methods (#5586) Co-authored-by: Claus Vium --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e5877a484a..09080e7b2c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -541,6 +541,8 @@ namespace MediaBrowser.Controller.MediaEncoding .Append(encodingOptions.VaapiDevice) .Append(' '); } + + arg.Append("-autorotate 0 "); } if (state.IsVideoRequest @@ -585,6 +587,8 @@ namespace MediaBrowser.Controller.MediaEncoding .Append("-init_hw_device qsv@va ") .Append("-hwaccel_output_format vaapi "); } + + arg.Append("-autorotate 0 "); } } @@ -592,7 +596,7 @@ namespace MediaBrowser.Controller.MediaEncoding && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecDecoder) { - arg.Append("-hwaccel_output_format cuda "); + arg.Append("-hwaccel_output_format cuda -autorotate 0 "); } if (state.IsVideoRequest From 7fa525c83b7573e61124fa1c64a3b27569e66b6d Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 22 Mar 2021 17:04:09 +0000 Subject: [PATCH 159/282] Added more tests --- Jellyfin.sln | 17 +++++++++++------ .../NetworkParseTests.cs | 11 +++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index 7b81f43462..9d5cd98e26 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30503.244 MinimumVisualStudioVersion = 10.0.40219.1 @@ -70,15 +71,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jell EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -210,6 +211,10 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 19f0faa8fe..de81ad0a6d 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -518,7 +518,9 @@ namespace Jellyfin.Networking.Tests [Theory] [InlineData("185.10.10.10,200.200.200.200", "79.2.3.4", true)] [InlineData("185.10.10.10", "185.10.10.10", false)] - public void HasRemoteAccess_GivenNonEmptyWhitelist_AllowsOnlyIpsInWhitelist(string addresses, string remoteIp, bool denied) + [InlineData("", "100.100.100.100", false)] + + public void HasRemoteAccess_GivenWhitelist_AllowsOnlyIpsInWhitelist(string addresses, string remoteIp, bool denied) { // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // If left blank, all remote addresses will be allowed. @@ -534,9 +536,10 @@ namespace Jellyfin.Networking.Tests } [Theory] - [InlineData("185.10.10.10", "79.2.3.4", false)] // blacklist - [InlineData("185.10.10.10", "185.10.10.10", true)] // blacklist - public void HasRemoteAccess_GivenNonEmptyBlacklist_BlacklistTheIps(string addresses, string remoteIp, bool denied) + [InlineData("185.10.10.10", "79.2.3.4", false)] + [InlineData("185.10.10.10", "185.10.10.10", true)] + [InlineData("", "100.100.100.100", false)] + public void HasRemoteAccess_GivenBlacklist_BlacklistTheIps(string addresses, string remoteIp, bool denied) { // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // If left blank, all remote addresses will be allowed. From 4bd345fbabd7832bcb91a920ec81dc14a7420218 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 22 Mar 2021 17:21:12 +0000 Subject: [PATCH 160/282] DLNA Exception catching --- Emby.Dlna/PlayTo/Device.cs | 41 +++++++++++++++---- Emby.Dlna/PlayTo/PlayToManager.cs | 5 +++ Emby.Dlna/PlayTo/SsdpHttpClient.cs | 15 +++++-- .../NetworkParseTests.cs | 4 +- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 7bf7047fbb..abd99bbc3c 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -219,7 +219,7 @@ namespace Emby.Dlna.PlayTo { var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); + var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); if (command == null) { return false; @@ -259,7 +259,7 @@ namespace Emby.Dlna.PlayTo { var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); + var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); if (command == null) { return; @@ -290,7 +290,7 @@ namespace Emby.Dlna.PlayTo { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); + var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); if (command == null) { return; @@ -323,7 +323,7 @@ namespace Emby.Dlna.PlayTo _logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header); - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); + var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); if (command == null) { return; @@ -403,6 +403,10 @@ namespace Emby.Dlna.PlayTo public async Task SetPlay(CancellationToken cancellationToken) { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + if (avCommands == null) + { + return; + } await SetPlay(avCommands, cancellationToken).ConfigureAwait(false); @@ -413,7 +417,7 @@ namespace Emby.Dlna.PlayTo { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); + var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); if (command == null) { return; @@ -437,7 +441,7 @@ namespace Emby.Dlna.PlayTo { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); + var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); if (command == null) { return; @@ -565,7 +569,7 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); + var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); if (command == null) { return; @@ -615,7 +619,7 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); + var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); if (command == null) { return; @@ -702,6 +706,10 @@ namespace Emby.Dlna.PlayTo } var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + if (rendererCommands == null) + { + return null; + } var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, @@ -770,6 +778,11 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + if (rendererCommands == null) + { + return (false, null); + } + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, @@ -951,6 +964,10 @@ namespace Emby.Dlna.PlayTo var httpClient = new SsdpHttpClient(_httpClientFactory); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); + if (document == null) + { + return null; + } AvCommands = TransportCommands.Create(document); return AvCommands; @@ -979,6 +996,10 @@ namespace Emby.Dlna.PlayTo var httpClient = new SsdpHttpClient(_httpClientFactory); _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); + if (document == null) + { + return null; + } RendererCommands = TransportCommands.Create(document); return RendererCommands; @@ -1010,6 +1031,10 @@ namespace Emby.Dlna.PlayTo var ssdpHttpClient = new SsdpHttpClient(httpClientFactory); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); + if (document == null) + { + return null; + } var friendlyNames = new List(); diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index a6793a7081..8272e505a0 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -178,6 +178,11 @@ namespace Emby.Dlna.PlayTo if (controller == null) { var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false); + if (device == null) + { + _logger.LogError("Ignoring device as xml response is invalid."); + return; + } string deviceName = device.Properties.Name; diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index b7643fb279..e750f5bbc3 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -94,10 +94,17 @@ namespace Emby.Dlna.PlayTo options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return await XDocument.LoadAsync( - stream, - LoadOptions.PreserveWhitespace, - cancellationToken).ConfigureAwait(false); + try + { + return await XDocument.LoadAsync( + stream, + LoadOptions.PreserveWhitespace, + cancellationToken).ConfigureAwait(false); + } + catch + { + return null; + } } private async Task PostSoapDataAsync( diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index c3469035eb..28b5a4691b 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -83,7 +83,7 @@ namespace Jellyfin.Networking.Tests /// /// Checks IP address formats. /// - /// + /// IP Address. [Theory] [InlineData("127.0.0.1")] [InlineData("127.0.0.1:123")] @@ -107,7 +107,7 @@ namespace Jellyfin.Networking.Tests /// /// Checks IP address formats. /// - /// + /// IP Address. [Theory] [InlineData("127.0.0.1")] [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")] From 4637bbc723b2462ca01cb6cc5bd7e5e8b70b10c2 Mon Sep 17 00:00:00 2001 From: Kenneth SB Date: Mon, 22 Mar 2021 15:24:00 +0000 Subject: [PATCH 161/282] Translated using Weblate (Danish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/ --- Emby.Server.Implementations/Localization/Core/da.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 4ee4eb989f..051d6d009f 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -1,5 +1,5 @@ { - "Albums": "Albums", + "Albums": "Albummer", "AppDeviceValues": "App: {0}, Enhed: {1}", "Application": "Applikation", "Artists": "Kunstnere", From 7dedeb6c795cb7764ec1cd1d2cd943f5c5506847 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 22 Mar 2021 20:53:55 +0100 Subject: [PATCH 162/282] change HLS endpoint defaults to false --- Emby.Dlna/PlayTo/PlayToController.cs | 2 +- Jellyfin.Api/Controllers/AudioController.cs | 24 +++---- .../Controllers/DynamicHlsController.cs | 72 +++++++++---------- .../Controllers/UniversalAudioController.cs | 8 +-- .../Controllers/VideoHlsController.cs | 12 ++-- Jellyfin.Api/Controllers/VideosController.cs | 12 ++-- MediaBrowser.Model/Dlna/StreamInfo.cs | 2 +- 7 files changed, 66 insertions(+), 66 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 5abc1bc134..6021da51db 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -945,7 +945,7 @@ namespace Emby.Dlna.PlayTo request.LiveStreamId = values.GetValueOrDefault("LiveStreamId"); // Be careful, IsDirectStream==true by default (Static != false or not in query). - // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true. + // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? false. request.IsDirectStream = !string.Equals("false", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 8b1813b200..a6e70e72d3 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers { Id = itemId, Container = container, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -168,7 +168,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -177,13 +177,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, @@ -309,7 +309,7 @@ namespace Jellyfin.Api.Controllers { Id = itemId, Container = container, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -333,7 +333,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -342,13 +342,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index f6c23c5aad..c4e75fe859 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -225,7 +225,7 @@ namespace Jellyfin.Api.Controllers var streamingRequest = new HlsVideoRequestDto { Id = itemId, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -249,7 +249,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -258,13 +258,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, @@ -392,7 +392,7 @@ namespace Jellyfin.Api.Controllers var streamingRequest = new HlsAudioRequestDto { Id = itemId, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -416,7 +416,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -425,13 +425,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, @@ -555,7 +555,7 @@ namespace Jellyfin.Api.Controllers var streamingRequest = new VideoRequestDto { Id = itemId, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -579,7 +579,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -588,13 +588,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, @@ -720,7 +720,7 @@ namespace Jellyfin.Api.Controllers var streamingRequest = new StreamingRequestDto { Id = itemId, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -744,7 +744,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -753,13 +753,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, @@ -890,7 +890,7 @@ namespace Jellyfin.Api.Controllers { Id = itemId, Container = container, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -914,7 +914,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -923,13 +923,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, @@ -1062,7 +1062,7 @@ namespace Jellyfin.Api.Controllers { Id = itemId, Container = container, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -1086,7 +1086,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -1095,13 +1095,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 0c2e6f19f1..dcdd8b3677 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -219,10 +219,10 @@ namespace Jellyfin.Api.Controllers AudioBitRate = audioBitRate ?? maxStreamingBitrate, StartTimeTicks = startTimeTicks, SubtitleMethod = SubtitleDeliveryMethod.Hls, - RequireAvc = true, - DeInterlace = true, - RequireNonAnamorphic = true, - EnableMpegtsM2TsMode = true, + RequireAvc = false, + DeInterlace = false, + RequireNonAnamorphic = false, + EnableMpegtsM2TsMode = false, TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()), Context = EncodingContext.Static, StreamOptions = new Dictionary(), diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 620eef5680..e95410d024 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -223,7 +223,7 @@ namespace Jellyfin.Api.Controllers { Id = itemId, Container = container, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -247,7 +247,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -256,13 +256,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 99654e7b0d..699ca53278 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -386,7 +386,7 @@ namespace Jellyfin.Api.Controllers { Id = itemId, Container = container, - Static = @static ?? true, + Static = @static ?? false, Params = @params, Tag = tag, DeviceProfileId = deviceProfileId, @@ -410,7 +410,7 @@ namespace Jellyfin.Api.Controllers Level = level, Framerate = framerate, MaxFramerate = maxFramerate, - CopyTimestamps = copyTimestamps ?? true, + CopyTimestamps = copyTimestamps ?? false, StartTimeTicks = startTimeTicks, Width = width, Height = height, @@ -419,13 +419,13 @@ namespace Jellyfin.Api.Controllers SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, MaxRefFrames = maxRefFrames, MaxVideoBitDepth = maxVideoBitDepth, - RequireAvc = requireAvc ?? true, - DeInterlace = deInterlace ?? true, - RequireNonAnamorphic = requireNonAnamorphic ?? true, + RequireAvc = requireAvc ?? false, + DeInterlace = deInterlace ?? false, + RequireNonAnamorphic = requireNonAnamorphic ?? false, TranscodingMaxAudioChannels = transcodingMaxAudioChannels, CpuCoreLimit = cpuCoreLimit, LiveStreamId = liveStreamId, - EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false, VideoCodec = videoCodec, SubtitleCodec = subtitleCodec, TranscodeReasons = transcodeReasons, diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 29da5d9e7f..475bea4cdb 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -631,7 +631,7 @@ namespace MediaBrowser.Model.Dlna } // Be careful, IsDirectStream==true by default (Static != false or not in query). - // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true. + // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? false. if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) && string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase)) { From 74e14b4ca591b9043435ee1065827101379b1318 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 22 Mar 2021 22:34:47 +0100 Subject: [PATCH 163/282] fix isdirectstream default --- Emby.Dlna/PlayTo/PlayToController.cs | 6 +----- MediaBrowser.Model/Dlna/StreamInfo.cs | 4 +--- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 6021da51db..25ba18add8 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -943,11 +943,7 @@ namespace Emby.Dlna.PlayTo request.DeviceId = values.GetValueOrDefault("DeviceId"); request.MediaSourceId = values.GetValueOrDefault("MediaSourceId"); request.LiveStreamId = values.GetValueOrDefault("LiveStreamId"); - - // Be careful, IsDirectStream==true by default (Static != false or not in query). - // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? false. - request.IsDirectStream = !string.Equals("false", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); - + request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); request.StartPositionTicks = GetLongValue(values, "StartPositionTicks"); diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 475bea4cdb..252872847a 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -630,10 +630,8 @@ namespace MediaBrowser.Model.Dlna continue; } - // Be careful, IsDirectStream==true by default (Static != false or not in query). - // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? false. if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) && - string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase)) + string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase)) { continue; } From 6765f6ab172bcb5a6a98ba84087c5b51288f05d7 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 23 Mar 2021 00:12:14 +0000 Subject: [PATCH 164/282] fix compilation --- .../Middleware/IpBasedAccessValidationMiddleware.cs | 3 +-- tests/Jellyfin.Networking.Tests/NetworkParseTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs index 96e6fd8340..7d92bd7d31 100644 --- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs +++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs @@ -29,9 +29,8 @@ namespace Jellyfin.Server.Middleware /// /// The current HTTP context. /// The network manager. - /// The server configuration manager. /// The async task. - public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) + public async Task Invoke(HttpContext httpContext, INetworkManager networkManager) { if (httpContext.IsLocal()) { diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index de81ad0a6d..5d22b4d334 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -83,7 +83,7 @@ namespace Jellyfin.Networking.Tests /// /// Checks IP address formats. /// - /// + /// IP Address. [Theory] [InlineData("127.0.0.1")] [InlineData("127.0.0.1:123")] @@ -107,7 +107,7 @@ namespace Jellyfin.Networking.Tests /// /// Checks IP address formats. /// - /// + /// IP Address. [Theory] [InlineData("127.0.0.1")] [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")] From 572600b38ec560ee57e9954e2967014a042a1c06 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 23 Mar 2021 15:47:55 +0100 Subject: [PATCH 165/282] Use conditional operator instead of if/else block --- .../Parsers/MovieNfoParserTests.cs | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index 76e10b4515..4c556b4d6b 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -56,26 +56,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers .Returns(new UserItemData()); var directoryService = new Mock(); - if (MediaBrowser.Common.System.OperatingSystem.Id != OperatingSystemId.Windows) + _localImageFileMetadata = new FileSystemMetadata() { - _localImageFileMetadata = new FileSystemMetadata() - { - Exists = true, - FullName = "/media/movies/Justice League (2017).jpg" - }; - directoryService.Setup(x => x.GetFile(_localImageFileMetadata.FullName)) - .Returns(_localImageFileMetadata); - } - else - { - _localImageFileMetadata = new FileSystemMetadata() - { - Exists = true, - FullName = "C:\\media\\movies\\Justice League (2017).jpg" - }; - directoryService.Setup(x => x.GetFile(_localImageFileMetadata.FullName)) - .Returns(_localImageFileMetadata); - } + Exists = true, + FullName = MediaBrowser.Common.System.OperatingSystem.Id == OperatingSystemId.Windows ? + "C:\\media\\movies\\Justice League (2017).jpg" + : "/media/movies/Justice League (2017).jpg" + }; + directoryService.Setup(x => x.GetFile(_localImageFileMetadata.FullName)) + .Returns(_localImageFileMetadata); _userDataManager = userData.Object; _parser = new MovieNfoParser( From 19e4ef82dda1cbba9d533047950afb42c425749a Mon Sep 17 00:00:00 2001 From: David Date: Tue, 23 Mar 2021 17:16:10 +0100 Subject: [PATCH 166/282] Remove conversion from IPAddress to string to IPAddress --- .../HttpServer/Security/SessionContext.cs | 2 +- Jellyfin.Api/Controllers/UserController.cs | 25 +++++++++++++------ Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 2 +- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 5 ++-- Jellyfin.Api/Helpers/RequestHelpers.cs | 2 +- .../Extensions/HttpContextExtensions.cs | 4 +-- .../LocalAccessHandlerTests.cs | 3 ++- 7 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 040b6b9e4e..dd77b45d89 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.HttpServer.Security var authorization = _authContext.GetAuthorizationInfo(requestContext); var user = authorization.User; - return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp(), user); + return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user); } public SessionInfo GetSession(object requestContext) diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 43ee309b76..3c0d2aca16 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -21,6 +21,7 @@ using MediaBrowser.Model.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers { @@ -36,6 +37,7 @@ namespace Jellyfin.Api.Controllers private readonly IDeviceManager _deviceManager; private readonly IAuthorizationContext _authContext; private readonly IServerConfigurationManager _config; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. @@ -46,13 +48,15 @@ namespace Jellyfin.Api.Controllers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public UserController( IUserManager userManager, ISessionManager sessionManager, INetworkManager networkManager, IDeviceManager deviceManager, IAuthorizationContext authContext, - IServerConfigurationManager config) + IServerConfigurationManager config, + ILogger logger) { _userManager = userManager; _sessionManager = sessionManager; @@ -60,6 +64,7 @@ namespace Jellyfin.Api.Controllers _deviceManager = deviceManager; _authContext = authContext; _config = config; + _logger = logger; } /// @@ -118,7 +123,7 @@ namespace Jellyfin.Api.Controllers return NotFound("User not found"); } - var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIp()); + var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIp().ToString()); return result; } @@ -204,7 +209,7 @@ namespace Jellyfin.Api.Controllers DeviceName = auth.Device, Password = request.Pw, PasswordSha1 = request.Password, - RemoteEndPoint = HttpContext.GetNormalizedRemoteIp(), + RemoteEndPoint = HttpContext.GetNormalizedRemoteIp().ToString(), Username = request.Username }).ConfigureAwait(false); @@ -291,7 +296,7 @@ namespace Jellyfin.Api.Controllers user.Username, request.CurrentPw, request.CurrentPw, - HttpContext.GetNormalizedRemoteIp(), + HttpContext.GetNormalizedRemoteIp().ToString(), false).ConfigureAwait(false); if (success == null) @@ -483,7 +488,7 @@ namespace Jellyfin.Api.Controllers await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false); } - var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIp()); + var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIp().ToString()); return result; } @@ -498,8 +503,14 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public async Task> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPasswordRequest) { + var ip = HttpContext.GetNormalizedRemoteIp(); var isLocal = HttpContext.IsLocal() - || _networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIp()); + || _networkManager.IsInLocalNetwork(ip); + + if (isLocal) + { + _logger.LogWarning("Password reset proccess initiated from outside the local network with IP: {IP}", ip); + } var result = await _userManager.StartForgotPasswordProcess(forgotPasswordRequest.EnteredUsername, isLocal).ConfigureAwait(false); @@ -581,7 +592,7 @@ namespace Jellyfin.Api.Controllers var result = users .OrderBy(u => u.Username) - .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIp())); + .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIp().ToString())); return result; } diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 16380f0bba..751b48682d 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -434,7 +434,7 @@ namespace Jellyfin.Api.Helpers } } - private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream, bool enableAdaptiveBitrateStreaming, string ipAddress) + private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream, bool enableAdaptiveBitrateStreaming, IPAddress ipAddress) { // Within the local network this will likely do more harm than good. if (_networkManager.IsInLocalNetwork(ipAddress)) diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index ce6740fc99..a2d0e7030a 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Linq; +using System.Net; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -179,7 +180,7 @@ namespace Jellyfin.Api.Helpers bool enableTranscoding, bool allowVideoStreamCopy, bool allowAudioStreamCopy, - string ipAddress) + IPAddress ipAddress) { var streamBuilder = new StreamBuilder(_mediaEncoder, _logger); @@ -551,7 +552,7 @@ namespace Jellyfin.Api.Helpers } } - private int? GetMaxBitrate(int? clientMaxBitrate, User user, string ipAddress) + private int? GetMaxBitrate(int? clientMaxBitrate, User user, IPAddress ipAddress) { var maxBitrate = clientMaxBitrate; var remoteClientMaxBitrate = user.RemoteClientBitrateLimit ?? 0; diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 94856e03ed..56585aeab5 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -84,7 +84,7 @@ namespace Jellyfin.Api.Helpers authorization.Version, authorization.DeviceId, authorization.Device, - request.HttpContext.GetNormalizedRemoteIp(), + request.HttpContext.GetNormalizedRemoteIp().ToString(), user); if (session == null) diff --git a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs index 19fa95480d..e51ad42d12 100644 --- a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs +++ b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Common.Extensions /// /// The HTTP context. /// The remote caller IP address. - public static string GetNormalizedRemoteIp(this HttpContext context) + public static IPAddress GetNormalizedRemoteIp(this HttpContext context) { // Default to the loopback address if no RemoteIpAddress is specified (i.e. during integration tests) var ip = context.Connection.RemoteIpAddress ?? IPAddress.Loopback; @@ -35,7 +35,7 @@ namespace MediaBrowser.Common.Extensions ip = ip.MapToIPv4(); } - return ip.ToString(); + return ip; } } } diff --git a/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs index 09ffa84689..5b3d784ffa 100644 --- a/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Net; using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; @@ -41,7 +42,7 @@ namespace Jellyfin.Api.Tests.Auth.LocalAccessPolicy public async Task LocalAccessOnly(bool isInLocalNetwork, bool shouldSucceed) { _networkManagerMock - .Setup(n => n.IsInLocalNetwork(It.IsAny())) + .Setup(n => n.IsInLocalNetwork(It.IsAny())) .Returns(isInLocalNetwork); TestHelpers.SetupConfigurationManager(_configurationManagerMock, true); From c2af50c51dd4d49ce44604ba5b34d77fad01a130 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 23 Mar 2021 17:39:55 +0100 Subject: [PATCH 167/282] Add tests for IsInNetwork --- .../NetworkManagerTests.cs | 63 +++++++++++++++++++ .../NetworkParseTests.cs | 28 +-------- 2 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs diff --git a/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs new file mode 100644 index 0000000000..1cad625b73 --- /dev/null +++ b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs @@ -0,0 +1,63 @@ +using System.Net; +using Jellyfin.Networking.Configuration; +using Jellyfin.Networking.Manager; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace Jellyfin.Networking.Tests +{ + public class NetworkManagerTests + { + /// + /// Checks that the given IP address is in the specified network(s). + /// + /// Network address(es). + /// The IP to check. + [Theory] + [InlineData("192.168.2.1/24", "192.168.2.123")] + [InlineData("192.168.2.1/24, !192.168.2.122/32", "192.168.2.123")] + [InlineData("fd23:184f:2029:0::/56", "fd23:184f:2029:0:3139:7386:67d7:d517")] + [InlineData("fd23:184f:2029:0::/56, !fd23:184f:2029:0:3139:7386:67d7:d518/128", "fd23:184f:2029:0:3139:7386:67d7:d517")] + public void InNetwork_True_Success(string network, string value) + { + var ip = IPAddress.Parse(value); + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + LocalNetworkSubnets = network.Split(',') + }; + + using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger()); + + Assert.True(networkManager.IsInLocalNetwork(ip)); + } + + /// + /// Checks that thge given IP address is not in the network provided. + /// + /// Network address(es). + /// The IP to check. + [Theory] + [InlineData("192.168.10.0/24", "192.168.11.1")] + [InlineData("192.168.10.0/24, !192.168.10.60/32", "192.168.10.60")] + [InlineData("192.168.10.0/24", "fd23:184f:2029:0:3139:7386:67d7:d517")] + [InlineData("fd23:184f:2029:0::/56", "fd24:184f:2029:0:3139:7386:67d7:d517")] + [InlineData("fd23:184f:2029:0::/56, !fd23:184f:2029:0:3139:7386:67d7:d500/120", "fd23:184f:2029:0:3139:7386:67d7:d517")] + [InlineData("fd23:184f:2029:0::/56", "192.168.10.60")] + public void InNetwork_False_Success(string network, string value) + { + var ip = IPAddress.Parse(value); + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + LocalNetworkSubnets = network.Split(',') + }; + + using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger()); + + Assert.False(nm.IsInLocalNetwork(ip)); + } + } +} diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index c3469035eb..d8541d76bb 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Networking.Tests { public class NetworkParseTests { - private static IConfigurationManager GetMockConfig(NetworkConfiguration conf) + internal static IConfigurationManager GetMockConfig(NetworkConfiguration conf) { var configManager = new Mock { @@ -54,32 +54,6 @@ namespace Jellyfin.Networking.Tests Assert.Equal(nm.GetInternalBindAddresses().AsString(), value); } - /// - /// Check that the value given is in the network provided. - /// - /// Network address. - /// Value to check. - [Theory] - [InlineData("192.168.10.0/24, !192.168.10.60/32", "192.168.10.60")] - public void IsInNetwork(string network, string value) - { - if (network == null) - { - throw new ArgumentNullException(nameof(network)); - } - - var conf = new NetworkConfiguration() - { - EnableIPV6 = true, - EnableIPV4 = true, - LocalNetworkSubnets = network.Split(',') - }; - - using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); - - Assert.False(nm.IsInLocalNetwork(value)); - } - /// /// Checks IP address formats. /// From a4cac09d5b90dda5b7d7978e083676394022ffc1 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 23 Mar 2021 19:25:32 +0100 Subject: [PATCH 168/282] Use |= --- .../Manager/MetadataService.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 494e479a57..437b43ecaf 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -473,7 +473,7 @@ namespace MediaBrowser.Providers.Manager if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue) || (originalProductionYear ?? -1) != (item.ProductionYear ?? -1)) { - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } return updateType; @@ -493,7 +493,7 @@ namespace MediaBrowser.Providers.Manager if (currentList.Length != item.Genres.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) { - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } } @@ -514,7 +514,7 @@ namespace MediaBrowser.Providers.Manager if (currentList.Length != item.Studios.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) { - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } } @@ -529,7 +529,7 @@ namespace MediaBrowser.Providers.Manager { if (item.UpdateRatingToItems(children)) { - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } } @@ -686,7 +686,7 @@ namespace MediaBrowser.Providers.Manager var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType>(), cancellationToken) .ConfigureAwait(false); - refreshResult.UpdateType = refreshResult.UpdateType | remoteResult.UpdateType; + refreshResult.UpdateType |= remoteResult.UpdateType; refreshResult.ErrorMessage = remoteResult.ErrorMessage; refreshResult.Failures += remoteResult.Failures; } @@ -709,12 +709,12 @@ namespace MediaBrowser.Providers.Manager foreach (var remoteImage in localItem.RemoteImages) { await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false); - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate; + refreshResult.UpdateType |= ItemUpdateType.ImageUpdate; } if (imageService.MergeImages(item, localItem.Images)) { - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate; + refreshResult.UpdateType |= ItemUpdateType.ImageUpdate; } if (localItem.UserDataList != null) @@ -723,7 +723,7 @@ namespace MediaBrowser.Providers.Manager } MergeData(localItem, temp, Array.Empty(), !options.ReplaceAllMetadata, true); - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport; + refreshResult.UpdateType |= ItemUpdateType.MetadataImport; // Only one local provider allowed per item if (item.IsLocked || localItem.Item.IsLocked || IsFullLocalMetadata(localItem.Item)) @@ -755,7 +755,7 @@ namespace MediaBrowser.Providers.Manager var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType>(), cancellationToken) .ConfigureAwait(false); - refreshResult.UpdateType = refreshResult.UpdateType | remoteResult.UpdateType; + refreshResult.UpdateType |= remoteResult.UpdateType; refreshResult.ErrorMessage = remoteResult.ErrorMessage; refreshResult.Failures += remoteResult.Failures; } @@ -851,7 +851,7 @@ namespace MediaBrowser.Providers.Manager MergeData(result, temp, Array.Empty(), false, false); MergeNewData(temp.Item, id); - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload; + refreshResult.UpdateType |= ItemUpdateType.MetadataDownload; } else { From d77507ba09a010dc653814ea620aafa42348a373 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 24 Mar 2021 18:09:40 +0100 Subject: [PATCH 169/282] Rewrite PasswordHash.Parse to work with ReadOnlySpans --- .../Cryptography/PasswordHash.cs | 121 +++++++++++---- .../Cryptography/PasswordHashTests.cs | 139 ++++++++++++++++++ .../PasswordHashTests.cs | 31 ---- 3 files changed, 233 insertions(+), 58 deletions(-) create mode 100644 tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/PasswordHashTests.cs diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index 3e2eae1c8e..81e980bf2f 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#nullable enable using System; using System.Collections.Generic; @@ -30,6 +31,16 @@ namespace MediaBrowser.Common.Cryptography public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary parameters) { + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + if (id.Length == 0) + { + throw new ArgumentException("String can't be empty", nameof(id)); + } + Id = id; _hash = hash; _salt = salt; @@ -59,58 +70,109 @@ namespace MediaBrowser.Common.Cryptography /// Return the hashed password. public ReadOnlySpan Hash => _hash; - public static PasswordHash Parse(string hashString) + public static PasswordHash Parse(ReadOnlySpan hashString) { - // The string should at least contain the hash function and the hash itself - string[] splitted = hashString.Split('$'); - if (splitted.Length < 3) + if (hashString.IsEmpty) + { + throw new ArgumentException("String can't be empty", nameof(hashString)); + } + + if (hashString[0] != '$') { - throw new ArgumentException("String doesn't contain enough segments", nameof(hashString)); + throw new FormatException("Hash string must start with a $"); } - // Start at 1, the first index shouldn't contain any data - int index = 1; + // Ignore first $ + hashString = hashString[1..]; - // Name of the hash function - string id = splitted[index++]; + int nextSegment = hashString.IndexOf('$'); + if (hashString.IsEmpty || nextSegment == 0) + { + throw new FormatException("Hash string must contain a valid id"); + } + else if (nextSegment == -1) + { + return new PasswordHash(hashString.ToString(), Array.Empty()); + } + + ReadOnlySpan id = hashString[..nextSegment]; + hashString = hashString[(nextSegment + 1)..]; + Dictionary? parameters = null; + + nextSegment = hashString.IndexOf('$'); // Optional parameters - Dictionary parameters = new Dictionary(); - if (splitted[index].IndexOf('=', StringComparison.Ordinal) != -1) + ReadOnlySpan parametersSpan = nextSegment == -1 ? hashString : hashString[..nextSegment]; + if (parametersSpan.Contains('=')) { - foreach (string paramset in splitted[index++].Split(',')) + while (!parametersSpan.IsEmpty) { - if (string.IsNullOrEmpty(paramset)) + ReadOnlySpan parameter; + int index = parametersSpan.IndexOf(','); + if (index == -1) + { + parameter = parametersSpan; + parametersSpan = ReadOnlySpan.Empty; + } + else { - continue; + parameter = parametersSpan[..index]; + parametersSpan = parametersSpan[(index + 1)..]; } - string[] fields = paramset.Split('='); - if (fields.Length != 2) + int splitIndex = parameter.IndexOf('='); + if (splitIndex == -1 || splitIndex == 0 || splitIndex == parameter.Length - 1) { - throw new InvalidDataException($"Malformed parameter in password hash string {paramset}"); + throw new FormatException($"Malformed parameter in password hash string"); } - parameters.Add(fields[0], fields[1]); + (parameters ??= new Dictionary()).Add( + parameter[..splitIndex].ToString(), + parameter[(splitIndex + 1)..].ToString()); + } + + if (nextSegment == -1) + { + // parameters can't be null here + return new PasswordHash(id.ToString(), Array.Empty(), Array.Empty(), parameters!); } + + hashString = hashString[(nextSegment + 1)..]; + nextSegment = hashString.IndexOf('$'); + } + + if (nextSegment == 0) + { + throw new FormatException($"Hash string contains an empty segment"); } byte[] hash; byte[] salt; - // Check if the string also contains a salt - if (splitted.Length - index == 2) + if (nextSegment == -1) { - salt = Convert.FromHexString(splitted[index++]); - hash = Convert.FromHexString(splitted[index++]); + salt = Array.Empty(); + hash = Convert.FromHexString(hashString); } else { - salt = Array.Empty(); - hash = Convert.FromHexString(splitted[index++]); + salt = Convert.FromHexString(hashString[..nextSegment]); + hashString = hashString[(nextSegment + 1)..]; + nextSegment = hashString.IndexOf('$'); + if (nextSegment != -1) + { + throw new FormatException("Hash string contains too many segments"); + } + + if (hashString.IsEmpty) + { + throw new FormatException("Hash segment is empty"); + } + + hash = Convert.FromHexString(hashString); } - return new PasswordHash(id, hash, salt, parameters); + return new PasswordHash(id.ToString(), hash, salt, parameters ?? new Dictionary()); } private void SerializeParameters(StringBuilder stringBuilder) @@ -147,8 +209,13 @@ namespace MediaBrowser.Common.Cryptography .Append(Convert.ToHexString(_salt)); } - return str.Append('$') - .Append(Convert.ToHexString(_hash)).ToString(); + if (_hash.Length != 0) + { + str.Append('$') + .Append(Convert.ToHexString(_hash)); + } + + return str.ToString(); } } } diff --git a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs new file mode 100644 index 0000000000..6da0a3037e --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Cryptography; +using Xunit; + +namespace Jellyfin.Common.Tests.Cryptography +{ + public static class PasswordHashTests + { + [Fact] + public static void Ctor_Null_ThrowsArgumentNullException() + { + Assert.Throws(() => new PasswordHash(null!, Array.Empty())); + } + + [Fact] + public static void Ctor_Empty_ThrowsArgumentException() + { + Assert.Throws(() => new PasswordHash(string.Empty, Array.Empty())); + } + + public static IEnumerable Parse_Valid_TestData() + { + yield return new object[] + { + "$PBKDF2", + new PasswordHash("PBKDF2", Array.Empty()) + }; + + yield return new object[] + { + "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + new PasswordHash( + "PBKDF2", + Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), + Array.Empty(), + new Dictionary() + { + { "iterations", "1000" } + }) + }; + + yield return new object[] + { + "$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + new PasswordHash( + "PBKDF2", + Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), + Array.Empty(), + new Dictionary() + { + { "iterations", "1000" }, + { "m", "120" } + }) + }; + + yield return new object[] + { + "$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + new PasswordHash( + "PBKDF2", + Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), + Convert.FromHexString("69F420"), + new Dictionary() + { + { "iterations", "1000" }, + { "m", "120" } + }) + }; + + yield return new object[] + { + "$PBKDF2$iterations=1000,m=120", + new PasswordHash( + "PBKDF2", + Array.Empty(), + Array.Empty(), + new Dictionary() + { + { "iterations", "1000" }, + { "m", "120" } + }) + }; + } + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse_Valid_Success(string passwordHashString, PasswordHash expected) + { + var passwordHash = PasswordHash.Parse(passwordHashString); + Assert.Equal(expected.Id, passwordHash.Id); + Assert.Equal(expected.Parameters, passwordHash.Parameters); + Assert.Equal(expected.Salt.ToArray(), passwordHash.Salt.ToArray()); + Assert.Equal(expected.Hash.ToArray(), passwordHash.Hash.ToArray()); + Assert.Equal(expected.ToString(), passwordHash.ToString()); + } + + [Theory] + [InlineData("$PBKDF2")] + [InlineData("$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] + [InlineData("$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] + [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] + [InlineData("$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] + [InlineData("$PBKDF2$iterations=1000,m=120")] + public static void ToString_Roundtrip_Success(string passwordHash) + { + Assert.Equal(passwordHash, PasswordHash.Parse(passwordHash).ToString()); + } + + [Fact] + public static void Parse_Null_ThrowsArgumentException() + { + Assert.Throws(() => PasswordHash.Parse(null)); + } + + [Fact] + public static void Parse_Empty_ThrowsArgumentException() + { + Assert.Throws(() => PasswordHash.Parse(string.Empty)); + } + + [Theory] + [InlineData("$")] // No id + [InlineData("$$")] // Empty segments + [InlineData("PBKDF2$")] // Doesn't start with $ + [InlineData("$PBKDF2$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty segment + [InlineData("$PBKDF2$iterations=1000$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty salt segment + [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter + [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter + [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter + [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $ + [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment + [InlineData("$PBKDF2$69F420$")] // Empty hash + public static void Parse_InvalidFormat_ThrowsFormatException(string passwordHash) + { + Assert.Throws(() => PasswordHash.Parse(passwordHash)); + } + } +} diff --git a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs deleted file mode 100644 index c4422bd105..0000000000 --- a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using MediaBrowser.Common; -using MediaBrowser.Common.Cryptography; -using Xunit; - -namespace Jellyfin.Common.Tests -{ - public class PasswordHashTests - { - [Theory] - [InlineData( - "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", - "PBKDF2", - "", - "62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] - public void ParseTest(string passwordHash, string id, string salt, string hash) - { - var pass = PasswordHash.Parse(passwordHash); - Assert.Equal(id, pass.Id); - Assert.Equal(salt, Convert.ToHexString(pass.Salt)); - Assert.Equal(hash, Convert.ToHexString(pass.Hash)); - } - - [Theory] - [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] - public void ToStringTest(string passwordHash) - { - Assert.Equal(passwordHash, PasswordHash.Parse(passwordHash).ToString()); - } - } -} From c2cd7fa0b2441212ac0de16ffcef18883ee8c665 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 24 Mar 2021 18:39:33 +0100 Subject: [PATCH 170/282] Add more PasswordHash tests --- .../Cryptography/PasswordHashTests.cs | 66 ++++++++++++++++--- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs index 6da0a3037e..e6c325bac0 100644 --- a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs +++ b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs @@ -21,31 +21,34 @@ namespace Jellyfin.Common.Tests.Cryptography public static IEnumerable Parse_Valid_TestData() { + // Id yield return new object[] { "$PBKDF2", new PasswordHash("PBKDF2", Array.Empty()) }; + // Id + parameter yield return new object[] { - "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + "$PBKDF2$iterations=1000", new PasswordHash( "PBKDF2", - Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), + Array.Empty(), Array.Empty(), new Dictionary() { - { "iterations", "1000" } + { "iterations", "1000" }, }) }; + // Id + parameters yield return new object[] { - "$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + "$PBKDF2$iterations=1000,m=120", new PasswordHash( "PBKDF2", - Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), + Array.Empty(), Array.Empty(), new Dictionary() { @@ -54,13 +57,50 @@ namespace Jellyfin.Common.Tests.Cryptography }) }; + // Id + hash yield return new object[] { - "$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + "$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + new PasswordHash( + "PBKDF2", + Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), + Array.Empty(), + new Dictionary()) + }; + + // Id + salt + hash + yield return new object[] + { + "$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", new PasswordHash( "PBKDF2", Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), Convert.FromHexString("69F420"), + new Dictionary()) + }; + + // Id + parameter + hash + yield return new object[] + { + "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + new PasswordHash( + "PBKDF2", + Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), + Array.Empty(), + new Dictionary() + { + { "iterations", "1000" } + }) + }; + + // Id + parameters + hash + yield return new object[] + { + "$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + new PasswordHash( + "PBKDF2", + Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), + Array.Empty(), new Dictionary() { { "iterations", "1000" }, @@ -68,13 +108,14 @@ namespace Jellyfin.Common.Tests.Cryptography }) }; + // Id + parameters + salt + hash yield return new object[] { - "$PBKDF2$iterations=1000,m=120", + "$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", new PasswordHash( "PBKDF2", - Array.Empty(), - Array.Empty(), + Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"), + Convert.FromHexString("69F420"), new Dictionary() { { "iterations", "1000" }, @@ -101,6 +142,7 @@ namespace Jellyfin.Common.Tests.Cryptography [InlineData("$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] [InlineData("$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] + [InlineData("$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] [InlineData("$PBKDF2$iterations=1000,m=120")] public static void ToString_Roundtrip_Success(string passwordHash) { @@ -125,11 +167,15 @@ namespace Jellyfin.Common.Tests.Cryptography [InlineData("PBKDF2$")] // Doesn't start with $ [InlineData("$PBKDF2$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty segment [InlineData("$PBKDF2$iterations=1000$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty salt segment + [InlineData("$PBKDF2$iterations=1000$69F420$")] // Empty hash segment [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $ - [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment + [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment + [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment + [InlineData("$PBKDF2$iterations=$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt + [InlineData("$PBKDF2$iterations=$69F420$invalid hash")] // Invalid hash [InlineData("$PBKDF2$69F420$")] // Empty hash public static void Parse_InvalidFormat_ThrowsFormatException(string passwordHash) { From 136136dea95321a1b691d3d5cafeac4f946aaf50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Aceda=C5=84ski?= Date: Wed, 24 Mar 2021 20:26:01 +0100 Subject: [PATCH 171/282] Fix incorrect responses for HEAD /audio//stream Without this fix my Samsung Soundbar (HW-Q80R) fails to play using DLNA and returns "Error: Resource not found (716)" instead. I had a look on tcpdump network logs between Jellyfin and the soundbar and noticed that the device performs a HEAD request for the media before responding to the DLNA UPNP control request from Jellyfin (or BubbleUPNP Android App). Jellyfin retuns 204 No Content response, which is unusual. Common web servers generally return 200 OK if the GET would return content, and this is not-very-clearly suggested [in HTTP spec](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1) The other patch is to ensure, that invalid Content-Length: 0 is not returned with the HEAD response in the streaming case. I think in both cases we still don't return the same headers with HEAD as with GET (e.g. Content-Length or Accept-Ranges), but at least we don't return anything misleading. --- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index f828b1d9d6..a567fce1e4 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -46,7 +46,8 @@ namespace Jellyfin.Api.Helpers if (isHeadRequest) { - return new FileContentResult(Array.Empty(), contentType); + httpContext.Response.Headers[HeaderNames.ContentType] = contentType; + return new OkResult(); } return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType); @@ -71,7 +72,7 @@ namespace Jellyfin.Api.Helpers // if the request is a head request, return a NoContent result with the same headers as it would with a GET request if (isHeadRequest) { - return new NoContentResult(); + return new OkResult(); } return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true }; From 066c19a26b1dbc36c20439426110738b2ce1c2d6 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 24 Mar 2021 21:06:03 +0100 Subject: [PATCH 172/282] Fix possible null ref exception --- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 46a7feb7ff..c18838caf7 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -208,7 +208,7 @@ namespace Emby.Server.Implementations.Library /// Gets or sets the list of entity resolution ignore rules. /// /// The entity resolution ignore rules. - private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } + private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } = Array.Empty(); /// /// Gets or sets the list of currently registered entity resolvers. From 86852178c21f19944360ab0728042faa127d0128 Mon Sep 17 00:00:00 2001 From: cocool97 <34218602+cocool97@users.noreply.github.com> Date: Wed, 24 Mar 2021 21:23:59 +0100 Subject: [PATCH 173/282] Update MediaBrowser.Controller/LiveTv/ChannelInfo.cs Co-authored-by: Cody Robibero --- MediaBrowser.Controller/LiveTv/ChannelInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index 0f3f878475..166c4d77c0 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -46,9 +46,9 @@ namespace MediaBrowser.Controller.LiveTv public ChannelType ChannelType { get; set; } /// - /// Gets or sets the group of the channel + /// Gets or sets the group of the channel. /// - /// The group of the channel + /// The group of the channel. public string ChannelGroup { get; set; } /// From b1e8a8565f316e9b3d65b3a681384dfc8d743b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Aceda=C5=84ski?= Date: Wed, 24 Mar 2021 22:46:08 +0100 Subject: [PATCH 174/282] Update Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs Co-authored-by: Claus Vium --- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index a567fce1e4..b0fd59e5e3 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -69,7 +69,7 @@ namespace Jellyfin.Api.Helpers { httpContext.Response.ContentType = contentType; - // if the request is a head request, return a NoContent result with the same headers as it would with a GET request + // if the request is a head request, return an OkResult (200) with the same headers as it would with a GET request if (isHeadRequest) { return new OkResult(); From ef9eba8bc9f697ed6d8bf973d7f5c8d7865ecd18 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 25 Mar 2021 11:45:27 +0100 Subject: [PATCH 175/282] Ignore format for ISO files --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 6 ++++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 09080e7b2c..92b9a8c7eb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -313,6 +313,12 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } + // ISO files don't have an ffmpeg format + if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + return container; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 47cf020b4e..205933ae20 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -370,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource) { var prefix = "file"; - if (mediaSource.VideoType == VideoType.BluRay || mediaSource.VideoType == VideoType.Iso) + if (mediaSource.VideoType == VideoType.BluRay) { prefix = "bluray"; } From 5bb7d99b48feb66a393da73f4a056b6dd5f38739 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 25 Mar 2021 13:16:09 +0100 Subject: [PATCH 176/282] Remove DVDs from files exempt from chapter image extraction --- Emby.Server.Implementations/MediaEncoder/EncodingManager.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index c6e931448d..a9dab91383 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -82,11 +82,6 @@ namespace Emby.Server.Implementations.MediaEncoder return false; } - if (video.VideoType == VideoType.Dvd) - { - return false; - } - if (video.IsShortcut) { return false; From b3d084044e2d98590d9c939090ba522aa66dff82 Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 25 Mar 2021 15:09:37 +0100 Subject: [PATCH 177/282] enable range processing for download endpoints --- Jellyfin.Api/Controllers/LibraryController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index f8e8825ef0..1d4bbe61e8 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -114,7 +114,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path)); + return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true); } /// @@ -666,7 +666,7 @@ namespace Jellyfin.Api.Controllers } // TODO determine non-ASCII validity. - return PhysicalFile(path, MimeTypes.GetMimeType(path), filename); + return PhysicalFile(path, MimeTypes.GetMimeType(path), filename, true); } /// From 4cea6d9ccfa5bddaef27800aac1c125ae22747d7 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 25 Mar 2021 18:53:36 +0100 Subject: [PATCH 178/282] Apply suggestions from code review Co-authored-by: Cody Robibero --- MediaBrowser.Common/Cryptography/PasswordHash.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index 81e980bf2f..f2ecc4741b 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Common.Cryptography int splitIndex = parameter.IndexOf('='); if (splitIndex == -1 || splitIndex == 0 || splitIndex == parameter.Length - 1) { - throw new FormatException($"Malformed parameter in password hash string"); + throw new FormatException("Malformed parameter in password hash string"); } (parameters ??= new Dictionary()).Add( @@ -143,7 +143,7 @@ namespace MediaBrowser.Common.Cryptography if (nextSegment == 0) { - throw new FormatException($"Hash string contains an empty segment"); + throw new FormatException("Hash string contains an empty segment"); } byte[] hash; From 73fe9d3f696e5d144047e173553b1d1dba03c5d1 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 25 Mar 2021 18:06:25 -0600 Subject: [PATCH 179/282] Allow subtitle format to be set from query parameter. --- Jellyfin.Api/Controllers/SubtitleController.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 16a47f2d88..e260efc9aa 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -185,6 +185,7 @@ namespace Jellyfin.Api.Controllers /// The item id. /// The media source id. /// The subtitle stream index. + /// The (route) format of the returned subtitle. /// The format of the returned subtitle. /// Optional. The end position of the subtitle in ticks. /// Optional. Whether to copy the timestamps. @@ -192,19 +193,25 @@ namespace Jellyfin.Api.Controllers /// Optional. The start position of the subtitle in ticks. /// File returned. /// A with the subtitle file. - [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")] + [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{routeFormat}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesFile("text/*")] public async Task GetSubtitle( [FromRoute, Required] Guid itemId, [FromRoute, Required] string mediaSourceId, [FromRoute, Required] int index, - [FromRoute, Required] string format, + [FromRoute, Required] string routeFormat, + [FromQuery] string? format, [FromQuery] long? endPositionTicks, [FromQuery] bool copyTimestamps = false, [FromQuery] bool addVttTimeMap = false, [FromQuery] long startPositionTicks = 0) { + if (string.IsNullOrEmpty(format)) + { + format = routeFormat; + } + if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase)) { format = "json"; @@ -255,13 +262,14 @@ namespace Jellyfin.Api.Controllers /// The media source id. /// The subtitle stream index. /// Optional. The start position of the subtitle in ticks. + /// The (route) format of the returned subtitle. /// The format of the returned subtitle. /// Optional. The end position of the subtitle in ticks. /// Optional. Whether to copy the timestamps. /// Optional. Whether to add a VTT time map. /// File returned. /// A with the subtitle file. - [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks}/Stream.{format}")] + [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks}/Stream.{routeFormat}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesFile("text/*")] public Task GetSubtitleWithTicks( @@ -269,7 +277,8 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string mediaSourceId, [FromRoute, Required] int index, [FromRoute, Required] long startPositionTicks, - [FromRoute, Required] string format, + [FromRoute, Required] string routeFormat, + [FromQuery] string? format, [FromQuery] long? endPositionTicks, [FromQuery] bool copyTimestamps = false, [FromQuery] bool addVttTimeMap = false) @@ -278,6 +287,7 @@ namespace Jellyfin.Api.Controllers itemId, mediaSourceId, index, + routeFormat, format, endPositionTicks, copyTimestamps, From e0ff51cf2af1d4b90e572ab3e012746748909ed8 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 25 Mar 2021 20:30:15 -0600 Subject: [PATCH 180/282] Mark query parameters as obsolete --- Jellyfin.Api/Controllers/SubtitleController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index e260efc9aa..a21d377acb 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -201,7 +201,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string mediaSourceId, [FromRoute, Required] int index, [FromRoute, Required] string routeFormat, - [FromQuery] string? format, + [FromQuery, ParameterObsolete] string? format, [FromQuery] long? endPositionTicks, [FromQuery] bool copyTimestamps = false, [FromQuery] bool addVttTimeMap = false, @@ -278,7 +278,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] int index, [FromRoute, Required] long startPositionTicks, [FromRoute, Required] string routeFormat, - [FromQuery] string? format, + [FromQuery, ParameterObsolete] string? format, [FromQuery] long? endPositionTicks, [FromQuery] bool copyTimestamps = false, [FromQuery] bool addVttTimeMap = false) From db2fbcef2b6398dc845b69affde0aef2cc432056 Mon Sep 17 00:00:00 2001 From: lmaonator Date: Fri, 26 Mar 2021 12:15:44 +0100 Subject: [PATCH 181/282] Fix stream selection having no effect when casting When casting to jellyfin-mpv-shim from jellyfin-web in the browser, jellyfin-web sends data about which version (for grouped items) and which streams the user selected in the browser to the "Sessions/{sessionId}/Playing" API endpoint. The API endpoint currently doesn't forward them to jellyfin-mpv-shim through the Play command, which results in the default streams being played instead of the browser selected ones. PlayRequest already has the properties and they are already sent to the cast client by SendPlayCommand when present. jellyfin-mpv-shim will already use them to select the wanted streams when it receives the Play command. All that's needed to make it work is to take the parameters and assign them to PlayRequest. --- Jellyfin.Api/Controllers/SessionController.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index e2269a2ce2..e178e3389a 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -153,6 +153,10 @@ namespace Jellyfin.Api.Controllers /// The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now. /// The ids of the items to play, comma delimited. /// The starting position of the first item. + /// Optional. The media source id. + /// Optional. The index of the audio stream to play. + /// Optional. The index of the subtitle stream to play. + /// Optional. The start index. /// Instruction sent to session. /// A . [HttpPost("Sessions/{sessionId}/Playing")] @@ -162,13 +166,21 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string sessionId, [FromQuery, Required] PlayCommand playCommand, [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds, - [FromQuery] long? startPositionTicks) + [FromQuery] long? startPositionTicks, + [FromQuery] string mediaSourceId, + [FromQuery] int? audioStreamIndex, + [FromQuery] int? subtitleStreamIndex, + [FromQuery] int? startIndex) { var playRequest = new PlayRequest { ItemIds = itemIds, StartPositionTicks = startPositionTicks, - PlayCommand = playCommand + PlayCommand = playCommand, + MediaSourceId = mediaSourceId, + AudioStreamIndex = audioStreamIndex, + SubtitleStreamIndex = subtitleStreamIndex, + StartIndex = startIndex }; _sessionManager.SendPlayCommand( From 5b758c47119c0520ff543f7b8e812f67533ce4f2 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 26 Mar 2021 07:07:45 -0600 Subject: [PATCH 182/282] Mark query parameters as obsolete --- .../Controllers/SubtitleController.cs | 64 ++++++++++++------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index a21d377acb..7a44b26a4b 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -182,35 +182,42 @@ namespace Jellyfin.Api.Controllers /// /// Gets subtitles in a specified format. /// + /// The (route) item id. + /// The (route) media source id. + /// The (route) subtitle stream index. + /// The (route) format of the returned subtitle. /// The item id. /// The media source id. /// The subtitle stream index. - /// The (route) format of the returned subtitle. /// The format of the returned subtitle. /// Optional. The end position of the subtitle in ticks. /// Optional. Whether to copy the timestamps. /// Optional. Whether to add a VTT time map. - /// Optional. The start position of the subtitle in ticks. + /// The start position of the subtitle in ticks. /// File returned. /// A with the subtitle file. [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{routeFormat}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesFile("text/*")] public async Task GetSubtitle( - [FromRoute, Required] Guid itemId, - [FromRoute, Required] string mediaSourceId, - [FromRoute, Required] int index, + [FromRoute, Required] Guid routeItemId, + [FromRoute, Required] string routeMediaSourceId, + [FromRoute, Required] int routeIndex, [FromRoute, Required] string routeFormat, + [FromQuery, ParameterObsolete] Guid? itemId, + [FromQuery, ParameterObsolete] string? mediaSourceId, + [FromQuery, ParameterObsolete] int? index, [FromQuery, ParameterObsolete] string? format, [FromQuery] long? endPositionTicks, [FromQuery] bool copyTimestamps = false, [FromQuery] bool addVttTimeMap = false, [FromQuery] long startPositionTicks = 0) { - if (string.IsNullOrEmpty(format)) - { - format = routeFormat; - } + // Set parameters to route value if not provided via query. + itemId ??= routeItemId; + mediaSourceId ??= routeMediaSourceId; + index ??= routeIndex; + format ??= routeFormat; if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase)) { @@ -219,9 +226,9 @@ namespace Jellyfin.Api.Controllers if (string.IsNullOrEmpty(format)) { - var item = (Video)_libraryManager.GetItemById(itemId); + var item = (Video)_libraryManager.GetItemById(itemId.Value); - var idString = itemId.ToString("N", CultureInfo.InvariantCulture); + var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture); var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false) .First(i => string.Equals(i.Id, mediaSourceId ?? idString, StringComparison.Ordinal)); @@ -233,7 +240,7 @@ namespace Jellyfin.Api.Controllers if (string.Equals(format, "vtt", StringComparison.OrdinalIgnoreCase) && addVttTimeMap) { - await using Stream stream = await EncodeSubtitles(itemId, mediaSourceId, index, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false); + await using Stream stream = await EncodeSubtitles(itemId.Value, mediaSourceId, index.Value, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false); using var reader = new StreamReader(stream); var text = await reader.ReadToEndAsync().ConfigureAwait(false); @@ -245,9 +252,9 @@ namespace Jellyfin.Api.Controllers return File( await EncodeSubtitles( - itemId, + itemId.Value, mediaSourceId, - index, + index.Value, format, startPositionTicks, endPositionTicks, @@ -258,41 +265,52 @@ namespace Jellyfin.Api.Controllers /// /// Gets subtitles in a specified format. /// + /// The (route) item id. + /// The (route) media source id. + /// The (route) subtitle stream index. + /// The (route) start position of the subtitle in ticks. + /// The (route) format of the returned subtitle. /// The item id. /// The media source id. /// The subtitle stream index. - /// Optional. The start position of the subtitle in ticks. - /// The (route) format of the returned subtitle. + /// The start position of the subtitle in ticks. /// The format of the returned subtitle. /// Optional. The end position of the subtitle in ticks. /// Optional. Whether to copy the timestamps. /// Optional. Whether to add a VTT time map. /// File returned. /// A with the subtitle file. - [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks}/Stream.{routeFormat}")] + [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/{routeStartPositionTicks}/Stream.{routeFormat}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesFile("text/*")] public Task GetSubtitleWithTicks( - [FromRoute, Required] Guid itemId, - [FromRoute, Required] string mediaSourceId, - [FromRoute, Required] int index, - [FromRoute, Required] long startPositionTicks, + [FromRoute, Required] Guid routeItemId, + [FromRoute, Required] string routeMediaSourceId, + [FromRoute, Required] int routeIndex, + [FromRoute, Required] long routeStartPositionTicks, [FromRoute, Required] string routeFormat, + [FromQuery, ParameterObsolete] Guid? itemId, + [FromQuery, ParameterObsolete] string? mediaSourceId, + [FromQuery, ParameterObsolete] int? index, + [FromQuery, ParameterObsolete] long? startPositionTicks, [FromQuery, ParameterObsolete] string? format, [FromQuery] long? endPositionTicks, [FromQuery] bool copyTimestamps = false, [FromQuery] bool addVttTimeMap = false) { return GetSubtitle( + routeItemId, + routeMediaSourceId, + routeIndex, + routeFormat, itemId, mediaSourceId, index, - routeFormat, format, endPositionTicks, copyTimestamps, addVttTimeMap, - startPositionTicks); + startPositionTicks ?? routeStartPositionTicks); } /// From 694d772b113b3f734e559b1dd083823dd931aad6 Mon Sep 17 00:00:00 2001 From: lmaonator <33778605+lmaonator@users.noreply.github.com> Date: Fri, 26 Mar 2021 14:13:45 +0100 Subject: [PATCH 183/282] Update Jellyfin.Api/Controllers/SessionController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/SessionController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index e178e3389a..0703d4255b 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -167,7 +167,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, Required] PlayCommand playCommand, [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds, [FromQuery] long? startPositionTicks, - [FromQuery] string mediaSourceId, + [FromQuery] string? mediaSourceId, [FromQuery] int? audioStreamIndex, [FromQuery] int? subtitleStreamIndex, [FromQuery] int? startIndex) From 78f7fdeaccee7f321740ede24dee797622b44f8b Mon Sep 17 00:00:00 2001 From: David Date: Fri, 26 Mar 2021 17:06:01 +0100 Subject: [PATCH 184/282] Rename methods and optimize allocations --- .../Library/PathExtensions.cs | 4 +-- .../Providers/ProviderIdParsers.cs | 34 ++++++++++--------- .../Parsers/BaseNfoParser.cs | 16 ++++----- .../Providers/ProviderIdParserTests.cs | 19 ++++++----- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 72fe5a854a..73236bad83 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -44,8 +44,8 @@ namespace Emby.Server.Implementations.Library // for imdbid we also accept pattern matching if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase)) { - var match = ProviderIdParsers.TryParseImdbId(str, out var imdbId); - return match ? imdbId : null; + var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId); + return match ? imdbId.ToString() : null; } return null; diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs index 7744124eaa..26eaccac5f 100644 --- a/MediaBrowser.Common/Providers/ProviderIdParsers.cs +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -12,6 +12,7 @@ namespace MediaBrowser.Common.Providers { private const int ImdbMinNumbers = 7; private const int ImdbMaxNumbers = 8; + private const string ImdbPrefix = "tt"; /// /// Parses an IMDb id from a string. @@ -19,9 +20,9 @@ namespace MediaBrowser.Common.Providers /// The text to parse. /// The parsed IMDb id. /// True if parsing was successful, false otherwise. - public static bool TryParseImdbId(ReadOnlySpan text, [NotNullWhen(true)] out string? imdbId) + public static bool TryFindImdbId(ReadOnlySpan text, [NotNullWhen(true)] out ReadOnlySpan imdbId) { - var tt = "tt".AsSpan(); + var tt = ImdbPrefix.AsSpan(); // imdb id is at least 9 chars (tt + 7 numbers) while (text.Length >= 2 + ImdbMinNumbers) @@ -33,9 +34,10 @@ namespace MediaBrowser.Common.Providers return false; } - text = text.Slice(ttPos + tt.Length); - var i = 0; - for (; i < Math.Min(text.Length, ImdbMaxNumbers); i++) + text = text.Slice(ttPos); + var i = 2; + var limit = Math.Min(text.Length, ImdbMaxNumbers + 2); + for (; i < limit; i++) { var c = text[i]; if (!IsDigit(c)) @@ -44,10 +46,10 @@ namespace MediaBrowser.Common.Providers } } - // skip if more than 8 digits - if (i <= ImdbMaxNumbers && i >= ImdbMinNumbers) + // skip if more than 8 digits + 2 chars for tt + if (i <= ImdbMaxNumbers + 2 && i >= ImdbMinNumbers + 2) { - imdbId = string.Concat(tt, text.Slice(0, i)); + imdbId = text.Slice(0, i); return true; } @@ -64,8 +66,8 @@ namespace MediaBrowser.Common.Providers /// The text with the url to parse. /// The parsed TMDb id. /// True if parsing was successful, false otherwise. - public static bool TryParseTmdbMovieId(ReadOnlySpan text, [NotNullWhen(true)] out string? tmdbId) - => TryParseProviderId(text, "themoviedb.org/movie/", out tmdbId); + public static bool TryFindTmdbMovieId(ReadOnlySpan text, [NotNullWhen(true)] out ReadOnlySpan tmdbId) + => TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId); /// /// Parses an TMDb id from a series url. @@ -73,8 +75,8 @@ namespace MediaBrowser.Common.Providers /// The text with the url to parse. /// The parsed TMDb id. /// True if parsing was successful, false otherwise. - public static bool TryParseTmdbSeriesId(ReadOnlySpan text, [NotNullWhen(true)] out string? tmdbId) - => TryParseProviderId(text, "themoviedb.org/tv/", out tmdbId); + public static bool TryFindTmdbSeriesId(ReadOnlySpan text, [NotNullWhen(true)] out ReadOnlySpan tmdbId) + => TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId); /// /// Parses an TVDb id from a url. @@ -82,10 +84,10 @@ namespace MediaBrowser.Common.Providers /// The text with the url to parse. /// The parsed TVDb id. /// True if parsing was successful, false otherwise. - public static bool TryParseTvdbId(ReadOnlySpan text, [NotNullWhen(true)] out string? tvdbId) - => TryParseProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId); + public static bool TryFindTvdbId(ReadOnlySpan text, [NotNullWhen(true)] out ReadOnlySpan tvdbId) + => TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId); - private static bool TryParseProviderId(ReadOnlySpan text, ReadOnlySpan searchString, [NotNullWhen(true)] out string? providerId) + private static bool TryFindProviderId(ReadOnlySpan text, ReadOnlySpan searchString, [NotNullWhen(true)] out ReadOnlySpan providerId) { var searchPos = text.IndexOf(searchString); if (searchPos == -1) @@ -109,7 +111,7 @@ namespace MediaBrowser.Common.Providers if (i >= 1) { - providerId = text.Slice(0, i).ToString(); + providerId = text.Slice(0, i); return true; } diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index d2fa120e8c..c5627f880c 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -219,29 +219,29 @@ namespace MediaBrowser.XbmcMetadata.Parsers protected void ParseProviderLinks(T item, string xml) { - if (ProviderIdParsers.TryParseImdbId(xml, out var imdbId)) + if (ProviderIdParsers.TryFindImdbId(xml, out var imdbId)) { - item.SetProviderId(MetadataProvider.Imdb, imdbId); + item.SetProviderId(MetadataProvider.Imdb, imdbId.ToString()); } if (item is Movie) { - if (ProviderIdParsers.TryParseTmdbMovieId(xml, out var tmdbId)) + if (ProviderIdParsers.TryFindTmdbMovieId(xml, out var tmdbId)) { - item.SetProviderId(MetadataProvider.Tmdb, tmdbId); + item.SetProviderId(MetadataProvider.Tmdb, tmdbId.ToString()); } } if (item is Series) { - if (ProviderIdParsers.TryParseTmdbSeriesId(xml, out var tmdbId)) + if (ProviderIdParsers.TryFindTmdbSeriesId(xml, out var tmdbId)) { - item.SetProviderId(MetadataProvider.Tmdb, tmdbId); + item.SetProviderId(MetadataProvider.Tmdb, tmdbId.ToString()); } - if (ProviderIdParsers.TryParseTvdbId(xml, out var tvdbId)) + if (ProviderIdParsers.TryFindTvdbId(xml, out var tvdbId)) { - item.SetProviderId(MetadataProvider.Tvdb, tvdbId); + item.SetProviderId(MetadataProvider.Tvdb, tvdbId.ToString()); } } } diff --git a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs index cfe1ea86bf..1ce54c59b5 100644 --- a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs +++ b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Providers; +using System; +using MediaBrowser.Common.Providers; using Xunit; namespace Jellyfin.Common.Tests.Providers @@ -20,9 +21,9 @@ namespace Jellyfin.Common.Tests.Providers [InlineData("tt123456789", true, "tt12345678")] public void Parse_Imdb(string text, bool shouldSucceed, string? imdbId) { - var succeeded = ProviderIdParsers.TryParseImdbId(text, out string? parsedId); + var succeeded = ProviderIdParsers.TryFindImdbId(text, out ReadOnlySpan parsedId); Assert.Equal(shouldSucceed, succeeded); - Assert.Equal(imdbId, parsedId); + Assert.Equal(imdbId ?? Span.Empty.ToString(), parsedId.ToString()); } [Theory] @@ -32,9 +33,9 @@ namespace Jellyfin.Common.Tests.Providers [InlineData("https://www.themoviedb.org/tv/1668-friends", false, null)] public void Parse_TmdbMovie(string text, bool shouldSucceed, string? tmdbId) { - var succeeded = ProviderIdParsers.TryParseTmdbMovieId(text, out string? parsedId); + var succeeded = ProviderIdParsers.TryFindTmdbMovieId(text, out ReadOnlySpan parsedId); Assert.Equal(shouldSucceed, succeeded); - Assert.Equal(tmdbId, parsedId); + Assert.Equal(tmdbId ?? Span.Empty.ToString(), parsedId.ToString()); } [Theory] @@ -44,9 +45,9 @@ namespace Jellyfin.Common.Tests.Providers [InlineData("https://www.themoviedb.org/movie/30287-fallo", false, null)] public void Parse_TmdbSeries(string text, bool shouldSucceed, string? tmdbId) { - var succeeded = ProviderIdParsers.TryParseTmdbSeriesId(text, out string? parsedId); + var succeeded = ProviderIdParsers.TryFindTmdbSeriesId(text, out ReadOnlySpan parsedId); Assert.Equal(shouldSucceed, succeeded); - Assert.Equal(tmdbId, parsedId); + Assert.Equal(tmdbId ?? Span.Empty.ToString(), parsedId.ToString()); } [Theory] @@ -56,9 +57,9 @@ namespace Jellyfin.Common.Tests.Providers [InlineData("https://www.themoviedb.org/tv/1668-friends", false, null)] public void Parse_Tvdb(string text, bool shouldSucceed, string? tvdbId) { - var succeeded = ProviderIdParsers.TryParseTvdbId(text, out string? parsedId); + var succeeded = ProviderIdParsers.TryFindTvdbId(text, out ReadOnlySpan parsedId); Assert.Equal(shouldSucceed, succeeded); - Assert.Equal(tvdbId, parsedId); + Assert.Equal(tvdbId ?? Span.Empty.ToString(), parsedId.ToString()); } } } From 7670189561302a88b576b3417594e036143ec7d7 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 27 Mar 2021 00:26:56 +0100 Subject: [PATCH 185/282] make directoryservice cache case sensitive --- .../Providers/DirectoryService.cs | 6 +- .../DirectoryServiceTests.cs | 200 ++++++++++++++++++ .../Jellyfin.Controller.Tests.csproj | 1 + 3 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 16fd1d42b0..5c92069b44 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -12,11 +12,11 @@ namespace MediaBrowser.Controller.Providers { private readonly IFileSystem _fileSystem; - private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary _cache = new (StringComparer.Ordinal); - private readonly ConcurrentDictionary _fileCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary _fileCache = new (StringComparer.Ordinal); - private readonly ConcurrentDictionary> _filePathCache = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary> _filePathCache = new (StringComparer.Ordinal); public DirectoryService(IFileSystem fileSystem) { diff --git a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs new file mode 100644 index 0000000000..feffb50e82 --- /dev/null +++ b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs @@ -0,0 +1,200 @@ +using System.Linq; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; +using Moq; +using Xunit; + +namespace Jellyfin.Controller.Tests +{ + public class DirectoryServiceTests + { + private const string LowerCasePath = "/music/someartist"; + private const string UpperCasePath = "/music/SOMEARTIST"; + + private static readonly FileSystemMetadata[] _lowerCaseFileSystemMetadata = + { + new () + { + FullName = LowerCasePath + "/Artwork", + IsDirectory = true + }, + new () + { + FullName = LowerCasePath + "/Some Other Folder", + IsDirectory = true + }, + new () + { + FullName = LowerCasePath + "/Song 2.mp3", + IsDirectory = false + }, + new () + { + FullName = LowerCasePath + "/Song 3.mp3", + IsDirectory = false + } + }; + + private static readonly FileSystemMetadata[] _upperCaseFileSystemMetadata = + { + new () + { + FullName = UpperCasePath + "/Lyrics", + IsDirectory = true + }, + new () + { + FullName = UpperCasePath + "/Song 1.mp3", + IsDirectory = false + } + }; + + [Fact] + public void GetFileSystemEntries_GivenPathsWithDifferentCasing_CachesAll() + { + var fileSystemMock = new Mock(); + fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata); + fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata); + var directoryService = new DirectoryService(fileSystemMock.Object); + + var upperCaseResult = directoryService.GetFileSystemEntries(UpperCasePath); + var lowerCaseResult = directoryService.GetFileSystemEntries(LowerCasePath); + + Assert.Equal(_upperCaseFileSystemMetadata, upperCaseResult); + Assert.Equal(_lowerCaseFileSystemMetadata, lowerCaseResult); + } + + [Fact] + public void GetFiles_GivenPathsWithDifferentCasing_ReturnsCorrectFiles() + { + var fileSystemMock = new Mock(); + fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata); + fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata); + var directoryService = new DirectoryService(fileSystemMock.Object); + + var upperCaseResult = directoryService.GetFiles(UpperCasePath); + var lowerCaseResult = directoryService.GetFiles(LowerCasePath); + + Assert.Equal(_upperCaseFileSystemMetadata.Where(f => !f.IsDirectory), upperCaseResult); + Assert.Equal(_lowerCaseFileSystemMetadata.Where(f => !f.IsDirectory), lowerCaseResult); + } + + [Fact] + public void GetFile_GivenFilePathsWithDifferentCasing_ReturnsCorrectFile() + { + const string lowerCasePath = "/music/someartist/song 1.mp3"; + var lowerCaseFileSystemMetadata = new FileSystemMetadata + { + FullName = lowerCasePath, + Exists = true + }; + const string upperCasePath = "/music/SOMEARTIST/SONG 1.mp3"; + var upperCaseFileSystemMetadata = new FileSystemMetadata + { + FullName = upperCasePath, + Exists = false + }; + var fileSystemMock = new Mock(); + fileSystemMock.Setup(f => f.GetFileInfo(It.Is(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata); + fileSystemMock.Setup(f => f.GetFileInfo(It.Is(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata); + var directoryService = new DirectoryService(fileSystemMock.Object); + + var lowerCaseResult = directoryService.GetFile(lowerCasePath); + var upperCaseResult = directoryService.GetFile(upperCasePath); + + Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseResult); + Assert.Null(upperCaseResult); + } + + [Fact] + public void GetFile_GivenCachedPath_ReturnsCachedFile() + { + const string path = "/music/someartist/song 1.mp3"; + var cachedFileSystemMetadata = new FileSystemMetadata + { + FullName = path, + Exists = true + }; + var newFileSystemMetadata = new FileSystemMetadata + { + FullName = "/music/SOMEARTIST/song 1.mp3", + Exists = true + }; + + var fileSystemMock = new Mock(); + fileSystemMock.Setup(f => f.GetFileInfo(It.Is(x => x == path))).Returns(cachedFileSystemMetadata); + var directoryService = new DirectoryService(fileSystemMock.Object); + + var result = directoryService.GetFile(path); + fileSystemMock.Setup(f => f.GetFileInfo(It.Is(x => x == path))).Returns(newFileSystemMetadata); + var secondResult = directoryService.GetFile(path); + + Assert.Equal(cachedFileSystemMetadata, result); + Assert.Equal(cachedFileSystemMetadata, secondResult); + } + + [Fact] + public void GetFilePaths_GivenCachedFilePathWithoutClear_ReturnsOnlyCachedPaths() + { + const string path = "/music/someartist"; + + var cachedPaths = new[] + { + "/music/someartist/song 1.mp3", + "/music/someartist/song 2.mp3", + "/music/someartist/song 3.mp3", + "/music/someartist/song 4.mp3", + }; + var newPaths = new[] + { + "/music/someartist/song 5.mp3", + "/music/someartist/song 6.mp3", + "/music/someartist/song 7.mp3", + "/music/someartist/song 8.mp3", + }; + + var fileSystemMock = new Mock(); + fileSystemMock.Setup(f => f.GetFilePaths(It.Is(x => x == path), false)).Returns(cachedPaths); + var directoryService = new DirectoryService(fileSystemMock.Object); + + var result = directoryService.GetFilePaths(path); + fileSystemMock.Setup(f => f.GetFilePaths(It.Is(x => x == path), false)).Returns(newPaths); + var secondResult = directoryService.GetFilePaths(path); + + Assert.Equal(cachedPaths, result); + Assert.Equal(cachedPaths, secondResult); + } + + [Fact] + public void GetFilePaths_GivenCachedFilePathWithClear_ReturnsNewPaths() + { + const string path = "/music/someartist"; + + var cachedPaths = new[] + { + "/music/someartist/song 1.mp3", + "/music/someartist/song 2.mp3", + "/music/someartist/song 3.mp3", + "/music/someartist/song 4.mp3", + }; + var newPaths = new[] + { + "/music/someartist/song 5.mp3", + "/music/someartist/song 6.mp3", + "/music/someartist/song 7.mp3", + "/music/someartist/song 8.mp3", + }; + + var fileSystemMock = new Mock(); + fileSystemMock.Setup(f => f.GetFilePaths(It.Is(x => x == path), false)).Returns(cachedPaths); + var directoryService = new DirectoryService(fileSystemMock.Object); + + var result = directoryService.GetFilePaths(path); + fileSystemMock.Setup(f => f.GetFilePaths(It.Is(x => x == path), false)).Returns(newPaths); + var secondResult = directoryService.GetFilePaths(path, true); + + Assert.Equal(cachedPaths, result); + Assert.Equal(newPaths, secondResult); + } + } +} diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index 6dec25aa4e..c56ccb3653 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -16,6 +16,7 @@ + From afe3b5999e00c7c705afe224e41755f5426e6b85 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 26 Mar 2021 17:40:55 -0600 Subject: [PATCH 186/282] Fix route naming --- Jellyfin.Api/Controllers/SubtitleController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 7a44b26a4b..1669a659dc 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers /// The start position of the subtitle in ticks. /// File returned. /// A with the subtitle file. - [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{routeFormat}")] + [HttpGet("Videos/{routeItemId}/routeMediaSourceId/Subtitles/{routeIndex}/Stream.{routeFormat}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesFile("text/*")] public async Task GetSubtitle( From aae2aad0f25867f651c94a30d02a1893442d65da Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 27 Mar 2021 08:16:48 +0000 Subject: [PATCH 187/282] changed split to single quotes --- .../NetworkParseTests.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 5d22b4d334..39fba1fb25 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -201,29 +201,29 @@ namespace Jellyfin.Networking.Tests using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); // Test included. - Collection nc = nm.CreateIPCollection(settings.Split(","), false); + Collection nc = nm.CreateIPCollection(settings.Split(','), false); Assert.Equal(nc.AsString(), result1); // Test excluded. - nc = nm.CreateIPCollection(settings.Split(","), true); + nc = nm.CreateIPCollection(settings.Split(','), true); Assert.Equal(nc.AsString(), result3); conf.EnableIPV6 = false; nm.UpdateSettings(conf); // Test IP4 included. - nc = nm.CreateIPCollection(settings.Split(","), false); + nc = nm.CreateIPCollection(settings.Split(','), false); Assert.Equal(nc.AsString(), result2); // Test IP4 excluded. - nc = nm.CreateIPCollection(settings.Split(","), true); + nc = nm.CreateIPCollection(settings.Split(','), true); Assert.Equal(nc.AsString(), result4); conf.EnableIPV6 = true; nm.UpdateSettings(conf); // Test network addresses of collection. - nc = nm.CreateIPCollection(settings.Split(","), false); + nc = nm.CreateIPCollection(settings.Split(','), false); nc = nc.AsNetworks(); Assert.Equal(nc.AsString(), result5); } @@ -262,8 +262,8 @@ namespace Jellyfin.Networking.Tests using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); - Collection nc1 = nm.CreateIPCollection(settings.Split(","), false); - Collection nc2 = nm.CreateIPCollection(compare.Split(","), false); + Collection nc1 = nm.CreateIPCollection(settings.Split(','), false); + Collection nc2 = nm.CreateIPCollection(compare.Split(','), false); Assert.Equal(nc1.Union(nc2).AsString(), result); } @@ -372,10 +372,10 @@ namespace Jellyfin.Networking.Tests using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); // Test included, IP6. - Collection ncSource = nm.CreateIPCollection(source.Split(",")); - Collection ncDest = nm.CreateIPCollection(dest.Split(",")); + Collection ncSource = nm.CreateIPCollection(source.Split(',')); + Collection ncDest = nm.CreateIPCollection(dest.Split(',')); Collection ncResult = ncSource.Union(ncDest); - Collection resultCollection = nm.CreateIPCollection(result.Split(",")); + Collection resultCollection = nm.CreateIPCollection(result.Split(',')); Assert.True(ncResult.Compare(resultCollection)); } @@ -527,7 +527,7 @@ namespace Jellyfin.Networking.Tests var conf = new NetworkConfiguration() { EnableIPV4 = true, - RemoteIPFilter = addresses.Split(","), + RemoteIPFilter = addresses.Split(','), IsRemoteIPFilterBlacklist = false }; using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); @@ -546,7 +546,7 @@ namespace Jellyfin.Networking.Tests var conf = new NetworkConfiguration() { EnableIPV4 = true, - RemoteIPFilter = addresses.Split(","), + RemoteIPFilter = addresses.Split(','), IsRemoteIPFilterBlacklist = true }; From 364e8931af70b97881638109858734f4ecaa0d0a Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sat, 27 Mar 2021 11:48:59 +0300 Subject: [PATCH 188/282] Check appropriate profile type --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index bf33691c77..167fc984e6 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -514,6 +514,8 @@ namespace MediaBrowser.Model.Dlna private static List GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable directPlayProfiles) { + var mediaType = videoStream != null ? DlnaProfileType.Video : DlnaProfileType.Audio; + var containerSupported = false; var audioSupported = false; var videoSupported = false; @@ -521,7 +523,7 @@ namespace MediaBrowser.Model.Dlna foreach (var profile in directPlayProfiles) { // Check container type - if (profile.SupportsContainer(item.Container)) + if (profile.Type == mediaType && profile.SupportsContainer(item.Container)) { containerSupported = true; From 81e3e5ca4883309b38377729e0bbd1b9f163ec7c Mon Sep 17 00:00:00 2001 From: Brian Arnold Date: Fri, 26 Mar 2021 10:02:23 -0400 Subject: [PATCH 189/282] Changed SessionController.SendMessageCommand implementation receive data in the POST body, as that is how the jellyfin-web client currently posts the data to the server. Resolves: #5628 --- Jellyfin.Api/Controllers/SessionController.cs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 0703d4255b..ba68b4cbf8 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -313,9 +313,7 @@ namespace Jellyfin.Api.Controllers /// Issues a command to a client to display a message to the user. /// /// The session id. - /// The message test. - /// The message header. - /// The message timeout. If omitted the user will have to confirm viewing the message. + /// The object containing Header, Message Text, and TimeoutMs. /// Message sent. /// A . [HttpPost("Sessions/{sessionId}/Message")] @@ -323,18 +321,25 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SendMessageCommand( [FromRoute, Required] string sessionId, - [FromQuery, Required] string text, - [FromQuery] string? header, - [FromQuery] long? timeoutMs) + [FromBody] MessageCommand command) { - var command = new MessageCommand + if (command == null) + { + throw new ArgumentException("Request body may not be null"); + } + //Need to check if message.Text is null, since [Required] can't be applied to properties of a deserialized object. + if (string.IsNullOrWhiteSpace(command.Text)) + { + throw new ArgumentNullException("Message Text may not be empty."); + } + var nullCorrectedCommand = new MessageCommand { - Header = string.IsNullOrEmpty(header) ? "Message from Server" : header, - TimeoutMs = timeoutMs, - Text = text + Header = string.IsNullOrWhiteSpace(command.Header) ? "Message from Server" : command.Header, + TimeoutMs = command.TimeoutMs, + Text = command.Text }; - _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, command, CancellationToken.None); + _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, nullCorrectedCommand, CancellationToken.None); return NoContent(); } From f114ba57dd24ed38786a478df7a641f81dd9d5c7 Mon Sep 17 00:00:00 2001 From: Brian Arnold Date: Fri, 26 Mar 2021 10:37:59 -0400 Subject: [PATCH 190/282] Fixed comment and code block runes to match coding standards required by Jellyfin team. --- Jellyfin.Api/Controllers/SessionController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index ba68b4cbf8..82aa32a7ab 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -327,11 +327,13 @@ namespace Jellyfin.Api.Controllers { throw new ArgumentException("Request body may not be null"); } - //Need to check if message.Text is null, since [Required] can't be applied to properties of a deserialized object. + + // Need to check if message.Text is null, since [Required] can't be applied to properties of a deserialized object. if (string.IsNullOrWhiteSpace(command.Text)) { throw new ArgumentNullException("Message Text may not be empty."); } + var nullCorrectedCommand = new MessageCommand { Header = string.IsNullOrWhiteSpace(command.Header) ? "Message from Server" : command.Header, From 998833ea6e4c52f842949029b278008b276e3e6b Mon Sep 17 00:00:00 2001 From: "Brian C. Arnold" Date: Sat, 27 Mar 2021 23:28:08 -0400 Subject: [PATCH 191/282] Removed null check for body object and user [Required] attribute in replacement. --- Jellyfin.Api/Controllers/SessionController.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 82aa32a7ab..34dfb6ce7f 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -321,12 +321,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SendMessageCommand( [FromRoute, Required] string sessionId, - [FromBody] MessageCommand command) + [FromBody, Required] MessageCommand command) { - if (command == null) - { - throw new ArgumentException("Request body may not be null"); - } // Need to check if message.Text is null, since [Required] can't be applied to properties of a deserialized object. if (string.IsNullOrWhiteSpace(command.Text)) From 1669cb661858676e40f0e7f89205e4e93111da85 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 29 Mar 2021 10:35:29 +0200 Subject: [PATCH 192/282] Split valid and invalid tests --- .../Providers/ProviderIdParsers.cs | 4 +- .../Providers/ProviderIdParserTests.cs | 100 +++++++++++------- 2 files changed, 61 insertions(+), 43 deletions(-) diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs index 26eaccac5f..64c2e19766 100644 --- a/MediaBrowser.Common/Providers/ProviderIdParsers.cs +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -22,12 +22,10 @@ namespace MediaBrowser.Common.Providers /// True if parsing was successful, false otherwise. public static bool TryFindImdbId(ReadOnlySpan text, [NotNullWhen(true)] out ReadOnlySpan imdbId) { - var tt = ImdbPrefix.AsSpan(); - // imdb id is at least 9 chars (tt + 7 numbers) while (text.Length >= 2 + ImdbMinNumbers) { - var ttPos = text.IndexOf(tt); + var ttPos = text.IndexOf(ImdbPrefix); if (ttPos == -1) { imdbId = default; diff --git a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs index 1ce54c59b5..ef9d31cc19 100644 --- a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs +++ b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs @@ -7,59 +7,79 @@ namespace Jellyfin.Common.Tests.Providers public class ProviderIdParserTests { [Theory] - [InlineData("tt123456", false, null)] - [InlineData("tt1234567", true, "tt1234567")] - [InlineData("tt12345678", true, "tt12345678")] - [InlineData("https://www.imdb.com/title/tt123456", false, null)] - [InlineData("https://www.imdb.com/title/tt1234567", true, "tt1234567")] - [InlineData("https://www.imdb.com/title/tt12345678", true, "tt12345678")] - [InlineData(@"multiline\nhttps://www.imdb.com/title/tt1234567", true, "tt1234567")] - [InlineData(@"multiline\nhttps://www.imdb.com/title/tt12345678", true, "tt12345678")] - [InlineData("Jellyfin", false, null)] - [InlineData("tt1234567tt7654321", true, "tt1234567")] - [InlineData("tt12345678tt7654321", true, "tt12345678")] - [InlineData("tt123456789", true, "tt12345678")] - public void Parse_Imdb(string text, bool shouldSucceed, string? imdbId) + [InlineData("tt1234567", "tt1234567")] + [InlineData("tt12345678", "tt12345678")] + [InlineData("https://www.imdb.com/title/tt1234567", "tt1234567")] + [InlineData("https://www.imdb.com/title/tt12345678", "tt12345678")] + [InlineData(@"multiline\nhttps://www.imdb.com/title/tt1234567", "tt1234567")] + [InlineData(@"multiline\nhttps://www.imdb.com/title/tt12345678", "tt12345678")] + [InlineData("tt1234567tt7654321", "tt1234567")] + [InlineData("tt12345678tt7654321", "tt12345678")] + [InlineData("tt123456789", "tt12345678")] + public void FindImdbId_Valid_Success(string text, string expected) { - var succeeded = ProviderIdParsers.TryFindImdbId(text, out ReadOnlySpan parsedId); - Assert.Equal(shouldSucceed, succeeded); - Assert.Equal(imdbId ?? Span.Empty.ToString(), parsedId.ToString()); + Assert.True(ProviderIdParsers.TryFindImdbId(text, out ReadOnlySpan parsedId)); + Assert.Equal(expected, parsedId.ToString()); } [Theory] - [InlineData("https://www.themoviedb.org/movie/30287-fallo", true, "30287")] - [InlineData("themoviedb.org/movie/30287", true, "30287")] - [InlineData("https://www.themoviedb.org/movie/fallo-30287", false, null)] - [InlineData("https://www.themoviedb.org/tv/1668-friends", false, null)] - public void Parse_TmdbMovie(string text, bool shouldSucceed, string? tmdbId) + [InlineData("tt123456")] + [InlineData("https://www.imdb.com/title/tt123456")] + [InlineData("Jellyfin")] + public void FindImdbId_Invalid_Success(string text) { - var succeeded = ProviderIdParsers.TryFindTmdbMovieId(text, out ReadOnlySpan parsedId); - Assert.Equal(shouldSucceed, succeeded); - Assert.Equal(tmdbId ?? Span.Empty.ToString(), parsedId.ToString()); + Assert.False(ProviderIdParsers.TryFindImdbId(text, out _)); } [Theory] - [InlineData("https://www.themoviedb.org/tv/1668-friends", true, "1668")] - [InlineData("themoviedb.org/tv/1668", true, "1668")] - [InlineData("https://www.themoviedb.org/tv/friends-1668", false, null)] - [InlineData("https://www.themoviedb.org/movie/30287-fallo", false, null)] - public void Parse_TmdbSeries(string text, bool shouldSucceed, string? tmdbId) + [InlineData("https://www.themoviedb.org/movie/30287-fallo", "30287")] + [InlineData("themoviedb.org/movie/30287", "30287")] + public void FindTmdbMovieId_Valid_Success(string text, string expected) { - var succeeded = ProviderIdParsers.TryFindTmdbSeriesId(text, out ReadOnlySpan parsedId); - Assert.Equal(shouldSucceed, succeeded); - Assert.Equal(tmdbId ?? Span.Empty.ToString(), parsedId.ToString()); + Assert.True(ProviderIdParsers.TryFindTmdbMovieId(text, out ReadOnlySpan parsedId)); + Assert.Equal(expected, parsedId.ToString()); } [Theory] - [InlineData("https://www.thetvdb.com/?tab=series&id=121361", true, "121361")] - [InlineData("thetvdb.com/?tab=series&id=121361", true, "121361")] - [InlineData("thetvdb.com/?tab=series&id=Jellyfin121361", false, null)] - [InlineData("https://www.themoviedb.org/tv/1668-friends", false, null)] - public void Parse_Tvdb(string text, bool shouldSucceed, string? tvdbId) + [InlineData("https://www.themoviedb.org/movie/fallo-30287")] + [InlineData("https://www.themoviedb.org/tv/1668-friends")] + public void FindTmdbMovieId_Invalid_Success(string text) { - var succeeded = ProviderIdParsers.TryFindTvdbId(text, out ReadOnlySpan parsedId); - Assert.Equal(shouldSucceed, succeeded); - Assert.Equal(tvdbId ?? Span.Empty.ToString(), parsedId.ToString()); + Assert.False(ProviderIdParsers.TryFindTmdbMovieId(text, out _)); + } + + [Theory] + [InlineData("https://www.themoviedb.org/tv/1668-friends", "1668")] + [InlineData("themoviedb.org/tv/1668", "1668")] + public void FindTmdbSeriesId_Valid_Success(string text, string expected) + { + Assert.True(ProviderIdParsers.TryFindTmdbSeriesId(text, out ReadOnlySpan parsedId)); + Assert.Equal(expected, parsedId.ToString()); + } + + [Theory] + [InlineData("https://www.themoviedb.org/tv/friends-1668")] + [InlineData("https://www.themoviedb.org/movie/30287-fallo")] + public void FindTmdbSeriesId_Invalid_Success(string text) + { + Assert.False(ProviderIdParsers.TryFindTmdbSeriesId(text, out _)); + } + + [Theory] + [InlineData("https://www.thetvdb.com/?tab=series&id=121361", "121361")] + [InlineData("thetvdb.com/?tab=series&id=121361", "121361")] + public void FindTvdbId_Valid_Success(string text, string expected) + { + Assert.True(ProviderIdParsers.TryFindTvdbId(text, out ReadOnlySpan parsedId)); + Assert.Equal(expected, parsedId.ToString()); + } + + [Theory] + [InlineData("thetvdb.com/?tab=series&id=Jellyfin121361")] + [InlineData("https://www.themoviedb.org/tv/1668-friends")] + public void FindTvdbId_Invalid_Success(string text) + { + Assert.False(ProviderIdParsers.TryFindTvdbId(text, out _)); } } } From 023467ebcee2ada86ba99d2dccb00141ab0ed264 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Mar 2021 12:00:44 +0000 Subject: [PATCH 193/282] Bump Serilog.AspNetCore from 3.4.0 to 4.1.0 Bumps [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore) from 3.4.0 to 4.1.0. - [Release notes](https://github.com/serilog/serilog-aspnetcore/releases) - [Commits](https://github.com/serilog/serilog-aspnetcore/compare/v3.4.0...v4.1.0) Signed-off-by: dependabot[bot] --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 09799307b7..b7406d849a 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -41,7 +41,7 @@ - + From 949ef2ed10bd79387fe3e2e1ff34af98c89ee3b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Mar 2021 12:00:57 +0000 Subject: [PATCH 194/282] Bump BlurHashSharp from 1.1.1 to 1.2.0 Bumps [BlurHashSharp](https://github.com/Bond-009/BlurHashSharp) from 1.1.1 to 1.2.0. - [Release notes](https://github.com/Bond-009/BlurHashSharp/releases) - [Commits](https://github.com/Bond-009/BlurHashSharp/commits/v1.2.0) Signed-off-by: dependabot[bot] --- Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 3fc44640b0..5f9c4d6792 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -20,7 +20,7 @@ - + From ca25301e649342ef5598822fdd9ffc6eb2cd5065 Mon Sep 17 00:00:00 2001 From: Brian Arnold Date: Mon, 29 Mar 2021 10:10:44 -0400 Subject: [PATCH 195/282] Added Required attribute to Text property of MessageCommand. --- Jellyfin.Api/Controllers/SessionController.cs | 7 ------- MediaBrowser.Model/Session/MessageCommand.cs | 4 +++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 34dfb6ce7f..14686222be 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -323,13 +323,6 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string sessionId, [FromBody, Required] MessageCommand command) { - - // Need to check if message.Text is null, since [Required] can't be applied to properties of a deserialized object. - if (string.IsNullOrWhiteSpace(command.Text)) - { - throw new ArgumentNullException("Message Text may not be empty."); - } - var nullCorrectedCommand = new MessageCommand { Header = string.IsNullOrWhiteSpace(command.Header) ? "Message from Server" : command.Header, diff --git a/MediaBrowser.Model/Session/MessageCommand.cs b/MediaBrowser.Model/Session/MessageCommand.cs index 09abfbb3f2..0ee3c720e5 100644 --- a/MediaBrowser.Model/Session/MessageCommand.cs +++ b/MediaBrowser.Model/Session/MessageCommand.cs @@ -1,12 +1,14 @@ #nullable disable #pragma warning disable CS1591 +using System.ComponentModel.DataAnnotations; + namespace MediaBrowser.Model.Session { public class MessageCommand { public string Header { get; set; } - + [Required(AllowEmptyStrings = false)] public string Text { get; set; } public long? TimeoutMs { get; set; } From 54107ae88262d835cac2d5a6f335b0c10d050b1a Mon Sep 17 00:00:00 2001 From: Brian Arnold Date: Mon, 29 Mar 2021 11:40:07 -0400 Subject: [PATCH 196/282] Fix spacing requirement for MessageCommand. --- MediaBrowser.Model/Session/MessageCommand.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Model/Session/MessageCommand.cs b/MediaBrowser.Model/Session/MessageCommand.cs index 0ee3c720e5..cc9db8e6c5 100644 --- a/MediaBrowser.Model/Session/MessageCommand.cs +++ b/MediaBrowser.Model/Session/MessageCommand.cs @@ -8,6 +8,7 @@ namespace MediaBrowser.Model.Session public class MessageCommand { public string Header { get; set; } + [Required(AllowEmptyStrings = false)] public string Text { get; set; } From 80fe48fda972eff0d6b58ac8a9e2f63d44a1b23e Mon Sep 17 00:00:00 2001 From: BrianCArnold Date: Tue, 30 Mar 2021 07:29:40 -0400 Subject: [PATCH 197/282] Update SessionController.cs Removed unnecessary construction of POCO to represent data from POST body --- Jellyfin.Api/Controllers/SessionController.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 14686222be..b64cbe30c7 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -323,12 +323,10 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string sessionId, [FromBody, Required] MessageCommand command) { - var nullCorrectedCommand = new MessageCommand + if (string.IsNullOrWhiteSpace(command.Header)) { - Header = string.IsNullOrWhiteSpace(command.Header) ? "Message from Server" : command.Header, - TimeoutMs = command.TimeoutMs, - Text = command.Text - }; + command.Header = "Message from Server"; + } _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, nullCorrectedCommand, CancellationToken.None); From 8c6bd2537c47dafab46ed9ca756b9f91f995603c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Tue, 30 Mar 2021 15:15:16 +0200 Subject: [PATCH 198/282] Return Major.Minor.Build instead of Major.Minor.Build.Revision for OpenAPI version --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index d26ac251c0..2b34370a09 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -260,7 +260,7 @@ namespace Jellyfin.Server.Extensions { return serviceCollection.AddSwaggerGen(c => { - var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString() ?? "0.0.0.1"; + var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString(3) ?? "0.0.1"; c.SwaggerDoc("api-docs", new OpenApiInfo { Title = "Jellyfin API", From af03b280bc91f65712af2cc854e8bf1ad349748a Mon Sep 17 00:00:00 2001 From: BrianCArnold Date: Wed, 31 Mar 2021 01:23:51 -0400 Subject: [PATCH 199/282] Update SessionController.cs --- Jellyfin.Api/Controllers/SessionController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index b64cbe30c7..1981e213e8 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -323,7 +323,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string sessionId, [FromBody, Required] MessageCommand command) { - if (string.IsNullOrWhiteSpace(command.Header)) + if (string.IsNullOrWhiteSpace(command.Header)) { command.Header = "Message from Server"; } From 4fa2a32d81e263ef51140e2ae8259a02fdfb0d7d Mon Sep 17 00:00:00 2001 From: BrianCArnold Date: Wed, 31 Mar 2021 01:24:38 -0400 Subject: [PATCH 200/282] Apply suggestions from code review Co-authored-by: Claus Vium --- Jellyfin.Api/Controllers/SessionController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 1981e213e8..7bd0b6918f 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -325,10 +325,10 @@ namespace Jellyfin.Api.Controllers { if (string.IsNullOrWhiteSpace(command.Header)) { - command.Header = "Message from Server"; + command.Header = "Message from Server"; } - _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, nullCorrectedCommand, CancellationToken.None); + _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, command, CancellationToken.None); return NoContent(); } From 28a0eb6d53b472fd62d4cc6001770e0cabdf38cd Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 1 Apr 2021 14:28:03 +0200 Subject: [PATCH 201/282] set original title in tmdbmovieprovider --- MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 4963777bc4..833d1ae389 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -175,6 +175,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var movie = new Movie { Name = movieResult.Title ?? movieResult.OriginalTitle, + OriginalTitle = movieResult.OriginalTitle, Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture), Tagline = movieResult.Tagline, ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray() From df60d176b8105b36e186191bef7dc6811ce5dbcd Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 1 Apr 2021 15:27:28 +0200 Subject: [PATCH 202/282] ensure only valid images are saved in ItemImageProvider --- MediaBrowser.Providers/Manager/ItemImageProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index ffc6889fa2..4471a25b2f 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -469,6 +469,7 @@ namespace MediaBrowser.Providers.Manager try { using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await _providerManager.SaveImage( From 11f7ab4dd4deec133a18e2f77e1d3a6dff2b9f3e Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 1 Apr 2021 19:13:03 +0200 Subject: [PATCH 203/282] Add tests for CopyToExtensions --- .../Extensions/CopyToExtensionsTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs diff --git a/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs new file mode 100644 index 0000000000..9903409fa6 --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Extensions; +using Xunit; + +namespace Jellyfin.Common.Tests.Extensions +{ + public static class CopyToExtensionsTests + { + public static IEnumerable CopyTo_Valid_Correct_TestData() + { + yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 } }; + yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } }; + } + + [Theory] + [MemberData(nameof(CopyTo_Valid_Correct_TestData))] + public static void CopyTo_Valid_Correct(IReadOnlyList source, IList destination, int index, IList expected) + { + source.CopyTo(destination, index); + Assert.Equal(expected, destination); + } + + public static IEnumerable CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData() + { + yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 }; + yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 }; + yield return new object[] { new[] { 0, 1, 2 }, Array.Empty(), 0 }; + yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 }; + yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 }; + } + + [Theory] + [MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))] + public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException(IReadOnlyList source, IList destination, int index) + { + Assert.Throws(() => source.CopyTo(destination, index)); + } + } +} From aa769573387b051fb79246885be8eb5e8cf97e76 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 1 Apr 2021 19:16:00 +0200 Subject: [PATCH 204/282] Remove useless code --- Emby.Dlna/ConnectionManager/ControlHandler.cs | 2 +- Emby.Dlna/ContentDirectory/ControlHandler.cs | 45 ++++++------------- .../MediaReceiverRegistrar/ControlHandler.cs | 2 +- Emby.Dlna/Service/BaseControlHandler.cs | 2 +- .../Library/Resolvers/TV/SeriesResolver.cs | 22 +-------- .../LiveTv/EmbyTV/EpgChannelData.cs | 43 +++++++----------- 6 files changed, 35 insertions(+), 81 deletions(-) diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs index 2f8d197a7a..1a1790ee6a 100644 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs @@ -31,7 +31,7 @@ namespace Emby.Dlna.ConnectionManager } /// - protected override void WriteResult(string methodName, IDictionary methodParams, XmlWriter xmlWriter) + protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter) { if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 27f1fdabad..713f950990 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -121,7 +121,7 @@ namespace Emby.Dlna.ContentDirectory } /// - protected override void WriteResult(string methodName, IDictionary methodParams, XmlWriter xmlWriter) + protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter) { if (xmlWriter == null) { @@ -201,8 +201,8 @@ namespace Emby.Dlna.ContentDirectory /// /// Adds a "XSetBookmark" element to the xml document. /// - /// The . - private void HandleXSetBookmark(IDictionary sparams) + /// The method parameters. + private void HandleXSetBookmark(IReadOnlyDictionary sparams) { var id = sparams["ObjectID"]; @@ -305,35 +305,18 @@ namespace Emby.Dlna.ContentDirectory return builder.ToString(); } - /// - /// Returns the value in the key of the dictionary, or defaultValue if it doesn't exist. - /// - /// The . - /// The key. - /// The defaultValue. - /// The . - public static string GetValueOrDefault(IDictionary sparams, string key, string defaultValue) - { - if (sparams != null && sparams.TryGetValue(key, out string val)) - { - return val; - } - - return defaultValue; - } - /// /// Builds the "Browse" xml response. /// /// The . - /// The . + /// The method parameters. /// The device Id to use. - private void HandleBrowse(XmlWriter xmlWriter, IDictionary sparams, string deviceId) + private void HandleBrowse(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId) { var id = sparams["ObjectID"]; var flag = sparams["BrowseFlag"]; - var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); - var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty)); + var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); + var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty)); var provided = 0; @@ -435,9 +418,9 @@ namespace Emby.Dlna.ContentDirectory /// Builds the response to the "X_BrowseByLetter request. /// /// The . - /// The . + /// The method parameters. /// The device id. - private void HandleXBrowseByLetter(XmlWriter xmlWriter, IDictionary sparams, string deviceId) + private void HandleXBrowseByLetter(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId) { // TODO: Implement this method HandleSearch(xmlWriter, sparams, deviceId); @@ -447,13 +430,13 @@ namespace Emby.Dlna.ContentDirectory /// Builds a response to the "Search" request. /// /// The xmlWriter. - /// The sparams. + /// The method parameters. /// The deviceId. - private void HandleSearch(XmlWriter xmlWriter, IDictionary sparams, string deviceId) + private void HandleSearch(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId) { - var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", string.Empty)); - var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty)); - var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); + var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", string.Empty)); + var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty)); + var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); // sort example: dc:title, dc:date diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 464f71a6f1..d8fb127420 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -24,7 +24,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar } /// - protected override void WriteResult(string methodName, IDictionary methodParams, XmlWriter xmlWriter) + protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter) { if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 8d2486fee6..fda8346f9e 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -210,7 +210,7 @@ namespace Emby.Dlna.Service } } - protected abstract void WriteResult(string methodName, IDictionary methodParams, XmlWriter xmlWriter); + protected abstract void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter); private void LogRequest(ControlRequest request) { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 732bfd94dc..e8b5f4fe71 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -127,7 +127,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV { if (child.IsDirectory) { - if (IsSeasonFolder(child.FullName, isTvContentType, libraryManager)) + if (IsSeasonFolder(child.FullName, isTvContentType)) { logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName); return true; @@ -160,24 +160,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return false; } - /// - /// Determines whether [is place holder] [the specified path]. - /// - /// The path. - /// true if [is place holder] [the specified path]; otherwise, false. - /// path - private static bool IsVideoPlaceHolder(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - var extension = Path.GetExtension(path); - - return string.Equals(extension, ".disc", StringComparison.OrdinalIgnoreCase); - } - /// /// Determines whether [is season folder] [the specified path]. /// @@ -185,7 +167,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// if set to true [is tv content type]. /// The library manager. /// true if [is season folder] [the specified path]; otherwise, false. - private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager) + private static bool IsSeasonFolder(string path, bool isTvContentType) { var seasonNumber = SeasonPathParser.Parse(path, isTvContentType, isTvContentType).SeasonNumber; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs index 463d0ed0a3..8c27ca76e3 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs @@ -9,55 +9,44 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV internal class EpgChannelData { + + private readonly Dictionary _channelsById; + + private readonly Dictionary _channelsByNumber; + + private readonly Dictionary _channelsByName; + public EpgChannelData(IEnumerable channels) { - ChannelsById = new Dictionary(StringComparer.OrdinalIgnoreCase); - ChannelsByNumber = new Dictionary(StringComparer.OrdinalIgnoreCase); - ChannelsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + _channelsById = new Dictionary(StringComparer.OrdinalIgnoreCase); + _channelsByNumber = new Dictionary(StringComparer.OrdinalIgnoreCase); + _channelsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var channel in channels) { - ChannelsById[channel.Id] = channel; + _channelsById[channel.Id] = channel; if (!string.IsNullOrEmpty(channel.Number)) { - ChannelsByNumber[channel.Number] = channel; + _channelsByNumber[channel.Number] = channel; } var normalizedName = NormalizeName(channel.Name ?? string.Empty); if (!string.IsNullOrWhiteSpace(normalizedName)) { - ChannelsByName[normalizedName] = channel; + _channelsByName[normalizedName] = channel; } } } - private Dictionary ChannelsById { get; set; } - - private Dictionary ChannelsByNumber { get; set; } - - private Dictionary ChannelsByName { get; set; } - public ChannelInfo GetChannelById(string id) - { - ChannelsById.TryGetValue(id, out var result); - - return result; - } + => _channelsById.GetValueOrDefault(id); public ChannelInfo GetChannelByNumber(string number) - { - ChannelsByNumber.TryGetValue(number, out var result); - - return result; - } + => _channelsByNumber.GetValueOrDefault(number); public ChannelInfo GetChannelByName(string name) - { - ChannelsByName.TryGetValue(name, out var result); - - return result; - } + => _channelsByName.GetValueOrDefault(name); public static string NormalizeName(string value) { From 21e7ceae8e15e19bdb111e51aba41e6a3ac3333d Mon Sep 17 00:00:00 2001 From: Max Rumpf Date: Thu, 1 Apr 2021 19:18:14 +0200 Subject: [PATCH 205/282] StreamBuilder tweaks (#5668) Co-authored-by: Cody Robibero --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 33 ++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index bf33691c77..8299059a5e 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -674,7 +674,7 @@ namespace MediaBrowser.Model.Dlna var videoStream = item.VideoStream; - // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough + // TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, options, PlayMethod.DirectPlay); var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, options, PlayMethod.DirectStream); bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1); @@ -1017,14 +1017,15 @@ namespace MediaBrowser.Model.Dlna } DeviceProfile profile = options.Profile; + string container = mediaSource.Container; // See if it can be direct played DirectPlayProfile directPlay = null; - foreach (var i in profile.DirectPlayProfiles) + foreach (var p in profile.DirectPlayProfiles) { - if (i.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(i, mediaSource, videoStream, audioStream)) + if (p.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(p, container, videoStream, audioStream)) { - directPlay = i; + directPlay = p; break; } } @@ -1032,23 +1033,23 @@ namespace MediaBrowser.Model.Dlna if (directPlay == null) { _logger.LogInformation( - "Profile: {0}, No video direct play profiles found for {1} with codec {2}", - profile?.Name ?? "Unknown Profile", - mediaSource?.Path ?? "Unknown path", - videoStream?.Codec ?? "Unknown codec"); + "Container: {Container}, Video: {Video}, Audio: {Audio} cannot be direct played by profile: {Profile} for path: {Path}", + container, + videoStream?.Codec ?? "no video", + audioStream?.Codec ?? "no audio", + profile.Name ?? "unknown profile", + mediaSource.Path ?? "unknown path"); return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles)); } - string container = mediaSource.Container; - var conditions = new List(); - foreach (var i in profile.ContainerProfiles) + foreach (var p in profile.ContainerProfiles) { - if (i.Type == DlnaProfileType.Video - && i.ContainsContainer(container)) + if (p.Type == DlnaProfileType.Video + && p.ContainsContainer(container)) { - foreach (var c in i.Conditions) + foreach (var c in p.Conditions) { conditions.Add(c); } @@ -1896,10 +1897,10 @@ namespace MediaBrowser.Model.Dlna return true; } - private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream) + private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, string container, MediaStream videoStream, MediaStream audioStream) { // Check container type - if (!profile.SupportsContainer(item.Container)) + if (!profile.SupportsContainer(container)) { return false; } From c533b204961b8fe19272b31b5fb5473be0eb801b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 1 Apr 2021 19:39:00 +0200 Subject: [PATCH 206/282] Remove ManagedFileSystem.IsRootPath `Path.IsPathRooted` should be used instead --- .../IO/ManagedFileSystem.cs | 32 ++++++------------- MediaBrowser.Model/IO/IFileSystem.cs | 7 ---- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index c0e757543d..679795dd2c 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.IO } var extension = Path.GetExtension(filename); - return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); + return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); } /// @@ -487,26 +487,9 @@ namespace Emby.Server.Implementations.IO throw new ArgumentNullException(nameof(path)); } - var separatorChar = Path.DirectorySeparatorChar; - - return path.IndexOf(parentPath.TrimEnd(separatorChar) + separatorChar, StringComparison.OrdinalIgnoreCase) != -1; - } - - public virtual bool IsRootPath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - var parent = Path.GetDirectoryName(path); - - if (!string.IsNullOrEmpty(parent)) - { - return false; - } - - return true; + return path.Contains( + Path.TrimEndingDirectorySeparator(parentPath) + Path.DirectorySeparatorChar, + _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); } public virtual string NormalizePath(string path) @@ -521,7 +504,7 @@ namespace Emby.Server.Implementations.IO return path; } - return path.TrimEnd(Path.DirectorySeparatorChar); + return Path.TrimEndingDirectorySeparator(path); } public virtual bool AreEqual(string path1, string path2) @@ -536,7 +519,10 @@ namespace Emby.Server.Implementations.IO return false; } - return string.Equals(NormalizePath(path1), NormalizePath(path2), StringComparison.OrdinalIgnoreCase); + return string.Equals( + NormalizePath(path1), + NormalizePath(path2), + _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); } public virtual string GetFileNameWithoutExtension(FileSystemMetadata info) diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index ef08ecec66..e5c26430a8 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -117,13 +117,6 @@ namespace MediaBrowser.Model.IO /// true if [contains sub path] [the specified parent path]; otherwise, false. bool ContainsSubPath(string parentPath, string path); - /// - /// Determines whether [is root path] [the specified path]. - /// - /// The path. - /// true if [is root path] [the specified path]; otherwise, false. - bool IsRootPath(string path); - /// /// Normalizes the path. /// From a0258618acc66b80e9f5cdd535a31baf12857fb0 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 1 Apr 2021 21:24:34 +0200 Subject: [PATCH 207/282] Update Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs Co-authored-by: Claus Vium --- .../Library/Resolvers/TV/SeriesResolver.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index e8b5f4fe71..4a9d2cf8ca 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -165,7 +165,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// /// The path. /// if set to true [is tv content type]. - /// The library manager. /// true if [is season folder] [the specified path]; otherwise, false. private static bool IsSeasonFolder(string path, bool isTvContentType) { From 820a3730161b98d5510847feee684797c23a3510 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Apr 2021 22:27:11 +0000 Subject: [PATCH 208/282] Bump Microsoft.Extensions.Diagnostics.HealthChecks from 5.0.3 to 5.0.4 Bumps [Microsoft.Extensions.Diagnostics.HealthChecks](https://github.com/dotnet/aspnetcore) from 5.0.3 to 5.0.4. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.3...v5.0.4) Signed-off-by: dependabot[bot] --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index b7406d849a..e7f83aff16 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -37,7 +37,7 @@ - + From 7c7020532666714dc22ac87356b059c6d4c5520c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Apr 2021 22:42:12 +0000 Subject: [PATCH 209/282] Bump Microsoft.NET.Test.Sdk from 16.9.1 to 16.9.4 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.9.1 to 16.9.4. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.9.1...v16.9.4) Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj | 2 +- .../Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj | 2 +- tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj | 2 +- .../Jellyfin.MediaEncoding.Tests.csproj | 2 +- tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj | 2 +- tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj | 2 +- .../Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- .../Jellyfin.XbmcMetadata.Tests.csproj | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 577b61d02a..57277be150 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 017a67e9f9..8018b29663 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index c56ccb3653..ad16276989 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index 5d52f94c02..c2c0dca1bb 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index 4cc1d37ee9..8321d02552 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -21,7 +21,7 @@ - + diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index 0c7e262f59..c5b51ef766 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index cc12a99a61..ebb134fc3c 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index a76c0e9a06..d5268facc9 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index c3c258b680..fe26ec3ae6 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -24,7 +24,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 34cef90050..079021e61b 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index a310b0ea96..3ba49ff6a2 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index 9380fe2af4..4132205c3f 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -16,7 +16,7 @@ - + From 0a00a73fec7822a1a10b3fc48494108651ab1a98 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 3 Apr 2021 01:46:31 +0200 Subject: [PATCH 210/282] Remove useless null check --- .../Json/Converters/JsonCommaDelimitedArrayConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs index d9f6519e98..2ec702165d 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -26,8 +26,8 @@ namespace MediaBrowser.Common.Json.Converters { if (reader.TokenType == JsonTokenType.String) { - var stringEntries = reader.GetString()?.Split(',', StringSplitOptions.RemoveEmptyEntries); - if (stringEntries == null || stringEntries.Length == 0) + var stringEntries = reader.GetString().Split(',', StringSplitOptions.RemoveEmptyEntries); + if (stringEntries.Length == 0) { return Array.Empty(); } From 31d1dbfda630e45a8884f1a10d172f8cc83df9b0 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 3 Apr 2021 06:54:09 -0600 Subject: [PATCH 211/282] Add SessionDiscoveryInfo to generated api-docs --- .../Extensions/ApiServiceCollectionExtensions.cs | 2 +- .../{WebsocketModelFilter.cs => AdditionalModelFilter.cs} | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) rename Jellyfin.Server/Filters/{WebsocketModelFilter.cs => AdditionalModelFilter.cs} (82%) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index d26ac251c0..233eab21b6 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -319,7 +319,7 @@ namespace Jellyfin.Server.Extensions c.OperationFilter(); c.OperationFilter(); c.OperationFilter(); - c.DocumentFilter(); + c.DocumentFilter(); }); } diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/AdditionalModelFilter.cs similarity index 82% rename from Jellyfin.Server/Filters/WebsocketModelFilter.cs rename to Jellyfin.Server/Filters/AdditionalModelFilter.cs index 38afb201db..87a59e0b45 100644 --- a/Jellyfin.Server/Filters/WebsocketModelFilter.cs +++ b/Jellyfin.Server/Filters/AdditionalModelFilter.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Plugins; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.ApiClient; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; @@ -9,9 +10,9 @@ using Swashbuckle.AspNetCore.SwaggerGen; namespace Jellyfin.Server.Filters { /// - /// Add models used in websocket messaging. + /// Add models not directly used by the API, but used for discovery and websockets. /// - public class WebsocketModelFilter : IDocumentFilter + public class AdditionalModelFilter : IDocumentFilter { /// public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) @@ -27,6 +28,7 @@ namespace Jellyfin.Server.Filters context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository); } } } From 36da7a06d7f433d88ef94b770036815847e4dd89 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> Date: Sun, 4 Apr 2021 02:09:57 +0300 Subject: [PATCH 212/282] Less negation Co-authored-by: Bond-009 --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 167fc984e6..b7327bc12b 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -514,7 +514,7 @@ namespace MediaBrowser.Model.Dlna private static List GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable directPlayProfiles) { - var mediaType = videoStream != null ? DlnaProfileType.Video : DlnaProfileType.Audio; + var mediaType = videoStream == null ? DlnaProfileType.Audio : DlnaProfileType.Video; var containerSupported = false; var audioSupported = false; From 2ed0801be25921335b6509b6ebf9af61ad458409 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 4 Apr 2021 01:45:15 +0200 Subject: [PATCH 213/282] Fix possible nullref when `ProviderManager.SaveMetadata` gets called before `ProviderManager.AddParts` ``` Error Message: System.ArgumentNullException : Value cannot be null. (Parameter 'source') Stack Trace: at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate) at MediaBrowser.Providers.Manager.ProviderManager.SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable`1 savers) in D:\a\1\s\MediaBrowser.Providers\Manager\ProviderManager.cs:line 674 at MediaBrowser.Providers.Manager.ProviderManager.SaveMetadata(BaseItem item, ItemUpdateType updateType) in D:\a\1\s\MediaBrowser.Providers\Manager\ProviderManager.cs:line 655 at Emby.Server.Implementations.Library.LibraryManager.RunMetadataSavers(BaseItem item, ItemUpdateType updateReason) in D:\a\1\s\Emby.Server.Implementations\Library\LibraryManager.cs:line 2012 at Emby.Server.Implementations.Library.LibraryManager.UpdateItemsAsync(IReadOnlyList`1 items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) in D:\a\1\s\Emby.Server.Implementations\Library\LibraryManager.cs:line 1970 at Emby.Server.Implementations.Library.LibraryManager.CreateRootFolder() in D:\a\1\s\Emby.Server.Implementations\Library\LibraryManager.cs:line 775 at Emby.Server.Implementations.Library.LibraryManager.get_RootFolder() in D:\a\1\s\Emby.Server.Implementations\Library\LibraryManager.cs:line 180 at Emby.Server.Implementations.IO.LibraryMonitor.Start() in D:\a\1\s\Emby.Server.Implementations\IO\LibraryMonitor.cs:line 135 at Emby.Server.Implementations.IO.LibraryMonitorStartup.RunAsync() in D:\a\1\s\Emby.Server.Implementations\IO\LibraryMonitorStartup.cs:line 26 at Emby.Server.Implementations.ApplicationHost.StartEntryPoints(IEnumerable`1 entryPoints, Boolean isBeforeStartup)+MoveNext() in D:\a\1\s\Emby.Server.Implementations\ApplicationHost.cs:line 541 at System.Threading.Tasks.Task.WhenAll(IEnumerable`1 tasks) at Emby.Server.Implementations.ApplicationHost.RunStartupTasksAsync(CancellationToken cancellationToken) in D:\a\1\s\Emby.Server.Implementations\ApplicationHost.cs:line 525 at Jellyfin.Server.Integration.Tests.JellyfinApplicationFactory.CreateServer(IWebHostBuilder builder) in D:\a\1\s\tests\Jellyfin.Server.Integration.Tests\JellyfinApplicationFactory.cs:line 101 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer() at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient() at Jellyfin.Server.Integration.Tests.OpenApiSpecTests.GetSpec_ReturnsCorrectResponse() in D:\a\1\s\tests\Jellyfin.Server.Integration.Tests\OpenApiSpecTests.cs:line 26 --- End of stack trace from previous location --- ``` --- MediaBrowser.Controller/Providers/IProviderManager.cs | 7 +++++-- MediaBrowser.Providers/Manager/ProviderManager.cs | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 2f5b1d4a32..7bc56c82ad 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -91,8 +91,11 @@ namespace MediaBrowser.Controller.Providers /// /// Adds the metadata providers. /// - void AddParts(IEnumerable imageProviders, IEnumerable metadataServices, IEnumerable metadataProviders, - IEnumerable savers, + void AddParts( + IEnumerable imageProviders, + IEnumerable metadataServices, + IEnumerable metadataProviders, + IEnumerable metadataSavers, IEnumerable externalIds); /// diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index d581dd4341..b4b0b826f4 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -60,8 +60,8 @@ namespace MediaBrowser.Providers.Manager private IMetadataService[] _metadataServices = Array.Empty(); private IMetadataProvider[] _metadataProviders = Array.Empty(); - private IEnumerable _savers; - private IExternalId[] _externalIds; + private IMetadataSaver[] _savers = Array.Empty(); + private IExternalId[] _externalIds = Array.Empty(); private bool _isProcessingRefreshQueue; private bool _disposed; @@ -125,7 +125,7 @@ namespace MediaBrowser.Providers.Manager _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray(); _savers = metadataSavers - .Where(i => !(i is IConfigurableProvider configurable) || configurable.IsEnabled) + .Where(i => i is not IConfigurableProvider configurable || configurable.IsEnabled) .ToArray(); } From e0f513232b0b03135fa09fe39862c10982cb469e Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sun, 4 Apr 2021 14:58:20 +0200 Subject: [PATCH 214/282] Reduce nesting --- MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index b2d4db894d..e65c16ee22 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -85,12 +85,14 @@ namespace MediaBrowser.MediaEncoding.Probing { var val = GetDictionaryValue(tags, key); - if (!string.IsNullOrEmpty(val)) + if (string.IsNullOrEmpty(val)) { - if (DateTime.TryParse(val, out var i)) - { - return i.ToUniversalTime(); - } + return null; + } + + if (DateTime.TryParse(val, out var i)) + { + return i.ToUniversalTime(); } return null; From 8d27e10cb696fb440ec7773aae69441d0651e64a Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sun, 4 Apr 2021 15:04:01 +0200 Subject: [PATCH 215/282] Interpret ffprobe date as UTC Currently, dates are parsed according to the local time, which results in potentially wrong data being stored in the database after normalizing to UTC - e.g. 2021-04-04 would be stored as '2021-04-03 22:00:00Z' and displayed in the UI as 03.04.2021. --- MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index e65c16ee22..da37687e8f 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; namespace MediaBrowser.MediaEncoding.Probing { @@ -90,7 +91,7 @@ namespace MediaBrowser.MediaEncoding.Probing return null; } - if (DateTime.TryParse(val, out var i)) + if (DateTime.TryParse(val, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out var i)) { return i.ToUniversalTime(); } From 873ad72c1893e87d1f8a0a744ebfe53b87781521 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sun, 4 Apr 2021 15:08:08 +0200 Subject: [PATCH 216/282] Support MKV DATE_RELEASED tag for PremiereDate https://www.matroska.org/technical/tagging.html#temporal-information --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 75067315f9..a87104cd67 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -131,6 +131,7 @@ namespace MediaBrowser.MediaEncoding.Probing info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ?? + FFProbeHelpers.GetDictionaryDateTime(tags, "date_released") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "date"); if (isAudio) From e6d487f7ea311c91c307138f4d38dbd25281f7cf Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 5 Apr 2021 01:53:00 +0200 Subject: [PATCH 217/282] Add test for ProbeResultNormalizer.GetMediaInfo --- .../Probing/ProbeResultNormalizerTests.cs | 56 ++++++++++++++ .../Test Data/Probing/some_matadata.json | 74 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs new file mode 100644 index 0000000000..69e2aa4372 --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -0,0 +1,56 @@ +using System.IO; +using System.Text.Json; +using MediaBrowser.Common.Json; +using MediaBrowser.MediaEncoding.Probing; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.MediaInfo; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace Jellyfin.MediaEncoding.Tests.Probing +{ + public class ProbeResultNormalizerTests + { + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private readonly ProbeResultNormalizer _probeResultNormalizer = new ProbeResultNormalizer(new NullLogger(), null); + + [Fact] + public void GetMediaInfo_MetaData_Success() + { + var bytes = File.ReadAllBytes("Test Data/Probing/some_matadata.json"); + var internalMediaInfoResult = JsonSerializer.Deserialize(bytes, _jsonOptions); + MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/some_matadata.mkv", MediaProtocol.File); + + Assert.Single(res.MediaStreams); + + Assert.NotNull(res.VideoStream); + Assert.Equal("4:3", res.VideoStream.AspectRatio); + Assert.Equal(25f, res.VideoStream.AverageFrameRate); + Assert.Equal(8, res.VideoStream.BitDepth); + Assert.Equal(69432, res.VideoStream.BitRate); + Assert.Equal("h264", res.VideoStream.Codec); + Assert.Equal("1/50", res.VideoStream.CodecTimeBase); + Assert.Equal(240, res.VideoStream.Height); + Assert.Equal(320, res.VideoStream.Width); + Assert.Equal(0, res.VideoStream.Index); + Assert.False(res.VideoStream.IsAnamorphic); + Assert.True(res.VideoStream.IsAVC); + Assert.True(res.VideoStream.IsDefault); + Assert.False(res.VideoStream.IsExternal); + Assert.False(res.VideoStream.IsForced); + Assert.False(res.VideoStream.IsInterlaced); + Assert.False(res.VideoStream.IsTextSubtitleStream); + Assert.Equal(13d, res.VideoStream.Level); + Assert.Equal("4", res.VideoStream.NalLengthSize); + Assert.Equal("yuv444p", res.VideoStream.PixelFormat); + Assert.Equal("High 4:4:4 Predictive", res.VideoStream.Profile); + Assert.Equal(25f, res.VideoStream.RealFrameRate); + Assert.Equal(1, res.VideoStream.RefFrames); + Assert.Equal("1/1000", res.VideoStream.TimeBase); + Assert.Equal(MediaStreamType.Video, res.VideoStream.Type); + + Assert.Empty(res.Chapters); + Assert.Equal("Just color bars", res.Overview); + } + } +} diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json new file mode 100644 index 0000000000..720fc5c8fa --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json @@ -0,0 +1,74 @@ +{ + "streams": [ + { + "index": 0, + "codec_name": "h264", + "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", + "profile": "High 4:4:4 Predictive", + "codec_type": "video", + "codec_time_base": "1/50", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "width": 320, + "height": 240, + "coded_width": 320, + "coded_height": 240, + "closed_captions": 0, + "has_b_frames": 2, + "sample_aspect_ratio": "1:1", + "display_aspect_ratio": "4:3", + "pix_fmt": "yuv444p", + "level": 13, + "chroma_location": "left", + "field_order": "progressive", + "refs": 1, + "is_avc": "true", + "nal_length_size": "4", + "r_frame_rate": "25/1", + "avg_frame_rate": "25/1", + "time_base": "1/1000", + "start_pts": 0, + "start_time": "0.000000", + "bits_per_raw_sample": "8", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + }, + "tags": { + "ENCODER": "Lavc57.107.100 libx264", + "DURATION": "00:00:01.000000000" + } + } + ], + "chapters": [ + + ], + "format": { + "filename": "some_metadata.mkv", + "nb_streams": 1, + "nb_programs": 0, + "format_name": "matroska,webm", + "format_long_name": "Matroska / WebM", + "start_time": "0.000000", + "duration": "1.000000", + "size": "8679", + "bit_rate": "69432", + "probe_score": 100, + "tags": { + "DESCRIPTION": "Just color bars", + "ARCHIVAL": "yes", + "PRESERVE_THIS": "okay", + "ENCODER": "Lavf57.83.100" + } + } +} From 14d0acf28572c6b5f5ecac6728b7a8184683b5f2 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 5 Apr 2021 15:12:47 +0200 Subject: [PATCH 218/282] add simple auth handling to websocketmanager --- Emby.Server.Implementations/HttpServer/WebSocketManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index d6cf6233e4..1bee1ac310 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -14,15 +14,18 @@ namespace Emby.Server.Implementations.HttpServer public class WebSocketManager : IWebSocketManager { private readonly IWebSocketListener[] _webSocketListeners; + private readonly IAuthService _authService; private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; public WebSocketManager( + IAuthService authService, IEnumerable webSocketListeners, ILogger logger, ILoggerFactory loggerFactory) { _webSocketListeners = webSocketListeners.ToArray(); + _authService = authService; _logger = logger; _loggerFactory = loggerFactory; } @@ -30,6 +33,7 @@ namespace Emby.Server.Implementations.HttpServer /// public async Task WebSocketRequestHandler(HttpContext context) { + _ = _authService.Authenticate(context.Request); try { _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); From ba958c70d5359183660f94542954585bb6f67d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Tue, 6 Apr 2021 17:24:01 +0200 Subject: [PATCH 219/282] (jellyfin-web): Switch to npm --- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 41dd3d081e..6552546eaa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ARG JELLYFIN_WEB_VERSION=master RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ - && yarn install \ + && npm i \ && mv dist /dist FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster-slim as builder diff --git a/Dockerfile.arm b/Dockerfile.arm index e0eaca0edd..1829982f5e 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -10,7 +10,7 @@ ARG JELLYFIN_WEB_VERSION=master RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ - && yarn install \ + && npm i \ && mv dist /dist diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index db7de935cf..994df32ddc 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -10,7 +10,7 @@ ARG JELLYFIN_WEB_VERSION=master RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ - && yarn install \ + && npm i \ && mv dist /dist From 8d6713af65e730c4508cea241439db315217ee8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Tue, 6 Apr 2021 17:33:51 +0200 Subject: [PATCH 220/282] Use npm ci instead of npm i --- Dockerfile | 2 +- Dockerfile.arm | 2 +- Dockerfile.arm64 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6552546eaa..cafee737a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ARG JELLYFIN_WEB_VERSION=master RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ - && npm i \ + && npm ci --no-audit \ && mv dist /dist FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster-slim as builder diff --git a/Dockerfile.arm b/Dockerfile.arm index 1829982f5e..d63dbee758 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -10,7 +10,7 @@ ARG JELLYFIN_WEB_VERSION=master RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ - && npm i \ + && npm ci --no-audit \ && mv dist /dist diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 994df32ddc..e95999f2a6 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -10,7 +10,7 @@ ARG JELLYFIN_WEB_VERSION=master RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ - && npm i \ + && npm ci --no-audit \ && mv dist /dist From 95327b842e9eb50ca2c53740674b8ed2f6615eae Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 6 Apr 2021 20:02:06 +0200 Subject: [PATCH 221/282] Enable NetAnalyzers for more projects --- Emby.Drawing/NullImageEncoder.cs | 2 +- Emby.Notifications/Emby.Notifications.csproj | 6 ++---- Emby.Photos/Emby.Photos.csproj | 6 ++---- .../Emby.Server.Implementations.csproj | 6 ++---- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index 2a1cfd3da5..1c05aa9161 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -32,7 +32,7 @@ namespace Emby.Drawing => throw new NotImplementedException(); /// - public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) + public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat) { throw new NotImplementedException(); } diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj index 526a27229f..5a2aea6423 100644 --- a/Emby.Notifications/Emby.Notifications.csproj +++ b/Emby.Notifications/Emby.Notifications.csproj @@ -11,6 +11,8 @@ true true enable + AllEnabledByDefault + ../jellyfin.ruleset @@ -30,8 +32,4 @@ - - ../jellyfin.ruleset - - diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index e64a658c51..2b66181599 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -24,6 +24,8 @@ true true enable + AllEnabledByDefault + ../jellyfin.ruleset @@ -33,8 +35,4 @@ - - ../jellyfin.ruleset - - diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index be552ef93a..9248053f54 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -46,6 +46,8 @@ true AD0001 + AllEnabledByDefault + ../jellyfin.ruleset @@ -55,10 +57,6 @@ - - ../jellyfin.ruleset - - From e85ac4d975c19e47637e11e76294de90e0481309 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Apr 2021 18:35:36 +0000 Subject: [PATCH 222/282] Bump Microsoft.AspNetCore.Authorization from 5.0.3 to 5.0.5 Bumps [Microsoft.AspNetCore.Authorization](https://github.com/dotnet/aspnetcore) from 5.0.3 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.3...v5.0.5) Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index d5372d7526..d6dc5a2dc3 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -15,7 +15,7 @@ - + From 3651ee5ed32273b1b6e7f63afe0b9923235e4d14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Apr 2021 18:35:51 +0000 Subject: [PATCH 223/282] Bump Microsoft.AspNetCore.Mvc.Testing from 5.0.3 to 5.0.5 Bumps [Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore) from 5.0.3 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.3...v5.0.5) Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 57277be150..f288561b74 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 079021e61b..8d4d9e3d23 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 3ba49ff6a2..4a5cf1feee 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -13,7 +13,7 @@ - + From 65f880be323e154655505c702822ce381fc5569e Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 6 Apr 2021 20:59:47 +0100 Subject: [PATCH 224/282] Keep plugin status after update. --- Emby.Server.Implementations/Plugins/PluginManager.cs | 7 ++++--- .../Updates/InstallationManager.cs | 12 +++++++----- MediaBrowser.Common/Plugins/IPluginManager.cs | 4 +++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 700396c4cf..3a8296455b 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; @@ -368,7 +369,7 @@ namespace Emby.Server.Implementations.Plugins } /// - public async Task GenerateManifest(PackageInfo packageInfo, Version version, string path) + public async Task GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status) { if (packageInfo == null) { @@ -411,9 +412,9 @@ namespace Emby.Server.Implementations.Plugins Overview = packageInfo.Overview, Owner = packageInfo.Owner, TargetAbi = versionInfo.TargetAbi ?? string.Empty, - Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp), + Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture), Version = versionInfo.Version, - Status = PluginStatus.Active, + Status = status == PluginStatus.Disabled ? PluginStatus.Disabled : PluginStatus.Active, // Keep disabled state. AutoUpdate = true, ImagePath = imagePath }; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index fc34f93cdd..e7307aefef 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -22,6 +22,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Updates; using Microsoft.Extensions.Logging; @@ -194,7 +195,7 @@ namespace Emby.Server.Implementations.Updates var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber); if (plugin != null) { - await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path); + await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path, plugin.Manifest.Status).ConfigureAwait(false); } // Remove versions with a target ABI greater then the current application version. @@ -500,7 +501,8 @@ namespace Emby.Server.Implementations.Updates var plugins = _pluginManager.Plugins; foreach (var plugin in plugins) { - if (plugin.Manifest?.AutoUpdate == false) + // Don't auto update when plugin marked not to, or when it's disabled. + if (plugin.Manifest?.AutoUpdate == false || plugin.Manifest?.Status == MediaBrowser.Model.Plugins.PluginStatus.Disabled) { continue; } @@ -515,7 +517,7 @@ namespace Emby.Server.Implementations.Updates } } - private async Task PerformPackageInstallation(InstallationInfo package, CancellationToken cancellationToken) + private async Task PerformPackageInstallation(InstallationInfo package, PluginStatus status, CancellationToken cancellationToken) { var extension = Path.GetExtension(package.SourceUrl); if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase)) @@ -567,7 +569,7 @@ namespace Emby.Server.Implementations.Updates stream.Position = 0; _zipClient.ExtractAllFromZip(stream, targetDir, true); - await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir); + await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false); _pluginManager.ImportPluginFrom(targetDir); } @@ -576,7 +578,7 @@ namespace Emby.Server.Implementations.Updates LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version)) ?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version)); - await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false); + await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false); _logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version); return plugin != null; diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index f9a8fb6f7a..0e2e814cb0 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Updates; using Microsoft.Extensions.DependencyInjection; @@ -51,8 +52,9 @@ namespace MediaBrowser.Common.Plugins /// The used to generate a manifest. /// Version to be installed. /// The path where to save the manifest. + /// Initial status of the plugin. /// True if successful. - Task GenerateManifest(PackageInfo packageInfo, Version version, string path); + Task GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status); /// /// Imports plugin details from a folder. From fbd9141ecdb6ecd8c8b887c33b0b04370f7002e3 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 6 Apr 2021 22:46:54 +0200 Subject: [PATCH 225/282] Add tests for unauthenticated websocket access --- .../WebSocketTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs diff --git a/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs b/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs new file mode 100644 index 0000000000..ffdc04ebaf --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests +{ + public sealed class WebSocketTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + + public WebSocketTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task WebSocket_Unauthenticated_ThrowsInvalidOperationException() + { + var server = _factory.Server; + var client = server.CreateWebSocketClient(); + + await Assert.ThrowsAsync( + () => client.ConnectAsync( + new UriBuilder(server.BaseAddress) + { + Scheme = "ws", + Path = "websocket" + }.Uri, CancellationToken.None)); + } + } +} From d772fddfb3fcddd5f540cf2650eb7e405a6aec2c Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 7 Apr 2021 13:09:00 +0200 Subject: [PATCH 226/282] make custompref value nullable --- .../Entities/CustomItemDisplayPreferences.cs | 4 +- ...110544_NullableCustomPrefValue.Designer.cs | 520 ++++++++++++++++++ .../20210407110544_NullableCustomPrefValue.cs | 35 ++ .../Migrations/JellyfinDbModelSnapshot.cs | 10 +- .../Users/DisplayPreferencesManager.cs | 2 +- .../IDisplayPreferencesManager.cs | 2 +- 6 files changed, 563 insertions(+), 10 deletions(-) create mode 100644 Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.cs diff --git a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs index cc46248c75..de37fb5446 100644 --- a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs @@ -17,7 +17,7 @@ namespace Jellyfin.Data.Entities /// The client. /// The preference key. /// The preference value. - public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string key, string value) + public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string key, string? value) { UserId = userId; ItemId = itemId; @@ -75,6 +75,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - public string Value { get; set; } + public string? Value { get; set; } } } diff --git a/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs new file mode 100644 index 0000000000..d332d19f28 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs @@ -0,0 +1,520 @@ +#pragma warning disable CS1591 +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20210407110544_NullableCustomPrefValue")] + partial class NullableCustomPrefValue + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "ItemId", "Client", "Key") + .IsUnique(); + + b.ToTable("CustomItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChromecastVersion") + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("DashboardTheme") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("EnableNextVideoInfoOverlay") + .HasColumnType("INTEGER"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("ScrollDirection") + .HasColumnType("INTEGER"); + + b.Property("ShowBackdrop") + .HasColumnType("INTEGER"); + + b.Property("ShowSidebar") + .HasColumnType("INTEGER"); + + b.Property("SkipBackwardLength") + .HasColumnType("INTEGER"); + + b.Property("SkipForwardLength") + .HasColumnType("INTEGER"); + + b.Property("TvHome") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "ItemId", "Client") + .IsUnique(); + + b.ToTable("DisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DisplayPreferencesId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisplayPreferencesId"); + + b.ToTable("HomeSection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Permission_Permissions_Guid"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Preference_Preferences_Guid"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxActiveSessions") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null) + .WithMany("HomeSections") + .HasForeignKey("DisplayPreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ItemDisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("Permission_Permissions_Guid"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("Preference_Preferences_Guid"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Navigation("HomeSections"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Navigation("AccessSchedules"); + + b.Navigation("DisplayPreferences"); + + b.Navigation("ItemDisplayPreferences"); + + b.Navigation("Permissions"); + + b.Navigation("Preferences"); + + b.Navigation("ProfileImage"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.cs b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.cs new file mode 100644 index 0000000000..ade68612c0 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.cs @@ -0,0 +1,35 @@ +#pragma warning disable CS1591 +// +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class NullableCustomPrefValue : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + schema: "jellyfin", + table: "CustomItemDisplayPreferences", + type: "TEXT", + nullable: true, + oldClrType: typeof(string), + oldType: "TEXT"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + schema: "jellyfin", + table: "CustomItemDisplayPreferences", + type: "TEXT", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "TEXT", + oldNullable: true); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 1614a88efb..6a523ba685 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "5.0.0"); + .HasAnnotation("ProductVersion", "5.0.3"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -110,7 +110,6 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT"); b.Property("Value") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); @@ -448,8 +447,8 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => { b.HasOne("Jellyfin.Data.Entities.User", null) - .WithOne("DisplayPreferences") - .HasForeignKey("Jellyfin.Data.Entities.DisplayPreferences", "UserId") + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); @@ -502,8 +501,7 @@ namespace Jellyfin.Server.Implementations.Migrations { b.Navigation("AccessSchedules"); - b.Navigation("DisplayPreferences") - .IsRequired(); + b.Navigation("DisplayPreferences"); b.Navigation("ItemDisplayPreferences"); diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index a3e9516b9f..c89e3c74d5 100644 --- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -68,7 +68,7 @@ namespace Jellyfin.Server.Implementations.Users } /// - public IDictionary ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client) + public Dictionary ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client) { return _dbContext.CustomItemDisplayPreferences .AsQueryable() diff --git a/MediaBrowser.Controller/IDisplayPreferencesManager.cs b/MediaBrowser.Controller/IDisplayPreferencesManager.cs index 041eeea62a..87b6f81c5c 100644 --- a/MediaBrowser.Controller/IDisplayPreferencesManager.cs +++ b/MediaBrowser.Controller/IDisplayPreferencesManager.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Controller /// The item id. /// The client string. /// The dictionary of custom item display preferences. - IDictionary ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client); + Dictionary ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client); /// /// Sets the custom item display preference for the user and client. From 4892e0cf064a2a392881b38a5965324aa80eac0c Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 7 Apr 2021 13:17:59 +0200 Subject: [PATCH 227/282] fix build...somehow --- MediaBrowser.Controller/IDisplayPreferencesManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/IDisplayPreferencesManager.cs b/MediaBrowser.Controller/IDisplayPreferencesManager.cs index 87b6f81c5c..be1d974a47 100644 --- a/MediaBrowser.Controller/IDisplayPreferencesManager.cs +++ b/MediaBrowser.Controller/IDisplayPreferencesManager.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Controller /// The item id. /// The client string. /// The dictionary of custom item display preferences. - Dictionary ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client); + Dictionary ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client); /// /// Sets the custom item display preference for the user and client. From 45a16c19a76d8fac36d3acf864f8030fb30d4e1c Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 7 Apr 2021 21:29:15 +0200 Subject: [PATCH 228/282] Add code to test authenticated endpoints --- .../AuthHelper.cs | 59 +++++++++++++++++++ .../Controllers/ActivityLogControllerTests.cs | 30 ++++++++++ 2 files changed, 89 insertions(+) create mode 100644 tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs new file mode 100644 index 0000000000..4b8fac07fb --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs @@ -0,0 +1,59 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Models.StartupDtos; +using Jellyfin.Api.Models.UserDtos; +using MediaBrowser.Common.Json; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests +{ + public static class AuthHelper + { + public const string AuthHeaderName = "X-Emby-Authorization"; + public const string DummyAuthHeader = "MediaBrowser Client=\"Jellyfin.Server Integration Tests\", DeviceId=\"69420\", Device=\"Apple II\", Version=\"10.8.0\""; + + public static async Task CompleteStartupAsync(HttpClient client) + { + var jsonOptions = JsonDefaults.Options; + var userResponse = await client.GetByteArrayAsync("/Startup/User").ConfigureAwait(false); + var user = JsonSerializer.Deserialize(userResponse, jsonOptions); + + using var completeResponse = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty())).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NoContent, completeResponse.StatusCode); + + using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes( + new AuthenticateUserByName() + { + Username = user!.Name, + Pw = user.Password, + }, + jsonOptions)); + content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + content.Headers.Add("X-Emby-Authorization", DummyAuthHeader); + + using var authResponse = await client.PostAsync("/Users/AuthenticateByName", content).ConfigureAwait(false); + var auth = await JsonSerializer.DeserializeAsync( + await authResponse.Content.ReadAsStreamAsync().ConfigureAwait(false), + jsonOptions).ConfigureAwait(false); + + return auth!.AccessToken; + } + + public static void AddAuthHeader(this HttpRequestHeaders headers, string accessToken) + { + headers.Add(AuthHeaderName, DummyAuthHeader + $", Token={accessToken}"); + } + + private class AuthenticationResultDto + { + public string AccessToken { get; set; } = string.Empty; + + public string ServerId { get; set; } = string.Empty; + } + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs new file mode 100644 index 0000000000..4657ee6e2f --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs @@ -0,0 +1,30 @@ +using System.Net; +using System.Net.Mime; +using System.Threading.Tasks; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers +{ + public sealed class ActivityLogControllerTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + private string? _accessToken; + + public ActivityLogControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task ActivityLog_GetEntries_Ok() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var response = await client.GetAsync("System/ActivityLog/Entries").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); + } + } +} From 7c457da9ab0803a467e8508203b527378066c099 Mon Sep 17 00:00:00 2001 From: Brian Arnold Date: Thu, 8 Apr 2021 02:39:58 -0400 Subject: [PATCH 229/282] Fixed issue with determining if a directory was a directory or file when it contained a '.' character in the directory path. Resolves: #2845 --- MediaBrowser.Controller/Playlists/Playlist.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index e8b7be7e20..faaceb828c 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Text.Json.Serialization; using System.Threading; @@ -43,6 +44,16 @@ namespace MediaBrowser.Controller.Playlists public static bool IsPlaylistFile(string path) { + //When a directory contains a `.`, calling "HasExtension" will return true, even if that location is a directory that exists. + //This kills the PlaylistXmlSaver, because instead of saving in "config/data/playlists/MyList2.0/playlist.xml", + //It saves the information in "config/data/playlists/MyList2.xml". So we just need to see if it's actually a real directory first. + //Lucky for us, when a new playlist is created, the directory on the drive is created first, then the Playlist object is created. + //And if there's not a directory there, then we can just check if it has an extension. + if (new System.IO.DirectoryInfo(path).Exists) + { //This is a directory, and therefore definitely not a playlist file. + return false; + } + //Well, there's no directory there, so if it /looks/ like a file path, then it probably is. return System.IO.Path.HasExtension(path); } From 2314487e38d77f5d3a123ad2f0e76ab6ea2134c7 Mon Sep 17 00:00:00 2001 From: BrianCArnold Date: Thu, 8 Apr 2021 04:03:43 -0400 Subject: [PATCH 230/282] Update MediaBrowser.Controller/Playlists/Playlist.cs Included suggested change from cvium Co-authored-by: Claus Vium --- MediaBrowser.Controller/Playlists/Playlist.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index faaceb828c..089d241bae 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -44,17 +44,8 @@ namespace MediaBrowser.Controller.Playlists public static bool IsPlaylistFile(string path) { - //When a directory contains a `.`, calling "HasExtension" will return true, even if that location is a directory that exists. - //This kills the PlaylistXmlSaver, because instead of saving in "config/data/playlists/MyList2.0/playlist.xml", - //It saves the information in "config/data/playlists/MyList2.xml". So we just need to see if it's actually a real directory first. - //Lucky for us, when a new playlist is created, the directory on the drive is created first, then the Playlist object is created. - //And if there's not a directory there, then we can just check if it has an extension. - if (new System.IO.DirectoryInfo(path).Exists) - { //This is a directory, and therefore definitely not a playlist file. - return false; - } - //Well, there's no directory there, so if it /looks/ like a file path, then it probably is. - return System.IO.Path.HasExtension(path); + // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot). + return Path.HasExtension(path) && !Directory.Exists(path); } [JsonIgnore] From a2acfb02e994f9829c5a0131f583e720b3466ce8 Mon Sep 17 00:00:00 2001 From: Brian Arnold Date: Thu, 8 Apr 2021 05:19:28 -0400 Subject: [PATCH 231/282] Can't reference System.IO.Path as 'Path', even though System.IO is in the usings, because there is a Path property of the class. --- MediaBrowser.Controller/Playlists/Playlist.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 089d241bae..977b14cb08 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Controller.Playlists public static bool IsPlaylistFile(string path) { // The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot). - return Path.HasExtension(path) && !Directory.Exists(path); + return System.IO.Path.HasExtension(path) && !Directory.Exists(path); } [JsonIgnore] From b1faf8c2e832d269c1bc6017037a17533913abf4 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 8 Apr 2021 07:36:13 -0600 Subject: [PATCH 232/282] Update to dotnet 5.0.5 --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- .../Jellyfin.Server.Implementations.csproj | 8 ++++---- Jellyfin.Server/Jellyfin.Server.csproj | 4 ++-- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- deployment/Dockerfile.debian.amd64 | 2 +- deployment/Dockerfile.debian.arm64 | 2 +- deployment/Dockerfile.debian.armhf | 2 +- deployment/Dockerfile.linux.amd64 | 2 +- deployment/Dockerfile.linux.amd64-musl | 2 +- deployment/Dockerfile.linux.arm64 | 2 +- deployment/Dockerfile.linux.armhf | 2 +- deployment/Dockerfile.macos | 2 +- deployment/Dockerfile.portable | 2 +- deployment/Dockerfile.ubuntu.amd64 | 2 +- deployment/Dockerfile.ubuntu.arm64 | 2 +- deployment/Dockerfile.ubuntu.armhf | 2 +- deployment/Dockerfile.windows.amd64 | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index d5372d7526..d6dc5a2dc3 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -15,7 +15,7 @@ - + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 96a4fa2fb4..2c6a176b69 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -27,13 +27,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index e7f83aff16..61171c0189 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -37,8 +37,8 @@ - - + + diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index f622a042ac..4db99f0b08 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -36,7 +36,7 @@ - + diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64 index 4280726135..ec0321f477 100644 --- a/deployment/Dockerfile.debian.amd64 +++ b/deployment/Dockerfile.debian.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64 index b540efc09a..8fd5ddb932 100644 --- a/deployment/Dockerfile.debian.arm64 +++ b/deployment/Dockerfile.debian.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf index 426ce02fcd..14615d19fb 100644 --- a/deployment/Dockerfile.debian.armhf +++ b/deployment/Dockerfile.debian.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64 index 3b91515f36..1f6ca15586 100644 --- a/deployment/Dockerfile.linux.amd64 +++ b/deployment/Dockerfile.linux.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl index 2ca9072bae..6af5d8baf5 100644 --- a/deployment/Dockerfile.linux.amd64-musl +++ b/deployment/Dockerfile.linux.amd64-musl @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64 index 03efd306d7..15b59e29d1 100644 --- a/deployment/Dockerfile.linux.arm64 +++ b/deployment/Dockerfile.linux.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf index 585572204b..71a0fda215 100644 --- a/deployment/Dockerfile.linux.armhf +++ b/deployment/Dockerfile.linux.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos index b37afdcfbc..9291bcbb94 100644 --- a/deployment/Dockerfile.macos +++ b/deployment/Dockerfile.macos @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index 686b20197c..e98ba74f87 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index 3513bf8ecb..d1fd8818e6 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index 5acdf0d178..8e79d417cf 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index 42f757d058..627caa95a8 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64 index 6ed1193fb0..5723abcae6 100644 --- a/deployment/Dockerfile.windows.amd64 +++ b/deployment/Dockerfile.windows.amd64 @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 57277be150..f288561b74 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 079021e61b..8d4d9e3d23 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 3ba49ff6a2..4a5cf1feee 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -13,7 +13,7 @@ - + From b645bb20deb92225e17dd87fcc7fbf2fc1631e80 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 8 Apr 2021 14:38:25 +0100 Subject: [PATCH 233/282] Update Emby.Server.Implementations/Updates/InstallationManager.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index e7307aefef..653b1381b9 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -502,7 +502,7 @@ namespace Emby.Server.Implementations.Updates foreach (var plugin in plugins) { // Don't auto update when plugin marked not to, or when it's disabled. - if (plugin.Manifest?.AutoUpdate == false || plugin.Manifest?.Status == MediaBrowser.Model.Plugins.PluginStatus.Disabled) + if (plugin.Manifest?.AutoUpdate == false || plugin.Manifest?.Status == PluginStatus.Disabled) { continue; } From cc0f191228d7444a598370ac93ba7312700d58b1 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Thu, 8 Apr 2021 09:57:17 -0400 Subject: [PATCH 234/282] Disable hevc encoding by default --- MediaBrowser.Model/Configuration/EncodingOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index a9b2803017..365bbeef66 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Model.Configuration EnableDecodingColorDepth10Vp9 = true; EnableEnhancedNvdecDecoder = true; EnableHardwareEncoding = true; - AllowHevcEncoding = true; + AllowHevcEncoding = false; EnableSubtitleExtraction = true; HardwareDecodingCodecs = new string[] { "h264", "vc1" }; } From 391554d391c1a045a0922fd5f0b43459afdfc550 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 9 Apr 2021 01:04:49 +0200 Subject: [PATCH 235/282] Add tests for UserController --- .../AuthHelper.cs | 2 +- .../Controllers/ActivityLogControllerTests.cs | 2 +- .../Controllers/UserControllerTests.cs | 170 ++++++++++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs index 4b8fac07fb..ea6838682a 100644 --- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs +++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs @@ -44,7 +44,7 @@ namespace Jellyfin.Server.Integration.Tests return auth!.AccessToken; } - public static void AddAuthHeader(this HttpRequestHeaders headers, string accessToken) + public static void AddAuthHeader(this HttpHeaders headers, string accessToken) { headers.Add(AuthHeaderName, DummyAuthHeader + $", Token={accessToken}"); } diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs index 4657ee6e2f..be89fbc9a9 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs @@ -8,7 +8,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers public sealed class ActivityLogControllerTests : IClassFixture { private readonly JellyfinApplicationFactory _factory; - private string? _accessToken; + private static string? _accessToken; public ActivityLogControllerTests(JellyfinApplicationFactory factory) { diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs new file mode 100644 index 0000000000..6584490de5 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs @@ -0,0 +1,170 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Models.UserDtos; +using MediaBrowser.Common.Json; +using MediaBrowser.Model.Dto; +using Xunit; +using Xunit.Priority; + +namespace Jellyfin.Server.Integration.Tests.Controllers +{ + [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] + public sealed class UserControllerTests : IClassFixture + { + private const string TestUsername = "testUser01"; + + private readonly JellyfinApplicationFactory _factory; + private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.Options; + private static string? _accessToken; + private static Guid _testUserId = Guid.Empty; + + public UserControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + private Task CreateUserByName(HttpClient httpClient, CreateUserByName request) + { + using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions)); + postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + return httpClient.PostAsync("Users/New", postContent); + } + + private Task UpdateUserPassword(HttpClient httpClient, Guid userId, UpdateUserPassword request) + { + using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions)); + postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + return httpClient.PostAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", postContent); + } + + [Fact] + [Priority(-1)] + public async Task GetPublicUsers_Valid_Success() + { + var client = _factory.CreateClient(); + + using var response = await client.GetAsync("Users/Public").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var users = await JsonSerializer.DeserializeAsync( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false); + // User are hidden by default + Assert.Empty(users); + } + + [Fact] + [Priority(-1)] + public async Task GetUsers_Valid_Success() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + using var response = await client.GetAsync("Users").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var users = await JsonSerializer.DeserializeAsync( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false); + Assert.Single(users); + Assert.False(users![0].HasConfiguredPassword); + } + + [Fact] + [Priority(0)] + public async Task New_Valid_Success() + { + var client = _factory.CreateClient(); + + // access token can't be null here as the previous test populated it + client.DefaultRequestHeaders.AddAuthHeader(_accessToken!); + + var createRequest = new CreateUserByName() + { + Name = TestUsername + }; + + using var response = await CreateUserByName(client, createRequest).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var user = await JsonSerializer.DeserializeAsync( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false); + Assert.Equal(TestUsername, user!.Name); + Assert.False(user.HasPassword); + Assert.False(user.HasConfiguredPassword); + + _testUserId = user.Id; + + Console.WriteLine(user.Id.ToString("N", CultureInfo.InvariantCulture)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("‼️")] + [Priority(0)] + public async Task New_Invalid_Fail(string? username) + { + var client = _factory.CreateClient(); + + // access token can't be null here as the previous test populated it + client.DefaultRequestHeaders.AddAuthHeader(_accessToken!); + + var createRequest = new CreateUserByName() + { + Name = username + }; + + using var response = await CreateUserByName(client, createRequest).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + [Priority(1)] + public async Task UpdateUserPassword_Valid_Success() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken!); + + var createRequest = new UpdateUserPassword() + { + NewPw = "4randomPa$$word" + }; + + using var response = await UpdateUserPassword(client, _testUserId, createRequest).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + var users = await JsonSerializer.DeserializeAsync( + await client.GetStreamAsync("Users").ConfigureAwait(false), _jsonOpions).ConfigureAwait(false); + var user = users!.First(x => x.Id == _testUserId); + Assert.True(user.HasPassword); + Assert.True(user.HasConfiguredPassword); + } + + [Fact] + [Priority(2)] + public async Task UpdateUserPassword_Empty_RemoveSetPassword() + { + var client = _factory.CreateClient(); + + client.DefaultRequestHeaders.AddAuthHeader(_accessToken!); + + var createRequest = new UpdateUserPassword() + { + CurrentPw = "4randomPa$$word", + }; + + using var response = await UpdateUserPassword(client, _testUserId, createRequest).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + var users = await JsonSerializer.DeserializeAsync( + await client.GetStreamAsync("Users").ConfigureAwait(false), _jsonOpions).ConfigureAwait(false); + var user = users!.First(x => x.Id == _testUserId); + Assert.False(user.HasPassword); + Assert.False(user.HasConfiguredPassword); + } + } +} From db530e61f5ac7047f8c5b88b3a30aa17e9253bbc Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 9 Apr 2021 11:32:19 +0200 Subject: [PATCH 236/282] move IsPlayed to outerquery IsPlayed is a column in UserDatas and does not belong in the inner query. None of the other UserDatas columns are in the innerquery. --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 2ae8054478..694805ebe9 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -5415,7 +5415,6 @@ AND Type = @InternalPersonType)"); ItemIds = query.ItemIds, TopParentIds = query.TopParentIds, ParentId = query.ParentId, - IsPlayed = query.IsPlayed, IsAiring = query.IsAiring, IsMovie = query.IsMovie, IsSports = query.IsSports, @@ -5441,6 +5440,7 @@ AND Type = @InternalPersonType)"); var outerQuery = new InternalItemsQuery(query.User) { + IsPlayed = query.IsPlayed, IsFavorite = query.IsFavorite, IsFavoriteOrLiked = query.IsFavoriteOrLiked, IsLiked = query.IsLiked, From a21b2714e7957640afa140361d9886877e57d6df Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 9 Apr 2021 12:03:56 +0200 Subject: [PATCH 237/282] fetching images should not kill the scanner --- .../Library/LibraryManager.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index c18838caf7..494eb59292 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1914,12 +1914,17 @@ namespace Emby.Server.Implementations.Library } catch (ArgumentException) { - _logger.LogWarning("Cannot get image index for {0}", img.Path); + _logger.LogWarning("Cannot get image index for {ImagePath}", img.Path); continue; } catch (InvalidOperationException) { - _logger.LogWarning("Cannot fetch image from {0}", img.Path); + _logger.LogWarning("Cannot fetch image from {ImagePath}", img.Path); + continue; + } + catch (HttpRequestException ex) + { + _logger.LogWarning("Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode); continue; } } @@ -1932,7 +1937,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Cannot get image dimensions for {0}", image.Path); + _logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path); image.Width = 0; image.Height = 0; continue; @@ -1944,7 +1949,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path); + _logger.LogError(ex, "Cannot compute blurhash for {ImagePath}", image.Path); image.BlurHash = string.Empty; } @@ -1954,7 +1959,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Cannot update DateModified for {0}", image.Path); + _logger.LogError(ex, "Cannot update DateModified for {ImagePath}", image.Path); } } From ce4f73022116bb7910d7a73daf69f7a0624b2437 Mon Sep 17 00:00:00 2001 From: Mohamed Akram Date: Fri, 9 Apr 2021 20:02:23 +0400 Subject: [PATCH 238/282] Add support for TMDB series absolute and DVD order --- .../MediaBrowser.Providers.csproj | 2 +- .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 2 +- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 2 +- .../Plugins/Tmdb/TmdbClientManager.cs | 69 ++++++++++++++++++- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 152ea664a1..de7860b2e1 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -22,7 +22,7 @@ - + diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index d923366242..ba18c542fe 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, null, null, cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken) .ConfigureAwait(false); var stills = episodeResult?.Images?.Stills; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index b455e56349..36e7fe91a1 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } var episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) .ConfigureAwait(false); if (episodeResult == null) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index bf0f027fce..3aa673274f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -125,7 +125,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb tmdbId, language: TmdbUtils.NormalizeLanguage(language), includeImageLanguage: imageLanguages, - extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings, + extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings | TvShowMethods.EpisodeGroups, cancellationToken: cancellationToken).ConfigureAwait(false); if (series != null) @@ -136,6 +136,56 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return series; } + /// + /// Gets a tv show episode group from the TMDb API based on the show id and the display order. + /// + /// The tv show's TMDb id. + /// The display order. + /// The tv show's language. + /// A comma-separated list of image languages. + /// The cancellation token. + /// The TMDb tv show episode group information or null if not found. + public async Task GetSeriesGroupAsync(int tvShowId, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken) + { + var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; + if (_memoryCache.TryGetValue(key, out TvGroupCollection group)) + { + return group; + } + + await EnsureClientConfigAsync().ConfigureAwait(false); + + TvGroupType? groupType = + displayOrder == "absolute" ? TvGroupType.Absolute : + displayOrder == "dvd" ? TvGroupType.DVD : + null; + + if (groupType == null) + { + return null; + } + + var series = await GetSeriesAsync(tvShowId, language, imageLanguages, cancellationToken); + var episodeGroupId = series.EpisodeGroups.Results.Find(g => g.Type == groupType)?.Id; + + if (episodeGroupId == null) + { + return null; + } + + group = await _tmDbClient.GetTvEpisodeGroupsAsync( + episodeGroupId, + language: TmdbUtils.NormalizeLanguage(language), + cancellationToken: cancellationToken).ConfigureAwait(false); + + if (group != null) + { + _memoryCache.Set(key, group, TimeSpan.FromHours(CacheDurationInHours)); + } + + return group; + } + /// /// Gets a tv season from the TMDb API based on the tv show's TMDb id. /// @@ -177,13 +227,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The tv show's TMDb id. /// The season number. /// The episode number. + /// The display order. /// The episode's language. /// A comma-separated list of image languages. /// The cancellation token. /// The TMDb tv episode information or null if not found. - public async Task GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string language, string imageLanguages, CancellationToken cancellationToken) + public async Task GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken) { - var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{language}"; + var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; if (_memoryCache.TryGetValue(key, out TvEpisode episode)) { return episode; @@ -191,6 +242,18 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); + var group = await GetSeriesGroupAsync(tvShowId, displayOrder, language, imageLanguages, cancellationToken); + if (group != null) + { + var season = group.Groups.Find(s => s.Order == seasonNumber); + var ep = season?.Episodes.Find(e => e.Order == episodeNumber - 1); + if (ep != null) + { + seasonNumber = ep.SeasonNumber; + episodeNumber = ep.EpisodeNumber; + } + } + episode = await _tmDbClient.GetTvEpisodeAsync( tvShowId, seasonNumber, From 1a3352003d0736d4d18513fb0055ae3b6452cded Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 9 Apr 2021 23:02:36 +0200 Subject: [PATCH 239/282] don't die on dangling symlinks --- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 679795dd2c..7c9b7c2d01 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -249,9 +249,18 @@ namespace Emby.Server.Implementations.IO // Issue #2354 get the size of files behind symbolic links if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)) { - using (Stream thisFileStream = File.OpenRead(fileInfo.FullName)) + try { - result.Length = thisFileStream.Length; + using (Stream thisFileStream = File.OpenRead(fileInfo.FullName)) + { + result.Length = thisFileStream.Length; + } + } + catch (FileNotFoundException ex) + { + // Dangling symlinks cannot be detected before opening the file unfortunately... + Logger.LogError("Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", ex); + result.Exists = false; } } From f2cba352e58f5d52693875365fdd400ad7c11863 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 9 Apr 2021 23:30:07 +0200 Subject: [PATCH 240/282] catch ioexception and include stack trace --- Emby.Server.Implementations/Library/LibraryManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 494eb59292..7f29e3e99a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1917,14 +1917,14 @@ namespace Emby.Server.Implementations.Library _logger.LogWarning("Cannot get image index for {ImagePath}", img.Path); continue; } - catch (InvalidOperationException) + catch (Exception ex) when (ex is InvalidOperationException || ex is IOException) { - _logger.LogWarning("Cannot fetch image from {ImagePath}", img.Path); + _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path); continue; } catch (HttpRequestException ex) { - _logger.LogWarning("Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode); + _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode); continue; } } From ef527df28f1a48f31aa4bbab36c9443075b68084 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 10 Apr 2021 00:06:48 +0200 Subject: [PATCH 241/282] Set mediatype to Audio for playlists in a music library --- .../Library/Resolvers/PlaylistResolver.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index c76d41e5cc..5f051321f6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -63,7 +63,8 @@ namespace Emby.Server.Implementations.Library.Resolvers { Path = args.Path, Name = Path.GetFileNameWithoutExtension(args.Path), - IsInMixedFolder = true + IsInMixedFolder = true, + PlaylistMediaType = MediaType.Audio }; } } From f99237cf9b7c2c33c18fefafe79d50b5b414781f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 10 Apr 2021 00:16:37 +0200 Subject: [PATCH 242/282] Update Emby.Server.Implementations/IO/ManagedFileSystem.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 7c9b7c2d01..3893a15771 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -259,7 +259,7 @@ namespace Emby.Server.Implementations.IO catch (FileNotFoundException ex) { // Dangling symlinks cannot be detected before opening the file unfortunately... - Logger.LogError("Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", ex); + Logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName); result.Exists = false; } } From 321e383965cf593991c0cbd4e9367cce4e408dc2 Mon Sep 17 00:00:00 2001 From: Ian Walton Date: Fri, 9 Apr 2021 19:22:22 -0400 Subject: [PATCH 243/282] Fix setting audio stream in PlaybackInfo for jellyfin-web. --- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index a2d0e7030a..f07271821d 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -283,6 +283,7 @@ namespace Jellyfin.Api.Helpers if (streamInfo != null) { SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; } } } @@ -327,6 +328,7 @@ namespace Jellyfin.Api.Helpers if (streamInfo != null) { SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; } } } @@ -354,6 +356,7 @@ namespace Jellyfin.Api.Helpers // Do this after the above so that StartPositionTicks is set SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; } } else @@ -391,6 +394,7 @@ namespace Jellyfin.Api.Helpers // Do this after the above so that StartPositionTicks is set SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex; } } } From 117736aac9d0267c3a1a26192d564fc4e5541fe5 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 10 Apr 2021 17:09:45 +0200 Subject: [PATCH 244/282] Fix LogUnmatchedProfile formatting --- Emby.Dlna/DlnaManager.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index c94d803e1e..3417076dc1 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -126,14 +126,14 @@ namespace Emby.Dlna var builder = new StringBuilder(); builder.AppendLine("No matching device profile found. The default will need to be used."); - builder.Append("FriendlyName:").AppendLine(profile.FriendlyName); - builder.Append("Manufacturer:").AppendLine(profile.Manufacturer); - builder.Append("ManufacturerUrl:").AppendLine(profile.ManufacturerUrl); - builder.Append("ModelDescription:").AppendLine(profile.ModelDescription); - builder.Append("ModelName:").AppendLine(profile.ModelName); - builder.Append("ModelNumber:").AppendLine(profile.ModelNumber); - builder.Append("ModelUrl:").AppendLine(profile.ModelUrl); - builder.Append("SerialNumber:").AppendLine(profile.SerialNumber); + builder.Append("FriendlyName: ").AppendLine(profile.FriendlyName); + builder.Append("Manufacturer: ").AppendLine(profile.Manufacturer); + builder.Append("ManufacturerUrl: ").AppendLine(profile.ManufacturerUrl); + builder.Append("ModelDescription: ").AppendLine(profile.ModelDescription); + builder.Append("ModelName: ").AppendLine(profile.ModelName); + builder.Append("ModelNumber: ").AppendLine(profile.ModelNumber); + builder.Append("ModelUrl: ").AppendLine(profile.ModelUrl); + builder.Append("SerialNumber: ").AppendLine(profile.SerialNumber); _logger.LogInformation(builder.ToString()); } From be9cb7af2c38c41e466b565f39109489fdfb61aa Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 10 Apr 2021 22:42:09 +0200 Subject: [PATCH 245/282] Minor improvements to IPObjects --- MediaBrowser.Common/Net/IPHost.cs | 8 ++++---- MediaBrowser.Common/Net/IPNetAddress.cs | 16 ++++++++-------- MediaBrowser.Common/Net/IPObject.cs | 16 +++------------- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index d67b6b8e15..fb3ef9b12b 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -135,7 +135,7 @@ namespace MediaBrowser.Common.Net } // See if it's an IPv6 with port address e.g. [::1] or [::1]:120. - int i = host.IndexOf("]", StringComparison.OrdinalIgnoreCase); + int i = host.IndexOf(']', StringComparison.Ordinal); if (i != -1) { return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj); @@ -389,8 +389,8 @@ namespace MediaBrowser.Common.Net /// protected override IPObject CalculateNetworkAddress() { - var netAddr = NetworkAddressOf(this[0], PrefixLength); - return new IPNetAddress(netAddr.Address, netAddr.PrefixLength); + var (address, prefixLength) = NetworkAddressOf(this[0], PrefixLength); + return new IPNetAddress(address, prefixLength); } /// @@ -427,7 +427,7 @@ namespace MediaBrowser.Common.Net // Resolves the host name - so save a DNS lookup. if (string.Equals(HostName, "localhost", StringComparison.OrdinalIgnoreCase)) { - _addresses = new IPAddress[] { new IPAddress(Ipv4Loopback), new IPAddress(Ipv6Loopback) }; + _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback }; return; } diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs index 59e37a5c6c..589aad4b09 100644 --- a/MediaBrowser.Common/Net/IPNetAddress.cs +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Common.Net /// /// IP6Loopback address host. /// - public static readonly IPNetAddress IP6Loopback = IPNetAddress.Parse("::1"); + public static readonly IPNetAddress IP6Loopback = new IPNetAddress(IPAddress.IPv6Loopback); /// /// Object's IP address. @@ -113,7 +113,7 @@ namespace MediaBrowser.Common.Net } // Is it a network? - string[] tokens = addr.Split("/"); + string[] tokens = addr.Split('/'); if (tokens.Length == 2) { @@ -171,8 +171,8 @@ namespace MediaBrowser.Common.Net address = address.MapToIPv4(); } - var altAddress = NetworkAddressOf(address, PrefixLength); - return NetworkAddress.Address.Equals(altAddress.Address) && NetworkAddress.PrefixLength >= altAddress.PrefixLength; + var (altAddress, altPrefix) = NetworkAddressOf(address, PrefixLength); + return NetworkAddress.Address.Equals(altAddress) && NetworkAddress.PrefixLength >= altPrefix; } /// @@ -196,8 +196,8 @@ namespace MediaBrowser.Common.Net return NetworkAddress.PrefixLength <= netaddrObj.PrefixLength; } - var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength); - return NetworkAddress.Address.Equals(altAddress.Address); + var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength).address; + return NetworkAddress.Address.Equals(altAddress); } return false; @@ -270,8 +270,8 @@ namespace MediaBrowser.Common.Net /// protected override IPObject CalculateNetworkAddress() { - var value = NetworkAddressOf(_address, PrefixLength); - return new IPNetAddress(value.Address, value.PrefixLength); + var (address, prefixLength) = NetworkAddressOf(_address, PrefixLength); + return new IPNetAddress(address, prefixLength); } } } diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs index 69cd57f8ae..3542dcd75e 100644 --- a/MediaBrowser.Common/Net/IPObject.cs +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -10,16 +10,6 @@ namespace MediaBrowser.Common.Net /// public abstract class IPObject : IEquatable { - /// - /// IPv6 Loopback address. - /// - protected static readonly byte[] Ipv6Loopback = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; - - /// - /// IPv4 Loopback address. - /// - protected static readonly byte[] Ipv4Loopback = { 127, 0, 0, 1 }; - /// /// The network address of this object. /// @@ -64,7 +54,7 @@ namespace MediaBrowser.Common.Net /// IP Address to convert. /// Subnet prefix. /// IPAddress. - public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength) + public static (IPAddress address, byte prefixLength) NetworkAddressOf(IPAddress address, byte prefixLength) { if (address == null) { @@ -78,7 +68,7 @@ namespace MediaBrowser.Common.Net if (IsLoopback(address)) { - return (Address: address, PrefixLength: prefixLength); + return (address, prefixLength); } // An ip address is just a list of bytes, each one representing a segment on the network. @@ -110,7 +100,7 @@ namespace MediaBrowser.Common.Net } // Return the network address for the prefix. - return (Address: new IPAddress(addressBytes), PrefixLength: prefixLength); + return (new IPAddress(addressBytes), prefixLength); } /// From f2e74917550e8c5d532d180d01eeaa01d92a6cf2 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 10 Apr 2021 22:58:32 +0200 Subject: [PATCH 246/282] Do not check permissions for Folders collectiontype --- Jellyfin.Api/Controllers/ItemsController.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 2c9760f6d0..74cf3b1624 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -246,8 +246,13 @@ namespace Jellyfin.Api.Controllers folder = _libraryManager.GetUserRootFolder(); } - if (folder is IHasCollectionType hasCollectionType - && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) + string? collectionType = null; + if (folder is IHasCollectionType hasCollectionType) + { + collectionType = hasCollectionType.CollectionType; + } + + if (string.Equals(collectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) { recursive = true; includeItemTypes = new[] { BaseItemKind.Playlist }; @@ -270,10 +275,11 @@ namespace Jellyfin.Api.Controllers } } - if (!(item is UserRootFolder) + if (item is not UserRootFolder && !isInEnabledFolder && !user.HasPermission(PermissionKind.EnableAllFolders) - && !user.HasPermission(PermissionKind.EnableAllChannels)) + && !user.HasPermission(PermissionKind.EnableAllChannels) + && !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase)) { _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name); return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}."); From 42bcf171d98276cd8472a8400e6bd960f475e19b Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 10 Apr 2021 23:54:35 +0200 Subject: [PATCH 247/282] Use sync Serialize when writing scheduled tasks to disk --- .../ScheduledTasks/ScheduledTaskWorker.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index a145a84237..3cc2cefb99 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -178,7 +178,8 @@ namespace Emby.Server.Implementations.ScheduledTasks lock (_lastExecutionResultSyncLock) { using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); - JsonSerializer.SerializeAsync(createStream, value, _jsonOptions); + using Utf8JsonWriter jsonStream = new Utf8JsonWriter(createStream); + JsonSerializer.Serialize(jsonStream, value, _jsonOptions); } } } @@ -578,7 +579,8 @@ namespace Emby.Server.Implementations.ScheduledTasks Directory.CreateDirectory(Path.GetDirectoryName(path)); using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); - JsonSerializer.SerializeAsync(createStream, triggers, _jsonOptions); + using Utf8JsonWriter jsonWriter = new Utf8JsonWriter(createStream); + JsonSerializer.Serialize(jsonWriter, triggers, _jsonOptions); } /// From 35cfd760d41a27676dfbe9f215ba913d9d71451b Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 11 Apr 2021 00:27:53 +0200 Subject: [PATCH 248/282] Do not touch "old" local artwork unless saving locally --- MediaBrowser.Providers/Manager/ImageSaver.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 5bb85be7bf..fb1d4f4906 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -172,7 +172,9 @@ namespace MediaBrowser.Providers.Manager SetImagePath(item, type, imageIndex, savedPaths[0]); // Delete the current path - if (currentImageIsLocalFile && !savedPaths.Contains(currentImagePath, StringComparer.OrdinalIgnoreCase)) + if (currentImageIsLocalFile + && !savedPaths.Contains(currentImagePath, StringComparer.OrdinalIgnoreCase) + && (saveLocally || currentImagePath.Contains(_config.ApplicationPaths.InternalMetadataPath, StringComparison.OrdinalIgnoreCase))) { var currentPath = currentImagePath; From 5fc664fd4f8e600887a4220abb366b75cc92ac01 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 11 Apr 2021 00:35:32 +0200 Subject: [PATCH 249/282] Add test for handling dangling symlinks --- jellyfin.ruleset | 2 ++ .../IO/ManagedFileSystemTests.cs | 25 +++++++++++++++++++ ...llyfin.Server.Implementations.Tests.csproj | 1 + 3 files changed, 28 insertions(+) diff --git a/jellyfin.ruleset b/jellyfin.ruleset index b012d2b003..19c0a08b23 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -78,5 +78,7 @@ + + diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs index 671c59b2ec..5a535ac51f 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs @@ -1,3 +1,7 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.InteropServices; using AutoFixture; using AutoFixture.AutoMoq; using Emby.Server.Implementations.IO; @@ -38,5 +42,26 @@ namespace Jellyfin.Server.Implementations.Tests.IO Assert.Equal(expectedAbsolutePath, generatedPath); } } + + [SkippableFact] + public void GetFileInfo_DanglingSymlink_ExistsFalse() + { + Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + + string testFileDir = Path.Combine(Path.GetTempPath(), "jellyfin-test-data"); + string testFileName = Path.Combine(testFileDir, Path.GetRandomFileName() + "-danglingsym.link"); + + Directory.CreateDirectory(testFileDir); + Assert.Equal(0, symlink("thispathdoesntexist", testFileName)); + Assert.True(File.Exists(testFileName)); + + var metadata = _sut.GetFileInfo(testFileName); + Assert.False(metadata.Exists); + } + + [SuppressMessage("Naming Rules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Have to")] + [DllImport("libc", SetLastError = true, CharSet = CharSet.Ansi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)] + private static extern int symlink(string target, string linkpath); } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index fe26ec3ae6..ee59dad5a8 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -28,6 +28,7 @@ + From 383aa4e4d9cf4b0997b9277df838728102e0db3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Wed, 17 Mar 2021 00:15:09 +0100 Subject: [PATCH 250/282] Add Resize to fill box alternative to image endpoints --- Jellyfin.Api/Controllers/ImageController.cs | 124 +++++++++++++++--- .../Drawing/ImageHelper.cs | 2 +- .../Drawing/ImageProcessingOptions.cs | 4 + MediaBrowser.Model/Drawing/DrawingUtils.cs | 48 +++++++ 4 files changed, 161 insertions(+), 17 deletions(-) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 2119e8e2c1..1da5677939 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -490,6 +490,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Supply the cache tag from the item object to receive strong caching headers. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. The of the returned image. @@ -528,7 +530,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex) + [FromQuery] int? imageIndex, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetItemById(itemId); if (item == null) @@ -549,6 +553,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -570,6 +576,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Supply the cache tag from the item object to receive strong caching headers. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. The of the returned image. @@ -607,7 +615,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? unplayedCount, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer) + [FromQuery] string? foregroundLayer, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetItemById(itemId); if (item == null) @@ -628,6 +638,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -648,6 +660,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Supply the cache tag from the item object to receive strong caching headers. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Determines the output format of the image - original,gif,jpg,png. @@ -686,7 +700,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromRoute, Required] int imageIndex) + [FromRoute, Required] int imageIndex, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetItemById(itemId); if (item == null) @@ -707,6 +723,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -731,6 +749,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -765,7 +785,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromRoute, Required] int imageIndex) + [FromRoute, Required] int imageIndex, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetArtist(name); if (item == null) @@ -786,6 +808,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -810,6 +834,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -844,7 +870,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex) + [FromQuery] int? imageIndex, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetGenre(name); if (item == null) @@ -865,6 +893,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -890,6 +920,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -923,7 +955,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer) + [FromQuery] string? foregroundLayer, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetGenre(name); if (item == null) @@ -944,6 +978,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -968,6 +1004,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -1002,7 +1040,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex) + [FromQuery] int? imageIndex, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetMusicGenre(name); if (item == null) @@ -1023,6 +1063,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -1048,6 +1090,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -1081,7 +1125,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer) + [FromQuery] string? foregroundLayer, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetMusicGenre(name); if (item == null) @@ -1102,6 +1148,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -1126,6 +1174,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -1160,7 +1210,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex) + [FromQuery] int? imageIndex, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetPerson(name); if (item == null) @@ -1181,6 +1233,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -1206,6 +1260,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -1239,7 +1295,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer) + [FromQuery] string? foregroundLayer, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetPerson(name); if (item == null) @@ -1260,6 +1318,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -1284,6 +1344,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -1318,7 +1380,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex) + [FromQuery] int? imageIndex, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetStudio(name); if (item == null) @@ -1339,6 +1403,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -1364,6 +1430,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -1397,7 +1465,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer) + [FromQuery] string? foregroundLayer, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var item = _libraryManager.GetStudio(name); if (item == null) @@ -1418,6 +1488,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -1442,6 +1514,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -1476,7 +1550,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex) + [FromQuery] int? imageIndex, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var user = _userManager.GetUserById(userId); if (user?.ProfileImage == null) @@ -1514,6 +1590,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -1540,6 +1618,8 @@ namespace Jellyfin.Api.Controllers /// The fixed image width to return. /// The fixed image height to return. /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Width of box to fill. + /// Height of box to fill. /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. /// Optional. Add a played indicator. /// Optional. Blur image. @@ -1573,7 +1653,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer) + [FromQuery] string? foregroundLayer, + [FromQuery] int? fillHeight, + [FromQuery] int? fillWidth) { var user = _userManager.GetUserById(userId); if (user?.ProfileImage == null) @@ -1611,6 +1693,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, + fillHeight, + fillWidth, cropWhitespace, addPlayedIndicator, blur, @@ -1695,6 +1779,8 @@ namespace Jellyfin.Api.Controllers int? width, int? height, int? quality, + int? fillHeight, + int? fillWidth, bool? cropWhitespace, // TODO: Remove bool? addPlayedIndicator, int? blur, @@ -1773,7 +1859,9 @@ namespace Jellyfin.Api.Controllers outputFormats, cacheDuration, responseHeaders, - isHeadRequest).ConfigureAwait(false); + isHeadRequest, + fillHeight, + fillWidth).ConfigureAwait(false); } private ImageFormat[] GetOutputFormats(ImageFormat? format) @@ -1871,7 +1959,9 @@ namespace Jellyfin.Api.Controllers IReadOnlyCollection supportedFormats, TimeSpan? cacheDuration, IDictionary headers, - bool isHeadRequest) + bool isHeadRequest, + int? fillHeight, + int? fillWidth) { if (!imageInfo.IsLocalFile && item != null) { @@ -1887,6 +1977,8 @@ namespace Jellyfin.Api.Controllers ItemId = itemId, MaxHeight = maxHeight, MaxWidth = maxWidth, + FillHeight = fillHeight, + FillWidth = fillWidth, Quality = quality ?? 100, Width = width, AddPlayedIndicator = addPlayedIndicator ?? false, diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index 87c28d5773..7407261196 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Drawing { // Determine the output size based on incoming parameters var newSize = DrawingUtils.Resize(originalImageSize.Value, options.Width ?? 0, options.Height ?? 0, options.MaxWidth ?? 0, options.MaxHeight ?? 0); - + newSize = DrawingUtils.ResizeFill(newSize, options.FillWidth, options.FillHeight); return newSize; } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 22de9a43e8..4befb29c42 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -32,6 +32,10 @@ namespace MediaBrowser.Controller.Drawing public int? MaxHeight { get; set; } + public int? FillWidth { get; set; } + + public int? FillHeight { get; set; } + public int Quality { get; set; } public IReadOnlyCollection SupportedOutputFormats { get; set; } diff --git a/MediaBrowser.Model/Drawing/DrawingUtils.cs b/MediaBrowser.Model/Drawing/DrawingUtils.cs index 1512c52337..8fb9bdc0ac 100644 --- a/MediaBrowser.Model/Drawing/DrawingUtils.cs +++ b/MediaBrowser.Model/Drawing/DrawingUtils.cs @@ -57,6 +57,54 @@ namespace MediaBrowser.Model.Drawing return new ImageDimensions(newWidth, newHeight); } + /// + /// Resizes to fill box. + /// Returns original size if both width and height are null or zero. + /// + /// The original size object. + /// A new fixed width, if desired. + /// A new fixed height, if desired. + /// A new size object or size. + public static ImageDimensions ResizeFill( + ImageDimensions size, + int? fillWidth, + int? fillHeight) + { + // Return original size if input is invalid. + if ( + (fillWidth == null && fillHeight == null) + || (fillWidth == 0 || fillHeight == 0)) + { + return size; + } + + if (fillWidth == null || fillWidth == 0) + { + fillWidth = 1; + } + + if (fillHeight == null || fillHeight == 0) + { + fillHeight = 1; + } + + double widthRatio = (double)size.Width / (double)fillWidth; + double heightRatio = (double)size.Height / (double)fillHeight!; + // min() + double scaleRatio = widthRatio > heightRatio ? heightRatio : widthRatio; + + // Clamp to current size. + if (scaleRatio < 1) + { + return size; + } + + int newWidth = Convert.ToInt32(Math.Ceiling((double)size.Width / scaleRatio)); + int newHeight = Convert.ToInt32(Math.Ceiling((double)size.Height / scaleRatio)); + + return new ImageDimensions(newWidth, newHeight); + } + /// /// Gets the new width. /// From 13d0837b78011925d308e7ff593207b0e1cb330f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Wed, 17 Mar 2021 19:45:28 +0100 Subject: [PATCH 251/282] (mostly)Fix ResizeFill --- Jellyfin.Api/Controllers/ImageController.cs | 134 +++++++++--------- .../Drawing/ImageProcessingOptions.cs | 10 ++ MediaBrowser.Model/Drawing/DrawingUtils.cs | 12 +- 3 files changed, 82 insertions(+), 74 deletions(-) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 1da5677939..55c2fe735e 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -521,6 +521,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] string? tag, [FromQuery] bool? cropWhitespace, [FromQuery] ImageFormat? format, @@ -530,9 +532,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] int? imageIndex) { var item = _libraryManager.GetItemById(itemId); if (item == null) @@ -553,8 +553,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -607,6 +607,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] string? tag, [FromQuery] bool? cropWhitespace, [FromQuery] ImageFormat? format, @@ -615,9 +617,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? unplayedCount, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] string? foregroundLayer) { var item = _libraryManager.GetItemById(itemId); if (item == null) @@ -638,8 +638,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -691,6 +691,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromRoute, Required] string tag, [FromQuery] bool? cropWhitespace, [FromRoute, Required] ImageFormat format, @@ -700,9 +702,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromRoute, Required] int imageIndex, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromRoute, Required] int imageIndex) { var item = _libraryManager.GetItemById(itemId); if (item == null) @@ -723,8 +723,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -780,14 +780,14 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromRoute, Required] int imageIndex, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromRoute, Required] int imageIndex) { var item = _libraryManager.GetArtist(name); if (item == null) @@ -808,8 +808,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -865,14 +865,14 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] int? imageIndex) { var item = _libraryManager.GetGenre(name); if (item == null) @@ -893,8 +893,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -951,13 +951,13 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] string? foregroundLayer) { var item = _libraryManager.GetGenre(name); if (item == null) @@ -978,8 +978,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -1035,14 +1035,14 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] int? imageIndex) { var item = _libraryManager.GetMusicGenre(name); if (item == null) @@ -1063,8 +1063,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -1121,13 +1121,13 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] string? foregroundLayer) { var item = _libraryManager.GetMusicGenre(name); if (item == null) @@ -1148,8 +1148,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -1205,14 +1205,14 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] int? imageIndex) { var item = _libraryManager.GetPerson(name); if (item == null) @@ -1233,8 +1233,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -1291,13 +1291,13 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] string? foregroundLayer) { var item = _libraryManager.GetPerson(name); if (item == null) @@ -1318,8 +1318,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -1375,14 +1375,14 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] int? imageIndex) { var item = _libraryManager.GetStudio(name); if (item == null) @@ -1403,8 +1403,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -1461,13 +1461,13 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] string? foregroundLayer) { var item = _libraryManager.GetStudio(name); if (item == null) @@ -1488,8 +1488,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -1545,14 +1545,14 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, - [FromQuery] int? imageIndex, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] int? imageIndex) { var user = _userManager.GetUserById(userId); if (user?.ProfileImage == null) @@ -1590,8 +1590,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -1649,13 +1649,13 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, [FromQuery] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, - [FromQuery] string? foregroundLayer, - [FromQuery] int? fillHeight, - [FromQuery] int? fillWidth) + [FromQuery] string? foregroundLayer) { var user = _userManager.GetUserById(userId); if (user?.ProfileImage == null) @@ -1693,8 +1693,8 @@ namespace Jellyfin.Api.Controllers width, height, quality, - fillHeight, fillWidth, + fillHeight, cropWhitespace, addPlayedIndicator, blur, @@ -1779,8 +1779,8 @@ namespace Jellyfin.Api.Controllers int? width, int? height, int? quality, - int? fillHeight, int? fillWidth, + int? fillHeight, bool? cropWhitespace, // TODO: Remove bool? addPlayedIndicator, int? blur, @@ -1844,11 +1844,13 @@ namespace Jellyfin.Api.Controllers item, itemId, imageIndex, + width, height, - maxHeight, maxWidth, + maxHeight, + fillWidth, + fillHeight, quality, - width, addPlayedIndicator, percentPlayed, unplayedCount, @@ -1859,9 +1861,7 @@ namespace Jellyfin.Api.Controllers outputFormats, cacheDuration, responseHeaders, - isHeadRequest, - fillHeight, - fillWidth).ConfigureAwait(false); + isHeadRequest).ConfigureAwait(false); } private ImageFormat[] GetOutputFormats(ImageFormat? format) @@ -1944,11 +1944,13 @@ namespace Jellyfin.Api.Controllers BaseItem? item, Guid itemId, int? index, + int? width, int? height, - int? maxHeight, int? maxWidth, + int? maxHeight, + int? fillWidth, + int? fillHeight, int? quality, - int? width, bool? addPlayedIndicator, double? percentPlayed, int? unplayedCount, @@ -1959,9 +1961,7 @@ namespace Jellyfin.Api.Controllers IReadOnlyCollection supportedFormats, TimeSpan? cacheDuration, IDictionary headers, - bool isHeadRequest, - int? fillHeight, - int? fillWidth) + bool isHeadRequest) { if (!imageInfo.IsLocalFile && item != null) { diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 4befb29c42..e1a2811ac7 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -97,6 +97,16 @@ namespace MediaBrowser.Controller.Drawing return false; } + if (FillWidth.HasValue && sizeValue.Width > FillWidth.Value) + { + return false; + } + + if (FillHeight.HasValue && sizeValue.Height > FillHeight.Value) + { + return false; + } + return true; } diff --git a/MediaBrowser.Model/Drawing/DrawingUtils.cs b/MediaBrowser.Model/Drawing/DrawingUtils.cs index 8fb9bdc0ac..c2a144f817 100644 --- a/MediaBrowser.Model/Drawing/DrawingUtils.cs +++ b/MediaBrowser.Model/Drawing/DrawingUtils.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Model.Drawing } /// - /// Resizes to fill box. + /// Scale down to fill box. /// Returns original size if both width and height are null or zero. /// /// The original size object. @@ -71,9 +71,8 @@ namespace MediaBrowser.Model.Drawing int? fillHeight) { // Return original size if input is invalid. - if ( - (fillWidth == null && fillHeight == null) - || (fillWidth == 0 || fillHeight == 0)) + if ((fillWidth == null || fillWidth == 0) + && (fillHeight == null || fillHeight == 0)) { return size; } @@ -89,9 +88,8 @@ namespace MediaBrowser.Model.Drawing } double widthRatio = (double)size.Width / (double)fillWidth; - double heightRatio = (double)size.Height / (double)fillHeight!; - // min() - double scaleRatio = widthRatio > heightRatio ? heightRatio : widthRatio; + double heightRatio = (double)size.Height / (double)fillHeight; + double scaleRatio = Math.Min(widthRatio, heightRatio); // Clamp to current size. if (scaleRatio < 1) From e57c1655fb2b7d92db33ff3a3b6b09a09b69cf68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Wed, 17 Mar 2021 20:46:45 +0100 Subject: [PATCH 252/282] Remove GetSizeEstimate & GetEstimatedAspectRatio from MediaBrowser.Controller.Drawing.ImageHelper Rework GetCacheFilePath to take requested with and height parameters in stead of using estimated output size --- Emby.Drawing/ImageProcessor.cs | 112 +++++++++++++++--- .../Drawing/ImageHelper.cs | 66 +---------- 2 files changed, 101 insertions(+), 77 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 978173d5a2..646620c435 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -171,11 +171,26 @@ namespace Emby.Drawing return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } - ImageDimensions newSize = ImageHelper.GetNewImageSize(options, null); int quality = options.Quality; ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); - string cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); + string cacheFilePath = GetCacheFilePath( + originalImagePath, + options.Width, + options.Height, + options.MaxWidth, + options.MaxHeight, + options.FillWidth, + options.FillHeight, + quality, + dateModified, + outputFormat, + options.AddPlayedIndicator, + options.PercentPlayed, + options.UnplayedCount, + options.Blur, + options.BackgroundColor, + options.ForegroundLayer); try { @@ -241,48 +256,111 @@ namespace Emby.Drawing /// /// Gets the cache file path based on a set of parameters. /// - private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer) + private string GetCacheFilePath( + string originalPath, + int? width, + int? height, + int? maxWidth, + int? maxHeight, + int? fillWidth, + int? fillHeight, + int quality, + DateTime dateModified, + ImageFormat format, + bool addPlayedIndicator, + double percentPlayed, + int? unwatchedCount, + int? blur, + string backgroundColor, + string foregroundLayer) { - var filename = originalPath - + "width=" + outputSize.Width - + "height=" + outputSize.Height - + "quality=" + quality - + "datemodified=" + dateModified.Ticks - + "f=" + format; + System.Text.StringBuilder filename = new System.Text.StringBuilder(128); + filename.Append(originalPath); + + filename.Append(",quality="); + filename.Append(quality); + + filename.Append(",datemodified="); + filename.Append(dateModified.Ticks); + + filename.Append(",f="); + filename.Append(format); + + if (width.HasValue) + { + filename.Append(",width="); + filename.Append(width.Value); + } + + if (height.HasValue) + { + filename.Append(",height="); + filename.Append(height.Value); + } + + if (maxWidth.HasValue) + { + filename.Append(",maxwidth="); + filename.Append(maxWidth.Value); + } + + if (maxHeight.HasValue) + { + filename.Append(",maxheight="); + filename.Append(maxHeight.Value); + } + + if (fillWidth.HasValue) + { + filename.Append(",fillwidth="); + filename.Append(fillWidth.Value); + } + + if (fillHeight.HasValue) + { + filename.Append(",fillheight="); + filename.Append(fillHeight.Value); + } if (addPlayedIndicator) { - filename += "pl=true"; + filename.Append(",pl=true"); } if (percentPlayed > 0) { - filename += "p=" + percentPlayed; + filename.Append(",p="); + filename.Append(percentPlayed); } if (unwatchedCount.HasValue) { - filename += "p=" + unwatchedCount.Value; + filename.Append(",p="); + filename.Append(unwatchedCount.Value); } if (blur.HasValue) { - filename += "blur=" + blur.Value; + filename.Append(",blur="); + filename.Append(blur.Value); } if (!string.IsNullOrEmpty(backgroundColor)) { - filename += "b=" + backgroundColor; + filename.Append(",b="); + filename.Append(backgroundColor); } if (!string.IsNullOrEmpty(foregroundLayer)) { - filename += "fl=" + foregroundLayer; + filename.Append(",fl="); + filename.Append(foregroundLayer); } - filename += "v=" + Version; + filename.Append(",v="); + filename.Append(Version); - return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant()); + return GetCachePath(ResizedImageCachePath, filename.ToString(), "." + format.ToString().ToLowerInvariant()); } /// diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index 7407261196..181f8e905f 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#nullable enable using System; using MediaBrowser.Controller.Entities; @@ -9,67 +10,12 @@ namespace MediaBrowser.Controller.Drawing { public static class ImageHelper { - public static ImageDimensions GetNewImageSize(ImageProcessingOptions options, ImageDimensions? originalImageSize) + public static ImageDimensions GetNewImageSize(ImageProcessingOptions options, ImageDimensions originalImageSize) { - if (originalImageSize.HasValue) - { - // Determine the output size based on incoming parameters - var newSize = DrawingUtils.Resize(originalImageSize.Value, options.Width ?? 0, options.Height ?? 0, options.MaxWidth ?? 0, options.MaxHeight ?? 0); - newSize = DrawingUtils.ResizeFill(newSize, options.FillWidth, options.FillHeight); - return newSize; - } - - return GetSizeEstimate(options); - } - - private static ImageDimensions GetSizeEstimate(ImageProcessingOptions options) - { - if (options.Width.HasValue && options.Height.HasValue) - { - return new ImageDimensions(options.Width.Value, options.Height.Value); - } - - double aspect = GetEstimatedAspectRatio(options.Image.Type, options.Item); - - int? width = options.Width ?? options.MaxWidth; - - if (width.HasValue) - { - int heightValue = Convert.ToInt32((double)width.Value / aspect); - return new ImageDimensions(width.Value, heightValue); - } - - var height = options.Height ?? options.MaxHeight ?? 200; - int widthValue = Convert.ToInt32(aspect * height); - return new ImageDimensions(widthValue, height); - } - - private static double GetEstimatedAspectRatio(ImageType type, BaseItem item) - { - switch (type) - { - case ImageType.Art: - case ImageType.Backdrop: - case ImageType.Chapter: - case ImageType.Screenshot: - case ImageType.Thumb: - return 1.78; - case ImageType.Banner: - return 5.4; - case ImageType.Box: - case ImageType.BoxRear: - case ImageType.Disc: - case ImageType.Menu: - case ImageType.Profile: - return 1; - case ImageType.Logo: - return 2.58; - case ImageType.Primary: - double defaultPrimaryImageAspectRatio = item.GetDefaultPrimaryImageAspectRatio(); - return defaultPrimaryImageAspectRatio > 0 ? defaultPrimaryImageAspectRatio : 2.0 / 3; - default: - return 1; - } + // Determine the output size based on incoming parameters + var newSize = DrawingUtils.Resize(originalImageSize, options.Width ?? 0, options.Height ?? 0, options.MaxWidth ?? 0, options.MaxHeight ?? 0); + newSize = DrawingUtils.ResizeFill(newSize, options.FillWidth, options.FillHeight); + return newSize; } } } From afff226514a6f645a6738f1e407208826b84f1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Thu, 18 Mar 2021 23:40:45 +0100 Subject: [PATCH 253/282] Apply suggestions from code review Co-authored-by: Cody Robibero --- MediaBrowser.Model/Drawing/DrawingUtils.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Model/Drawing/DrawingUtils.cs b/MediaBrowser.Model/Drawing/DrawingUtils.cs index c2a144f817..5567927682 100644 --- a/MediaBrowser.Model/Drawing/DrawingUtils.cs +++ b/MediaBrowser.Model/Drawing/DrawingUtils.cs @@ -87,8 +87,8 @@ namespace MediaBrowser.Model.Drawing fillHeight = 1; } - double widthRatio = (double)size.Width / (double)fillWidth; - double heightRatio = (double)size.Height / (double)fillHeight; + double widthRatio = size.Width / (double)fillWidth; + double heightRatio = size.Height / (double)fillHeight; double scaleRatio = Math.Min(widthRatio, heightRatio); // Clamp to current size. @@ -97,8 +97,8 @@ namespace MediaBrowser.Model.Drawing return size; } - int newWidth = Convert.ToInt32(Math.Ceiling((double)size.Width / scaleRatio)); - int newHeight = Convert.ToInt32(Math.Ceiling((double)size.Height / scaleRatio)); + int newWidth = Convert.ToInt32(Math.Ceiling(size.Width / scaleRatio)); + int newHeight = Convert.ToInt32(Math.Ceiling(size.Height / scaleRatio)); return new ImageDimensions(newWidth, newHeight); } From e0edbc5754ad4d6afad1f01874ca2085d4f0791d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Mon, 29 Mar 2021 00:46:19 +0200 Subject: [PATCH 254/282] Apply suggestions from code review Co-authored-by: Claus Vium --- Emby.Drawing/ImageProcessor.cs | 2 +- MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 646620c435..78b53f36a2 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -274,7 +274,7 @@ namespace Emby.Drawing string backgroundColor, string foregroundLayer) { - System.Text.StringBuilder filename = new System.Text.StringBuilder(128); + var filename = new StringBuilder(128); filename.Append(originalPath); filename.Append(",quality="); diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index e1a2811ac7..230a0af60c 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -97,12 +97,7 @@ namespace MediaBrowser.Controller.Drawing return false; } - if (FillWidth.HasValue && sizeValue.Width > FillWidth.Value) - { - return false; - } - - if (FillHeight.HasValue && sizeValue.Height > FillHeight.Value) + if (sizeValue.Width > FillWidth || sizeValue.Height > FillHeight) { return false; } From d0857cf6553ffa9560b42720af04e0b5ebcc9c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Mon, 29 Mar 2021 00:48:59 +0200 Subject: [PATCH 255/282] Fix build, increase StringBuilder starting size --- Emby.Drawing/ImageProcessor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 78b53f36a2..7d952aa23b 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; @@ -274,7 +275,7 @@ namespace Emby.Drawing string backgroundColor, string foregroundLayer) { - var filename = new StringBuilder(128); + var filename = new StringBuilder(256); filename.Append(originalPath); filename.Append(",quality="); From 01491796a285a621c6aebe4f886ee74413e5fbee Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 11 Apr 2021 12:57:28 +0200 Subject: [PATCH 256/282] Enable Workstation GC mode --- Jellyfin.Server/Jellyfin.Server.csproj | 1 + debian/conf/jellyfin | 5 +++++ fedora/jellyfin.env | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index e7f83aff16..98d9903440 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -9,6 +9,7 @@ jellyfin Exe net5.0 + false false true true diff --git a/debian/conf/jellyfin b/debian/conf/jellyfin index 7cbfa88ee8..9ebaf2bd88 100644 --- a/debian/conf/jellyfin +++ b/debian/conf/jellyfin @@ -33,6 +33,11 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg" # [OPTIONAL] run Jellyfin without the web app #JELLYFIN_NOWEBAPP_OPT="--nowebclient" +# [OPTIONAL] run Jellyfin with ASP.NET Server Garbage Collection (uses more RAM and less CPU than Workstation GC) +# 0 = Workstation +# 1 = Server +#COMPlus_gcServer=1 + # # SysV init/Upstart options # diff --git a/fedora/jellyfin.env b/fedora/jellyfin.env index bf64acd3f9..56b7a3558d 100644 --- a/fedora/jellyfin.env +++ b/fedora/jellyfin.env @@ -35,3 +35,7 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh" # [OPTIONAL] run Jellyfin without the web app #JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp" +# [OPTIONAL] run Jellyfin with ASP.NET Server Garbage Collection (uses more RAM and less CPU than Workstation GC) +# 0 = Workstation +# 1 = Server +#COMPlus_gcServer=1 From a4ffc7a813389e8cc2e075d3c9e740a66896ad5c Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 12 Apr 2021 00:28:17 +0200 Subject: [PATCH 257/282] Fix random failing of tests Fully initialize the configuration manager at the init stage ``` Failed Jellyfin.Server.Integration.Tests.Controllers.ActivityLogControllerTests.ActivityLog_GetEntries_Ok [2 s] Error Message: MediaBrowser.Common.Extensions.ResourceNotFoundException : Configuration with key metadata not found. Stack Trace: at Emby.Server.Implementations.AppBase.BaseConfigurationManager.<>c__DisplayClass43_0.b__0(String k) in D:\a\1\s\Emby.Server.Implementations\AppBase\BaseConfigurationManager.cs:line 309 at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory) at Emby.Server.Implementations.AppBase.BaseConfigurationManager.GetConfiguration(String key) in D:\a\1\s\Emby.Server.Implementations\AppBase\BaseConfigurationManager.cs:line 300 at MediaBrowser.Common.Configuration.ConfigurationManagerExtensions.GetConfiguration[T](IConfigurationManager manager, String key) in D:\a\1\s\MediaBrowser.Common\Configuration\IConfigurationManager.cs:line 88 at MediaBrowser.Controller.Library.MetadataConfigurationExtensions.GetMetadataConfiguration(IConfigurationManager config) in D:\a\1\s\MediaBrowser.Controller\Library\MetadataConfigurationStore.cs:line 28 at Emby.Server.Implementations.Library.ResolverHelper.SetDateCreated(BaseItem item, IFileSystem fileSystem, FileSystemMetadata info) in D:\a\1\s\Emby.Server.Implementations\Library\ResolverHelper.cs:line 159 at Emby.Server.Implementations.Library.ResolverHelper.EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args) in D:\a\1\s\Emby.Server.Implementations\Library\ResolverHelper.cs:line 153 at Emby.Server.Implementations.Library.ResolverHelper.SetInitialItemValues(BaseItem item, ItemResolveArgs args, IFileSystem fileSystem, ILibraryManager libraryManager) in D:\a\1\s\Emby.Server.Implementations\Library\ResolverHelper.cs:line 81 at Emby.Server.Implementations.Library.LibraryManager.ResolveItem(ItemResolveArgs args, IItemResolver[] resolvers) in D:\a\1\s\Emby.Server.Implementations\Library\LibraryManager.cs:line 480 at Emby.Server.Implementations.Library.LibraryManager.ResolvePath(FileSystemMetadata fileInfo, IDirectoryService directoryService, IItemResolver[] resolvers, Folder parent, String collectionType, LibraryOptions libraryOptions) in D:\a\1\s\Emby.Server.Implementations\Library\LibraryManager.cs:line 618 at Emby.Server.Implementations.Library.LibraryManager.ResolvePath(FileSystemMetadata fileInfo, Folder parent) in D:\a\1\s\Emby.Server.Implementations\Library\LibraryManager.cs:line 536 at Emby.Server.Implementations.Library.LibraryManager.CreateRootFolder() in D:\a\1\s\Emby.Server.Implementations\Library\LibraryManager.cs:line 732 at Emby.Server.Implementations.Library.LibraryManager.get_RootFolder() in D:\a\1\s\Emby.Server.Implementations\Library\LibraryManager.cs:line 180 at Emby.Server.Implementations.IO.LibraryMonitor.Start() in D:\a\1\s\Emby.Server.Implementations\IO\LibraryMonitor.cs:line 135 at Emby.Server.Implementations.IO.LibraryMonitorStartup.RunAsync() in D:\a\1\s\Emby.Server.Implementations\IO\LibraryMonitorStartup.cs:line 26 at Emby.Server.Implementations.ApplicationHost.StartEntryPoints(IEnumerable`1 entryPoints, Boolean isBeforeStartup)+MoveNext() in D:\a\1\s\Emby.Server.Implementations\ApplicationHost.cs:line 541 at System.Threading.Tasks.Task.WhenAll(IEnumerable`1 tasks) at Emby.Server.Implementations.ApplicationHost.RunStartupTasksAsync(CancellationToken cancellationToken) in D:\a\1\s\Emby.Server.Implementations\ApplicationHost.cs:line 525 at Jellyfin.Server.Integration.Tests.JellyfinApplicationFactory.CreateServer(IWebHostBuilder builder) in D:\a\1\s\tests\Jellyfin.Server.Integration.Tests\JellyfinApplicationFactory.cs:line 101 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer() at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient() at Jellyfin.Server.Integration.Tests.Controllers.ActivityLogControllerTests.ActivityLog_GetEntries_Ok() in D:\a\1\s\tests\Jellyfin.Server.Integration.Tests\Controllers\ActivityLogControllerTests.cs:line 21 --- End of stack trace from previous location --- ``` --- .../ApplicationHost.cs | 87 ++++++++----------- Jellyfin.Server/Migrations/MigrationRunner.cs | 8 +- Jellyfin.Server/Program.cs | 2 +- .../MetadataConfigurationExtensions.cs | 15 ++++ .../Library/MetadataConfigurationStore.cs | 12 +-- 5 files changed, 59 insertions(+), 65 deletions(-) create mode 100644 MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 3846de5fd4..0512adf106 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -210,7 +210,7 @@ namespace Emby.Server.Implementations /// Gets or sets the configuration manager. /// /// The configuration manager. - protected IConfigurationManager ConfigurationManager { get; set; } + public ServerConfigurationManager ConfigurationManager { get; set; } /// /// Gets or sets the service provider. @@ -232,12 +232,6 @@ namespace Emby.Server.Implementations /// public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey]; - /// - /// Gets the server configuration manager. - /// - /// The server configuration manager. - public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager; - /// /// Initializes a new instance of the class. /// @@ -255,43 +249,26 @@ namespace Emby.Server.Implementations IFileSystem fileSystem, IServiceCollection serviceCollection) { - _xmlSerializer = new MyXmlSerializer(); - - ServiceCollection = serviceCollection; - ApplicationPaths = applicationPaths; LoggerFactory = loggerFactory; - _fileSystemManager = fileSystem; - - ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); - // Have to migrate settings here as migration subsystem not yet initialised. - MigrateNetworkConfiguration(); - - // Have to pre-register the NetworkConfigurationFactory, as the configuration sub-system is not yet initialised. - ConfigurationManager.RegisterConfiguration(); - NetManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger()); - - Logger = LoggerFactory.CreateLogger(); - _startupOptions = options; _startupConfig = startupConfig; + _fileSystemManager = fileSystem; + ServiceCollection = serviceCollection; - // Initialize runtime stat collection - if (ServerConfigurationManager.Configuration.EnableMetrics) - { - DotNetRuntimeStatsBuilder.Default().StartCollecting(); - } - + Logger = LoggerFactory.CreateLogger(); fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; ApplicationVersionString = ApplicationVersion.ToString(3); ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString; + _xmlSerializer = new MyXmlSerializer(); + ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); _pluginManager = new PluginManager( LoggerFactory.CreateLogger(), this, - ServerConfigurationManager.Configuration, + ConfigurationManager.Configuration, ApplicationPaths.PluginsPath, ApplicationVersion); } @@ -306,9 +283,9 @@ namespace Emby.Server.Implementations if (!File.Exists(path)) { var networkSettings = new NetworkConfiguration(); - ClassMigrationHelper.CopyProperties(ServerConfigurationManager.Configuration, networkSettings); + ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings); _xmlSerializer.SerializeToFile(networkSettings, path); - Logger?.LogDebug("Successfully migrated network settings."); + Logger.LogDebug("Successfully migrated network settings."); } } @@ -545,7 +522,21 @@ namespace Emby.Server.Implementations /// public void Init() { - var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration(); + DiscoverTypes(); + + ConfigurationManager.AddParts(GetExports()); + + // Have to migrate settings here as migration subsystem not yet initialised. + MigrateNetworkConfiguration(); + NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger()); + + // Initialize runtime stat collection + if (ConfigurationManager.Configuration.EnableMetrics) + { + DotNetRuntimeStatsBuilder.Default().StartCollecting(); + } + + var networkConfiguration = ConfigurationManager.GetNetworkConfiguration(); HttpPort = networkConfiguration.HttpServerPortNumber; HttpsPort = networkConfiguration.HttpsPortNumber; @@ -563,8 +554,6 @@ namespace Emby.Server.Implementations }; Certificate = GetCertificate(CertificateInfo); - DiscoverTypes(); - RegisterServices(); _pluginManager.RegisterServices(ServiceCollection); @@ -579,7 +568,8 @@ namespace Emby.Server.Implementations ServiceCollection.AddMemoryCache(); - ServiceCollection.AddSingleton(ConfigurationManager); + ServiceCollection.AddSingleton(ConfigurationManager); + ServiceCollection.AddSingleton(ConfigurationManager); ServiceCollection.AddSingleton(this); ServiceCollection.AddSingleton(_pluginManager); ServiceCollection.AddSingleton(ApplicationPaths); @@ -606,8 +596,6 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton(this); ServiceCollection.AddSingleton(ApplicationPaths); - ServiceCollection.AddSingleton(ServerConfigurationManager); - ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); @@ -796,7 +784,7 @@ namespace Emby.Server.Implementations { // For now there's no real way to inject these properly BaseItem.Logger = Resolve>(); - BaseItem.ConfigurationManager = ServerConfigurationManager; + BaseItem.ConfigurationManager = ConfigurationManager; BaseItem.LibraryManager = Resolve(); BaseItem.ProviderManager = Resolve(); BaseItem.LocalizationManager = Resolve(); @@ -818,13 +806,12 @@ namespace Emby.Server.Implementations /// private void FindParts() { - if (!ServerConfigurationManager.Configuration.IsPortAuthorized) + if (!ConfigurationManager.Configuration.IsPortAuthorized) { - ServerConfigurationManager.Configuration.IsPortAuthorized = true; + ConfigurationManager.Configuration.IsPortAuthorized = true; ConfigurationManager.SaveConfiguration(); } - ConfigurationManager.AddParts(GetExports()); _pluginManager.CreatePlugins(); _urlPrefixes = GetUrlPrefixes().ToArray(); @@ -928,7 +915,7 @@ namespace Emby.Server.Implementations protected void OnConfigurationUpdated(object sender, EventArgs e) { var requiresRestart = false; - var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration(); + var networkConfiguration = ConfigurationManager.GetNetworkConfiguration(); // Don't do anything if these haven't been set yet if (HttpPort != 0 && HttpsPort != 0) @@ -937,10 +924,10 @@ namespace Emby.Server.Implementations if (networkConfiguration.HttpServerPortNumber != HttpPort || networkConfiguration.HttpsPortNumber != HttpsPort) { - if (ServerConfigurationManager.Configuration.IsPortAuthorized) + if (ConfigurationManager.Configuration.IsPortAuthorized) { - ServerConfigurationManager.Configuration.IsPortAuthorized = false; - ServerConfigurationManager.SaveConfiguration(); + ConfigurationManager.Configuration.IsPortAuthorized = false; + ConfigurationManager.SaveConfiguration(); requiresRestart = true; } @@ -1156,7 +1143,7 @@ namespace Emby.Server.Implementations } /// - public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.GetNetworkConfiguration().EnableHttps; + public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps; /// public string GetSmartApiUrl(IPAddress ipAddress, int? port = null) @@ -1240,14 +1227,14 @@ namespace Emby.Server.Implementations Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp), Host = host, Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort), - Path = ServerConfigurationManager.GetNetworkConfiguration().BaseUrl + Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl }.ToString().TrimEnd('/'); } public string FriendlyName => - string.IsNullOrEmpty(ServerConfigurationManager.Configuration.ServerName) + string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName) ? Environment.MachineName - : ServerConfigurationManager.Configuration.ServerName; + : ConfigurationManager.Configuration.ServerName; /// /// Shuts down. diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 305660ae65..cf938ab8cd 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -40,15 +40,15 @@ namespace Jellyfin.Server.Migrations .Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m)) .OfType() .ToArray(); - var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration(MigrationsListStore.StoreKey); + var migrationOptions = ((IConfigurationManager)host.ConfigurationManager).GetConfiguration(MigrationsListStore.StoreKey); - if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0) + if (!host.ConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0) { // If startup wizard is not finished, this is a fresh install. // Don't run any migrations, just mark all of them as applied. logger.LogInformation("Marking all known migrations as applied because this is a fresh install"); migrationOptions.Applied.AddRange(migrations.Where(m => !m.PerformOnNewInstall).Select(m => (m.Id, m.Name))); - host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); } var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet(); @@ -77,7 +77,7 @@ namespace Jellyfin.Server.Migrations // Mark the migration as completed logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name); migrationOptions.Applied.Add((migrationRoutine.Id, migrationRoutine.Name)); - host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); + host.ConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name); } } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 4f203b7a9d..464e024190 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -173,7 +173,7 @@ namespace Jellyfin.Server // If hosting the web client, validate the client content path if (startupConfig.HostWebClient()) { - string? webContentPath = appHost.ServerConfigurationManager.ApplicationPaths.WebPath; + string? webContentPath = appHost.ConfigurationManager.ApplicationPaths.WebPath; if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0) { throw new InvalidOperationException( diff --git a/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs b/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs new file mode 100644 index 0000000000..884f9e7735 --- /dev/null +++ b/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs @@ -0,0 +1,15 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Controller.Library +{ + public static class MetadataConfigurationExtensions + { + public static MetadataConfiguration GetMetadataConfiguration(this IConfigurationManager config) + { + return config.GetConfiguration("metadata"); + } + } +} diff --git a/MediaBrowser.Controller/Library/MetadataConfigurationStore.cs b/MediaBrowser.Controller/Library/MetadataConfigurationStore.cs index f16304db01..a6be6c0d3c 100644 --- a/MediaBrowser.Controller/Library/MetadataConfigurationStore.cs +++ b/MediaBrowser.Controller/Library/MetadataConfigurationStore.cs @@ -14,18 +14,10 @@ namespace MediaBrowser.Controller.Library { new ConfigurationStore { - Key = "metadata", - ConfigurationType = typeof(MetadataConfiguration) + Key = "metadata", + ConfigurationType = typeof(MetadataConfiguration) } }; } } - - public static class MetadataConfigurationExtensions - { - public static MetadataConfiguration GetMetadataConfiguration(this IConfigurationManager config) - { - return config.GetConfiguration("metadata"); - } - } } From cc59abd54e7be6a58187bfb3d01dbe0ec7ea9a1d Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Mon, 12 Apr 2021 07:50:24 -0600 Subject: [PATCH 258/282] Mark cropWhitespace parameter as obsolete (#5751) --- Jellyfin.Api/Controllers/ImageController.cs | 45 +++++++-------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 55c2fe735e..36facf75a4 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -524,7 +524,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, [FromQuery] string? tag, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] ImageFormat? format, [FromQuery] bool? addPlayedIndicator, [FromQuery] double? percentPlayed, @@ -555,7 +555,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -610,7 +609,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, [FromQuery] string? tag, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] ImageFormat? format, [FromQuery] bool? addPlayedIndicator, [FromQuery] double? percentPlayed, @@ -640,7 +639,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -694,7 +692,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, [FromRoute, Required] string tag, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromRoute, Required] ImageFormat format, [FromQuery] bool? addPlayedIndicator, [FromRoute, Required] double percentPlayed, @@ -725,7 +723,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -782,7 +779,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -810,7 +807,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -867,7 +863,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -895,7 +891,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -953,7 +948,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -980,7 +975,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -1037,7 +1031,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -1065,7 +1059,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -1123,7 +1116,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -1150,7 +1143,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -1207,7 +1199,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -1235,7 +1227,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -1293,7 +1284,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -1320,7 +1311,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -1377,7 +1367,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -1405,7 +1395,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -1463,7 +1452,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -1490,7 +1479,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -1547,7 +1535,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -1592,7 +1580,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -1651,7 +1638,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery] bool? cropWhitespace, + [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, [FromQuery] string? backgroundColor, @@ -1695,7 +1682,6 @@ namespace Jellyfin.Api.Controllers quality, fillWidth, fillHeight, - cropWhitespace, addPlayedIndicator, blur, backgroundColor, @@ -1781,7 +1767,6 @@ namespace Jellyfin.Api.Controllers int? quality, int? fillWidth, int? fillHeight, - bool? cropWhitespace, // TODO: Remove bool? addPlayedIndicator, int? blur, string? backgroundColor, @@ -1823,8 +1808,6 @@ namespace Jellyfin.Api.Controllers } } - cropWhitespace ??= imageType == ImageType.Logo || imageType == ImageType.Art; - var outputFormats = GetOutputFormats(format); TimeSpan? cacheDuration = null; From dc4714fe4098eed42114adc3455f4a2982cc18d6 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 12 Apr 2021 19:54:32 +0200 Subject: [PATCH 259/282] fix webp compatibility testing (#5787) --- Jellyfin.Api/Controllers/ImageController.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 36facf75a4..8f7500ac69 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -1859,17 +1859,15 @@ namespace Jellyfin.Api.Controllers private ImageFormat[] GetClientSupportedFormats() { - var acceptTypes = Request.Headers[HeaderNames.Accept]; - var supportedFormats = new List(); - if (acceptTypes.Count > 0) + var supportedFormats = Request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); + for (var i = 0; i < supportedFormats.Length; i++) { - foreach (var type in acceptTypes) + // Remove charsets etc. (anything after semi-colon) + var type = supportedFormats[i]; + int index = type.IndexOf(';', StringComparison.Ordinal); + if (index != -1) { - int index = type.IndexOf(';', StringComparison.Ordinal); - if (index != -1) - { - supportedFormats.Add(type.Substring(0, index)); - } + supportedFormats[i] = type.Substring(0, index); } } From 8045a488ceccc5c85195be3ef694a418c1c326aa Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 12 Apr 2021 22:01:35 +0200 Subject: [PATCH 260/282] Fix possible ArgumentNullException ``` Error Message: System.ArgumentNullException : Value cannot be null. (Parameter 'source') Stack Trace: at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) at System.Linq.Enumerable.Select[TSource,TResult](IEnumerable`1 source, Func`2 selector) at Emby.Server.Implementations.Library.LibraryManager.ResolveItem(ItemResolveArgs args, IItemResolver[] resolvers) in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 475 at Emby.Server.Implementations.Library.LibraryManager.ResolvePath(FileSystemMetadata fileInfo, IDirectoryService directoryService, IItemResolver[] resolvers, Folder parent, String collectionType, LibraryOptions libraryOptions) in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 618 at Emby.Server.Implementations.Library.LibraryManager.ResolvePath(FileSystemMetadata fileInfo, Folder parent) in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 536 at Emby.Server.Implementations.Library.LibraryManager.GetUserRootFolder() in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 812 at Emby.Server.Implementations.Library.LibraryManager.GetCollectionFolders(BaseItem item) in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 2080 at Emby.Server.Implementations.Library.LibraryManager.GetLibraryOptions(BaseItem item) in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 2116 at MediaBrowser.Providers.Manager.ProviderManager.SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable`1 savers) in /home/vsts/work/1/s/MediaBrowser.Providers/Manager/ProviderManager.cs:line 672 at MediaBrowser.Providers.Manager.ProviderManager.SaveMetadata(BaseItem item, ItemUpdateType updateType) in /home/vsts/work/1/s/MediaBrowser.Providers/Manager/ProviderManager.cs:line 655 at Emby.Server.Implementations.Library.LibraryManager.RunMetadataSavers(BaseItem item, ItemUpdateType updateReason) in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 2017 at Emby.Server.Implementations.Library.LibraryManager.UpdateItemsAsync(IReadOnlyList`1 items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 1975 at Emby.Server.Implementations.Library.LibraryManager.CreateRootFolder() in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 775 at Emby.Server.Implementations.Library.LibraryManager.get_RootFolder() in /home/vsts/work/1/s/Emby.Server.Implementations/Library/LibraryManager.cs:line 180 at Emby.Server.Implementations.IO.LibraryMonitor.Start() in /home/vsts/work/1/s/Emby.Server.Implementations/IO/LibraryMonitor.cs:line 135 at Emby.Server.Implementations.IO.LibraryMonitorStartup.RunAsync() in /home/vsts/work/1/s/Emby.Server.Implementations/IO/LibraryMonitorStartup.cs:line 26 at Emby.Server.Implementations.ApplicationHost.StartEntryPoints(IEnumerable`1 entryPoints, Boolean isBeforeStartup)+MoveNext() in /home/vsts/work/1/s/Emby.Server.Implementations/ApplicationHost.cs:line 518 at System.Threading.Tasks.Task.WhenAll(IEnumerable`1 tasks) at Emby.Server.Implementations.ApplicationHost.RunStartupTasksAsync(CancellationToken cancellationToken) in /home/vsts/work/1/s/Emby.Server.Implementations/ApplicationHost.cs:line 502 at Jellyfin.Server.Integration.Tests.JellyfinApplicationFactory.CreateServer(IWebHostBuilder builder) in /home/vsts/work/1/s/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs:line 101 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer() at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient() at Jellyfin.Server.Integration.Tests.Controllers.ActivityLogControllerTests.ActivityLog_GetEntries_Ok() in /home/vsts/work/1/s/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs:line 21 --- End of stack trace from previous location --- ``` --- Emby.Server.Implementations/Library/LibraryManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 7f29e3e99a..6a9f4174dc 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -196,13 +196,13 @@ namespace Emby.Server.Implementations.Library /// Gets or sets the postscan tasks. /// /// The postscan tasks. - private ILibraryPostScanTask[] PostscanTasks { get; set; } + private ILibraryPostScanTask[] PostscanTasks { get; set; } = Array.Empty(); /// /// Gets or sets the intro providers. /// /// The intro providers. - private IIntroProvider[] IntroProviders { get; set; } + private IIntroProvider[] IntroProviders { get; set; } = Array.Empty(); /// /// Gets or sets the list of entity resolution ignore rules. @@ -214,15 +214,15 @@ namespace Emby.Server.Implementations.Library /// Gets or sets the list of currently registered entity resolvers. /// /// The entity resolvers enumerable. - private IItemResolver[] EntityResolvers { get; set; } + private IItemResolver[] EntityResolvers { get; set; } = Array.Empty(); - private IMultiItemResolver[] MultiItemResolvers { get; set; } + private IMultiItemResolver[] MultiItemResolvers { get; set; } = Array.Empty(); /// /// Gets or sets the comparers. /// /// The comparers. - private IBaseItemComparer[] Comparers { get; set; } + private IBaseItemComparer[] Comparers { get; set; } = Array.Empty(); public bool IsScanRunning { get; private set; } From fe54e9ae9de932d2130f15d90df40da9dbc048fe Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Mon, 12 Apr 2021 17:16:26 -0700 Subject: [PATCH 261/282] Remove coupling to distro - The substring `-buster-slim` isn't useful/correct. - .NET 6 (from my team) is based exclusively on bullseye. - If you update just `DOTNET_VERSION` to `6.0`, `docker build` will fail. - If the goal is to force using `buster` throughout the Dockerfile, then making `DOTNET_VERSION` configurable isn't correct. - Context: https://github.com/dotnet/dotnet-docker/blob/main/src/runtime-deps/6.0/bullseye-slim/amd64/Dockerfile#L1 Just saw this as a drive by look at your code. Is intended as helpful context. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cafee737a1..ebe5eb00c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine- && npm ci --no-audit \ && mv dist /dist -FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster-slim as builder +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder WORKDIR /repo COPY . . ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 From 63355af5186756df0538fdf56e99c045864f9ff1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 13:09:10 +0000 Subject: [PATCH 262/282] Bump BlurHashSharp.SkiaSharp from 1.1.1 to 1.2.0 Bumps [BlurHashSharp.SkiaSharp](https://github.com/Bond-009/BlurHashSharp) from 1.1.1 to 1.2.0. - [Release notes](https://github.com/Bond-009/BlurHashSharp/releases) - [Commits](https://github.com/Bond-009/BlurHashSharp/commits/v1.2.0) Signed-off-by: dependabot[bot] --- Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 5f9c4d6792..ee43c2159a 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -21,7 +21,7 @@ - + From f1c4b541c1d3ef960921a9480022467a940adae0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 13:09:28 +0000 Subject: [PATCH 263/282] Bump TMDbLib from 1.7.3-alpha to 1.8.1 Bumps [TMDbLib](https://github.com/LordMike/TMDbLib) from 1.7.3-alpha to 1.8.1. - [Release notes](https://github.com/LordMike/TMDbLib/releases) - [Commits](https://github.com/LordMike/TMDbLib/compare/v1.7.3-alpha...v1.8.1) Signed-off-by: dependabot[bot] --- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 152ea664a1..de7860b2e1 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -22,7 +22,7 @@ - + From 2b948aead910f12ce89da9099d6cefce2810cf8a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 13 Apr 2021 19:53:09 +0200 Subject: [PATCH 264/282] Fix possible ArgumentNullException ``` System.ArgumentNullException: Value cannot be null. (Parameter 'source') at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate) at MediaBrowser.Providers.Manager.ProviderManager.GetImages(BaseItem item, IRemoteImageProvider provider, IReadOnlyCollection`1 preferredLanguages, CancellationToken cancellationToken, Nullable`1 type) in /home/bond/dev/jellyfin/MediaBrowser.Providers/Manager/ProviderManager.cs:line 280 ``` --- .../Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index a96fc8ed63..326c116b3b 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -54,7 +54,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (string.IsNullOrEmpty(tmdbId)) { - return null; + return Enumerable.Empty(); } var language = item.GetPreferredMetadataLanguage(); From 27202ae8aa2bd92d69a92328e8e762acddb64add Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 17:11:34 +0000 Subject: [PATCH 265/282] Bump AutoFixture from 4.15.0 to 4.16.0 Bumps [AutoFixture](https://github.com/AutoFixture/AutoFixture) from 4.15.0 to 4.16.0. - [Release notes](https://github.com/AutoFixture/AutoFixture/releases) - [Commits](https://github.com/AutoFixture/AutoFixture/compare/v4.15.0...v4.16.0) Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index f288561b74..1306783c94 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index ee59dad5a8..38de05ad07 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 8d4d9e3d23..d7e56ff135 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 4a5cf1feee..51d6333b73 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,7 +10,7 @@ - + From d459f625d53d3469f2334c1978ed2779fb0a433a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 17:11:49 +0000 Subject: [PATCH 266/282] Bump Newtonsoft.Json from 12.0.3 to 13.0.1 Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 12.0.3 to 13.0.1. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/12.0.3...13.0.1) Signed-off-by: dependabot[bot] --- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index de7860b2e1..cdb07a15da 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -19,7 +19,7 @@ - + From 0b774eac12a882fbdc1ed402f36aaf6e4341e2c2 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 14 Apr 2021 10:26:05 +0100 Subject: [PATCH 267/282] Enables the ability to bind to loopback address. (#5773) --- Jellyfin.Networking/Manager/NetworkManager.cs | 62 ++++++++++++------- MediaBrowser.Common/Net/NetworkExtensions.cs | 2 +- .../NetworkParseTests.cs | 10 +-- 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 2f5a5b6e33..73e8b2cd72 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -647,6 +647,16 @@ namespace Jellyfin.Networking.Manager _interfaceAddresses.AddItem(address, false); _interfaceNames[parts[2]] = Math.Abs(index); } + + if (IsIP4Enabled) + { + _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback); + } + + if (IsIP6Enabled) + { + _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback); + } } InitialiseLAN(config); @@ -978,8 +988,8 @@ namespace Jellyfin.Networking.Manager } // Read and parse bind addresses and exclusions, removing ones that don't exist. - _bindAddresses = CreateIPCollection(lanAddresses).Union(_interfaceAddresses); - _bindExclusions = CreateIPCollection(lanAddresses, true).Union(_interfaceAddresses); + _bindAddresses = CreateIPCollection(lanAddresses).ThatAreContainedInNetworks(_interfaceAddresses); + _bindExclusions = CreateIPCollection(lanAddresses, true).ThatAreContainedInNetworks(_interfaceAddresses); _logger.LogInformation("Using bind addresses: {0}", _bindAddresses.AsString()); _logger.LogInformation("Using bind exclusions: {0}", _bindExclusions.AsString()); } @@ -1153,36 +1163,40 @@ namespace Jellyfin.Networking.Manager } #pragma warning restore CA1031 // Do not catch general exception types } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in InitialiseInterfaces."); + } - _logger.LogDebug("Discovered {0} interfaces.", _interfaceAddresses.Count); - _logger.LogDebug("Interfaces addresses : {0}", _interfaceAddresses.AsString()); + // If for some reason we don't have an interface info, resolve our DNS name. + if (_interfaceAddresses.Count == 0) + { + _logger.LogError("No interfaces information available. Resolving DNS name."); + IPHost host = new IPHost(Dns.GetHostName()); + foreach (var a in host.GetAddresses()) + { + _interfaceAddresses.AddItem(a); + } - // If for some reason we don't have an interface info, resolve our DNS name. if (_interfaceAddresses.Count == 0) { - _logger.LogError("No interfaces information available. Resolving DNS name."); - IPHost host = new IPHost(Dns.GetHostName()); - foreach (var a in host.GetAddresses()) - { - _interfaceAddresses.AddItem(a); - } - - if (_interfaceAddresses.Count == 0) - { - _logger.LogWarning("No interfaces information available. Using loopback."); - // Last ditch attempt - use loopback address. - _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback, false); - if (IsIP6Enabled) - { - _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback, false); - } - } + _logger.LogWarning("No interfaces information available. Using loopback."); } } - catch (NetworkInformationException ex) + + if (IsIP4Enabled) { - _logger.LogError(ex, "Error in InitialiseInterfaces."); + _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback); + } + + if (IsIP6Enabled) + { + _interfaceAddresses.AddItem(IPNetAddress.IP6Loopback); } + + _logger.LogDebug("Discovered {0} interfaces.", _interfaceAddresses.Count); + _logger.LogDebug("Interfaces addresses : {0}", _interfaceAddresses.AsString()); } } diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs index 93cfb4817b..264bfacb49 100644 --- a/MediaBrowser.Common/Net/NetworkExtensions.cs +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -232,7 +232,7 @@ namespace MediaBrowser.Common.Net /// The . /// Collection to compare with. /// A collection containing all the matches. - public static Collection Union(this Collection source, Collection target) + public static Collection ThatAreContainedInNetworks(this Collection source, Collection target) { if (source.Count == 0) { diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 2d33569980..9b0da2b3c4 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -34,10 +34,10 @@ namespace Jellyfin.Networking.Tests [InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.0/24", "[192.168.1.208/24,200.200.200.200/24]")] // eth16 only [InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")] - // All interfaces excluded. - [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")] + // All interfaces excluded. (including loopbacks) + [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[127.0.0.1/8,::1/128]")] // vEthernet1 and vEthernet212 should be excluded. - [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")] + [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24,127.0.0.1/8,::1/128]")] // Overlapping interface, [InlineData("192.168.1.110/24,-20,br0|192.168.1.10/24,-16,br0|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.110/24,192.168.1.10/24]")] public void IgnoreVirtualInterfaces(string interfaces, string lan, string value) @@ -241,7 +241,7 @@ namespace Jellyfin.Networking.Tests Collection nc1 = nm.CreateIPCollection(settings.Split(','), false); Collection nc2 = nm.CreateIPCollection(compare.Split(','), false); - Assert.Equal(nc1.Union(nc2).AsString(), result); + Assert.Equal(nc1.ThatAreContainedInNetworks(nc2).AsString(), result); } [Theory] @@ -350,7 +350,7 @@ namespace Jellyfin.Networking.Tests // Test included, IP6. Collection ncSource = nm.CreateIPCollection(source.Split(',')); Collection ncDest = nm.CreateIPCollection(dest.Split(',')); - Collection ncResult = ncSource.Union(ncDest); + Collection ncResult = ncSource.ThatAreContainedInNetworks(ncDest); Collection resultCollection = nm.CreateIPCollection(result.Split(',')); Assert.True(ncResult.Compare(resultCollection)); } From 55700e0d1cc83557f5d5da3d5250bb3702215a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Tue, 9 Mar 2021 21:13:27 +0100 Subject: [PATCH 268/282] Add workflows for rebase and project automation --- .github/dependabot.yml | 8 +++- .github/workflows/automation.yml | 66 +++++++++++++++++++++++++++ .github/workflows/merge-conflicts.yml | 17 +++++++ .github/workflows/rebase.yml | 27 +++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/automation.yml create mode 100644 .github/workflows/merge-conflicts.yml create mode 100644 .github/workflows/rebase.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0874cae2e3..70bcd49737 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,4 +6,10 @@ updates: interval: weekly time: '12:00' open-pull-requests-limit: 10 - + +- package-ecosystem: github-actions + directory: '/' + schedule: + interval: weekly + time: '12:00' + open-pull-requests-limit: 10 diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml new file mode 100644 index 0000000000..db34693cc7 --- /dev/null +++ b/.github/workflows/automation.yml @@ -0,0 +1,66 @@ +name: Automation + +on: + pull_request: + issues: + issue_comment: + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Does PR has the stable backport label? + uses: Dreamcodeio/does-pr-has-label@v1.2 + id: checkLabel + with: + label: stable backport + + - name: Remove from 'Current Release' project + uses: alex-page/github-project-automation-plus@v0.5.1 + if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel + continue-on-error: true + with: + project: Current Release + action: delete + repo-token: ${{ secrets.GH_TOKEN }} + + - name: Add to 'Release Next' project + uses: alex-page/github-project-automation-plus@v0.5.1 + if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened' + continue-on-error: true + with: + project: Release Next + column: In progress + repo-token: ${{ secrets.GH_TOKEN }} + + - name: Add to 'Current Release' project + uses: alex-page/github-project-automation-plus@v0.5.1 + if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel + continue-on-error: true + with: + project: Current Release + column: In progress + repo-token: ${{ secrets.GH_TOKEN }} + + - name: Check number of comments from the team member + if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' + id: member_comments + run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)" + + - name: Move issue to needs triage + uses: alex-page/github-project-automation-plus@v0.5.1 + if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1 + continue-on-error: true + with: + project: Issue Triage for Main Repo + column: Needs triage + repo-token: ${{ secrets.GH_TOKEN }} + + - name: Add issue to triage project + uses: alex-page/github-project-automation-plus@v0.5.1 + if: github.event.issue.pull_request == '' && github.event.action == 'opened' + continue-on-error: true + with: + project: Issue Triage for Main Repo + column: Pending response + repo-token: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/merge-conflicts.yml b/.github/workflows/merge-conflicts.yml new file mode 100644 index 0000000000..3740ce7a8a --- /dev/null +++ b/.github/workflows/merge-conflicts.yml @@ -0,0 +1,17 @@ +name: 'Merge Conflicts' + +on: + push: + branches: + - master + pull_request_target: + types: + - synchronize +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: eps1lon/actions-label-merge-conflict@v2.0.0 + with: + dirtyLabel: 'merge conflict' + repoToken: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 0000000000..3172ec0d9f --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,27 @@ +name: Automatic Rebase +on: + issue_comment: + +jobs: + rebase: + name: Rebase + if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '@jellyfin-bot rebase') && github.event.comment.author_association == 'MEMBER' + runs-on: ubuntu-latest + steps: + - name: Notify as seen + uses: peter-evans/create-or-update-comment@v1.4.5 + with: + token: ${{ secrets.GH_TOKEN }} + comment-id: ${{ github.event.comment.id }} + reactions: '+1' + + - name: Checkout the latest code + uses: actions/checkout@v2 + with: + token: ${{ secrets.GH_TOKEN }} + fetch-depth: 0 + + - name: Automatic Rebase + uses: cirrus-actions/rebase@1.4 + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} From 1d4c8dcde3669b21cec2324ed535cfdd8e5737a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Apr 2021 12:51:54 +0000 Subject: [PATCH 269/282] Bump eps1lon/actions-label-merge-conflict from v2.0.0 to v2.0.1 Bumps [eps1lon/actions-label-merge-conflict](https://github.com/eps1lon/actions-label-merge-conflict) from v2.0.0 to v2.0.1. - [Release notes](https://github.com/eps1lon/actions-label-merge-conflict/releases) - [Changelog](https://github.com/eps1lon/actions-label-merge-conflict/blob/main/CHANGELOG.md) - [Commits](https://github.com/eps1lon/actions-label-merge-conflict/compare/v2.0.0...b8bf8341285ec9a4567d4318ba474fee998a6919) Signed-off-by: dependabot[bot] --- .github/workflows/merge-conflicts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-conflicts.yml b/.github/workflows/merge-conflicts.yml index 3740ce7a8a..ce808617a1 100644 --- a/.github/workflows/merge-conflicts.yml +++ b/.github/workflows/merge-conflicts.yml @@ -11,7 +11,7 @@ jobs: triage: runs-on: ubuntu-latest steps: - - uses: eps1lon/actions-label-merge-conflict@v2.0.0 + - uses: eps1lon/actions-label-merge-conflict@v2.0.1 with: dirtyLabel: 'merge conflict' repoToken: ${{ secrets.GH_TOKEN }} From b63f615fd4f92f40394d04c9ae764b35aadc000d Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Thu, 15 Apr 2021 07:39:59 -0600 Subject: [PATCH 270/282] Enable nullability for ServerDiscoveryInfo (#5804) --- Emby.Server.Implementations/Udp/UdpServer.cs | 7 +--- .../ApiClient/ServerDiscoveryInfo.cs | 41 ++++++++++++------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index d01184e0b6..db5265e79d 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -53,12 +53,7 @@ namespace Emby.Server.Implementations.Udp if (!string.IsNullOrEmpty(localUrl)) { - var response = new ServerDiscoveryInfo - { - Address = localUrl, - Id = _appHost.SystemId, - Name = _appHost.FriendlyName - }; + var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName); try { diff --git a/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs b/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs index fcc90a1f7a..f9f4745864 100644 --- a/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs +++ b/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs @@ -1,32 +1,43 @@ -#nullable disable -#pragma warning disable CS1591 - namespace MediaBrowser.Model.ApiClient { + /// + /// The server discovery info model. + /// public class ServerDiscoveryInfo { /// - /// Gets or sets the address. + /// Initializes a new instance of the class. + /// + /// The server address. + /// The server id. + /// The server name. + /// The endpoint address. + public ServerDiscoveryInfo(string address, string id, string name, string? endpointAddress = null) + { + Address = address; + Id = id; + Name = name; + EndpointAddress = endpointAddress; + } + + /// + /// Gets the address. /// - /// The address. - public string Address { get; set; } + public string Address { get; } /// - /// Gets or sets the server identifier. + /// Gets the server identifier. /// - /// The server identifier. - public string Id { get; set; } + public string Id { get; } /// - /// Gets or sets the name. + /// Gets the name. /// - /// The name. - public string Name { get; set; } + public string Name { get; } /// - /// Gets or sets the endpoint address. + /// Gets the endpoint address. /// - /// The endpoint address. - public string EndpointAddress { get; set; } + public string? EndpointAddress { get; } } } From bc1cc2d04ae0e823becf59964e5bdc5a74ae7741 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 17 Apr 2021 11:37:55 +0100 Subject: [PATCH 271/282] Remove unused using directives --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 2 -- Emby.Dlna/Main/DlnaEntryPoint.cs | 1 - .../MediaReceiverRegistrarXmlBuilder.cs | 1 - Emby.Dlna/PlayTo/SsdpHttpClient.cs | 1 - .../AppBase/ConfigurationHelper.cs | 1 - Emby.Server.Implementations/Channels/ChannelManager.cs | 1 - .../Collections/CollectionManager.cs | 3 --- Emby.Server.Implementations/ConfigurationOptions.cs | 1 - .../HttpServer/Security/AuthService.cs | 1 - Emby.Server.Implementations/IStartupOptions.cs | 1 - Emby.Server.Implementations/Images/ArtistImageProvider.cs | 8 -------- .../Images/DynamicImageProvider.cs | 1 - Emby.Server.Implementations/Library/PathExtensions.cs | 2 -- Emby.Server.Implementations/Library/SearchEngine.cs | 1 - Emby.Server.Implementations/Library/UserDataManager.cs | 2 +- Emby.Server.Implementations/Library/UserViewManager.cs | 1 - Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 1 - .../LiveTv/EmbyTV/ItemDataProvider.cs | 2 -- .../LiveTv/EmbyTV/SeriesTimerManager.cs | 1 - .../LiveTv/Listings/XmlTvListingsProvider.cs | 1 - .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 3 --- .../LiveTv/TunerHosts/M3uParser.cs | 1 - .../Localization/LocalizationManager.cs | 1 - .../MediaEncoder/EncodingManager.cs | 1 - .../QuickConnect/QuickConnectManager.cs | 1 - .../ScheduledTasks/ScheduledTaskWorker.cs | 1 - .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 2 +- .../ScheduledTasks/Tasks/PeopleValidationTask.cs | 2 +- .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 1 - .../ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs | 2 +- Jellyfin.Api/Controllers/PluginsController.cs | 1 - Jellyfin.Api/Extensions/DtoExtensions.cs | 1 - Jellyfin.Api/Helpers/AudioHelper.cs | 3 +-- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 1 - Jellyfin.Data/Entities/HomeSection.cs | 3 +-- Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs | 1 - Jellyfin.Networking/Configuration/NetworkConfiguration.cs | 1 - .../Configuration/NetworkConfigurationExtensions.cs | 1 - Jellyfin.Server/Filters/ParameterObsoleteFilter.cs | 1 - Jellyfin.Server/Formatters/CssOutputFormatter.cs | 3 +-- .../Middleware/IpBasedAccessValidationMiddleware.cs | 2 -- Jellyfin.Server/Middleware/LanFilteringMiddleware.cs | 3 --- .../Migrations/Routines/DisableTranscodingThrottling.cs | 1 - Jellyfin.Server/Program.cs | 2 -- Jellyfin.Server/StartupOptions.cs | 3 --- MediaBrowser.Common/Cryptography/PasswordHash.cs | 1 - MediaBrowser.Common/Net/INetworkManager.cs | 1 - .../BaseItemManager/BaseItemManager.cs | 1 - .../BaseItemManager/IBaseItemManager.cs | 1 - MediaBrowser.Controller/Drawing/ImageHelper.cs | 3 --- MediaBrowser.Controller/Entities/Folder.cs | 1 - .../Events/Updates/PluginUninstalledEventArgs.cs | 1 - MediaBrowser.Controller/IServerApplicationHost.cs | 3 --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 -- MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs | 1 - MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs | 1 - MediaBrowser.Controller/MediaEncoding/JobLogger.cs | 1 - .../MediaEncoding/MediaEncoderHelpers.cs | 5 ----- MediaBrowser.Controller/Playlists/Playlist.cs | 1 - MediaBrowser.Controller/Providers/IRemoteImageProvider.cs | 1 - .../Providers/IRemoteSearchProvider.cs | 1 - MediaBrowser.Controller/Subtitles/ISubtitleManager.cs | 1 - MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 1 - MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs | 1 - MediaBrowser.Model/Dto/NameIdPair.cs | 2 -- MediaBrowser.Model/LiveTv/TunerHostInfo.cs | 3 --- MediaBrowser.Model/Notifications/NotificationOptions.cs | 2 -- MediaBrowser.Providers/Manager/ProviderManager.cs | 1 - .../Plugins/AudioDb/AlbumImageProvider.cs | 1 - MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs | 1 - .../Plugins/AudioDb/ArtistImageProvider.cs | 1 - MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs | 1 - MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs | 2 -- .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 4 ++-- .../Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs | 1 - .../Json/JsonNullableGuidConverterTests.cs | 1 - .../Jellyfin.Model.Tests/Extensions/StringHelperTests.cs | 1 - .../AudioBook/AudioBookListResolverTests.cs | 1 - .../AudioBook/AudioBookResolverTests.cs | 1 - .../Subtitles/SubtitleParserTests.cs | 1 - tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs | 1 - .../IO/ManagedFileSystemTests.cs | 1 - .../Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs | 2 -- tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs | 1 - tests/Jellyfin.Server.Tests/ParseNetworkTests.cs | 1 - .../Parsers/MusicArtistNfoParserTests.cs | 1 - 86 files changed, 9 insertions(+), 125 deletions(-) diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 713f950990..90ba601b4a 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -7,7 +6,6 @@ using System.Linq; using System.Text; using System.Threading; using System.Xml; -using Emby.Dlna.Configuration; using Emby.Dlna.Didl; using Emby.Dlna.Service; using Jellyfin.Data.Entities; diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index d3e9a41ec4..bdfe430cf3 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.Linq; using System.Net.Http; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; using Emby.Dlna.Ssdp; diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs index 37840cd096..f3789a791c 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Emby.Dlna.Common; using Emby.Dlna.Service; -using MediaBrowser.Model.Dlna; namespace Emby.Dlna.MediaReceiverRegistrar { diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index e750f5bbc3..d9f1ce4907 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -2,7 +2,6 @@ using System; using System.Globalization; -using System.IO; using System.Net.Http; using System.Net.Mime; using System.Text; diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 3f70763830..29bac66340 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -3,7 +3,6 @@ using System; using System.IO; using System.Linq; -using MediaBrowser.Common.Extensions; using MediaBrowser.Model.Serialization; namespace Emby.Server.Implementations.AppBase diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 87ebe960ab..7324b0ee9f 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index db532ce5b4..e984afdbab 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -8,11 +7,9 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Collections; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index cd9dbb1bda..01dc728c1c 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Emby.Server.Implementations.HttpServer; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 4a0fc8239e..9afabf5272 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 -using System; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 0b823ff063..f719dc5f89 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 #nullable enable -using System; namespace Emby.Server.Implementations { diff --git a/Emby.Server.Implementations/Images/ArtistImageProvider.cs b/Emby.Server.Implementations/Images/ArtistImageProvider.cs index afa4ec7b1b..e96b64595c 100644 --- a/Emby.Server.Implementations/Images/ArtistImageProvider.cs +++ b/Emby.Server.Implementations/Images/ArtistImageProvider.cs @@ -2,20 +2,12 @@ using System; using System.Collections.Generic; -using System.Linq; -using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Images { diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 462eb03a80..50c5314820 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 6eaecff0f5..770cf6bb02 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -2,8 +2,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text.RegularExpressions; using MediaBrowser.Common.Providers; namespace Emby.Server.Implementations.Library diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 94602582b7..bcdf854ca3 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -12,7 +12,6 @@ using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; -using Microsoft.Extensions.Logging; using Genre = MediaBrowser.Controller.Entities.Genre; using Person = MediaBrowser.Controller.Entities.Person; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index d16275b192..e8caea1964 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -13,8 +13,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using Book = MediaBrowser.Controller.Entities.Book; using AudioBook = MediaBrowser.Controller.Entities.AudioBook; +using Book = MediaBrowser.Controller.Entities.Book; namespace Emby.Server.Implementations.Library { diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index b6b7ea9495..ac041bcf6c 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 91a21db60a..c9d9cc49af 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -17,7 +17,6 @@ using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index c20b080883..1cac9cb963 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; using MediaBrowser.Common.Json; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs index da707fec63..b1259de232 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs @@ -2,7 +2,6 @@ using System; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 76c8757370..6824aa4423 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Threading; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 68173a0ef7..1dcc786874 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -8,10 +8,8 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; @@ -19,7 +17,6 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 2af635492e..cc30a516d6 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 98de848bca..2fdc2b4d91 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -11,7 +11,6 @@ using MediaBrowser.Common.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Localization diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index a9dab91383..031b5d2e72 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -15,7 +15,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.MediaEncoder diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 7bed06de36..22739a0081 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -3,7 +3,6 @@ using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Security.Cryptography; -using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 3cc2cefb99..9c0e927058 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 649305fd56..2312c85d97 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -12,9 +12,9 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index c384cf4bbe..57d294a408 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index a69380cbb7..11a5fb79f4 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Updates; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index e470adcf48..51b620404b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -6,8 +6,8 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks { diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 24285bfb98..adec86a102 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -12,7 +12,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Json; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs index e0c744325f..06173315aa 100644 --- a/Jellyfin.Api/Extensions/DtoExtensions.cs +++ b/Jellyfin.Api/Extensions/DtoExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index 21ec2d32f9..9c35d1ec10 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -1,5 +1,4 @@ -using System; -using System.Net.Http; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 751b48682d..1bb504ad12 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; -using System.Net.Mime; using System.Security.Claims; using System.Text; using System.Threading; diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs index 5adc52491c..d03d0f7a86 100644 --- a/Jellyfin.Data/Entities/HomeSection.cs +++ b/Jellyfin.Data/Entities/HomeSection.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities diff --git a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs index 730deccaed..cc04d033aa 100644 --- a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Interfaces; namespace Jellyfin.Data.Entities.Libraries diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs index 91bf0015fd..faf814c060 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs @@ -1,7 +1,6 @@ #pragma warning disable CA1819 // Properties should not return arrays using System; -using MediaBrowser.Model.Configuration; namespace Jellyfin.Networking.Configuration { diff --git a/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs b/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs index e77b17ba92..8cbe398b07 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfigurationExtensions.cs @@ -1,4 +1,3 @@ -using Jellyfin.Networking.Configuration; using MediaBrowser.Common.Configuration; namespace Jellyfin.Networking.Configuration diff --git a/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs b/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs index e54044d0e9..b9ce221f5c 100644 --- a/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs +++ b/Jellyfin.Server/Filters/ParameterObsoleteFilter.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using Jellyfin.Api.Attributes; -using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; diff --git a/Jellyfin.Server/Formatters/CssOutputFormatter.cs b/Jellyfin.Server/Formatters/CssOutputFormatter.cs index e8dd48e4e6..cfc9d1ad3b 100644 --- a/Jellyfin.Server/Formatters/CssOutputFormatter.cs +++ b/Jellyfin.Server/Formatters/CssOutputFormatter.cs @@ -1,5 +1,4 @@ -using System; -using System.Text; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs index 7d92bd7d31..0afcd61a05 100644 --- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs +++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs @@ -1,9 +1,7 @@ using System.Net; using System.Threading.Tasks; -using Jellyfin.Networking.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Http; namespace Jellyfin.Server.Middleware diff --git a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs index 8065054a1e..67bf24d2a5 100644 --- a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs +++ b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs @@ -1,9 +1,6 @@ -using System; -using System.Linq; using System.Net; using System.Threading.Tasks; using Jellyfin.Networking.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Http; diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index bf0225e988..378e88e25b 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.Routines diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 464e024190..c10b2ddb3a 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -12,12 +12,10 @@ using System.Threading.Tasks; using CommandLine; using Emby.Server.Implementations; using Emby.Server.Implementations.IO; -using Jellyfin.Api.Controllers; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Extensions; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 6d8210527c..a1cecc8c63 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,10 +1,7 @@ -using System; using System.Collections.Generic; using CommandLine; using Emby.Server.Implementations; -using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.Udp; -using Emby.Server.Implementations.Updates; using MediaBrowser.Controller.Extensions; namespace Jellyfin.Server diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index f2ecc4741b..ec21d05803 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Text; namespace MediaBrowser.Common.Cryptography diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 012824f652..185df5b773 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Net; using System.Net.NetworkInformation; -using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Common.Net diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 31dd954028..a233c358e4 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Threading; -using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs index e1f5d05a60..8a87364270 100644 --- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -1,4 +1,3 @@ -using System; using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index 181f8e905f..596fcbc8cf 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -1,10 +1,7 @@ #pragma warning disable CS1591 #nullable enable -using System; -using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Drawing { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index cac5026f70..bdca5c0ee7 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Text.Json.Serialization; diff --git a/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs index a111e6d829..0f27be9bb7 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginUninstalledEventArgs.cs @@ -1,5 +1,4 @@ using Jellyfin.Data.Events; -using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 20bfa697e5..6a65a8e47a 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -3,10 +3,7 @@ using System; using System.Collections.Generic; using System.Net; -using System.Threading; -using System.Threading.Tasks; using MediaBrowser.Common; -using MediaBrowser.Common.Plugins; using MediaBrowser.Model.System; using Microsoft.AspNetCore.Http; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 92b9a8c7eb..1379efacbd 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -10,8 +10,6 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using Jellyfin.Data.Enums; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index dacd6dea6c..d47a689f47 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -9,7 +9,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using MediaBrowser.Model.Session; diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 5cbb579902..05dd1a69b1 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index cc8820f393..227c5f2587 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -3,7 +3,6 @@ using System; using System.Globalization; using System.IO; -using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 281d503721..89e01c08b6 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -1,10 +1,5 @@ #pragma warning disable CS1591 -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.MediaEncoding { diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 977b14cb08..a5b7363fb6 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Playlists diff --git a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs index ee8f5b860a..de1631dcf4 100644 --- a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs +++ b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; diff --git a/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs b/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs index 9592baa7c1..e401ed211c 100644 --- a/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs +++ b/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs @@ -3,7 +3,6 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Net; namespace MediaBrowser.Controller.Providers { diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index feb26bc101..6d63286ef5 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 205933ae20..36bf77c841 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -16,7 +16,6 @@ using MediaBrowser.Common.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Probing; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs index 8a7c032c54..7b7744163c 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -using MediaBrowser.Common.Json.Converters; namespace MediaBrowser.MediaEncoding.Probing { diff --git a/MediaBrowser.Model/Dto/NameIdPair.cs b/MediaBrowser.Model/Dto/NameIdPair.cs index 7f18b45028..31516947f0 100644 --- a/MediaBrowser.Model/Dto/NameIdPair.cs +++ b/MediaBrowser.Model/Dto/NameIdPair.cs @@ -1,8 +1,6 @@ #nullable disable #pragma warning disable CS1591 -using System; - namespace MediaBrowser.Model.Dto { public class NameIdPair diff --git a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs index 7d4bbb2d07..05576a0f8d 100644 --- a/MediaBrowser.Model/LiveTv/TunerHostInfo.cs +++ b/MediaBrowser.Model/LiveTv/TunerHostInfo.cs @@ -1,9 +1,6 @@ #nullable disable #pragma warning disable CS1591 -using System; -using MediaBrowser.Model.Dto; - namespace MediaBrowser.Model.LiveTv { public class TunerHostInfo diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index 94bb5d6e35..12e093b219 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -5,8 +5,6 @@ using System; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Users; namespace MediaBrowser.Model.Notifications { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index b4b0b826f4..3bb2c6f0b0 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -25,7 +25,6 @@ using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; using Priority_Queue; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs index 2adb119087..85a28747f5 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Plugins.AudioDb { diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index 00feeec1f9..25bb3f9ce3 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -19,7 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Music; namespace MediaBrowser.Providers.Plugins.AudioDb diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs index b8095ff04c..db8536cc92 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Plugins.AudioDb { diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index 59ecbc0171..cbb61fa353 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -18,7 +18,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Music; namespace MediaBrowser.Providers.Plugins.AudioDb diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index d35805a84b..46d3038905 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -6,9 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; -using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 833d1ae389..d22c1b50aa 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -7,8 +7,6 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using TMDbLib.Objects.Find; -using TMDbLib.Objects.Search; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -16,6 +14,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; +using TMDbLib.Objects.Find; +using TMDbLib.Objects.Search; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { diff --git a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs index 1e1cde9572..dbfad3c2ff 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Text.Json; using MediaBrowser.Common.Json.Converters; using Xunit; diff --git a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs index 22bc7afb96..cb3b66c4c5 100644 --- a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs +++ b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Text.Json; using MediaBrowser.Common.Json.Converters; using Xunit; diff --git a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs index 51633e157c..5864a05094 100644 --- a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs +++ b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs @@ -1,4 +1,3 @@ -using System; using MediaBrowser.Model.Extensions; using Xunit; diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs index e5768b6209..d9e77dd2e0 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using Emby.Naming.AudioBook; using Emby.Naming.Common; diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs index ad63adadc3..53b35c2d6c 100644 --- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Emby.Naming.AudioBook; using Emby.Naming.Common; diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs index f3abacb4f9..2446660f32 100644 --- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs +++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs @@ -1,4 +1,3 @@ -using System; using Emby.Naming.Common; using Emby.Naming.Subtitles; using Xunit; diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index d34f65409f..2f173b0ce0 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -1,4 +1,3 @@ -using System; using Emby.Naming.Common; using Emby.Naming.Video; using MediaBrowser.Model.Entities; diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs index 5a535ac51f..614a689759 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs index 3cbd638f90..0ade345a1a 100644 --- a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs @@ -1,8 +1,6 @@ using System.IO; using System.Reflection; -using System.Text.Json; using System.Threading.Tasks; -using MediaBrowser.Model.Branding; using Xunit; using Xunit.Abstractions; diff --git a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs index 4e5d0fcb63..0a463cfa39 100644 --- a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs +++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Reflection; using Emby.Server.Implementations; -using Jellyfin.Server; using MediaBrowser.Controller; using MediaBrowser.Model.IO; using Microsoft.Extensions.Configuration; diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs index 0b714e80a2..146b16cf94 100644 --- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs +++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using System.Text; using Jellyfin.Networking.Configuration; diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs index 3d8e13e966..8ca3dd96e3 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.Audio; From e841922ffd9211cfeee5cca1bda4c139cbda9379 Mon Sep 17 00:00:00 2001 From: Stephen Moore Date: Sat, 17 Apr 2021 17:21:40 +0100 Subject: [PATCH 272/282] Fix ArgumentOutOfRangeException scanning AudioBooks AudioResolver.ResolveMultipleAudio method can attempt to access the first item in a List without checking if the list is empty which throws an ArgumentOutOfRangeException and stops the 'Scan Library' process. --- .../Library/Resolvers/Audio/AudioResolver.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 90b6a8a7db..4ad84579d8 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -201,6 +201,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio continue; } + if (resolvedItem.Files.Count == 0) + { + continue; + } + var firstMedia = resolvedItem.Files[0]; var libraryItem = new T From 351b987982559b518de7654e85bb076b32ae0d10 Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 18 Apr 2021 12:34:33 +0200 Subject: [PATCH 273/282] Add Person to TypedBaseItems if it's new --- .../Manager/MetadataService.cs | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 437b43ecaf..f12586665f 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -223,13 +223,13 @@ namespace MediaBrowser.Providers.Manager var baseItem = result.Item; LibraryManager.UpdatePeople(baseItem, result.People); - await SavePeopleMetadataAsync(result.People, libraryOptions, cancellationToken).ConfigureAwait(false); + await SavePeopleMetadataAsync(result.People, cancellationToken).ConfigureAwait(false); } await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false); } - private async Task SavePeopleMetadataAsync(List people, LibraryOptions libraryOptions, CancellationToken cancellationToken) + private async Task SavePeopleMetadataAsync(IEnumerable people, CancellationToken cancellationToken) { var personsToSave = new List(); @@ -237,39 +237,44 @@ namespace MediaBrowser.Providers.Manager { cancellationToken.ThrowIfCancellationRequested(); - if (person.ProviderIds.Count > 0 || !string.IsNullOrWhiteSpace(person.ImageUrl)) + var itemUpdateType = ItemUpdateType.MetadataDownload; + var saveEntity = false; + var personEntity = LibraryManager.GetPerson(person.Name); + + // if PresentationUniqueKey is empty it's likely a new item. + if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey)) { - var itemUpdateType = ItemUpdateType.MetadataDownload; - var saveEntity = false; - var personEntity = LibraryManager.GetPerson(person.Name); - foreach (var id in person.ProviderIds) - { - if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase)) - { - personEntity.SetProviderId(id.Key, id.Value); - saveEntity = true; - } - } + personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey(); + saveEntity = true; + } - if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) + foreach (var id in person.ProviderIds) + { + if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase)) { - personEntity.SetImage( - new ItemImageInfo - { - Path = person.ImageUrl, - Type = ImageType.Primary - }, - 0); - + personEntity.SetProviderId(id.Key, id.Value); saveEntity = true; - itemUpdateType = ItemUpdateType.ImageUpdate; } + } - if (saveEntity) - { - personsToSave.Add(personEntity); - await LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); - } + if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) + { + personEntity.SetImage( + new ItemImageInfo + { + Path = person.ImageUrl, + Type = ImageType.Primary + }, + 0); + + saveEntity = true; + itemUpdateType = ItemUpdateType.ImageUpdate; + } + + if (saveEntity) + { + personsToSave.Add(personEntity); + await LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); } } From 8933389753cc95413576b2fcfdf2ccb160464ad3 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 19 Apr 2021 10:49:52 +0200 Subject: [PATCH 274/282] Respect user settings for transcode and remux --- .../Library/MediaSourceManager.cs | 12 +++++++++--- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index b2943020c5..d0b85f07d4 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -199,10 +199,15 @@ namespace Emby.Server.Implementations.Library { source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); } + else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding); + source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing); + } } } - return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList(); + return SortMediaSources(list); } public MediaProtocol GetPathProtocol(string path) @@ -436,7 +441,7 @@ namespace Emby.Server.Implementations.Library } } - private static IEnumerable SortMediaSources(IEnumerable sources) + private static List SortMediaSources(IEnumerable sources) { return sources.OrderBy(i => { @@ -451,8 +456,9 @@ namespace Emby.Server.Implementations.Library { var stream = i.VideoStream; - return stream == null || stream.Width == null ? 0 : stream.Width.Value; + return stream?.Width ?? 0; }) + .Where(i => i.Type != MediaSourceType.Placeholder) .ToList(); } diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index f07271821d..295cfaf089 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -309,7 +309,7 @@ namespace Jellyfin.Api.Helpers { if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) - && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) + && user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) { options.ForceDirectStream = true; } From a9ca3c8b0165d949f63cf3a16a99b62b72a6606d Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 19 Apr 2021 11:38:27 +0200 Subject: [PATCH 275/282] Fix notification disabled users list --- MediaBrowser.Model/Notifications/NotificationOptions.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index 12e093b219..09beb2ef72 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -93,16 +93,17 @@ namespace MediaBrowser.Model.Notifications { NotificationOption opt = GetOptions(notificationType); - return opt == null || - !opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase); + return opt == null + || !opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase); } public bool IsEnabledToMonitorUser(string type, Guid userId) { NotificationOption opt = GetOptions(type); - return opt != null && opt.Enabled && - !opt.DisabledMonitorUsers.Contains(userId.ToString(string.Empty), StringComparer.OrdinalIgnoreCase); + return opt != null + && opt.Enabled + && !opt.DisabledMonitorUsers.Contains(userId.ToString("N"), StringComparer.OrdinalIgnoreCase); } public bool IsEnabledToSendToUser(string type, string userId, User user) From 90b941b3f6646d34f831a2921bc5919bfe9871c0 Mon Sep 17 00:00:00 2001 From: Mohamed Akram Date: Mon, 19 Apr 2021 14:59:24 +0400 Subject: [PATCH 276/282] Add review changes --- .../Plugins/Tmdb/TmdbClientManager.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 3aa673274f..69f9649375 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -145,16 +145,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// A comma-separated list of image languages. /// The cancellation token. /// The TMDb tv show episode group information or null if not found. - public async Task GetSeriesGroupAsync(int tvShowId, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken) + private async Task GetSeriesGroupAsync(int tvShowId, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken) { - var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; - if (_memoryCache.TryGetValue(key, out TvGroupCollection group)) - { - return group; - } - - await EnsureClientConfigAsync().ConfigureAwait(false); - TvGroupType? groupType = displayOrder == "absolute" ? TvGroupType.Absolute : displayOrder == "dvd" ? TvGroupType.DVD : @@ -165,8 +157,16 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return null; } - var series = await GetSeriesAsync(tvShowId, language, imageLanguages, cancellationToken); - var episodeGroupId = series.EpisodeGroups.Results.Find(g => g.Type == groupType)?.Id; + var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; + if (_memoryCache.TryGetValue(key, out TvGroupCollection group)) + { + return group; + } + + await EnsureClientConfigAsync().ConfigureAwait(false); + + var series = await GetSeriesAsync(tvShowId, language, imageLanguages, cancellationToken).ConfigureAwait(false); + var episodeGroupId = series?.EpisodeGroups.Results.Find(g => g.Type == groupType)?.Id; if (episodeGroupId == null) { @@ -246,6 +246,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb if (group != null) { var season = group.Groups.Find(s => s.Order == seasonNumber); + // Episode order starts at 0 var ep = season?.Episodes.Find(e => e.Order == episodeNumber - 1); if (ep != null) { From 586e1fc58a52d343714958af713d102300ccfde0 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 19 Apr 2021 13:51:55 +0200 Subject: [PATCH 277/282] use IF NOT EXISTS in migration --- Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs b/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs index 2521d99527..6343c422d5 100644 --- a/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs +++ b/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs @@ -41,9 +41,9 @@ namespace Jellyfin.Server.Migrations.Routines var databasePath = Path.Join(_serverApplicationPaths.DataPath, DbFilename); using var connection = SQLite3.Open(databasePath, ConnectionFlags.ReadWrite, null); _logger.LogInformation("Creating index idx_TypedBaseItemsUserDataKeyType"); - connection.Execute("CREATE INDEX idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type);"); + connection.Execute("CREATE INDEX IF NOT EXISTS idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type);"); _logger.LogInformation("Creating index idx_PeopleNameListOrder"); - connection.Execute("CREATE INDEX idx_PeopleNameListOrder ON People(Name, ListOrder);"); + connection.Execute("CREATE INDEX IF NOT EXISTS idx_PeopleNameListOrder ON People(Name, ListOrder);"); } } -} \ No newline at end of file +} From 442bc8671bfd870641c67c54d1e365cc371bc3a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:08:51 +0000 Subject: [PATCH 278/282] Bump AutoFixture.AutoMoq from 4.15.0 to 4.16.0 Bumps [AutoFixture.AutoMoq](https://github.com/AutoFixture/AutoFixture) from 4.15.0 to 4.16.0. - [Release notes](https://github.com/AutoFixture/AutoFixture/releases) - [Commits](https://github.com/AutoFixture/AutoFixture/compare/v4.15.0...v4.16.0) Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 1306783c94..00c40466ea 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 38de05ad07..486899f4f0 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -23,7 +23,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index d7e56ff135..437175f95d 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 51d6333b73..e8fa55d9bc 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -11,7 +11,7 @@ - + From 64071873a0a68dd12a4a4995c7d9e4713763aab3 Mon Sep 17 00:00:00 2001 From: Mohamed Akram Date: Mon, 19 Apr 2021 16:51:44 +0400 Subject: [PATCH 279/282] Use StringComparison.Ordinal --- MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 69f9649375..05e5d3ced7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -148,8 +148,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb private async Task GetSeriesGroupAsync(int tvShowId, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken) { TvGroupType? groupType = - displayOrder == "absolute" ? TvGroupType.Absolute : - displayOrder == "dvd" ? TvGroupType.DVD : + string.Equals(displayOrder, "absolute", StringComparison.Ordinal) ? TvGroupType.Absolute : + string.Equals(displayOrder, "dvd", StringComparison.Ordinal) ? TvGroupType.DVD : null; if (groupType == null) From dc628d1e1c18f169e5c3fc9da3e257967149ca0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Apr 2021 13:07:31 +0000 Subject: [PATCH 280/282] Bump AutoFixture.Xunit2 from 4.15.0 to 4.16.0 Bumps [AutoFixture.Xunit2](https://github.com/AutoFixture/AutoFixture) from 4.15.0 to 4.16.0. - [Release notes](https://github.com/AutoFixture/AutoFixture/releases) - [Commits](https://github.com/AutoFixture/AutoFixture/compare/v4.15.0...v4.16.0) Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 00c40466ea..050d4c040c 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 437175f95d..0de92249a9 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index e8fa55d9bc..9e60dbcd9f 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -12,7 +12,7 @@ - + From cbb855e65fd56d44b6e5714dd6843bb1d9dee1b8 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 20 Apr 2021 13:24:15 +0200 Subject: [PATCH 281/282] Check for empty string when migrating displaypreferences --- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 07829c6969..5dc8c90d44 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -126,13 +126,13 @@ namespace Jellyfin.Server.Migrations.Routines ShowSidebar = dto.ShowSidebar, ScrollDirection = dto.ScrollDirection, ChromecastVersion = chromecastVersion, - SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) + SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) && !string.IsNullOrEmpty(length) ? int.Parse(length, CultureInfo.InvariantCulture) : 30000, - SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) + SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && !string.IsNullOrEmpty(length) ? int.Parse(length, CultureInfo.InvariantCulture) : 10000, - EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) + EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) && !string.IsNullOrEmpty(enabled) ? bool.Parse(enabled) : true, DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty, From 4d7c1fbdca44eb6e892b104b765b86e146ac9765 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 20 Apr 2021 13:43:05 +0200 Subject: [PATCH 282/282] use int.Parse --- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 5dc8c90d44..e25d291226 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -126,11 +126,11 @@ namespace Jellyfin.Server.Migrations.Routines ShowSidebar = dto.ShowSidebar, ScrollDirection = dto.ScrollDirection, ChromecastVersion = chromecastVersion, - SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) && !string.IsNullOrEmpty(length) - ? int.Parse(length, CultureInfo.InvariantCulture) + SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) && int.TryParse(length, out var skipForwardLength) + ? skipForwardLength : 30000, - SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && !string.IsNullOrEmpty(length) - ? int.Parse(length, CultureInfo.InvariantCulture) + SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && !string.IsNullOrEmpty(length) && int.TryParse(length, out var skipBackwardLength) + ? skipBackwardLength : 10000, EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) && !string.IsNullOrEmpty(enabled) ? bool.Parse(enabled)