From 3a9fcb6abd249759bd44b2fbcf437beee8f611c2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 12 Feb 2021 17:34:51 +0100 Subject: Rewrite packet writing code for HdHomerun --- tests/Jellyfin.Common.Tests/Crc32Tests.cs | 23 +++++++++++++ .../LiveTv/HdHomerunManagerTests.cs | 39 ++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/Jellyfin.Common.Tests/Crc32Tests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs (limited to 'tests') 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))); + } + } +} -- cgit v1.2.3 From e1bc322b709756529759fbb74cb003e133f30ab6 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 12 Feb 2021 18:35:54 +0100 Subject: Add test for WriteGetMessage --- .../TunerHosts/HdHomerun/HdHomerunManager.cs | 51 ++++++++++++---------- .../LiveTv/HdHomerunManagerTests.cs | 19 ++++++++ 2 files changed, 48 insertions(+), 22 deletions(-) (limited to 'tests') 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))); + } } } -- cgit v1.2.3 From dc9e2ad1a4a21677cb22d3a2c76dee40859462bb Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 13 Feb 2021 11:46:38 +0100 Subject: Add some more Crc32 tests --- tests/Jellyfin.Common.Tests/Crc32Tests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'tests') 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))); + } } } -- cgit v1.2.3 From aff0aea60fc52a2253f04749f11bcb02f6e1f67c Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 23 Feb 2021 14:14:02 +0100 Subject: Improve branch coverage --- Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs | 1 - .../JsonOmdbNotAvailableInt32Converter.cs | 2 +- tests/Jellyfin.Api.Tests/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 (limited to 'tests') 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] -- cgit v1.2.3 From acac21d8dc3eb9383136ff692606dc2f65adf405 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 23 Feb 2021 16:45:10 +0100 Subject: Improve tests --- .../EntryPoints/UdpServerEntryPoint.cs | 11 + Emby.Server.Implementations/Udp/UdpServer.cs | 2 +- .../Converters/JsonCommaDelimitedArrayConverter.cs | 2 +- .../Converters/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 (limited to 'tests') 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); + } + } +} -- cgit v1.2.3 From 032d72a8a7a1c3884d07b0f3b7bd93790988d414 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 23 Feb 2021 17:30:24 +0100 Subject: 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(-) (limited to 'tests') 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; } -- cgit v1.2.3 From f666b7e10268a5c19228d8fdb1bd313a642b0915 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 27 Feb 2021 20:12:55 +0000 Subject: fix --- Emby.Server.Implementations/ApplicationHost.cs | 22 ++++++++++++++++------ Emby.Server.Implementations/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(-) (limited to 'tests') 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); -- cgit v1.2.3 From 8836242559710cbe4577451c65f91133c3da4a79 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 28 Feb 2021 10:25:14 +0000 Subject: fixed tests --- tests/Jellyfin.Api.Tests/TestAppHost.cs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'tests') 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) { -- cgit v1.2.3 From 16694b0cfcc51ba009a0aedce0d383ab010e8b99 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 27 Feb 2021 22:46:03 +0100 Subject: Add nfo thumb tag support --- Emby.Server.Implementations/ApplicationHost.cs | 2 + .../Providers/MetadataResult.cs | 12 ++-- MediaBrowser.Providers/Manager/MetadataService.cs | 6 ++ MediaBrowser.XbmcMetadata/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(-) (limited to 'tests') 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 -- cgit v1.2.3 From ed0267252f1a2b5922db7a52ad63799a5009a6ce Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Mar 2021 20:00:00 +0100 Subject: 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(-) (limited to 'tests') 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))) { -- cgit v1.2.3 From ba62d9d1fe84dfb16c502ab7e105c6c6807770ab Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Mar 2021 20:35:38 +0100 Subject: Revert breaking change --- .../Entities/ProviderIdsExtensions.cs | 27 +++++++++++++++ .../Entities/ProviderIdsExtensionsTests.cs | 38 ++++++++++++++++++++++ 2 files changed, 65 insertions(+) (limited to 'tests') 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() { -- cgit v1.2.3 From a49f5d2a441d53076c0eae27fc115a97691f4856 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 3 Mar 2021 09:37:21 +0100 Subject: revert removal of null check --- MediaBrowser.Model/Entities/ProviderIdsExtensions.cs | 6 ++++++ .../Entities/ProviderIdsExtensionsTests.cs | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'tests') 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! }; -- cgit v1.2.3 From 8b72b902f53b32c0c0d69eeda3bd32f992ac53ee Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 3 Mar 2021 12:28:40 +0100 Subject: fix HasProviderId and add tests --- MediaBrowser.Model/Entities/ProviderIdsExtensions.cs | 2 +- .../Entities/ProviderIdsExtensionsTests.cs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) (limited to 'tests') 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() { -- cgit v1.2.3 From 37e374d33d73403470d07d814b5ee1367ca12e85 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 5 Mar 2021 14:09:23 +0100 Subject: 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(-) (limited to 'tests') 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 _)); + } } } -- cgit v1.2.3 From fd0b3ca5ef82164f5551369b789b9677f30af172 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 5 Mar 2021 07:48:45 -0700 Subject: Add JsonVersionConverter and tests --- .../Json/Converters/JsonVersionConverter.cs | 23 ++++++++++++++ MediaBrowser.Common/Json/JsonDefaults.cs | 1 + MediaBrowser.Common/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 (limited to 'tests') 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 -- cgit v1.2.3 From df1951cfe2baae539758b9592af1a16e51b69897 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 5 Mar 2021 08:30:49 -0700 Subject: 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(-) (limited to 'tests') 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 +} -- cgit v1.2.3 From bc661c16e19413cbe6a94832280e3a24b6cf3c20 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 6 Mar 2021 14:01:37 +0100 Subject: simplify --- .../Library/PathExtensions.cs | 26 +++++++--------------- .../Library/PathExtensionsTests.cs | 23 ++++++++++--------- 2 files changed, 21 insertions(+), 28 deletions(-) (limited to 'tests') 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); } } } -- cgit v1.2.3 From f638ee6b0918c2ef05ec11cfa43584d3efad68d1 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 6 Mar 2021 17:43:01 -0500 Subject: 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 +++----- Jellyfin.Data/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 - Jellyfin.Data/Entities/ItemDisplayPreferences.cs | 2 -- Jellyfin.Data/Entities/Libraries/Artwork.cs | 1 - Jellyfin.Data/Entities/Libraries/BookMetadata.cs | 2 -- Jellyfin.Data/Entities/Libraries/Chapter.cs | 3 +-- Jellyfin.Data/Entities/Libraries/Collection.cs | 2 +- Jellyfin.Data/Entities/Libraries/CollectionItem.cs | 13 +++++++++++-- Jellyfin.Data/Entities/Libraries/Company.cs | 2 +- Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs | 8 ++++---- Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs | 2 -- Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs | 6 +++--- Jellyfin.Data/Entities/Libraries/Genre.cs | 1 - Jellyfin.Data/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 - Jellyfin.Data/Entities/Libraries/MetadataProvider.cs | 1 - Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs | 5 +++-- Jellyfin.Data/Entities/Libraries/MovieMetadata.cs | 10 ++++------ Jellyfin.Data/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 +- Jellyfin.Data/Entities/Libraries/RatingSource.cs | 4 ++-- Jellyfin.Data/Entities/Libraries/Release.cs | 1 - Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs | 2 +- Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs | 9 ++++----- Jellyfin.Data/Entities/Preference.cs | 1 - Jellyfin.Data/Entities/User.cs | 17 +++++++---------- Jellyfin.Data/Jellyfin.Data.csproj | 1 + .../Events/Consumers/Session/PlaybackStartLogger.cs | 4 ++-- Jellyfin.Server.Implementations/Users/UserManager.cs | 6 +++--- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 4 +--- MediaBrowser.Controller/Drawing/IImageProcessor.cs | 2 +- tests/Jellyfin.Api.Tests/TestHelpers.cs | 4 ++-- 44 files changed, 93 insertions(+), 111 deletions(-) (limited to 'tests') 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)); -- cgit v1.2.3 From e0db17a9354ff511c31c7bcf02c41d868ed91c5a Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 7 Mar 2021 22:49:31 +0100 Subject: 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(-) (limited to 'tests') 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) { -- cgit v1.2.3 From 2e62c09f2e55b33c2ba8eeb97157495e2f8ab5a9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 8 Mar 2021 02:16:35 +0100 Subject: Fix casing CollectionType --- .../Entities/JsonLowerCaseConverter.cs | 29 +++++++++ MediaBrowser.Model/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 (limited to 'tests') 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; } + } + } +} -- cgit v1.2.3 From a031f7e410f7b599e1a50b2c54a7bd78f4187301 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 9 Mar 2021 00:07:21 +0000 Subject: 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(-) (limited to 'tests') 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() -- cgit v1.2.3 From 5241bd41ef4917e0a3071f961f08dd2eeec5a5dd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 01:28:21 +0100 Subject: 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(-) (limited to 'tests') 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); -- cgit v1.2.3 From 9ed7f429c01c3f54a154442250d3447fd66d1b02 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 03:04:47 +0100 Subject: 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/Jellyfin.Drawing.Skia.csproj | 1 - Jellyfin.Networking/Jellyfin.Networking.csproj | 1 - .../Jellyfin.Server.Implementations.csproj | 1 - Jellyfin.Server/Jellyfin.Server.csproj | 1 - Jellyfin.sln | 14 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 1 - .../MediaBrowser.Controller.csproj | 1 - .../MediaBrowser.LocalMetadata.csproj | 1 - .../BdInfo/BdInfoDirectoryInfo.cs | 2 +- MediaBrowser.MediaEncoding/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 +- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 4 +- .../Providers/BaseVideoNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs | 18 +- MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs | 13 +- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 16 +- .../Savers/EpisodeNfoSaver.cs | 31 +- MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs | 17 +- MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs | 11 +- MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs | 21 +- jellyfin.ruleset | 8 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 8 +- .../Jellyfin.Common.Tests.csproj | 8 +- .../Jellyfin.Controller.Tests.csproj | 8 +- .../Jellyfin.Dlna.Tests/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 | 42 ++ .../Jellyfin.Networking.Tests/NetworkParseTests.cs | 518 ++++++++++++++++++++ .../Jellyfin.Networking.Tests.csproj | 39 -- .../NetworkTesting/NetworkParseTests.cs | 519 --------------------- .../Jellyfin.Server.Implementations.Tests.csproj | 8 +- .../Jellyfin.XbmcMetadata.Tests.csproj | 8 +- 50 files changed, 750 insertions(+), 810 deletions(-) create mode 100644 MediaBrowser.MediaEncoding/FfmpegException.cs create mode 100644 tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj create mode 100644 tests/Jellyfin.Networking.Tests/NetworkParseTests.cs delete mode 100644 tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj delete mode 100644 tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs (limited to 'tests') 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/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj new file mode 100644 index 0000000000..61eead0e95 --- /dev/null +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -0,0 +1,42 @@ + + + + + {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} + + + + net5.0 + false + true + enable + true + AllEnabledByDefault + ../jellyfin-tests.ruleset + + + + + + + + + + + + + + + + + + + + + + + + DEBUG + + + diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs new file mode 100644 index 0000000000..c3469035eb --- /dev/null +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -0,0 +1,518 @@ +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 Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Networking.Tests +{ + public class NetworkParseTests + { + private static IConfigurationManager GetMockConfig(NetworkConfiguration conf) + { + var configManager = new Mock + { + CallBase = true + }; + configManager.Setup(x => x.GetConfiguration(It.IsAny())).Returns(conf); + return (IConfigurationManager)configManager.Object; + } + + /// + /// Checks the ability to ignore virtual interfaces. + /// + /// Mock network setup, in the format (IP address, interface index, interface name) | .... + /// LAN addresses. + /// Bind addresses that are excluded. + [Theory] + // All valid + [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", "[]")] + // 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]")] + public void IgnoreVirtualInterfaces(string interfaces, string lan, string value) + { + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + LocalNetworkSubnets = lan?.Split(';') ?? throw new ArgumentNullException(nameof(lan)) + }; + + NetworkManager.MockNetworkSettings = interfaces; + using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + NetworkManager.MockNetworkSettings = string.Empty; + + 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. + /// + /// + [Theory] + [InlineData("127.0.0.1")] + [InlineData("127.0.0.1:123")] + [InlineData("localhost")] + [InlineData("localhost:1345")] + [InlineData("www.google.co.uk")] + [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")] + [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")] + [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")] + [InlineData("fe80::7add:12ff:febb:c67b%16")] + [InlineData("[fe80::7add:12ff:febb:c67b%16]:123")] + [InlineData("fe80::7add:12ff:febb:c67b%16:123")] + [InlineData("[fe80::7add:12ff:febb:c67b%16]")] + [InlineData("192.168.1.2/255.255.255.0")] + [InlineData("192.168.1.2/24")] + public void ValidHostStrings(string address) + { + Assert.True(IPHost.TryParse(address, out _)); + } + + /// + /// Checks IP address formats. + /// + /// + [Theory] + [InlineData("127.0.0.1")] + [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")] + [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")] + [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]")] + [InlineData("fe80::7add:12ff:febb:c67b%16")] + [InlineData("[fe80::7add:12ff:febb:c67b%16]:123")] + [InlineData("fe80::7add:12ff:febb:c67b%16:123")] + [InlineData("[fe80::7add:12ff:febb:c67b%16]")] + [InlineData("192.168.1.2/255.255.255.0")] + [InlineData("192.168.1.2/24")] + public void ValidIPStrings(string address) + { + Assert.True(IPNetAddress.TryParse(address, out _)); + } + + /// + /// All should be invalid address strings. + /// + /// Invalid address strings. + [Theory] + [InlineData("256.128.0.0.0.1")] + [InlineData("127.0.0.1#")] + [InlineData("localhost!")] + [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517:1231")] + [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517:1231]")] + public void InvalidAddressString(string address) + { + Assert.False(IPNetAddress.TryParse(address, out _)); + Assert.False(IPHost.TryParse(address, out _)); + } + + /// + /// Test collection parsing. + /// + /// Collection to parse. + /// Included addresses from the collection. + /// Included IP4 addresses from the collection. + /// Excluded addresses from the collection. + /// Excluded IP4 addresses from the collection. + /// Network addresses of the collection. + [Theory] + [InlineData( + "127.0.0.1#", + "[]", + "[]", + "[]", + "[]", + "[]")] + [InlineData( + "!127.0.0.1", + "[]", + "[]", + "[127.0.0.1/32]", + "[127.0.0.1/32]", + "[]")] + [InlineData( + "", + "[]", + "[]", + "[]", + "[]", + "[]")] + [InlineData( + "192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, !10.10.10.10", + "[192.158.1.2/16,[127.0.0.1/32,::1/128],fd23:184f:2029:0:3139:7386:67d7:d517/128]", + "[192.158.1.2/16,127.0.0.1/32]", + "[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", + "[192.158.1.2/16,192.169.1.2/8]", + "[192.158.1.2/16,192.169.1.2/8]", + "[]", + "[]", + "[192.158.0.0/16,192.0.0.0/8]")] + public void TestCollections(string settings, string result1, string result2, string result3, string result4, string result5) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + }; + + using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + // Test included. + Collection nc = nm.CreateIPCollection(settings.Split(","), false); + Assert.Equal(nc.AsString(), result1); + + // Test excluded. + 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); + Assert.Equal(nc.AsString(), result2); + + // Test IP4 excluded. + 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 = nc.AsNetworks(); + Assert.Equal(nc.AsString(), result5); + } + + /// + /// Union two collections. + /// + /// Source. + /// Destination. + /// Result. + [Theory] + [InlineData("127.0.0.1", "fd23:184f:2029:0:3139:7386:67d7:d517/64,fd23:184f:2029:0:c0f0:8a8a:7605:fffa/128,fe80::3139:7386:67d7:d517%16/64,192.168.1.208/24,::1/128,127.0.0.1/8", "[127.0.0.1/32]")] + [InlineData("127.0.0.1", "127.0.0.1/8", "[127.0.0.1/32]")] + public void UnionCheck(string settings, string compare, string result) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + if (compare == null) + { + throw new ArgumentNullException(nameof(compare)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + }; + + using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + + Collection nc1 = nm.CreateIPCollection(settings.Split(","), false); + Collection nc2 = nm.CreateIPCollection(compare.Split(","), false); + + Assert.Equal(nc1.Union(nc2).AsString(), result); + } + + [Theory] + [InlineData("192.168.5.85/24", "192.168.5.1")] + [InlineData("192.168.5.85/24", "192.168.5.254")] + [InlineData("10.128.240.50/30", "10.128.240.48")] + [InlineData("10.128.240.50/30", "10.128.240.49")] + [InlineData("10.128.240.50/30", "10.128.240.50")] + [InlineData("10.128.240.50/30", "10.128.240.51")] + [InlineData("127.0.0.1/8", "127.0.0.1")] + public void IpV4SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPNetAddress.Parse(netMask); + Assert.True(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); + } + + [Theory] + [InlineData("192.168.5.85/24", "192.168.4.254")] + [InlineData("192.168.5.85/24", "191.168.5.254")] + [InlineData("10.128.240.50/30", "10.128.240.47")] + [InlineData("10.128.240.50/30", "10.128.240.52")] + [InlineData("10.128.240.50/30", "10.128.239.50")] + [InlineData("10.128.240.50/30", "10.127.240.51")] + public void IpV4SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPNetAddress.Parse(netMask); + Assert.False(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); + } + + [Theory] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0001:0000:0000:0000")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFF0")] + [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")] + public void IpV6SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPNetAddress.Parse(netMask); + Assert.True(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); + } + + [Theory] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFFF")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0000:0000:0000:0000")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0001:0000:0000:0000")] + [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFF0")] + [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0001")] + public void IpV6SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPNetAddress.Parse(netMask); + Assert.False(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); + } + + [Theory] + [InlineData("10.0.0.0/255.0.0.0", "10.10.10.1/32")] + [InlineData("10.0.0.0/8", "10.10.10.1/32")] + [InlineData("10.0.0.0/255.0.0.0", "10.10.10.1")] + + [InlineData("10.10.0.0/255.255.0.0", "10.10.10.1/32")] + [InlineData("10.10.0.0/16", "10.10.10.1/32")] + [InlineData("10.10.0.0/255.255.0.0", "10.10.10.1")] + + [InlineData("10.10.10.0/255.255.255.0", "10.10.10.1/32")] + [InlineData("10.10.10.0/24", "10.10.10.1/32")] + [InlineData("10.10.10.0/255.255.255.0", "10.10.10.1")] + + public void TestSubnetContains(string network, string ip) + { + Assert.True(IPNetAddress.TryParse(network, out var networkObj)); + Assert.True(IPNetAddress.TryParse(ip, out var ipObj)); + Assert.True(networkObj.Contains(ipObj)); + } + + [Theory] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "172.168.1.2/24", "172.168.1.2/24")] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "172.168.1.2/24, 10.10.10.1", "172.168.1.2/24,10.10.10.1/24")] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "192.168.1.2/255.255.255.0, 10.10.10.1", "192.168.1.2/24,10.10.10.1/24")] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "192.168.1.2/24, 100.10.10.1", "192.168.1.2/24")] + [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "194.168.1.2/24, 100.10.10.1", "")] + + public void TestCollectionEquality(string source, string dest, string result) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (dest == null) + { + throw new ArgumentNullException(nameof(dest)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true + }; + + 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 ncResult = ncSource.Union(ncDest); + Collection resultCollection = nm.CreateIPCollection(result.Split(",")); + 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")] + + public void TestEquals(string source, string dest) + { + Assert.True(IPNetAddress.Parse(source).Equals(IPNetAddress.Parse(dest))); + Assert.True(IPNetAddress.Parse(dest).Equals(IPNetAddress.Parse(source))); + } + + [Theory] + + // Testing bind interfaces. + // On my system eth16 is internal, eth11 external (Windows defines the indexes). + // + // This test is to replicate how DNLA requests work throughout the system. + + // User on internal network, we're bound internal and external - so result is internal. + [InlineData("192.168.1.1", "eth16,eth11", false, "eth16")] + // User on external network, we're bound internal and external - so result is external. + [InlineData("8.8.8.8", "eth16,eth11", false, "eth11")] + // User on internal network, we're bound internal only - so result is internal. + [InlineData("10.10.10.10", "eth16", false, "eth16")] + // User on internal network, no binding specified - so result is the 1st internal. + [InlineData("192.168.1.1", "", false, "eth16")] + // User on external network, internal binding only - so result is the 1st internal. + [InlineData("jellyfin.org", "eth16", false, "eth16")] + // User on external network, no binding - so result is the 1st external. + [InlineData("jellyfin.org", "", false, "eth11")] + // User assumed to be internal, no binding - so result is the 1st internal. + [InlineData("", "", false, "eth16")] + public void TestBindInterfaces(string source, string bindAddresses, bool ipv6enabled, string result) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (bindAddresses == null) + { + throw new ArgumentNullException(nameof(bindAddresses)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var conf = new NetworkConfiguration() + { + LocalNetworkAddresses = bindAddresses.Split(','), + EnableIPV6 = ipv6enabled, + EnableIPV4 = true + }; + + NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11"; + using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + NetworkManager.MockNetworkSettings = string.Empty; + + _ = nm.TryParseInterface(result, out Collection? resultObj); + + if (resultObj != null) + { + result = ((IPNetAddress)resultObj[0]).ToString(true); + var intf = nm.GetBindInterface(source, out int? _); + + Assert.Equal(intf, result); + } + } + + [Theory] + + // Testing bind interfaces. These are set for my system so won't work elsewhere. + // 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")] + + // User on external network, we're bound internal and external - so result is override. + [InlineData("8.8.8.8", "192.168.1.0/24", "eth16,eth11", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")] + + // User on internal network, we're bound internal only, but the address isn't in the LAN - so return the override. + [InlineData("10.10.10.10", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://internalButNotDefinedAsLan.com", "http://internalButNotDefinedAsLan.com")] + + // User on internal network, no binding specified - so result is the 1st internal. + [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")] + + // User on external network, internal binding only - so assumption is a proxy forward, return external override. + [InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")] + + // User on external network, no binding - so result is the 1st external which is overriden. + [InlineData("jellyfin.org", "192.168.1.0/24", "", false, "0.0.0.0 = http://helloworld.com", "http://helloworld.com")] + + // User assumed to be internal, no binding - so result is the 1st internal. + [InlineData("", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")] + + // 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) + { + throw new ArgumentNullException(nameof(lan)); + } + + if (bindAddresses == null) + { + throw new ArgumentNullException(nameof(bindAddresses)); + } + + var conf = new NetworkConfiguration() + { + LocalNetworkSubnets = lan.Split(','), + LocalNetworkAddresses = bindAddresses.Split(','), + EnableIPV6 = ipv6enabled, + EnableIPV4 = true, + PublishedServerUriBySubnet = new string[] { publishedServers } + }; + + NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11"; + using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); + NetworkManager.MockNetworkSettings = string.Empty; + + if (nm.TryParseInterface(result, out Collection? resultObj) && resultObj != null) + { + // Parse out IPAddresses so we can do a string comparison. (Ignore subnet masks). + result = ((IPNetAddress)resultObj[0]).ToString(true); + } + + var intf = nm.GetBindInterface(source, out int? _); + + Assert.Equal(intf, result); + } + } +} diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj deleted file mode 100644 index fd77397bae..0000000000 --- a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - - {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} - - - - net5.0 - false - enable - true - - - - - - - - - - - - - - - - - - - - - - ../../jellyfin-tests.ruleset - - - DEBUG - - diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs deleted file mode 100644 index 9f928ded1b..0000000000 --- a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs +++ /dev/null @@ -1,519 +0,0 @@ -using System; -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 Xunit; -using System.Collections.ObjectModel; - -namespace Jellyfin.Networking.Tests -{ - public class NetworkParseTests - { - private static IConfigurationManager GetMockConfig(NetworkConfiguration conf) - { - var configManager = new Mock - { - CallBase = true - }; - configManager.Setup(x => x.GetConfiguration(It.IsAny())).Returns(conf); - return (IConfigurationManager)configManager.Object; - } - - /// - /// Checks the ability to ignore virtual interfaces. - /// - /// Mock network setup, in the format (IP address, interface index, interface name) | .... - /// LAN addresses. - /// Bind addresses that are excluded. - [Theory] - // All valid - [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", "[]")] - // 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]")] - public void IgnoreVirtualInterfaces(string interfaces, string lan, string value) - { - var conf = new NetworkConfiguration() - { - EnableIPV6 = true, - EnableIPV4 = true, - LocalNetworkSubnets = lan?.Split(';') ?? throw new ArgumentNullException(nameof(lan)) - }; - - NetworkManager.MockNetworkSettings = interfaces; - using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); - NetworkManager.MockNetworkSettings = string.Empty; - - 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. - /// - /// - [Theory] - [InlineData("127.0.0.1")] - [InlineData("127.0.0.1:123")] - [InlineData("localhost")] - [InlineData("localhost:1345")] - [InlineData("www.google.co.uk")] - [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")] - [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")] - [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")] - [InlineData("fe80::7add:12ff:febb:c67b%16")] - [InlineData("[fe80::7add:12ff:febb:c67b%16]:123")] - [InlineData("fe80::7add:12ff:febb:c67b%16:123")] - [InlineData("[fe80::7add:12ff:febb:c67b%16]")] - [InlineData("192.168.1.2/255.255.255.0")] - [InlineData("192.168.1.2/24")] - public void ValidHostStrings(string address) - { - Assert.True(IPHost.TryParse(address, out _)); - } - - /// - /// Checks IP address formats. - /// - /// - [Theory] - [InlineData("127.0.0.1")] - [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")] - [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")] - [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]")] - [InlineData("fe80::7add:12ff:febb:c67b%16")] - [InlineData("[fe80::7add:12ff:febb:c67b%16]:123")] - [InlineData("fe80::7add:12ff:febb:c67b%16:123")] - [InlineData("[fe80::7add:12ff:febb:c67b%16]")] - [InlineData("192.168.1.2/255.255.255.0")] - [InlineData("192.168.1.2/24")] - public void ValidIPStrings(string address) - { - Assert.True(IPNetAddress.TryParse(address, out _)); - } - - - /// - /// All should be invalid address strings. - /// - /// Invalid address strings. - [Theory] - [InlineData("256.128.0.0.0.1")] - [InlineData("127.0.0.1#")] - [InlineData("localhost!")] - [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517:1231")] - [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517:1231]")] - public void InvalidAddressString(string address) - { - Assert.False(IPNetAddress.TryParse(address, out _)); - Assert.False(IPHost.TryParse(address, out _)); - } - - - /// - /// Test collection parsing. - /// - /// Collection to parse. - /// Included addresses from the collection. - /// Included IP4 addresses from the collection. - /// Excluded addresses from the collection. - /// Excluded IP4 addresses from the collection. - /// Network addresses of the collection. - [Theory] - [InlineData("127.0.0.1#", - "[]", - "[]", - "[]", - "[]", - "[]")] - [InlineData("!127.0.0.1", - "[]", - "[]", - "[127.0.0.1/32]", - "[127.0.0.1/32]", - "[]")] - [InlineData("", - "[]", - "[]", - "[]", - "[]", - "[]")] - [InlineData( - "192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, !10.10.10.10", - "[192.158.1.2/16,[127.0.0.1/32,::1/128],fd23:184f:2029:0:3139:7386:67d7:d517/128]", - "[192.158.1.2/16,127.0.0.1/32]", - "[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", - "[192.158.1.2/16,192.169.1.2/8]", - "[192.158.1.2/16,192.169.1.2/8]", - "[]", - "[]", - "[192.158.0.0/16,192.0.0.0/8]")] - public void TestCollections(string settings, string result1, string result2, string result3, string result4, string result5) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - - var conf = new NetworkConfiguration() - { - EnableIPV6 = true, - EnableIPV4 = true, - }; - - using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); - - // Test included. - Collection nc = nm.CreateIPCollection(settings.Split(","), false); - Assert.Equal(nc.AsString(), result1); - - // Test excluded. - 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); - Assert.Equal(nc.AsString(), result2); - - // Test IP4 excluded. - 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 = nc.AsNetworks(); - Assert.Equal(nc.AsString(), result5); - } - - /// - /// Union two collections. - /// - /// Source. - /// Destination. - /// Result. - [Theory] - [InlineData("127.0.0.1", "fd23:184f:2029:0:3139:7386:67d7:d517/64,fd23:184f:2029:0:c0f0:8a8a:7605:fffa/128,fe80::3139:7386:67d7:d517%16/64,192.168.1.208/24,::1/128,127.0.0.1/8", "[127.0.0.1/32]")] - [InlineData("127.0.0.1", "127.0.0.1/8", "[127.0.0.1/32]")] - public void UnionCheck(string settings, string compare, string result) - { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - - if (compare == null) - { - throw new ArgumentNullException(nameof(compare)); - } - - if (result == null) - { - throw new ArgumentNullException(nameof(result)); - } - - - var conf = new NetworkConfiguration() - { - EnableIPV6 = true, - EnableIPV4 = true, - }; - - using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); - - Collection nc1 = nm.CreateIPCollection(settings.Split(","), false); - Collection nc2 = nm.CreateIPCollection(compare.Split(","), false); - - Assert.Equal(nc1.Union(nc2).AsString(), result); - } - - [Theory] - [InlineData("192.168.5.85/24", "192.168.5.1")] - [InlineData("192.168.5.85/24", "192.168.5.254")] - [InlineData("10.128.240.50/30", "10.128.240.48")] - [InlineData("10.128.240.50/30", "10.128.240.49")] - [InlineData("10.128.240.50/30", "10.128.240.50")] - [InlineData("10.128.240.50/30", "10.128.240.51")] - [InlineData("127.0.0.1/8", "127.0.0.1")] - public void IpV4SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress) - { - var ipAddressObj = IPNetAddress.Parse(netMask); - Assert.True(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); - } - - [Theory] - [InlineData("192.168.5.85/24", "192.168.4.254")] - [InlineData("192.168.5.85/24", "191.168.5.254")] - [InlineData("10.128.240.50/30", "10.128.240.47")] - [InlineData("10.128.240.50/30", "10.128.240.52")] - [InlineData("10.128.240.50/30", "10.128.239.50")] - [InlineData("10.128.240.50/30", "10.127.240.51")] - public void IpV4SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress) - { - var ipAddressObj = IPNetAddress.Parse(netMask); - Assert.False(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); - } - - [Theory] - [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")] - [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF")] - [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0001:0000:0000:0000")] - [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFF0")] - [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")] - public void IpV6SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress) - { - var ipAddressObj = IPNetAddress.Parse(netMask); - Assert.True(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); - } - - [Theory] - [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFFF")] - [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0000:0000:0000:0000")] - [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0001:0000:0000:0000")] - [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFF0")] - [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0001")] - public void IpV6SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress) - { - var ipAddressObj = IPNetAddress.Parse(netMask); - Assert.False(ipAddressObj.Contains(IPAddress.Parse(ipAddress))); - } - - [Theory] - [InlineData("10.0.0.0/255.0.0.0", "10.10.10.1/32")] - [InlineData("10.0.0.0/8", "10.10.10.1/32")] - [InlineData("10.0.0.0/255.0.0.0", "10.10.10.1")] - - [InlineData("10.10.0.0/255.255.0.0", "10.10.10.1/32")] - [InlineData("10.10.0.0/16", "10.10.10.1/32")] - [InlineData("10.10.0.0/255.255.0.0", "10.10.10.1")] - - [InlineData("10.10.10.0/255.255.255.0", "10.10.10.1/32")] - [InlineData("10.10.10.0/24", "10.10.10.1/32")] - [InlineData("10.10.10.0/255.255.255.0", "10.10.10.1")] - - public void TestSubnetContains(string network, string ip) - { - Assert.True(IPNetAddress.TryParse(network, out var networkObj)); - Assert.True(IPNetAddress.TryParse(ip, out var ipObj)); - Assert.True(networkObj.Contains(ipObj)); - } - - [Theory] - [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "172.168.1.2/24", "172.168.1.2/24")] - [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "172.168.1.2/24, 10.10.10.1", "172.168.1.2/24,10.10.10.1/24")] - [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "192.168.1.2/255.255.255.0, 10.10.10.1", "192.168.1.2/24,10.10.10.1/24")] - [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "192.168.1.2/24, 100.10.10.1", "192.168.1.2/24")] - [InlineData("192.168.1.2/24,10.10.10.1/24,172.168.1.2/24", "194.168.1.2/24, 100.10.10.1", "")] - - public void TestCollectionEquality(string source, string dest, string result) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (dest == null) - { - throw new ArgumentNullException(nameof(dest)); - } - - if (result == null) - { - throw new ArgumentNullException(nameof(result)); - } - - var conf = new NetworkConfiguration() - { - EnableIPV6 = true, - EnableIPV4 = true - }; - - 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 ncResult = ncSource.Union(ncDest); - Collection resultCollection = nm.CreateIPCollection(result.Split(",")); - 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")] - - public void TestEquals(string source, string dest) - { - Assert.True(IPNetAddress.Parse(source).Equals(IPNetAddress.Parse(dest))); - Assert.True(IPNetAddress.Parse(dest).Equals(IPNetAddress.Parse(source))); - } - - [Theory] - - // Testing bind interfaces. - // On my system eth16 is internal, eth11 external (Windows defines the indexes). - // - // This test is to replicate how DNLA requests work throughout the system. - - // User on internal network, we're bound internal and external - so result is internal. - [InlineData("192.168.1.1", "eth16,eth11", false, "eth16")] - // User on external network, we're bound internal and external - so result is external. - [InlineData("8.8.8.8", "eth16,eth11", false, "eth11")] - // User on internal network, we're bound internal only - so result is internal. - [InlineData("10.10.10.10", "eth16", false, "eth16")] - // User on internal network, no binding specified - so result is the 1st internal. - [InlineData("192.168.1.1", "", false, "eth16")] - // User on external network, internal binding only - so result is the 1st internal. - [InlineData("jellyfin.org", "eth16", false, "eth16")] - // User on external network, no binding - so result is the 1st external. - [InlineData("jellyfin.org", "", false, "eth11")] - // User assumed to be internal, no binding - so result is the 1st internal. - [InlineData("", "", false, "eth16")] - public void TestBindInterfaces(string source, string bindAddresses, bool ipv6enabled, string result) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (bindAddresses == null) - { - throw new ArgumentNullException(nameof(bindAddresses)); - } - - if (result == null) - { - throw new ArgumentNullException(nameof(result)); - } - - var conf = new NetworkConfiguration() - { - LocalNetworkAddresses = bindAddresses.Split(','), - EnableIPV6 = ipv6enabled, - EnableIPV4 = true - }; - - NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11"; - using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); - NetworkManager.MockNetworkSettings = string.Empty; - - _ = nm.TryParseInterface(result, out Collection? resultObj); - - if (resultObj != null) - { - result = ((IPNetAddress)resultObj[0]).ToString(true); - var intf = nm.GetBindInterface(source, out int? _); - - Assert.Equal(intf, result); - } - } - - [Theory] - - // Testing bind interfaces. These are set for my system so won't work elsewhere. - // 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")] - - // User on external network, we're bound internal and external - so result is override. - [InlineData("8.8.8.8", "192.168.1.0/24", "eth16,eth11", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")] - - // User on internal network, we're bound internal only, but the address isn't in the LAN - so return the override. - [InlineData("10.10.10.10", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://internalButNotDefinedAsLan.com", "http://internalButNotDefinedAsLan.com")] - - // User on internal network, no binding specified - so result is the 1st internal. - [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")] - - // User on external network, internal binding only - so assumption is a proxy forward, return external override. - [InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")] - - // User on external network, no binding - so result is the 1st external which is overriden. - [InlineData("jellyfin.org", "192.168.1.0/24", "", false, "0.0.0.0 = http://helloworld.com", "http://helloworld.com")] - - // User assumed to be internal, no binding - so result is the 1st internal. - [InlineData("", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")] - - // 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) - { - throw new ArgumentNullException(nameof(lan)); - } - - if (bindAddresses == null) - { - throw new ArgumentNullException(nameof(bindAddresses)); - } - - var conf = new NetworkConfiguration() - { - LocalNetworkSubnets = lan.Split(','), - LocalNetworkAddresses = bindAddresses.Split(','), - EnableIPV6 = ipv6enabled, - EnableIPV4 = true, - PublishedServerUriBySubnet = new string[] { publishedServers } - }; - - NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11"; - using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger()); - NetworkManager.MockNetworkSettings = string.Empty; - - if (nm.TryParseInterface(result, out Collection? resultObj) && resultObj != null) - { - // Parse out IPAddresses so we can do a string comparison. (Ignore subnet masks). - result = ((IPNetAddress)resultObj[0]).ToString(true); - } - - var intf = nm.GetBindInterface(source, out int? _); - - Assert.Equal(intf, result); - } - } -} 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 - - -- cgit v1.2.3 From d202df6e8a1f36a253e9780e7ec1521bc0c4b75e Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 03:22:51 +0100 Subject: 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 - tests/Jellyfin.MediaEncoding.Tests/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 - tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj | 1 - 14 files changed, 14 deletions(-) (limited to 'tests') 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 -- cgit v1.2.3 From fa8bfece4e72c32f8350aaa947c81b2494f6bb77 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 1 Mar 2021 19:35:58 +0100 Subject: Split integration tests from unit tests --- Jellyfin.Server/Properties/AssemblyInfo.cs | 2 +- Jellyfin.sln | 2 + .../Controllers/BrandingControllerTests.cs | 53 --------- .../Controllers/DashboardControllerTests.cs | 86 --------------- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 7 +- .../JellyfinApplicationFactory.cs | 120 --------------------- tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs | 42 -------- tests/Jellyfin.Api.Tests/ParseNetworkTests.cs | 88 --------------- tests/Jellyfin.Api.Tests/TestAppHost.cs | 55 ---------- tests/Jellyfin.Api.Tests/TestPage.html | 9 -- tests/Jellyfin.Api.Tests/TestPlugin.cs | 43 -------- .../Controllers/BrandingControllerTests.cs | 53 +++++++++ .../Controllers/DashboardControllerTests.cs | 86 +++++++++++++++ .../Jellyfin.Server.Integration.Tests.csproj | 40 +++++++ .../JellyfinApplicationFactory.cs | 120 +++++++++++++++++++++ .../OpenApiSpecTests.cs | 42 ++++++++ .../TestAppHost.cs | 55 ++++++++++ .../TestPage.html | 9 ++ .../TestPlugin.cs | 43 ++++++++ .../Jellyfin.Server.Tests.csproj | 39 +++++++ tests/Jellyfin.Server.Tests/ParseNetworkTests.cs | 88 +++++++++++++++ 21 files changed, 580 insertions(+), 502 deletions(-) delete mode 100644 tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs delete mode 100644 tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs delete mode 100644 tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs delete mode 100644 tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs delete mode 100644 tests/Jellyfin.Api.Tests/ParseNetworkTests.cs delete mode 100644 tests/Jellyfin.Api.Tests/TestAppHost.cs delete mode 100644 tests/Jellyfin.Api.Tests/TestPage.html delete mode 100644 tests/Jellyfin.Api.Tests/TestPlugin.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj create mode 100644 tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/TestPage.html create mode 100644 tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs create mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj create mode 100644 tests/Jellyfin.Server.Tests/ParseNetworkTests.cs (limited to 'tests') 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/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs deleted file mode 100644 index 40933562db..0000000000 --- a/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Net.Mime; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using MediaBrowser.Model.Branding; -using Xunit; - -namespace Jellyfin.Api.Tests -{ - public sealed class BrandingControllerTests : IClassFixture - { - private readonly JellyfinApplicationFactory _factory; - - public BrandingControllerTests(JellyfinApplicationFactory factory) - { - _factory = factory; - } - - [Fact] - public async Task GetConfiguration_ReturnsCorrectResponse() - { - // Arrange - var client = _factory.CreateClient(); - - // Act - var response = await client.GetAsync("/Branding/Configuration"); - - // Assert - Assert.True(response.IsSuccessStatusCode); - Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); - Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); - var responseBody = await response.Content.ReadAsStreamAsync(); - _ = await JsonSerializer.DeserializeAsync(responseBody); - } - - [Theory] - [InlineData("/Branding/Css")] - [InlineData("/Branding/Css.css")] - public async Task GetCss_ReturnsCorrectResponse(string url) - { - // Arrange - var client = _factory.CreateClient(); - - // Act - var response = await client.GetAsync(url); - - // Assert - Assert.True(response.IsSuccessStatusCode); - Assert.Equal("text/css", response.Content.Headers.ContentType?.MediaType); - Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); - } - } -} diff --git a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs deleted file mode 100644 index 300b2697f2..0000000000 --- a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.IO; -using System.Net; -using System.Net.Mime; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using Jellyfin.Api.Models; -using MediaBrowser.Common.Json; -using Xunit; - -namespace Jellyfin.Api.Tests.Controllers -{ - public sealed class DashboardControllerTests : IClassFixture - { - private readonly JellyfinApplicationFactory _factory; - private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.GetOptions(); - - public DashboardControllerTests(JellyfinApplicationFactory factory) - { - _factory = factory; - } - - [Fact] - public async Task GetDashboardConfigurationPage_NonExistingPage_NotFound() - { - var client = _factory.CreateClient(); - - var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists").ConfigureAwait(false); - - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Fact] - public async Task GetDashboardConfigurationPage_ExistingPage_CorrectPage() - { - var client = _factory.CreateClient(); - - var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin").ConfigureAwait(false); - - 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")!); - Assert.Equal(await response.Content.ReadAsStringAsync(), reader.ReadToEnd()); - } - - [Fact] - public async Task GetDashboardConfigurationPage_BrokenPage_NotFound() - { - var client = _factory.CreateClient(); - - var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage").ConfigureAwait(false); - - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Fact] - public async Task GetConfigurationPages_NoParams_AllConfigurationPages() - { - var client = _factory.CreateClient(); - - var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false); - - Assert.True(response.IsSuccessStatusCode); - - var res = await response.Content.ReadAsStreamAsync(); - _ = await JsonSerializer.DeserializeAsync(res, _jsonOpions); - // TODO: check content - } - - [Fact] - public async Task GetConfigurationPages_True_MainMenuConfigurationPages() - { - var client = _factory.CreateClient(); - - var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false); - - Assert.True(response.IsSuccessStatusCode); - Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); - Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); - - var res = await response.Content.ReadAsStreamAsync(); - var data = await JsonSerializer.DeserializeAsync(res, _jsonOpions); - Assert.Empty(data); - } - } -} 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/JellyfinApplicationFactory.cs b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs deleted file mode 100644 index 92c5495c83..0000000000 --- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Concurrent; -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; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Serilog; -using Serilog.Extensions.Logging; - -namespace Jellyfin.Api.Tests -{ - /// - /// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests. - /// - public class JellyfinApplicationFactory : WebApplicationFactory - { - private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data"); - private readonly ConcurrentBag _disposableComponents = new ConcurrentBag(); - - /// - /// Initializes a new instance of the class. - /// - public JellyfinApplicationFactory() - { - // Perform static initialization that only needs to happen once per test-run - Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger(); - Program.PerformStaticInitialization(); - } - - /// - protected override IWebHostBuilder CreateWebHostBuilder() - { - return new WebHostBuilder(); - } - - /// - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - // Specify the startup command line options - var commandLineOpts = new StartupOptions - { - NoWebClient = true - }; - - // Use a temporary directory for the application paths - var webHostPathRoot = Path.Combine(_testPathRoot, "test-host-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName())); - Directory.CreateDirectory(Path.Combine(webHostPathRoot, "logs")); - Directory.CreateDirectory(Path.Combine(webHostPathRoot, "config")); - Directory.CreateDirectory(Path.Combine(webHostPathRoot, "cache")); - Directory.CreateDirectory(Path.Combine(webHostPathRoot, "jellyfin-web")); - var appPaths = new ServerApplicationPaths( - webHostPathRoot, - Path.Combine(webHostPathRoot, "logs"), - Path.Combine(webHostPathRoot, "config"), - Path.Combine(webHostPathRoot, "cache"), - Path.Combine(webHostPathRoot, "jellyfin-web")); - - // Create the logging config file - // TODO: We shouldn't need to do this since we are only logging to console - Program.InitLoggingConfigFile(appPaths).GetAwaiter().GetResult(); - - // Create a copy of the application configuration to use for startup - var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths); - - ILoggerFactory loggerFactory = new SerilogLoggerFactory(); - var serviceCollection = new ServiceCollection(); - _disposableComponents.Add(loggerFactory); - - // Create the app host and initialize it - var appHost = new TestAppHost( - appPaths, - loggerFactory, - commandLineOpts, - new ConfigurationBuilder().Build(), - new ManagedFileSystem(loggerFactory.CreateLogger(), appPaths), - serviceCollection); - _disposableComponents.Add(appHost); - appHost.Init(); - - // Configure the web host builder - Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths); - } - - /// - protected override TestServer CreateServer(IWebHostBuilder builder) - { - // Create the test server using the base implementation - var testServer = base.CreateServer(builder); - - // Finish initializing the app host - var appHost = (TestAppHost)testServer.Services.GetRequiredService(); - appHost.ServiceProvider = testServer.Services; - appHost.InitializeServices().GetAwaiter().GetResult(); - appHost.RunStartupTasksAsync(CancellationToken.None).GetAwaiter().GetResult(); - - return testServer; - } - - /// - protected override void Dispose(bool disposing) - { - foreach (var disposable in _disposableComponents) - { - disposable.Dispose(); - } - - _disposableComponents.Clear(); - - base.Dispose(disposing); - } - } -} diff --git a/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs deleted file mode 100644 index 03ab56d1f4..0000000000 --- a/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.IO; -using System.Reflection; -using System.Text.Json; -using System.Threading.Tasks; -using MediaBrowser.Model.Branding; -using Xunit; -using Xunit.Abstractions; - -namespace Jellyfin.Api.Tests -{ - public sealed class OpenApiSpecTests : IClassFixture - { - private readonly JellyfinApplicationFactory _factory; - private readonly ITestOutputHelper _outputHelper; - - public OpenApiSpecTests(JellyfinApplicationFactory factory, ITestOutputHelper outputHelper) - { - _factory = factory; - _outputHelper = outputHelper; - } - - [Fact] - public async Task GetSpec_ReturnsCorrectResponse() - { - // Arrange - var client = _factory.CreateClient(); - - // Act - var response = await client.GetAsync("/api-docs/openapi.json"); - - // Assert - response.EnsureSuccessStatusCode(); - Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString()); - - // Write out for publishing - var responseBody = await response.Content.ReadAsStringAsync(); - string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json")); - _outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath); - File.WriteAllText(outputPath, responseBody); - } - } -} diff --git a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs deleted file mode 100644 index 3984407ee9..0000000000 --- a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Globalization; -using System.Text; -using Jellyfin.Networking.Configuration; -using Jellyfin.Networking.Manager; -using Jellyfin.Server.Extensions; -using MediaBrowser.Common.Configuration; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using Xunit; - -namespace Jellyfin.Api.Tests -{ - public class ParseNetworkTests - { - /// - /// Order of the result has always got to be hosts, then networks. - /// - /// IP4 enabled. - /// IP6 enabled. - /// List to parse. - /// What it should match. - [Theory] - // [InlineData(true, true, "192.168.0.0/16,www.yahoo.co.uk", "::ffff:212.82.100.150,::ffff:192.168.0.0/16")] <- fails on Max. www.yahoo.co.uk resolves to a different ip address. - // [InlineData(true, false, "192.168.0.0/16,www.yahoo.co.uk", "212.82.100.150,192.168.0.0/16")] - [InlineData(true, true, "192.168.t,127.0.0.1,1234.1232.12.1234", "::ffff:127.0.0.1")] - [InlineData(true, false, "192.168.x,127.0.0.1,1234.1232.12.1234", "127.0.0.1")] - [InlineData(true, true, "::1", "::1/128")] - public void TestNetworks(bool ip4, bool ip6, string hostList, string match) - { - using var nm = CreateNetworkManager(); - - var settings = new NetworkConfiguration - { - EnableIPV4 = ip4, - EnableIPV6 = ip6 - }; - - var result = match + ","; - ForwardedHeadersOptions options = new ForwardedHeadersOptions(); - - // Need this here as ::1 and 127.0.0.1 are in them by default. - options.KnownProxies.Clear(); - options.KnownNetworks.Clear(); - - ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(','), options); - - var sb = new StringBuilder(); - foreach (var item in options.KnownProxies) - { - sb.Append(item) - .Append(','); - } - - foreach (var item in options.KnownNetworks) - { - sb.Append(item.Prefix) - .Append('/') - .Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture)) - .Append(','); - } - - Assert.Equal(sb.ToString(), result); - } - - private static IConfigurationManager GetMockConfig(NetworkConfiguration conf) - { - var configManager = new Mock - { - CallBase = true - }; - configManager.Setup(x => x.GetConfiguration(It.IsAny())).Returns(conf); - return configManager.Object; - } - - private static NetworkManager CreateNetworkManager() - { - var conf = new NetworkConfiguration() - { - EnableIPV6 = true, - EnableIPV4 = true, - }; - - return new NetworkManager(GetMockConfig(conf), new NullLogger()); - } - } -} diff --git a/tests/Jellyfin.Api.Tests/TestAppHost.cs b/tests/Jellyfin.Api.Tests/TestAppHost.cs deleted file mode 100644 index eb4c9b3055..0000000000 --- a/tests/Jellyfin.Api.Tests/TestAppHost.cs +++ /dev/null @@ -1,55 +0,0 @@ -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; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Api.Tests -{ - /// - /// Implementation of the abstract class. - /// - public class TestAppHost : CoreAppHost - { - /// - /// Initializes a new instance of the class. - /// - /// 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) - { - } - - /// - protected override IEnumerable GetAssembliesWithPartsInternal() - { - foreach (var a in base.GetAssembliesWithPartsInternal()) - { - yield return a; - } - - yield return typeof(TestPlugin).Assembly; - } - } -} diff --git a/tests/Jellyfin.Api.Tests/TestPage.html b/tests/Jellyfin.Api.Tests/TestPage.html deleted file mode 100644 index 8037af8a60..0000000000 --- a/tests/Jellyfin.Api.Tests/TestPage.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - TestPlugin - - -

This is a Test Page.

- - diff --git a/tests/Jellyfin.Api.Tests/TestPlugin.cs b/tests/Jellyfin.Api.Tests/TestPlugin.cs deleted file mode 100644 index a3b4b6994f..0000000000 --- a/tests/Jellyfin.Api.Tests/TestPlugin.cs +++ /dev/null @@ -1,43 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Serialization; - -namespace Jellyfin.Api.Tests -{ - public class TestPlugin : BasePlugin, IHasWebPages - { - public TestPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) - : base(applicationPaths, xmlSerializer) - { - Instance = this; - } - - public static TestPlugin? Instance { get; private set; } - - public override Guid Id => new Guid("2d350a13-0bf7-4b61-859c-d5e601b5facf"); - - public override string Name => nameof(TestPlugin); - - public override string Description => "Server test Plugin."; - - public IEnumerable GetPages() - { - yield return new PluginPageInfo - { - Name = Name, - EmbeddedResourcePath = GetType().Namespace + ".TestPage.html" - }; - - yield return new PluginPageInfo - { - Name = "BrokenPage", - EmbeddedResourcePath = GetType().Namespace + ".foobar" - }; - } - } -} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs new file mode 100644 index 0000000000..fdd93f20ef --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs @@ -0,0 +1,53 @@ +using System.Net.Mime; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using MediaBrowser.Model.Branding; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests +{ + public sealed class BrandingControllerTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + + public BrandingControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetConfiguration_ReturnsCorrectResponse() + { + // Arrange + var client = _factory.CreateClient(); + + // Act + var response = await client.GetAsync("/Branding/Configuration"); + + // Assert + Assert.True(response.IsSuccessStatusCode); + Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); + Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); + var responseBody = await response.Content.ReadAsStreamAsync(); + _ = await JsonSerializer.DeserializeAsync(responseBody); + } + + [Theory] + [InlineData("/Branding/Css")] + [InlineData("/Branding/Css.css")] + public async Task GetCss_ReturnsCorrectResponse(string url) + { + // Arrange + var client = _factory.CreateClient(); + + // Act + var response = await client.GetAsync(url); + + // Assert + Assert.True(response.IsSuccessStatusCode); + Assert.Equal("text/css", response.Content.Headers.ContentType?.MediaType); + Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); + } + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs new file mode 100644 index 0000000000..88905fc6d0 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs @@ -0,0 +1,86 @@ +using System.IO; +using System.Net; +using System.Net.Mime; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Models; +using MediaBrowser.Common.Json; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers +{ + public sealed class DashboardControllerTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.GetOptions(); + + public DashboardControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetDashboardConfigurationPage_NonExistingPage_NotFound() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetDashboardConfigurationPage_ExistingPage_CorrectPage() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin").ConfigureAwait(false); + + Assert.True(response.IsSuccessStatusCode); + 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()); + } + + [Fact] + public async Task GetDashboardConfigurationPage_BrokenPage_NotFound() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetConfigurationPages_NoParams_AllConfigurationPages() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false); + + Assert.True(response.IsSuccessStatusCode); + + var res = await response.Content.ReadAsStreamAsync(); + _ = await JsonSerializer.DeserializeAsync(res, _jsonOpions); + // TODO: check content + } + + [Fact] + public async Task GetConfigurationPages_True_MainMenuConfigurationPages() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false); + + Assert.True(response.IsSuccessStatusCode); + Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); + Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); + + var res = await response.Content.ReadAsStreamAsync(); + var data = await JsonSerializer.DeserializeAsync(res, _jsonOpions); + Assert.Empty(data); + } + } +} 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.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs new file mode 100644 index 0000000000..94e6181023 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Concurrent; +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; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Extensions.Logging; + +namespace Jellyfin.Server.Integration.Tests +{ + /// + /// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests. + /// + public class JellyfinApplicationFactory : WebApplicationFactory + { + private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data"); + private readonly ConcurrentBag _disposableComponents = new ConcurrentBag(); + + /// + /// Initializes a new instance of the class. + /// + public JellyfinApplicationFactory() + { + // Perform static initialization that only needs to happen once per test-run + Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger(); + Program.PerformStaticInitialization(); + } + + /// + protected override IWebHostBuilder CreateWebHostBuilder() + { + return new WebHostBuilder(); + } + + /// + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + // Specify the startup command line options + var commandLineOpts = new StartupOptions + { + NoWebClient = true + }; + + // Use a temporary directory for the application paths + var webHostPathRoot = Path.Combine(_testPathRoot, "test-host-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName())); + Directory.CreateDirectory(Path.Combine(webHostPathRoot, "logs")); + Directory.CreateDirectory(Path.Combine(webHostPathRoot, "config")); + Directory.CreateDirectory(Path.Combine(webHostPathRoot, "cache")); + Directory.CreateDirectory(Path.Combine(webHostPathRoot, "jellyfin-web")); + var appPaths = new ServerApplicationPaths( + webHostPathRoot, + Path.Combine(webHostPathRoot, "logs"), + Path.Combine(webHostPathRoot, "config"), + Path.Combine(webHostPathRoot, "cache"), + Path.Combine(webHostPathRoot, "jellyfin-web")); + + // Create the logging config file + // TODO: We shouldn't need to do this since we are only logging to console + Program.InitLoggingConfigFile(appPaths).GetAwaiter().GetResult(); + + // Create a copy of the application configuration to use for startup + var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths); + + ILoggerFactory loggerFactory = new SerilogLoggerFactory(); + var serviceCollection = new ServiceCollection(); + _disposableComponents.Add(loggerFactory); + + // Create the app host and initialize it + var appHost = new TestAppHost( + appPaths, + loggerFactory, + commandLineOpts, + new ConfigurationBuilder().Build(), + new ManagedFileSystem(loggerFactory.CreateLogger(), appPaths), + serviceCollection); + _disposableComponents.Add(appHost); + appHost.Init(); + + // Configure the web host builder + Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths); + } + + /// + protected override TestServer CreateServer(IWebHostBuilder builder) + { + // Create the test server using the base implementation + var testServer = base.CreateServer(builder); + + // Finish initializing the app host + var appHost = (TestAppHost)testServer.Services.GetRequiredService(); + appHost.ServiceProvider = testServer.Services; + appHost.InitializeServices().GetAwaiter().GetResult(); + appHost.RunStartupTasksAsync(CancellationToken.None).GetAwaiter().GetResult(); + + return testServer; + } + + /// + protected override void Dispose(bool disposing) + { + foreach (var disposable in _disposableComponents) + { + disposable.Dispose(); + } + + _disposableComponents.Clear(); + + base.Dispose(disposing); + } + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs new file mode 100644 index 0000000000..3cbd638f90 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs @@ -0,0 +1,42 @@ +using System.IO; +using System.Reflection; +using System.Text.Json; +using System.Threading.Tasks; +using MediaBrowser.Model.Branding; +using Xunit; +using Xunit.Abstractions; + +namespace Jellyfin.Server.Integration.Tests +{ + public sealed class OpenApiSpecTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + private readonly ITestOutputHelper _outputHelper; + + public OpenApiSpecTests(JellyfinApplicationFactory factory, ITestOutputHelper outputHelper) + { + _factory = factory; + _outputHelper = outputHelper; + } + + [Fact] + public async Task GetSpec_ReturnsCorrectResponse() + { + // Arrange + var client = _factory.CreateClient(); + + // Act + var response = await client.GetAsync("/api-docs/openapi.json"); + + // Assert + response.EnsureSuccessStatusCode(); + Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString()); + + // Write out for publishing + var responseBody = await response.Content.ReadAsStringAsync(); + string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json")); + _outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath); + File.WriteAllText(outputPath, responseBody); + } + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs new file mode 100644 index 0000000000..4e5d0fcb63 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs @@ -0,0 +1,55 @@ +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; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Integration.Tests +{ + /// + /// Implementation of the abstract class. + /// + public class TestAppHost : CoreAppHost + { + /// + /// Initializes a new instance of the class. + /// + /// 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) + { + } + + /// + protected override IEnumerable GetAssembliesWithPartsInternal() + { + foreach (var a in base.GetAssembliesWithPartsInternal()) + { + yield return a; + } + + yield return typeof(TestPlugin).Assembly; + } + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/TestPage.html b/tests/Jellyfin.Server.Integration.Tests/TestPage.html new file mode 100644 index 0000000000..8037af8a60 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/TestPage.html @@ -0,0 +1,9 @@ + + + + TestPlugin + + +

This is a Test Page.

+ + diff --git a/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs b/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs new file mode 100644 index 0000000000..1d67ac4870 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs @@ -0,0 +1,43 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace Jellyfin.Server.Integration.Tests +{ + public class TestPlugin : BasePlugin, IHasWebPages + { + public TestPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + public static TestPlugin? Instance { get; private set; } + + public override Guid Id => new Guid("2d350a13-0bf7-4b61-859c-d5e601b5facf"); + + public override string Name => nameof(TestPlugin); + + public override string Description => "Server test Plugin."; + + public IEnumerable GetPages() + { + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = GetType().Namespace + ".TestPage.html" + }; + + yield return new PluginPageInfo + { + Name = "BrokenPage", + EmbeddedResourcePath = GetType().Namespace + ".foobar" + }; + } + } +} 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.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs new file mode 100644 index 0000000000..0b714e80a2 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs @@ -0,0 +1,88 @@ +using System; +using System.Globalization; +using System.Text; +using Jellyfin.Networking.Configuration; +using Jellyfin.Networking.Manager; +using Jellyfin.Server.Extensions; +using MediaBrowser.Common.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Tests +{ + public class ParseNetworkTests + { + /// + /// Order of the result has always got to be hosts, then networks. + /// + /// IP4 enabled. + /// IP6 enabled. + /// List to parse. + /// What it should match. + [Theory] + // [InlineData(true, true, "192.168.0.0/16,www.yahoo.co.uk", "::ffff:212.82.100.150,::ffff:192.168.0.0/16")] <- fails on Max. www.yahoo.co.uk resolves to a different ip address. + // [InlineData(true, false, "192.168.0.0/16,www.yahoo.co.uk", "212.82.100.150,192.168.0.0/16")] + [InlineData(true, true, "192.168.t,127.0.0.1,1234.1232.12.1234", "::ffff:127.0.0.1")] + [InlineData(true, false, "192.168.x,127.0.0.1,1234.1232.12.1234", "127.0.0.1")] + [InlineData(true, true, "::1", "::1/128")] + public void TestNetworks(bool ip4, bool ip6, string hostList, string match) + { + using var nm = CreateNetworkManager(); + + var settings = new NetworkConfiguration + { + EnableIPV4 = ip4, + EnableIPV6 = ip6 + }; + + var result = match + ","; + ForwardedHeadersOptions options = new ForwardedHeadersOptions(); + + // Need this here as ::1 and 127.0.0.1 are in them by default. + options.KnownProxies.Clear(); + options.KnownNetworks.Clear(); + + ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(','), options); + + var sb = new StringBuilder(); + foreach (var item in options.KnownProxies) + { + sb.Append(item) + .Append(','); + } + + foreach (var item in options.KnownNetworks) + { + sb.Append(item.Prefix) + .Append('/') + .Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture)) + .Append(','); + } + + Assert.Equal(sb.ToString(), result); + } + + private static IConfigurationManager GetMockConfig(NetworkConfiguration conf) + { + var configManager = new Mock + { + CallBase = true + }; + configManager.Setup(x => x.GetConfiguration(It.IsAny())).Returns(conf); + return configManager.Object; + } + + private static NetworkManager CreateNetworkManager() + { + var conf = new NetworkConfiguration() + { + EnableIPV6 = true, + EnableIPV4 = true, + }; + + return new NetworkManager(GetMockConfig(conf), new NullLogger()); + } + } +} -- cgit v1.2.3 From b9577d0fd986fcadd656a9438eba81f49f375ad8 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 2 Mar 2021 02:57:36 +0100 Subject: Check for specific status code instead of success --- .../Controllers/BrandingControllerTests.cs | 3 ++- .../Controllers/DashboardControllerTests.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'tests') 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); -- cgit v1.2.3 From 37eb7d6d49b35067c6711ba6f74a64242da419b0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 5 Mar 2021 12:34:22 +0100 Subject: Perform static initialization only once --- .../Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'tests') 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(); -- cgit v1.2.3 From a618d6053f78664ecb27cb01216ddb21833f5f89 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Mar 2021 14:09:28 +0100 Subject: Move TestPluginWithoutPages to the correct project --- tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs | 27 ---------------------- .../TestPluginWithoutPages.cs | 27 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 27 deletions(-) delete mode 100644 tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs create mode 100644 tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs (limited to 'tests') diff --git a/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs b/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs deleted file mode 100644 index 2d2f78a981..0000000000 --- a/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs +++ /dev/null @@ -1,27 +0,0 @@ -#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.Server.Integration.Tests/TestPluginWithoutPages.cs b/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs new file mode 100644 index 0000000000..ac10c47845 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.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.Server.Integration.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."; + } +} -- cgit v1.2.3 From 3824c09e774f35df18f37b56e2fc6101dc2022fe Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 10 Mar 2021 10:47:35 +0100 Subject: fix multiversion eligibility check for complex folder names --- Emby.Naming/Video/VideoListResolver.cs | 17 ++++++++++------- .../Jellyfin.Naming.Tests/Video/MultiVersionTests.cs | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) (limited to 'tests') 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() { -- cgit v1.2.3 From 954148eb6de1f276fb584aa70b384babea0e58ce Mon Sep 17 00:00:00 2001 From: David Date: Wed, 10 Mar 2021 17:04:15 +0100 Subject: Fix Radarr url nfo files --- MediaBrowser.XbmcMetadata/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 (limited to 'tests') 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