From 064a9cedbd205594fa529ff996b2cd359c5be07c Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Mon, 14 Dec 2020 13:07:39 +0100 Subject: [PATCH 001/612] No htlml encoding on server side --- .../HttpServer/Security/AuthorizationContext.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 024404ceb..ce2644b8d 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -275,17 +275,11 @@ namespace Emby.Server.Implementations.HttpServer.Security if (param.Length == 2) { - var value = NormalizeValue(param[1].Trim('"')); - result[param[0]] = value; + result[param[0]] = param[1].Trim('"'); } } return result; } - - private static string NormalizeValue(string value) - { - return string.IsNullOrEmpty(value) ? value : WebUtility.HtmlEncode(value); - } } } From 13bc57ecc39ebd485634f64f90ed526b134a521c Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Mon, 14 Dec 2020 13:08:07 +0100 Subject: [PATCH 002/612] No need to double check param length --- .../HttpServer/Security/AuthorizationContext.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index ce2644b8d..f335aa5c5 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -273,10 +273,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { var param = item.Trim().Split('=', 2); - if (param.Length == 2) - { - result[param[0]] = param[1].Trim('"'); - } + result[param[0]] = param[1].Trim('"'); } return result; From 305e5ebaf40d02f15664c49c26391531a548608d Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Mon, 14 Dec 2020 13:14:18 +0100 Subject: [PATCH 003/612] Allow commas in auth values when wappred in a double quote --- .../HttpServer/Security/AuthorizationContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index f335aa5c5..354e03715 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -265,7 +265,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // Remove uptil the first space authorizationHeader = parts[1]; - parts = authorizationHeader.Split(','); + parts = authorizationHeader.Split("\",\""); var result = new Dictionary(StringComparer.OrdinalIgnoreCase); From b611a108f89ab77b219aecc6d6155540f772cf32 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Mon, 14 Dec 2020 13:15:21 +0100 Subject: [PATCH 004/612] -fix split on comma and double quotes --- .../HttpServer/Security/AuthorizationContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 354e03715..5d85facab 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -265,7 +265,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // Remove uptil the first space authorizationHeader = parts[1]; - parts = authorizationHeader.Split("\",\""); + parts = authorizationHeader.Split("\","); var result = new Dictionary(StringComparer.OrdinalIgnoreCase); From 6e2cfc6569044251a7968fdeb16f245cecc46fe6 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Mon, 14 Dec 2020 14:05:53 +0100 Subject: [PATCH 005/612] Url decode for auth value --- .../HttpServer/Security/AuthorizationContext.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 5d85facab..0f8011495 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -273,10 +273,16 @@ namespace Emby.Server.Implementations.HttpServer.Security { var param = item.Trim().Split('=', 2); - result[param[0]] = param[1].Trim('"'); + var value =param[1].Trim('"'); + result[param[0]] = value; } return result; } + + private static string NormalizeValue(string value) + { + return string.IsNullOrEmpty(value) ? value : WebUtility.UrlDecode(value); + } } } From c6eefaac0975c351cfb20ace1c67d837dc0b7393 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Tue, 15 Dec 2020 21:01:42 +0100 Subject: [PATCH 006/612] Added function to split the authorization header parts --- .../Security/AuthorizationContext.cs | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 0f8011495..74a678aa6 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -265,7 +265,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // Remove uptil the first space authorizationHeader = parts[1]; - parts = authorizationHeader.Split("\","); + parts = GetParts(authorizationHeader); var result = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -274,7 +274,7 @@ namespace Emby.Server.Implementations.HttpServer.Security var param = item.Trim().Split('=', 2); var value =param[1].Trim('"'); - result[param[0]] = value; + result[param[0]] = NormalizeValue(value); } return result; @@ -284,5 +284,46 @@ namespace Emby.Server.Implementations.HttpServer.Security { return string.IsNullOrEmpty(value) ? value : WebUtility.UrlDecode(value); } + + public static string[] GetParts(string authtorizationHeader) + { + var result = new List(); + var escapeChars = new[] {'"', ','}; + var escaped = false; + var authtorizationHeaderChars = authtorizationHeader.ToCharArray(); + var value = new List(); + + for(var i = 0; i < authtorizationHeaderChars.Length; i++) + { + if(!escapeChars.Contains(authtorizationHeaderChars[i])) + { + value = value.Append(authtorizationHeaderChars[i]).ToList(); + } + else + { + escaped = (!escaped) == (authtorizationHeaderChars[i] == '"'); + if(authtorizationHeaderChars[i] == ',') + { + if(escaped) + { + value = value.Append(authtorizationHeaderChars[i]).ToList(); + } + else + { + result.Add(new string(value.ToArray())); + value = new List(); + } + } + else + { + value = value.Append(authtorizationHeaderChars[i]).ToList(); + } + } + } + // Add last value + result.Add(new string(value.ToArray())); + + return result.ToArray(); + } } } From 7c7f2316faa36fbdd6044ae27e05823e317d167f Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Tue, 15 Dec 2020 21:06:47 +0100 Subject: [PATCH 007/612] Added comments --- .../HttpServer/Security/AuthorizationContext.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 74a678aa6..855c61682 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -285,6 +285,11 @@ namespace Emby.Server.Implementations.HttpServer.Security return string.IsNullOrEmpty(value) ? value : WebUtility.UrlDecode(value); } + /// + /// Get the authorization header components. + /// + /// The authorization header. + /// string public static string[] GetParts(string authtorizationHeader) { var result = new List(); @@ -301,6 +306,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } else { + // Applying a XOR logic to evaluate wether it is opening or closing a value escaped = (!escaped) == (authtorizationHeaderChars[i] == '"'); if(authtorizationHeaderChars[i] == ',') { @@ -310,6 +316,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } else { + // Meeting a comma after a closing escape char means the value is complete result.Add(new string(value.ToArray())); value = new List(); } From a03880b687cf1b69c25bd8d207b2ebfb0c420571 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Sat, 2 Jan 2021 18:18:47 +0100 Subject: [PATCH 008/612] Improve get auth header parts using substring --- .../Security/AuthorizationContext.cs | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 855c61682..8fa20aea9 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -273,7 +273,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { var param = item.Trim().Split('=', 2); - var value =param[1].Trim('"'); + var value = param[1].Trim('"'); result[param[0]] = NormalizeValue(value); } @@ -293,42 +293,37 @@ namespace Emby.Server.Implementations.HttpServer.Security public static string[] GetParts(string authtorizationHeader) { var result = new List(); - var escapeChars = new[] {'"', ','}; + var escapeChars = new[] { '"', ',' }; var escaped = false; - var authtorizationHeaderChars = authtorizationHeader.ToCharArray(); - var value = new List(); - - for(var i = 0; i < authtorizationHeaderChars.Length; i++) + int start = 0; + int i = 0; + while (i < authtorizationHeader.Length) { - if(!escapeChars.Contains(authtorizationHeaderChars[i])) + var token = authtorizationHeader[i]; + if (escapeChars.Contains(token)) { - value = value.Append(authtorizationHeaderChars[i]).ToList(); - } - else - { - // Applying a XOR logic to evaluate wether it is opening or closing a value - escaped = (!escaped) == (authtorizationHeaderChars[i] == '"'); - if(authtorizationHeaderChars[i] == ',') + // Applying a XOR logic to evaluate whether it is opening or closing a value + escaped = (!escaped) == (token == '"'); + if (token == ',' && !escaped) { - if(escaped) + // Meeting a comma after a closing escape char means the value is complete + if (start < i) { - value = value.Append(authtorizationHeaderChars[i]).ToList(); + result.Add(authtorizationHeader[start..(i)]); } - else - { - // Meeting a comma after a closing escape char means the value is complete - result.Add(new string(value.ToArray())); - value = new List(); - } - } - else - { - value = value.Append(authtorizationHeaderChars[i]).ToList(); + + start = i + 1; } } + + i++; } + // Add last value - result.Add(new string(value.ToArray())); + if (start < i) + { + result.Add(authtorizationHeader[start..(i)]); + } return result.ToArray(); } From 452af30511a80e99e22794fc8009d7a6a9b9c904 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Sun, 3 Jan 2021 19:32:58 +0100 Subject: [PATCH 009/612] Added UrlDecode for authorization parts --- .../HttpServer/Security/AuthorizationContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 8fa20aea9..6b4588dbc 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -309,7 +309,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // Meeting a comma after a closing escape char means the value is complete if (start < i) { - result.Add(authtorizationHeader[start..(i)]); + result.Add(WebUtility.UrlDecode(authtorizationHeader[start..(i)])); } start = i + 1; @@ -322,7 +322,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // Add last value if (start < i) { - result.Add(authtorizationHeader[start..(i)]); + result.Add(WebUtility.UrlDecode(authtorizationHeader[start..(i)])); } return result.ToArray(); From 357429b8ea232619cdbdeda44e5f4b94c7a40f26 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 8 Jan 2021 14:48:08 +0100 Subject: [PATCH 010/612] Add .nfo ratings tag --- .../Parsers/BaseNfoParser.cs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index c287113c5..2a64bb243 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -624,6 +624,21 @@ namespace MediaBrowser.XbmcMetadata.Parsers break; } + case "ratings": + { + if (!reader.IsEmptyElement) + { + using var subtree = reader.ReadSubtree(); + FetchFromRatingsNode(subtree, item); + } + else + { + reader.Read(); + } + + break; + } + case "aired": case "formed": case "premiered": @@ -866,6 +881,90 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } + private void FetchFromRatingsNode(XmlReader reader, T item) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "rating": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + + var ratingName = reader.GetAttribute("name"); + + using var subtree = reader.ReadSubtree(); + FetchFromRatingNode(subtree, item, ratingName); + + break; + } + + default: + reader.Skip(); + break; + } + } + else + { + reader.Read(); + } + } + } + + private void FetchFromRatingNode(XmlReader reader, T item, string? ratingName) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "value": + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + if (float.TryParse(val.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var ratingValue)) + { + // if ratingName contains tomato --> assume critic rating + if (ratingName != null && ratingName.Contains("tomato", StringComparison.InvariantCultureIgnoreCase)) + { + item.CriticRating = ratingValue; + } + else + { + item.CommunityRating = ratingValue; + } + } + } + + break; + default: + reader.Skip(); + break; + } + } + else + { + reader.Read(); + } + } + } + /// /// Gets the persons from XML node. /// From 262c6ae249b91d3fb986994a369cb83aafeb1ce8 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 9 Jan 2021 15:33:39 +0100 Subject: [PATCH 011/612] Remove ',' hack --- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 2a64bb243..2954868fc 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -938,10 +938,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (float.TryParse(val.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var ratingValue)) + if (float.TryParse(val, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var ratingValue)) { // if ratingName contains tomato --> assume critic rating - if (ratingName != null && ratingName.Contains("tomato", StringComparison.InvariantCultureIgnoreCase)) + if (ratingName != null && ratingName.Contains("tomato", StringComparison.OrdinalIgnoreCase)) { item.CriticRating = ratingValue; } From cf9a03790b4b5c77411b03a73df112f060c0ed02 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 20 Jan 2021 20:32:45 +0100 Subject: [PATCH 012/612] Check rating name for "audience" --- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 2954868fc..2e1fa4375 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -941,7 +941,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (float.TryParse(val, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var ratingValue)) { // if ratingName contains tomato --> assume critic rating - if (ratingName != null && ratingName.Contains("tomato", StringComparison.OrdinalIgnoreCase)) + if (ratingName != null && + ratingName.Contains("tomato", StringComparison.OrdinalIgnoreCase) && + !ratingName.Contains("audience", StringComparison.OrdinalIgnoreCase)) { item.CriticRating = ratingValue; } From 991adc8efe76dbe07faaea1a96de0e746e0f2386 Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sat, 13 Feb 2021 15:28:37 +0100 Subject: [PATCH 013/612] Fix BaseItemKind conversion for PlaylistsFolder Return the correct ClientTypeName to allow Enum Parse Added dynamic unit tests to ensure all BaseItem concrete descend --- CONTRIBUTORS.md | 1 + .../Playlists/ManualPlaylistsFolder.cs | 5 ++ MediaBrowser.sln | 9 ++- .../BaseItemKindTests.cs | 62 +++++++++++++++++++ .../Jellyfin.Server.Tests.csproj | 48 ++++++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/Jellyfin.Server.Tests/BaseItemKindTests.cs create mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1200275d5..89788b234 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -207,3 +207,4 @@ - [Tim Hobbs](https://github.com/timhobbs) - [SvenVandenbrande](https://github.com/SvenVandenbrande) - [olsh](https://github.com/olsh) + - [lbenini] (https://github.com/lbenini) diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 358606b0d..4160f3a50 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -49,5 +49,10 @@ namespace Emby.Server.Implementations.Playlists query.Parent = null; return LibraryManager.GetItemsResult(query); } + + public override string GetClientTypeName() + { + return "ManualPlaylistsFolder"; + } } } diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 4e6687cce..c16e6032e 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -72,7 +72,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{B26F671A-D5C0-4461-B7C3-324EB167E4B3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -200,6 +202,10 @@ Global {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -214,6 +220,7 @@ Global {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} + {B26F671A-D5C0-4461-B7C3-324EB167E4B3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs b/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs new file mode 100644 index 000000000..282760bf9 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Entities; +using Xunit; + +namespace Jellyfin.Server.Tests +{ + public class BaseItemKindTests + { + [Theory] + [ClassData(typeof(GetBaseItemDescendant))] + public void BaseKindEnumTest(Type baseItemDescendantType) + { + var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes); + + Assert.NotNull(defaultConstructor); + if (defaultConstructor != null) + { + var instance = (BaseItem)defaultConstructor.Invoke(null); + var exception = Record.Exception(() => instance.GetBaseItemKind()); + Assert.Null(exception); + } + } + + private static bool IsProjectAssemblyName(string? name) + { + if (name == null) + { + return false; + } + + return name.Contains("Jellyfin", StringComparison.InvariantCulture) + || name.Contains("Emby", StringComparison.InvariantCulture) + || name.Contains("MediaBrowser", StringComparison.InvariantCulture) + || name.Contains("RSSDP", StringComparison.InvariantCulture); + } + + private class GetBaseItemDescendant : IEnumerable + { + public IEnumerator GetEnumerator() + { + var projectAssemblies = AppDomain.CurrentDomain.GetAssemblies() + .Where(x => IsProjectAssemblyName(x.FullName)); + + foreach (var projectAssembly in projectAssemblies) + { + var baseItemDescendantTypes = projectAssembly.GetTypes() + .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(BaseItem))); + + foreach (var descendantType in baseItemDescendantTypes) + { + yield return new object?[] { descendantType }; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } +} 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 000000000..55f600502 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -0,0 +1,48 @@ + + + + + {0FD15BDA-FA63-4FFF-9E6B-781F20DA88D9} + + + + net5.0 + false + true + enable + Jellyfin.Server.Tests + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + ../jellyfin-tests.ruleset + + + From c8395899ba8da1908b0cef54eff266c32baebc97 Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sat, 13 Feb 2021 19:40:15 +0100 Subject: [PATCH 014/612] Moved test to Jellyfin.Server.Implementation.Tests as by review Aligned code base to review comments: Jellyfin.Server.Implementation.Tests is the correct place --- MediaBrowser.sln | 7 --- .../BaseItem}/BaseItemKindTests.cs | 6 +-- .../Jellyfin.Server.Tests.csproj | 48 ------------------- 3 files changed, 3 insertions(+), 58 deletions(-) rename tests/{Jellyfin.Server.Tests => Jellyfin.Server.Implementations.Tests/BaseItem}/BaseItemKindTests.cs (88%) delete mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj diff --git a/MediaBrowser.sln b/MediaBrowser.sln index c16e6032e..122421f34 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -74,8 +74,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{B26F671A-D5C0-4461-B7C3-324EB167E4B3}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -202,10 +200,6 @@ Global {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -220,7 +214,6 @@ Global {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} - {B26F671A-D5C0-4461-B7C3-324EB167E4B3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs similarity index 88% rename from tests/Jellyfin.Server.Tests/BaseItemKindTests.cs rename to tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs index 282760bf9..3f56c82f4 100644 --- a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs @@ -5,7 +5,7 @@ using System.Linq; using MediaBrowser.Controller.Entities; using Xunit; -namespace Jellyfin.Server.Tests +namespace Jellyfin.Server.Implementations.Tests.BaseItem { public class BaseItemKindTests { @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Tests Assert.NotNull(defaultConstructor); if (defaultConstructor != null) { - var instance = (BaseItem)defaultConstructor.Invoke(null); + var instance = (MediaBrowser.Controller.Entities.BaseItem)defaultConstructor.Invoke(null); var exception = Record.Exception(() => instance.GetBaseItemKind()); Assert.Null(exception); } @@ -47,7 +47,7 @@ namespace Jellyfin.Server.Tests foreach (var projectAssembly in projectAssemblies) { var baseItemDescendantTypes = projectAssembly.GetTypes() - .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(BaseItem))); + .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); foreach (var descendantType in baseItemDescendantTypes) { diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj deleted file mode 100644 index 55f600502..000000000 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - - {0FD15BDA-FA63-4FFF-9E6B-781F20DA88D9} - - - - net5.0 - false - true - enable - Jellyfin.Server.Tests - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - ../jellyfin-tests.ruleset - - - From c4d142eda15644d045de884984cb644d7948837e Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sat, 13 Feb 2021 15:28:37 +0100 Subject: [PATCH 015/612] Fix BaseItemKind conversion for PlaylistsFolder Return the correct ClientTypeName to allow Enum Parse Added dynamic unit tests to ensure all BaseItem concrete descend --- CONTRIBUTORS.md | 1 + .../Playlists/ManualPlaylistsFolder.cs | 5 ++ Jellyfin.sln | 9 ++- .../BaseItemKindTests.cs | 62 +++++++++++++++++++ .../Jellyfin.Server.Tests.csproj | 48 ++++++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/Jellyfin.Server.Tests/BaseItemKindTests.cs create mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1200275d5..89788b234 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -207,3 +207,4 @@ - [Tim Hobbs](https://github.com/timhobbs) - [SvenVandenbrande](https://github.com/SvenVandenbrande) - [olsh](https://github.com/olsh) + - [lbenini] (https://github.com/lbenini) diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 358606b0d..4160f3a50 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -49,5 +49,10 @@ namespace Emby.Server.Implementations.Playlists query.Parent = null; return LibraryManager.GetItemsResult(query); } + + public override string GetClientTypeName() + { + return "ManualPlaylistsFolder"; + } } } diff --git a/Jellyfin.sln b/Jellyfin.sln index 4e6687cce..c16e6032e 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -72,7 +72,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{B26F671A-D5C0-4461-B7C3-324EB167E4B3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -200,6 +202,10 @@ Global {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -214,6 +220,7 @@ Global {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} + {B26F671A-D5C0-4461-B7C3-324EB167E4B3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs b/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs new file mode 100644 index 000000000..282760bf9 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Entities; +using Xunit; + +namespace Jellyfin.Server.Tests +{ + public class BaseItemKindTests + { + [Theory] + [ClassData(typeof(GetBaseItemDescendant))] + public void BaseKindEnumTest(Type baseItemDescendantType) + { + var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes); + + Assert.NotNull(defaultConstructor); + if (defaultConstructor != null) + { + var instance = (BaseItem)defaultConstructor.Invoke(null); + var exception = Record.Exception(() => instance.GetBaseItemKind()); + Assert.Null(exception); + } + } + + private static bool IsProjectAssemblyName(string? name) + { + if (name == null) + { + return false; + } + + return name.Contains("Jellyfin", StringComparison.InvariantCulture) + || name.Contains("Emby", StringComparison.InvariantCulture) + || name.Contains("MediaBrowser", StringComparison.InvariantCulture) + || name.Contains("RSSDP", StringComparison.InvariantCulture); + } + + private class GetBaseItemDescendant : IEnumerable + { + public IEnumerator GetEnumerator() + { + var projectAssemblies = AppDomain.CurrentDomain.GetAssemblies() + .Where(x => IsProjectAssemblyName(x.FullName)); + + foreach (var projectAssembly in projectAssemblies) + { + var baseItemDescendantTypes = projectAssembly.GetTypes() + .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(BaseItem))); + + foreach (var descendantType in baseItemDescendantTypes) + { + yield return new object?[] { descendantType }; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } +} 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 000000000..55f600502 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -0,0 +1,48 @@ + + + + + {0FD15BDA-FA63-4FFF-9E6B-781F20DA88D9} + + + + net5.0 + false + true + enable + Jellyfin.Server.Tests + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + ../jellyfin-tests.ruleset + + + From 2f8d15ed08015e58fea0842fea9591111b823a9c Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sat, 13 Feb 2021 19:40:15 +0100 Subject: [PATCH 016/612] Moved test to Jellyfin.Server.Implementation.Tests as by review Aligned code base to review comments: Jellyfin.Server.Implementation.Tests is the correct place --- Jellyfin.sln | 7 --- .../BaseItem}/BaseItemKindTests.cs | 6 +-- .../Jellyfin.Server.Tests.csproj | 48 ------------------- 3 files changed, 3 insertions(+), 58 deletions(-) rename tests/{Jellyfin.Server.Tests => Jellyfin.Server.Implementations.Tests/BaseItem}/BaseItemKindTests.cs (88%) delete mode 100644 tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj diff --git a/Jellyfin.sln b/Jellyfin.sln index c16e6032e..122421f34 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -74,8 +74,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{B26F671A-D5C0-4461-B7C3-324EB167E4B3}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -202,10 +200,6 @@ Global {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B26F671A-D5C0-4461-B7C3-324EB167E4B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -220,7 +214,6 @@ Global {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} - {B26F671A-D5C0-4461-B7C3-324EB167E4B3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs similarity index 88% rename from tests/Jellyfin.Server.Tests/BaseItemKindTests.cs rename to tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs index 282760bf9..3f56c82f4 100644 --- a/tests/Jellyfin.Server.Tests/BaseItemKindTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/BaseItem/BaseItemKindTests.cs @@ -5,7 +5,7 @@ using System.Linq; using MediaBrowser.Controller.Entities; using Xunit; -namespace Jellyfin.Server.Tests +namespace Jellyfin.Server.Implementations.Tests.BaseItem { public class BaseItemKindTests { @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Tests Assert.NotNull(defaultConstructor); if (defaultConstructor != null) { - var instance = (BaseItem)defaultConstructor.Invoke(null); + var instance = (MediaBrowser.Controller.Entities.BaseItem)defaultConstructor.Invoke(null); var exception = Record.Exception(() => instance.GetBaseItemKind()); Assert.Null(exception); } @@ -47,7 +47,7 @@ namespace Jellyfin.Server.Tests foreach (var projectAssembly in projectAssemblies) { var baseItemDescendantTypes = projectAssembly.GetTypes() - .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(BaseItem))); + .Where(targetType => targetType.IsClass && !targetType.IsAbstract && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem))); foreach (var descendantType in baseItemDescendantTypes) { diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj deleted file mode 100644 index 55f600502..000000000 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - - {0FD15BDA-FA63-4FFF-9E6B-781F20DA88D9} - - - - net5.0 - false - true - enable - Jellyfin.Server.Tests - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - ../jellyfin-tests.ruleset - - - From 078b6244ee060b2c5caddc3ba8a60633c4e95054 Mon Sep 17 00:00:00 2001 From: Luca Benini Date: Sun, 14 Feb 2021 12:46:28 +0100 Subject: [PATCH 017/612] Restored GUID in Jellyfin.XbmcMetadata.Tests Restored the project type guid as by review See https://github.com/dotnet/project-system/issues/1821 --- Jellyfin.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index 122421f34..4e6687cce 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -72,7 +72,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 9413d974f3f234dd3fc2225d318d7fced7257912 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 25 Feb 2021 20:38:18 -0500 Subject: [PATCH 018/612] Switch to using declarations in MediaBrowser.Providers --- .../Plugins/MusicBrainz/ArtistProvider.cs | 82 ++++----- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 174 ++++++++---------- .../Studios/StudiosImageProvider.cs | 26 ++- .../Subtitles/SubtitleManager.cs | 50 +++-- 4 files changed, 149 insertions(+), 183 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs index ce9392402..2eab95294 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs @@ -69,58 +69,52 @@ namespace MediaBrowser.Providers.Music private IEnumerable GetResultsFromResponse(Stream stream) { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) + using var oReader = new StreamReader(stream, Encoding.UTF8); + var settings = new XmlReaderSettings() { - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; - using (var reader = XmlReader.Create(oReader, settings)) - { - reader.MoveToContent(); - reader.Read(); + using var reader = XmlReader.Create(oReader, settings); + reader.MoveToContent(); + reader.Read(); - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) { - if (reader.NodeType == XmlNodeType.Element) + case "artist-list": { - switch (reader.Name) + if (reader.IsEmptyElement) { - case "artist-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistList(subReader).ToList(); - } - } - - default: - { - reader.Skip(); - break; - } + reader.Read(); + continue; } + + using var subReader = reader.ReadSubtree(); + return ParseArtistList(subReader).ToList(); } - else + + default: { - reader.Read(); + reader.Skip(); + break; } } - - return Enumerable.Empty(); + } + else + { + reader.Read(); } } + + return Enumerable.Empty(); } private IEnumerable ParseArtistList(XmlReader reader) @@ -145,13 +139,11 @@ namespace MediaBrowser.Providers.Music var mbzId = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) + using var subReader = reader.ReadSubtree(); + var artist = ParseArtist(subReader, mbzId); + if (artist != null) { - var artist = ParseArtist(subReader, mbzId); - if (artist != null) - { - yield return artist; - } + yield return artist; } break; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index e5ad0f3e0..0023d5959 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -128,53 +128,49 @@ namespace MediaBrowser.Providers.Music private IEnumerable GetResultsFromResponse(Stream stream) { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) + using var oReader = new StreamReader(stream, Encoding.UTF8); + var settings = new XmlReaderSettings() { - var settings = new XmlReaderSettings() + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using var reader = XmlReader.Create(oReader, settings); + var results = ReleaseResult.Parse(reader); + + return results.Select(i => + { + var result = new RemoteSearchResult { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true + Name = i.Title, + ProductionYear = i.Year }; - using (var reader = XmlReader.Create(oReader, settings)) + if (i.Artists.Count > 0) { - var results = ReleaseResult.Parse(reader); - - return results.Select(i => + result.AlbumArtist = new RemoteSearchResult { - var result = new RemoteSearchResult - { - Name = i.Title, - ProductionYear = i.Year - }; + SearchProviderName = Name, + Name = i.Artists[0].Item1 + }; - if (i.Artists.Count > 0) - { - result.AlbumArtist = new RemoteSearchResult - { - SearchProviderName = Name, - Name = i.Artists[0].Item1 - }; - - result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2); - } - - if (!string.IsNullOrWhiteSpace(i.ReleaseId)) - { - result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId); - } - - if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) - { - result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId); - } - - return result; - }); + result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2); } - } + + if (!string.IsNullOrWhiteSpace(i.ReleaseId)) + { + result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId); + } + + if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) + { + result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId); + } + + return result; + }); } /// @@ -339,10 +335,8 @@ namespace MediaBrowser.Providers.Music continue; } - using (var subReader = reader.ReadSubtree()) - { - return ParseReleaseList(subReader).ToList(); - } + using var subReader = reader.ReadSubtree(); + return ParseReleaseList(subReader).ToList(); } default: @@ -383,13 +377,11 @@ namespace MediaBrowser.Providers.Music var releaseId = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) + using var subReader = reader.ReadSubtree(); + var release = ParseRelease(subReader, releaseId); + if (release != null) { - var release = ParseRelease(subReader, releaseId); - if (release != null) - { - yield return release; - } + yield return release; } break; @@ -460,14 +452,12 @@ namespace MediaBrowser.Providers.Music case "artist-credit": { - using (var subReader = reader.ReadSubtree()) - { - var artist = ParseArtistCredit(subReader); + using var subReader = reader.ReadSubtree(); + var artist = ParseArtistCredit(subReader); - if (!string.IsNullOrEmpty(artist.Item1)) - { - result.Artists.Add(artist); - } + if (!string.IsNullOrEmpty(artist.Item1)) + { + result.Artists.Add(artist); } break; @@ -505,12 +495,10 @@ namespace MediaBrowser.Providers.Music switch (reader.Name) { case "name-credit": - { - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistNameCredit(subReader); - } - } + { + using var subReader = reader.ReadSubtree(); + return ParseArtistNameCredit(subReader); + } default: { @@ -545,10 +533,8 @@ namespace MediaBrowser.Providers.Music case "artist": { var id = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistArtistCredit(subReader, id); - } + using var subReader = reader.ReadSubtree(); + return ParseArtistArtistCredit(subReader, id); } default: @@ -647,47 +633,43 @@ namespace MediaBrowser.Providers.Music IgnoreComments = true }; - using (var reader = XmlReader.Create(oReader, settings)) + using var reader = XmlReader.Create(oReader, settings); + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + if (reader.NodeType == XmlNodeType.Element) { - if (reader.NodeType == XmlNodeType.Element) + switch (reader.Name) { - switch (reader.Name) + case "release-group-list": { - case "release-group-list": + if (reader.IsEmptyElement) { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) - { - return GetFirstReleaseGroupId(subReader); - } + reader.Read(); + continue; } - default: - { - reader.Skip(); - break; - } + using var subReader = reader.ReadSubtree(); + return GetFirstReleaseGroupId(subReader); + } + + default: + { + reader.Skip(); + break; } } - else - { - reader.Read(); - } } - - return null; + else + { + reader.Read(); + } } + + return null; } private string GetFirstReleaseGroupId(XmlReader reader) diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 5fcf6d9aa..9268ed714 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -171,25 +171,21 @@ namespace MediaBrowser.Providers.Studios public IEnumerable GetAvailableImages(string file) { - using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) + using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); + using var reader = new StreamReader(fileStream); + var lines = new List(); + + while (!reader.EndOfStream) { - using (var reader = new StreamReader(fileStream)) + var text = reader.ReadLine(); + + if (!string.IsNullOrWhiteSpace(text)) { - var lines = new List(); - - while (!reader.EndOfStream) - { - var text = reader.ReadLine(); - - if (!string.IsNullOrWhiteSpace(text)) - { - lines.Add(text); - } - } - - return lines; + lines.Add(text); } } + + return lines; } } } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 47e9d5ee8..adee8d6bc 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -187,31 +187,29 @@ namespace MediaBrowser.Providers.Subtitles { var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia; - using (var stream = response.Stream) - using (var memoryStream = new MemoryStream()) + using var stream = response.Stream; + using var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream).ConfigureAwait(false); + memoryStream.Position = 0; + + var savePaths = new List(); + var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); + + if (response.IsForced) { - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - - var savePaths = new List(); - var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); - - if (response.IsForced) - { - saveFileName += ".forced"; - } - - saveFileName += "." + response.Format.ToLowerInvariant(); - - if (saveInMediaFolder) - { - savePaths.Add(Path.Combine(video.ContainingFolderPath, saveFileName)); - } - - savePaths.Add(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); - - await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); + saveFileName += ".forced"; } + + saveFileName += "." + response.Format.ToLowerInvariant(); + + if (saveInMediaFolder) + { + savePaths.Add(Path.Combine(video.ContainingFolderPath, saveFileName)); + } + + savePaths.Add(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); + + await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); } private async Task TrySaveToFiles(Stream stream, List savePaths) @@ -228,10 +226,8 @@ namespace MediaBrowser.Providers.Subtitles { Directory.CreateDirectory(Path.GetDirectoryName(savePath)); - using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, FileStreamBufferSize, true)) - { - await stream.CopyToAsync(fs).ConfigureAwait(false); - } + using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, FileStreamBufferSize, true); + await stream.CopyToAsync(fs).ConfigureAwait(false); return; } From 414e918c01a2458aa28c654332a8bad4f79cfa90 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 26 Feb 2021 14:30:00 +0000 Subject: [PATCH 019/612] Optimized and added test --- .../Security/AuthorizationContext.cs | 39 +++++++------------ .../DefaultAuthorizationHandlerTests.cs | 14 ++++++- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 6b4588dbc..7a83b7213 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -263,26 +263,9 @@ namespace Emby.Server.Implementations.HttpServer.Security return null; } - // Remove uptil the first space + // Remove up until the first space authorizationHeader = parts[1]; - parts = GetParts(authorizationHeader); - - var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var item in parts) - { - var param = item.Trim().Split('=', 2); - - var value = param[1].Trim('"'); - result[param[0]] = NormalizeValue(value); - } - - return result; - } - - private static string NormalizeValue(string value) - { - return string.IsNullOrEmpty(value) ? value : WebUtility.UrlDecode(value); + return GetParts(authorizationHeader); } /// @@ -290,13 +273,15 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The authorization header. /// string - public static string[] GetParts(string authtorizationHeader) + public static Dictionary GetParts(string authtorizationHeader) { - var result = new List(); + var result = new Dictionary(); var escapeChars = new[] { '"', ',' }; var escaped = false; int start = 0; int i = 0; + string key = string.Empty; + while (i < authtorizationHeader.Length) { var token = authtorizationHeader[i]; @@ -309,12 +294,18 @@ namespace Emby.Server.Implementations.HttpServer.Security // Meeting a comma after a closing escape char means the value is complete if (start < i) { - result.Add(WebUtility.UrlDecode(authtorizationHeader[start..(i)])); + result[key] = WebUtility.UrlDecode(authtorizationHeader[start..i].Trim('"')); + key = string.Empty; } start = i + 1; } } + else if (!escaped && token == '=') + { + key = authtorizationHeader[start.. i]; + start = i + 1; + } i++; } @@ -322,10 +313,10 @@ namespace Emby.Server.Implementations.HttpServer.Security // Add last value if (start < i) { - result.Add(WebUtility.UrlDecode(authtorizationHeader[start..(i)])); + result[key] = WebUtility.UrlDecode(authtorizationHeader[start..i].Trim('"')); } - return result.ToArray(); + return result; } } } diff --git a/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs index a62fd8d5a..5387922ab 100644 --- a/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; +using Emby.Server.Implementations.HttpServer.Security; using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; using Jellyfin.Api.Constants; using MediaBrowser.Common.Configuration; @@ -49,5 +50,16 @@ namespace Jellyfin.Api.Tests.Auth.DefaultAuthorizationPolicy await _sut.HandleAsync(context); Assert.True(context.HasSucceeded); } + + [Theory] + [InlineData("x=\"123,123\",y=\"123\"", "x", "123,123")] + [InlineData("x=\"ab\"", "x", "ab")] + [InlineData("param=Hörbücher", "param", "Hörbücher")] + [InlineData("param=%22%Hörbücher", "param", "\"%Hörbücher")] + public void TestAuthHeaders(string input, string key, string value) + { + var dict = AuthorizationContext.GetParts(input); + Assert.True(string.Equals(dict[key], value, System.StringComparison.Ordinal)); + } } } From 415b03d719e054a3cc6998d89c3f4a13806e0a9e Mon Sep 17 00:00:00 2001 From: Mason Tran Date: Wed, 24 Mar 2021 02:35:44 -0400 Subject: [PATCH 020/612] Delay starting services until after network is online --- debian/jellyfin.service | 2 +- fedora/jellyfin.service | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/jellyfin.service b/debian/jellyfin.service index f1a8f4652..c9d1a4d13 100644 --- a/debian/jellyfin.service +++ b/debian/jellyfin.service @@ -1,6 +1,6 @@ [Unit] Description = Jellyfin Media Server -After = network.target +After = network-online.target [Service] Type = simple diff --git a/fedora/jellyfin.service b/fedora/jellyfin.service index b092ebf2f..f706b0ad3 100644 --- a/fedora/jellyfin.service +++ b/fedora/jellyfin.service @@ -1,5 +1,5 @@ [Unit] -After=network.target +After=network-online.target Description=Jellyfin is a free software media system that puts you in control of managing and streaming your media. [Service] From 36669ff45146dd8d4dc063b6a6b1d057f2d33464 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Mar 2021 19:08:11 -0400 Subject: [PATCH 021/612] Use correct setter access modifiers --- Jellyfin.Data/Entities/AccessSchedule.cs | 8 ++-- Jellyfin.Data/Entities/ActivityLog.cs | 7 ++- .../Entities/CustomItemDisplayPreferences.cs | 4 +- Jellyfin.Data/Entities/DisplayPreferences.cs | 12 +++-- Jellyfin.Data/Entities/Group.cs | 16 +++---- Jellyfin.Data/Entities/HomeSection.cs | 7 ++- Jellyfin.Data/Entities/ImageInfo.cs | 8 ++-- .../Entities/ItemDisplayPreferences.cs | 4 +- Jellyfin.Data/Entities/Libraries/Artwork.cs | 8 ++-- Jellyfin.Data/Entities/Libraries/Book.cs | 8 ++-- .../Entities/Libraries/BookMetadata.cs | 6 +-- Jellyfin.Data/Entities/Libraries/Chapter.cs | 8 ++-- .../Entities/Libraries/Collection.cs | 11 +++-- .../Entities/Libraries/CollectionItem.cs | 2 +- Jellyfin.Data/Entities/Libraries/Company.cs | 16 +++---- .../Entities/Libraries/CustomItem.cs | 8 ++-- Jellyfin.Data/Entities/Libraries/Episode.cs | 8 ++-- Jellyfin.Data/Entities/Libraries/Genre.cs | 6 +-- .../Entities/Libraries/ItemMetadata.cs | 37 +++++++--------- Jellyfin.Data/Entities/Libraries/Library.cs | 6 +-- .../Entities/Libraries/LibraryItem.cs | 10 ++--- Jellyfin.Data/Entities/Libraries/MediaFile.cs | 12 +++-- .../Entities/Libraries/MediaFileStream.cs | 6 +-- .../Entities/Libraries/MetadataProvider.cs | 6 +-- .../Entities/Libraries/MetadataProviderId.cs | 6 +-- Jellyfin.Data/Entities/Libraries/Movie.cs | 8 ++-- .../Entities/Libraries/MovieMetadata.cs | 6 +-- .../Entities/Libraries/MusicAlbum.cs | 10 ++--- .../Entities/Libraries/MusicAlbumMetadata.cs | 6 +-- Jellyfin.Data/Entities/Libraries/Person.cs | 16 +++---- .../Entities/Libraries/PersonRole.cs | 14 +++--- Jellyfin.Data/Entities/Libraries/Photo.cs | 8 ++-- Jellyfin.Data/Entities/Libraries/Rating.cs | 6 +-- .../Entities/Libraries/RatingSource.cs | 6 +-- Jellyfin.Data/Entities/Libraries/Release.cs | 16 +++---- Jellyfin.Data/Entities/Libraries/Season.cs | 10 ++--- Jellyfin.Data/Entities/Libraries/Series.cs | 11 ++--- .../Entities/Libraries/SeriesMetadata.cs | 6 +-- Jellyfin.Data/Entities/Libraries/Track.cs | 8 ++-- Jellyfin.Data/Entities/Permission.cs | 10 ++--- Jellyfin.Data/Entities/Preference.cs | 10 ++--- Jellyfin.Data/Entities/User.cs | 44 +++++++------------ 42 files changed, 179 insertions(+), 246 deletions(-) diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs index 7974d3add..befc4ca02 100644 --- a/Jellyfin.Data/Entities/AccessSchedule.cs +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -26,20 +26,20 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id of this instance. + /// Gets the id of this instance. /// /// /// Identity, Indexed, Required. /// [XmlIgnore] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the id of the associated user. + /// Gets the id of the associated user. /// [XmlIgnore] - public Guid UserId { get; protected set; } + public Guid UserId { get; private set; } /// /// Gets or sets the day of week. diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs index e4534e8b5..1d1b86552 100644 --- a/Jellyfin.Data/Entities/ActivityLog.cs +++ b/Jellyfin.Data/Entities/ActivityLog.cs @@ -38,11 +38,10 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the identity of this instance. - /// This is the key in the backing database. + /// Gets the identity of this instance. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -120,7 +119,7 @@ namespace Jellyfin.Data.Entities /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs index cc46248c7..f12737f65 100644 --- a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs @@ -27,13 +27,13 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the Id. + /// Gets the Id. /// /// /// Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the user Id. diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index 64cd6812a..646961238 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -1,6 +1,4 @@ -#pragma warning disable CA2227 - -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -35,13 +33,13 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the Id. + /// Gets the Id. /// /// /// Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the user Id. @@ -145,8 +143,8 @@ namespace Jellyfin.Data.Entities public string? TvHome { get; set; } /// - /// Gets or sets the home sections. + /// Gets the home sections. /// - public virtual ICollection HomeSections { get; protected set; } + public virtual ICollection HomeSections { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index b14e22b7b..14da0bb15 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -33,12 +31,12 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id of this group. + /// Gets the id of this group. /// /// /// Identity, Indexed, Required. /// - public Guid Id { get; protected set; } + public Guid Id { get; private set; } /// /// Gets or sets the group's name. @@ -52,17 +50,17 @@ namespace Jellyfin.Data.Entities /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the group's permissions. + /// Gets a collection containing the group's permissions. /// - public virtual ICollection Permissions { get; protected set; } + public virtual ICollection Permissions { get; private set; } /// - /// Gets or sets a collection containing the group's preferences. + /// Gets a collection containing the group's preferences. /// - public virtual ICollection Preferences { get; protected set; } + public virtual ICollection Preferences { get; private set; } /// public bool HasPermission(PermissionKind kind) diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs index 5adc52491..e194aa537 100644 --- a/Jellyfin.Data/Entities/HomeSection.cs +++ b/Jellyfin.Data/Entities/HomeSection.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities @@ -10,13 +9,13 @@ namespace Jellyfin.Data.Entities public class HomeSection { /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity. Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the Id of the associated display preferences. diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs index e0c37047d..b5c7a1c12 100644 --- a/Jellyfin.Data/Entities/ImageInfo.cs +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -20,18 +20,18 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the user id. + /// Gets the user id. /// - public Guid? UserId { get; protected set; } + public Guid? UserId { get; private set; } /// /// Gets or sets the path of the image. diff --git a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs index 4bfeb2fa3..948126d0a 100644 --- a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs @@ -29,13 +29,13 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the Id. + /// Gets the id. /// /// /// Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the user Id. diff --git a/Jellyfin.Data/Entities/Libraries/Artwork.cs b/Jellyfin.Data/Entities/Libraries/Artwork.cs index 84a524de2..923525fc5 100644 --- a/Jellyfin.Data/Entities/Libraries/Artwork.cs +++ b/Jellyfin.Data/Entities/Libraries/Artwork.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -30,13 +28,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the path. @@ -58,7 +56,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Book.cs b/Jellyfin.Data/Entities/Libraries/Book.cs index aea3d58d5..a838686d0 100644 --- a/Jellyfin.Data/Entities/Libraries/Book.cs +++ b/Jellyfin.Data/Entities/Libraries/Book.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -21,11 +19,11 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets a collection containing the metadata for this book. + /// Gets a collection containing the metadata for this book. /// - public virtual ICollection BookMetadata { get; protected set; } + public virtual ICollection BookMetadata { get; private set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs index 1ff4327b0..4a350d200 100644 --- a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -26,9 +24,9 @@ namespace Jellyfin.Data.Entities.Libraries public long? Isbn { get; set; } /// - /// Gets or sets a collection of the publishers for this book. + /// Gets a collection of the publishers for this book. /// - public virtual ICollection Publishers { get; protected set; } + public virtual ICollection Publishers { get; private set; } /// public ICollection Companies => Publishers; diff --git a/Jellyfin.Data/Entities/Libraries/Chapter.cs b/Jellyfin.Data/Entities/Libraries/Chapter.cs index 11f53ae20..3d81f713d 100644 --- a/Jellyfin.Data/Entities/Libraries/Chapter.cs +++ b/Jellyfin.Data/Entities/Libraries/Chapter.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -29,13 +27,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -74,7 +72,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; protected set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Collection.cs b/Jellyfin.Data/Entities/Libraries/Collection.cs index 4253b7ecc..7de601969 100644 --- a/Jellyfin.Data/Entities/Libraries/Collection.cs +++ b/Jellyfin.Data/Entities/Libraries/Collection.cs @@ -1,5 +1,4 @@ #pragma warning disable CA1711 // Identifiers should not have incorrect suffix -#pragma warning disable CA2227 using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -22,13 +21,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -42,12 +41,12 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing this collection's items. + /// Gets a collection containing this collection's items. /// - public virtual ICollection Items { get; protected set; } + public virtual ICollection Items { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs index e19362bdf..0cb4716db 100644 --- a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs +++ b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs @@ -29,7 +29,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the library item. diff --git a/Jellyfin.Data/Entities/Libraries/Company.cs b/Jellyfin.Data/Entities/Libraries/Company.cs index 09050bb52..1abbee445 100644 --- a/Jellyfin.Data/Entities/Libraries/Company.cs +++ b/Jellyfin.Data/Entities/Libraries/Company.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -22,27 +20,27 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the metadata. + /// Gets a collection containing the metadata. /// - public virtual ICollection CompanyMetadata { get; protected set; } + public virtual ICollection CompanyMetadata { get; private set; } /// - /// Gets or sets a collection containing this company's child companies. + /// Gets a collection containing this company's child companies. /// - public virtual ICollection ChildCompanies { get; protected set; } + public virtual ICollection ChildCompanies { get; private set; } /// public ICollection Companies => ChildCompanies; diff --git a/Jellyfin.Data/Entities/Libraries/CustomItem.cs b/Jellyfin.Data/Entities/Libraries/CustomItem.cs index 88d1a0c25..e27d01d86 100644 --- a/Jellyfin.Data/Entities/Libraries/CustomItem.cs +++ b/Jellyfin.Data/Entities/Libraries/CustomItem.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -21,11 +19,11 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets a collection containing the metadata for this item. + /// Gets a collection containing the metadata for this item. /// - public virtual ICollection CustomItemMetadata { get; protected set; } + public virtual ICollection CustomItemMetadata { get; private set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Episode.cs b/Jellyfin.Data/Entities/Libraries/Episode.cs index 458c7d9f5..ce2f0c617 100644 --- a/Jellyfin.Data/Entities/Libraries/Episode.cs +++ b/Jellyfin.Data/Entities/Libraries/Episode.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -26,11 +24,11 @@ namespace Jellyfin.Data.Entities.Libraries public int? EpisodeNumber { get; set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } /// - /// Gets or sets a collection containing the metadata for this episode. + /// Gets a collection containing the metadata for this episode. /// - public virtual ICollection EpisodeMetadata { get; protected set; } + public virtual ICollection EpisodeMetadata { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Genre.cs b/Jellyfin.Data/Entities/Libraries/Genre.cs index 9f3d65028..3b822ee82 100644 --- a/Jellyfin.Data/Entities/Libraries/Genre.cs +++ b/Jellyfin.Data/Entities/Libraries/Genre.cs @@ -19,13 +19,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -39,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; protected set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs index d12e011a8..d429a90c6 100644 --- a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -43,13 +41,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the title. @@ -99,12 +97,12 @@ namespace Jellyfin.Data.Entities.Libraries public DateTimeOffset? ReleaseDate { get; set; } /// - /// Gets or sets the date added. + /// Gets the date added. /// /// /// Required. /// - public DateTime DateAdded { get; protected set; } + public DateTime DateAdded { get; private set; } /// /// Gets or sets the date modified. @@ -114,37 +112,32 @@ namespace Jellyfin.Data.Entities.Libraries /// public DateTime DateModified { get; set; } - /// - /// Gets or sets the row version. - /// - /// - /// Required, ConcurrencyToken. - /// + /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the person roles for this item. + /// Gets a collection containing the person roles for this item. /// - public virtual ICollection PersonRoles { get; protected set; } + public virtual ICollection PersonRoles { get; private set; } /// - /// Gets or sets a collection containing the genres for this item. + /// Gets a collection containing the genres for this item. /// - public virtual ICollection Genres { get; protected set; } + public virtual ICollection Genres { get; private set; } /// - public virtual ICollection Artwork { get; protected set; } + public virtual ICollection Artwork { get; private set; } /// - /// Gets or sets a collection containing the ratings for this item. + /// Gets a collection containing the ratings for this item. /// - public virtual ICollection Ratings { get; protected set; } + public virtual ICollection Ratings { get; private set; } /// - /// Gets or sets a collection containing the metadata sources for this item. + /// Gets a collection containing the metadata sources for this item. /// - public virtual ICollection Sources { get; protected set; } + public virtual ICollection Sources { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Library.cs b/Jellyfin.Data/Entities/Libraries/Library.cs index e45384902..0db42a1c7 100644 --- a/Jellyfin.Data/Entities/Libraries/Library.cs +++ b/Jellyfin.Data/Entities/Libraries/Library.cs @@ -21,13 +21,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -49,7 +49,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs index 67ffad944..d889b871e 100644 --- a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs +++ b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs @@ -21,22 +21,22 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the date this library item was added. + /// Gets the date this library item was added. /// - public DateTime DateAdded { get; protected set; } + public DateTime DateAdded { get; private set; } /// [ConcurrencyCheck] - public uint RowVersion { get; protected set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the library of this item. diff --git a/Jellyfin.Data/Entities/Libraries/MediaFile.cs b/Jellyfin.Data/Entities/Libraries/MediaFile.cs index f3e2fe653..36e1e4777 100644 --- a/Jellyfin.Data/Entities/Libraries/MediaFile.cs +++ b/Jellyfin.Data/Entities/Libraries/MediaFile.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -33,13 +31,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the path relative to the library root. @@ -61,12 +59,12 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the streams in this file. + /// Gets a collection containing the streams in this file. /// - public virtual ICollection MediaFileStreams { get; protected set; } + public virtual ICollection MediaFileStreams { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs index ba21294fc..e24e73ecb 100644 --- a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs +++ b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs @@ -21,13 +21,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the stream number. @@ -39,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs index fb2587882..b27196078 100644 --- a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs +++ b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs @@ -25,13 +25,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -45,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs index 2a9c904c8..44c198518 100644 --- a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs +++ b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs @@ -27,13 +27,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the provider id. @@ -47,7 +47,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the metadata provider. diff --git a/Jellyfin.Data/Entities/Libraries/Movie.cs b/Jellyfin.Data/Entities/Libraries/Movie.cs index f89cacff4..499fafd0e 100644 --- a/Jellyfin.Data/Entities/Libraries/Movie.cs +++ b/Jellyfin.Data/Entities/Libraries/Movie.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -21,11 +19,11 @@ namespace Jellyfin.Data.Entities.Libraries } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } /// - /// Gets or sets a collection containing the metadata for this movie. + /// Gets a collection containing the metadata for this movie. /// - public virtual ICollection MovieMetadata { get; protected set; } + public virtual ICollection MovieMetadata { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs index fb181dea6..44b5f34d7 100644 --- a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Jellyfin.Data.Interfaces; @@ -62,9 +60,9 @@ namespace Jellyfin.Data.Entities.Libraries public string? Country { get; set; } /// - /// Gets or sets the studios that produced this movie. + /// Gets the studios that produced this movie. /// - public virtual ICollection Studios { get; protected set; } + public virtual ICollection Studios { get; private set; } /// public ICollection Companies => Studios; diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs index 4049cdac8..d6231bbf0 100644 --- a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs +++ b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; namespace Jellyfin.Data.Entities.Libraries @@ -20,13 +18,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets a collection containing the album metadata. + /// Gets a collection containing the album metadata. /// - public virtual ICollection MusicAlbumMetadata { get; protected set; } + public virtual ICollection MusicAlbumMetadata { get; private set; } /// - /// Gets or sets a collection containing the tracks. + /// Gets a collection containing the tracks. /// - public virtual ICollection Tracks { get; protected set; } + public virtual ICollection Tracks { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs index 3080bd692..691f3504f 100644 --- a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -51,8 +49,8 @@ namespace Jellyfin.Data.Entities.Libraries public string? Country { get; set; } /// - /// Gets or sets a collection containing the labels. + /// Gets a collection containing the labels. /// - public virtual ICollection Labels { get; protected set; } + public virtual ICollection Labels { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Person.cs b/Jellyfin.Data/Entities/Libraries/Person.cs index 159bd47be..8b67d920d 100644 --- a/Jellyfin.Data/Entities/Libraries/Person.cs +++ b/Jellyfin.Data/Entities/Libraries/Person.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -32,13 +30,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -61,12 +59,12 @@ namespace Jellyfin.Data.Entities.Libraries public string? SourceId { get; set; } /// - /// Gets or sets the date added. + /// Gets the date added. /// /// /// Required. /// - public DateTime DateAdded { get; protected set; } + public DateTime DateAdded { get; private set; } /// /// Gets or sets the date modified. @@ -78,12 +76,12 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a list of metadata sources for this person. + /// Gets a list of metadata sources for this person. /// - public virtual ICollection Sources { get; protected set; } + public virtual ICollection Sources { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/PersonRole.cs b/Jellyfin.Data/Entities/Libraries/PersonRole.cs index 988aa84ba..7d40bdf44 100644 --- a/Jellyfin.Data/Entities/Libraries/PersonRole.cs +++ b/Jellyfin.Data/Entities/Libraries/PersonRole.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -27,13 +25,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name of the person's role. @@ -55,7 +53,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; protected set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the person. @@ -66,12 +64,12 @@ namespace Jellyfin.Data.Entities.Libraries public virtual Person Person { get; set; } /// - public virtual ICollection Artwork { get; protected set; } + public virtual ICollection Artwork { get; private set; } /// - /// Gets or sets a collection containing the metadata sources for this person role. + /// Gets a collection containing the metadata sources for this person role. /// - public virtual ICollection Sources { get; protected set; } + public virtual ICollection Sources { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Photo.cs b/Jellyfin.Data/Entities/Libraries/Photo.cs index eb5c96267..4b459432b 100644 --- a/Jellyfin.Data/Entities/Libraries/Photo.cs +++ b/Jellyfin.Data/Entities/Libraries/Photo.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -21,11 +19,11 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets a collection containing the photo metadata. + /// Gets a collection containing the photo metadata. /// - public virtual ICollection PhotoMetadata { get; protected set; } + public virtual ICollection PhotoMetadata { get; private set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Rating.cs b/Jellyfin.Data/Entities/Libraries/Rating.cs index 6862012a8..58c8fa49e 100644 --- a/Jellyfin.Data/Entities/Libraries/Rating.cs +++ b/Jellyfin.Data/Entities/Libraries/Rating.cs @@ -19,13 +19,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the value. @@ -42,7 +42,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the rating type. diff --git a/Jellyfin.Data/Entities/Libraries/RatingSource.cs b/Jellyfin.Data/Entities/Libraries/RatingSource.cs index ae0d806ff..0f3a07324 100644 --- a/Jellyfin.Data/Entities/Libraries/RatingSource.cs +++ b/Jellyfin.Data/Entities/Libraries/RatingSource.cs @@ -21,13 +21,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -57,7 +57,7 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// /// Gets or sets the metadata source. diff --git a/Jellyfin.Data/Entities/Libraries/Release.cs b/Jellyfin.Data/Entities/Libraries/Release.cs index 21d403979..d3d52bf5c 100644 --- a/Jellyfin.Data/Entities/Libraries/Release.cs +++ b/Jellyfin.Data/Entities/Libraries/Release.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -31,13 +29,13 @@ namespace Jellyfin.Data.Entities.Libraries } /// - /// Gets or sets the id. + /// Gets the id. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// /// Gets or sets the name. @@ -51,17 +49,17 @@ namespace Jellyfin.Data.Entities.Libraries /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets a collection containing the media files for this release. + /// Gets a collection containing the media files for this release. /// - public virtual ICollection MediaFiles { get; protected set; } + public virtual ICollection MediaFiles { get; private set; } /// - /// Gets or sets a collection containing the chapters for this release. + /// Gets a collection containing the chapters for this release. /// - public virtual ICollection Chapters { get; protected set; } + public virtual ICollection Chapters { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Libraries/Season.cs b/Jellyfin.Data/Entities/Libraries/Season.cs index 04f723a1d..fc110b49d 100644 --- a/Jellyfin.Data/Entities/Libraries/Season.cs +++ b/Jellyfin.Data/Entities/Libraries/Season.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; namespace Jellyfin.Data.Entities.Libraries @@ -25,13 +23,13 @@ namespace Jellyfin.Data.Entities.Libraries public int? SeasonNumber { get; set; } /// - /// Gets or sets the season metadata. + /// Gets the season metadata. /// - public virtual ICollection SeasonMetadata { get; protected set; } + public virtual ICollection SeasonMetadata { get; private set; } /// - /// Gets or sets a collection containing the number of episodes. + /// Gets a collection containing the number of episodes. /// - public virtual ICollection Episodes { get; protected set; } + public virtual ICollection Episodes { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/Series.cs b/Jellyfin.Data/Entities/Libraries/Series.cs index 59508831e..0354433e0 100644 --- a/Jellyfin.Data/Entities/Libraries/Series.cs +++ b/Jellyfin.Data/Entities/Libraries/Series.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; @@ -16,7 +14,6 @@ namespace Jellyfin.Data.Entities.Libraries /// The library. public Series(Library library) : base(library) { - DateAdded = DateTime.UtcNow; Seasons = new HashSet(); SeriesMetadata = new HashSet(); } @@ -37,13 +34,13 @@ namespace Jellyfin.Data.Entities.Libraries public DateTime? FirstAired { get; set; } /// - /// Gets or sets a collection containing the series metadata. + /// Gets a collection containing the series metadata. /// - public virtual ICollection SeriesMetadata { get; protected set; } + public virtual ICollection SeriesMetadata { get; private set; } /// - /// Gets or sets a collection containing the seasons. + /// Gets a collection containing the seasons. /// - public virtual ICollection Seasons { get; protected set; } + public virtual ICollection Seasons { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs index 730deccae..b75b0a25b 100644 --- a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -63,9 +61,9 @@ namespace Jellyfin.Data.Entities.Libraries public string? Country { get; set; } /// - /// Gets or sets a collection containing the networks. + /// Gets a collection containing the networks. /// - public virtual ICollection Networks { get; protected set; } + public virtual ICollection Networks { get; private set; } /// public ICollection Companies => Networks; diff --git a/Jellyfin.Data/Entities/Libraries/Track.cs b/Jellyfin.Data/Entities/Libraries/Track.cs index 86a3edff8..d35400033 100644 --- a/Jellyfin.Data/Entities/Libraries/Track.cs +++ b/Jellyfin.Data/Entities/Libraries/Track.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System.Collections.Generic; using Jellyfin.Data.Interfaces; @@ -26,11 +24,11 @@ namespace Jellyfin.Data.Entities.Libraries public int? TrackNumber { get; set; } /// - public virtual ICollection Releases { get; protected set; } + public virtual ICollection Releases { get; private set; } /// - /// Gets or sets a collection containing the track metadata. + /// Gets a collection containing the track metadata. /// - public virtual ICollection TrackMetadata { get; protected set; } + public virtual ICollection TrackMetadata { get; private set; } } } diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index f059dedfa..005d1a10f 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -25,21 +25,21 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id of this permission. + /// Gets the id of this permission. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the type of this permission. + /// Gets the type of this permission. /// /// /// Required. /// - public PermissionKind Kind { get; protected set; } + public PermissionKind Kind { get; private set; } /// /// Gets or sets a value indicating whether the associated user has this permission. @@ -51,7 +51,7 @@ namespace Jellyfin.Data.Entities /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index a8813ab88..048caecee 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -24,21 +24,21 @@ namespace Jellyfin.Data.Entities } /// - /// Gets or sets the id of this preference. + /// Gets the id of this preference. /// /// /// Identity, Indexed, Required. /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; private set; } /// - /// Gets or sets the type of this preference. + /// Gets the type of this preference. /// /// /// Required. /// - public PreferenceKind Kind { get; protected set; } + public PreferenceKind Kind { get; private set; } /// /// Gets or sets the value of this preference. @@ -52,7 +52,7 @@ namespace Jellyfin.Data.Entities /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// public void OnSavingChanges() diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 74331726c..e309e54de 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA2227 - using System; using System.Collections.Generic; using System.ComponentModel; @@ -302,64 +300,54 @@ namespace Jellyfin.Data.Entities public virtual ImageInfo? ProfileImage { get; set; } /// - /// Gets or sets the user's display preferences. + /// Gets the user's display preferences. /// - /// - /// Required. - /// - public virtual ICollection DisplayPreferences { get; set; } + public virtual ICollection DisplayPreferences { get; private set; } /// /// Gets or sets the level of sync play permissions this user has. /// public SyncPlayUserAccessType SyncPlayAccess { get; set; } - /// - /// Gets or sets the row version. - /// - /// - /// Required, Concurrency Token. - /// + /// [ConcurrencyCheck] - public uint RowVersion { get; set; } + public uint RowVersion { get; private set; } /// - /// Gets or sets the list of access schedules this user has. + /// Gets the list of access schedules this user has. /// - public virtual ICollection AccessSchedules { get; protected set; } + public virtual ICollection AccessSchedules { get; private set; } /// - /// Gets or sets the list of item display preferences. + /// Gets the list of item display preferences. /// - public virtual ICollection ItemDisplayPreferences { get; protected set; } + public virtual ICollection ItemDisplayPreferences { get; private set; } /* /// - /// Gets or sets the list of groups this user is a member of. + /// Gets the list of groups this user is a member of. /// - [ForeignKey("Group_Groups_Guid")] - public virtual ICollection Groups { get; protected set; } + public virtual ICollection Groups { get; private set; } */ /// - /// Gets or sets the list of permissions this user has. + /// Gets the list of permissions this user has. /// [ForeignKey("Permission_Permissions_Guid")] - public virtual ICollection Permissions { get; protected set; } + public virtual ICollection Permissions { get; private set; } /* /// - /// Gets or sets the list of provider mappings this user has. + /// Gets the list of provider mappings this user has. /// - [ForeignKey("ProviderMapping_ProviderMappings_Id")] - public virtual ICollection ProviderMappings { get; protected set; } + public virtual ICollection ProviderMappings { get; private set; } */ /// - /// Gets or sets the list of preferences this user has. + /// Gets the list of preferences this user has. /// [ForeignKey("Preference_Preferences_Guid")] - public virtual ICollection Preferences { get; protected set; } + public virtual ICollection Preferences { get; private set; } /// public void OnSavingChanges() From 3ffef5794ea6ddda565d89a58667980f1c3bddc3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Mar 2021 19:21:03 -0400 Subject: [PATCH 022/612] Delete unnecessary indexes Multicolumn indexes can be queried on the first column without needing a separate index --- Jellyfin.Server.Implementations/JellyfinDb.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 39f842354..eda2fa65c 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -149,18 +149,10 @@ namespace Jellyfin.Server.Implementations modelBuilder.HasDefaultSchema("jellyfin"); - modelBuilder.Entity() - .HasIndex(entity => entity.UserId) - .IsUnique(false); - modelBuilder.Entity() .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client }) .IsUnique(); - modelBuilder.Entity() - .HasIndex(entity => entity.UserId) - .IsUnique(false); - modelBuilder.Entity() .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client, entity.Key }) .IsUnique(); From 0a579e5bbd3c36e15d322ee5d30c8ef3331863bf Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 17 Mar 2021 21:41:52 -0400 Subject: [PATCH 023/612] Configure user deletion behavior --- Jellyfin.Server.Implementations/JellyfinDb.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index eda2fa65c..88ed22086 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -149,6 +149,31 @@ namespace Jellyfin.Server.Implementations modelBuilder.HasDefaultSchema("jellyfin"); + modelBuilder.Entity() + .HasOne(u => u.ProfileImage) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.Permissions) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.Preferences) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.AccessSchedules) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(u => u.DisplayPreferences) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client }) .IsUnique(); From f1cadb27d9d6ddbb2d42777e4c546a73bd5bcfe3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 18 Mar 2021 15:37:36 -0400 Subject: [PATCH 024/612] Add id properties for preferences and permissions --- Jellyfin.Data/Entities/Permission.cs | 6 ++++++ Jellyfin.Data/Entities/Preference.cs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index 005d1a10f..2c5894318 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -1,5 +1,6 @@ #pragma warning disable CA1711 // Identifiers should not have incorrect suffix +using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Enums; @@ -33,6 +34,11 @@ namespace Jellyfin.Data.Entities [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; private set; } + /// + /// Gets or sets the id of the associated user. + /// + public Guid UserId { get; set; } + /// /// Gets the type of this permission. /// diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index 048caecee..a7d93f32c 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -32,6 +32,11 @@ namespace Jellyfin.Data.Entities [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; private set; } + /// + /// Gets or sets the id of the associated user. + /// + public Guid UserId { get; set; } + /// /// Gets the type of this preference. /// From 3c4187e7803afd1d95eac20b30d2063ca2134413 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 18 Mar 2021 20:09:11 -0400 Subject: [PATCH 025/612] Add indexes for user permissions and preferences --- Jellyfin.Server.Implementations/JellyfinDb.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 88ed22086..a2eaea3ca 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -174,6 +174,7 @@ namespace Jellyfin.Server.Implementations .WithOne() .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client }) .IsUnique(); @@ -181,6 +182,19 @@ namespace Jellyfin.Server.Implementations modelBuilder.Entity() .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client, entity.Key }) .IsUnique(); + + // Used to get a user's permissions or a specific permission for a user. + // Also prevents multiple values being created for a user. + // Filtered over non-null user ids for when other entities (groups, API keys) get permissions + modelBuilder.Entity() + .HasIndex(p => new { p.UserId, p.Kind }) + .HasFilter("[UserId] IS NOT NULL") + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(p => new { p.UserId, p.Kind }) + .HasFilter("[UserId] IS NOT NULL") + .IsUnique(); } } } From a07ad7122281247a48c1f8a48588f49c6f64f2c4 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 18 Mar 2021 20:24:37 -0400 Subject: [PATCH 026/612] Use NOCASE collation and index on username field --- Jellyfin.Server.Implementations/JellyfinDb.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index a2eaea3ca..2ee6f625f 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -149,6 +149,14 @@ namespace Jellyfin.Server.Implementations modelBuilder.HasDefaultSchema("jellyfin"); + // Collations + + modelBuilder.Entity() + .Property(user => user.Username) + .UseCollation("NOCASE"); + + // Delete behavior + modelBuilder.Entity() .HasOne(u => u.ProfileImage) .WithOne() @@ -174,6 +182,11 @@ namespace Jellyfin.Server.Implementations .WithOne() .OnDelete(DeleteBehavior.Cascade); + // Indexes + + modelBuilder.Entity() + .HasIndex(entity => entity.Username) + .IsUnique(); modelBuilder.Entity() .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client }) From ea0a9c2cca4fd0f6ff84f79420281a7738602e60 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 19 Mar 2021 00:26:00 -0400 Subject: [PATCH 027/612] Properly configure foreign keys --- Jellyfin.Server.Implementations/JellyfinDb.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 2ee6f625f..499d99a50 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -165,11 +165,13 @@ namespace Jellyfin.Server.Implementations modelBuilder.Entity() .HasMany(u => u.Permissions) .WithOne() + .HasForeignKey(p => p.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasMany(u => u.Preferences) .WithOne() + .HasForeignKey(p => p.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() From daa21c9e99a8b7b6e9a76c130f8e0943692bd1da Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 19 Mar 2021 00:26:07 -0400 Subject: [PATCH 028/612] Add migration --- Jellyfin.Data/Entities/Permission.cs | 2 +- Jellyfin.Data/Entities/Preference.cs | 2 +- Jellyfin.Server.Implementations/JellyfinDb.cs | 10 + ...181425_AddIndexesAndCollations.Designer.cs | 535 ++++++++++++++++++ .../20210320181425_AddIndexesAndCollations.cs | 240 ++++++++ .../Migrations/JellyfinDbModelSnapshot.cs | 42 +- 6 files changed, 814 insertions(+), 17 deletions(-) create mode 100644 Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.cs diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index 2c5894318..6d2e68077 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -37,7 +37,7 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets the id of the associated user. /// - public Guid UserId { get; set; } + public Guid? UserId { get; set; } /// /// Gets the type of this permission. diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index a7d93f32c..a6ab275d3 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -35,7 +35,7 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets the id of the associated user. /// - public Guid UserId { get; set; } + public Guid? UserId { get; set; } /// /// Gets the type of this preference. diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 499d99a50..db648472d 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -184,6 +184,16 @@ namespace Jellyfin.Server.Implementations .WithOne() .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .HasMany(u => u.ItemDisplayPreferences) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasMany(d => d.HomeSections) + .WithOne() + .OnDelete(DeleteBehavior.Cascade); + // Indexes modelBuilder.Entity() diff --git a/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs new file mode 100644 index 000000000..869676824 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs @@ -0,0 +1,535 @@ +#pragma warning disable CS1591 + +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20210320181425_AddIndexesAndCollations")] + partial class AddIndexesAndCollations + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client", "Key") + .IsUnique(); + + b.ToTable("CustomItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChromecastVersion") + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("DashboardTheme") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("EnableNextVideoInfoOverlay") + .HasColumnType("INTEGER"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("ScrollDirection") + .HasColumnType("INTEGER"); + + b.Property("ShowBackdrop") + .HasColumnType("INTEGER"); + + b.Property("ShowSidebar") + .HasColumnType("INTEGER"); + + b.Property("SkipBackwardLength") + .HasColumnType("INTEGER"); + + b.Property("SkipForwardLength") + .HasColumnType("INTEGER"); + + b.Property("TvHome") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client") + .IsUnique(); + + b.ToTable("DisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DisplayPreferencesId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisplayPreferencesId"); + + b.ToTable("HomeSection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxActiveSessions") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null) + .WithMany("HomeSections") + .HasForeignKey("DisplayPreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ItemDisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Navigation("HomeSections"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Navigation("AccessSchedules"); + + b.Navigation("DisplayPreferences"); + + b.Navigation("ItemDisplayPreferences"); + + b.Navigation("Permissions"); + + b.Navigation("Preferences"); + + b.Navigation("ProfileImage"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.cs b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.cs new file mode 100644 index 000000000..506e4ae66 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.cs @@ -0,0 +1,240 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class AddIndexesAndCollations : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ImageInfos_Users_UserId", + schema: "jellyfin", + table: "ImageInfos"); + + migrationBuilder.DropForeignKey( + name: "FK_Permissions_Users_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.DropForeignKey( + name: "FK_Preferences_Users_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropIndex( + name: "IX_Preferences_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropIndex( + name: "IX_Permissions_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.DropIndex( + name: "IX_DisplayPreferences_UserId", + schema: "jellyfin", + table: "DisplayPreferences"); + + migrationBuilder.DropIndex( + name: "IX_CustomItemDisplayPreferences_UserId", + schema: "jellyfin", + table: "CustomItemDisplayPreferences"); + + migrationBuilder.AlterColumn( + name: "Username", + schema: "jellyfin", + table: "Users", + type: "TEXT", + maxLength: 255, + nullable: false, + collation: "NOCASE", + oldClrType: typeof(string), + oldType: "TEXT", + oldMaxLength: 255); + + migrationBuilder.AddColumn( + name: "UserId", + schema: "jellyfin", + table: "Preferences", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "UserId", + schema: "jellyfin", + table: "Permissions", + type: "TEXT", + nullable: true); + + migrationBuilder.Sql("UPDATE Preferences SET UserId = Preference_Preferences_Guid"); + migrationBuilder.Sql("UPDATE Permissions SET UserId = Permission_Permissions_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Users_Username", + schema: "jellyfin", + table: "Users", + column: "Username", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Preferences_UserId_Kind", + schema: "jellyfin", + table: "Preferences", + columns: new[] { "UserId", "Kind" }, + unique: true, + filter: "[UserId] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_UserId_Kind", + schema: "jellyfin", + table: "Permissions", + columns: new[] { "UserId", "Kind" }, + unique: true, + filter: "[UserId] IS NOT NULL"); + + migrationBuilder.AddForeignKey( + name: "FK_ImageInfos_Users_UserId", + schema: "jellyfin", + table: "ImageInfos", + column: "UserId", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Permissions_Users_UserId", + schema: "jellyfin", + table: "Permissions", + column: "UserId", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Preferences_Users_UserId", + schema: "jellyfin", + table: "Preferences", + column: "UserId", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ImageInfos_Users_UserId", + schema: "jellyfin", + table: "ImageInfos"); + + migrationBuilder.DropForeignKey( + name: "FK_Permissions_Users_UserId", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.DropForeignKey( + name: "FK_Preferences_Users_UserId", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropIndex( + name: "IX_Users_Username", + schema: "jellyfin", + table: "Users"); + + migrationBuilder.DropIndex( + name: "IX_Preferences_UserId_Kind", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropIndex( + name: "IX_Permissions_UserId_Kind", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.DropColumn( + name: "UserId", + schema: "jellyfin", + table: "Preferences"); + + migrationBuilder.DropColumn( + name: "UserId", + schema: "jellyfin", + table: "Permissions"); + + migrationBuilder.AlterColumn( + name: "Username", + schema: "jellyfin", + table: "Users", + type: "TEXT", + maxLength: 255, + nullable: false, + oldClrType: typeof(string), + oldType: "TEXT", + oldMaxLength: 255, + oldCollation: "NOCASE"); + + migrationBuilder.CreateIndex( + name: "IX_Preferences_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences", + column: "Preference_Preferences_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions", + column: "Permission_Permissions_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_DisplayPreferences_UserId", + schema: "jellyfin", + table: "DisplayPreferences", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomItemDisplayPreferences_UserId", + schema: "jellyfin", + table: "CustomItemDisplayPreferences", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_ImageInfos_Users_UserId", + schema: "jellyfin", + table: "ImageInfos", + column: "UserId", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Permissions_Users_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions", + column: "Permission_Permissions_Guid", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Preferences_Users_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences", + column: "Preference_Preferences_Guid", + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 1614a88ef..d8e13ba0c 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "5.0.0"); + .HasAnnotation("ProductVersion", "5.0.3"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -115,8 +115,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("UserId"); - b.HasIndex("UserId", "ItemId", "Client", "Key") .IsUnique(); @@ -174,8 +172,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("UserId"); - b.HasIndex("UserId", "ItemId", "Client") .IsUnique(); @@ -289,12 +285,17 @@ namespace Jellyfin.Server.Implementations.Migrations .IsConcurrencyToken() .HasColumnType("INTEGER"); + b.Property("UserId") + .HasColumnType("TEXT"); + b.Property("Value") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("Permission_Permissions_Guid"); + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); b.ToTable("Permissions"); }); @@ -315,6 +316,9 @@ namespace Jellyfin.Server.Implementations.Migrations .IsConcurrencyToken() .HasColumnType("INTEGER"); + b.Property("UserId") + .HasColumnType("TEXT"); + b.Property("Value") .IsRequired() .HasMaxLength(65535) @@ -322,7 +326,9 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("Preference_Preferences_Guid"); + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); b.ToTable("Preferences"); }); @@ -429,10 +435,14 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Username") .IsRequired() .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); + b.HasIndex("Username") + .IsUnique(); + b.ToTable("Users"); }); @@ -448,8 +458,8 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => { b.HasOne("Jellyfin.Data.Entities.User", null) - .WithOne("DisplayPreferences") - .HasForeignKey("Jellyfin.Data.Entities.DisplayPreferences", "UserId") + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); @@ -467,7 +477,8 @@ namespace Jellyfin.Server.Implementations.Migrations { b.HasOne("Jellyfin.Data.Entities.User", null) .WithOne("ProfileImage") - .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => @@ -483,14 +494,16 @@ namespace Jellyfin.Server.Implementations.Migrations { b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Permissions") - .HasForeignKey("Permission_Permissions_Guid"); + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => { b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Preferences") - .HasForeignKey("Preference_Preferences_Guid"); + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => @@ -502,8 +515,7 @@ namespace Jellyfin.Server.Implementations.Migrations { b.Navigation("AccessSchedules"); - b.Navigation("DisplayPreferences") - .IsRequired(); + b.Navigation("DisplayPreferences"); b.Navigation("ItemDisplayPreferences"); From a7b29e2fe0bb08f4b8f37fa5aac7af66c7cb00e8 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 25 Mar 2021 19:48:30 -0400 Subject: [PATCH 029/612] Clean up user renaming --- Jellyfin.Server.Implementations/Users/UserManager.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 50d7612f2..f9a1a8ee9 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -144,7 +144,13 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("The new and old names must be different."); } - if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) + await using var dbContext = _dbProvider.CreateContext(); + + if (await dbContext.Users + .AsQueryable() + .Where(u => u.Username == newName && u.Id != user.Id) + .AnyAsync() + .ConfigureAwait(false)) { throw new ArgumentException(string.Format( CultureInfo.InvariantCulture, From 7364155579c52e5019cdcb71edc03546babe3fb3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 25 Mar 2021 19:49:52 -0400 Subject: [PATCH 030/612] Clean up user deletion --- Jellyfin.Server.Implementations/Users/UserManager.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index f9a1a8ee9..b6b45e69b 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -257,16 +257,6 @@ namespace Jellyfin.Server.Implementations.Users } await using var dbContext = _dbProvider.CreateContext(); - - // Clear all entities related to the user from the database. - if (user.ProfileImage != null) - { - dbContext.Remove(user.ProfileImage); - } - - dbContext.RemoveRange(user.Permissions); - dbContext.RemoveRange(user.Preferences); - dbContext.RemoveRange(user.AccessSchedules); dbContext.Users.Remove(user); await dbContext.SaveChangesAsync().ConfigureAwait(false); _users.Remove(userId); From a7c82b2681aa966e11958ee71d2d20fb20f8233f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Senart?= Date: Mon, 29 Mar 2021 12:11:38 +0200 Subject: [PATCH 031/612] [5644] [DLNA] [Music] Next track command from any DLNA device does not do anything. --- Emby.Dlna/PlayTo/Device.cs | 36 +++++++++++++++++++++++++ Emby.Dlna/PlayTo/PlayToController.cs | 40 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 7bf7047fb..9d45e89df 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -368,6 +368,42 @@ namespace Emby.Dlna.PlayTo RestartTimer(true); } + /* + * SetNextAvTransport is used to specify to the DLNA device what is the next track to play. + * Without that information, the next track command on the device does not work. + */ + public async Task SetNextAvTransport(string url, string header, string metaData, CancellationToken cancellationToken = default) + { + var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + + url = url.Replace("&", "&", StringComparison.Ordinal); + + _logger.LogDebug("{0} - SetNextAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header); + + var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetNextAVTransportURI"); + if (command == null) + { + return; + } + + var dictionary = new Dictionary + { + { "NextURI", url }, + { "NextURIMetaData", CreateDidlMeta(metaData) } + }; + + var service = GetAvTransportService(); + + if (service == null) + { + throw new InvalidOperationException("Unable to find service"); + } + + var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary); + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header, cancellationToken) + .ConfigureAwait(false); + } + private static string CreateDidlMeta(string value) { if (string.IsNullOrEmpty(value)) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 5abc1bc13..503c2eee2 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -102,6 +102,22 @@ namespace Emby.Dlna.PlayTo _deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft; } + /* + * Send a message to the DLNA device to notify what is the next track in the playlist. + */ + private async void SendNextTrackMessage(int currentPlayListItemIndex, CancellationToken cancellationToken) + { + if (currentPlayListItemIndex >= 0 && currentPlayListItemIndex < _playlist.Count - 1) + { + // The current playing item is indeed in the play list and we are not yet at the end of the playlist. + var nextItemIndex = currentPlayListItemIndex + 1; + var nextItem = _playlist[nextItemIndex]; + + // Send the SetNextAvTransport message. + await _device.SetNextAvTransport(nextItem.StreamUrl, GetDlnaHeaders(nextItem), nextItem.Didl, cancellationToken).ConfigureAwait(false); + } + } + private void OnDeviceUnavailable() { try @@ -153,6 +169,14 @@ namespace Emby.Dlna.PlayTo return; } + // Create the new play list item : mainly to have the normalized StreamUrl and find it in the playlist. + var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; + var newItem = CreatePlaylistItem(streamInfo.Item, user, 0, streamInfo.MediaSourceId, streamInfo.AudioStreamIndex, streamInfo.SubtitleStreamIndex); + + // Send a message to the DLNA device to notify what is the next track in the playlist. + var currentItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); + SendNextTrackMessage(currentItemIndex, CancellationToken.None); + var newItemProgress = GetProgressInfo(streamInfo); await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false); @@ -425,6 +449,11 @@ namespace Emby.Dlna.PlayTo var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex); await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); + + // Send a message to the DLNA device to notify what is the next track in the play list. + var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); + SendNextTrackMessage(newItemIndex, CancellationToken.None); + return; } @@ -623,6 +652,9 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false); + // Send a message to the DLNA device to notify what is the next track in the play list. + SendNextTrackMessage(index, CancellationToken.None); + var streamInfo = currentitem.StreamInfo; if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo)) { @@ -736,6 +768,10 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); + // Send a message to the DLNA device to notify what is the next track in the play list. + var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); + SendNextTrackMessage(newItemIndex, CancellationToken.None); + if (EnableClientSideSeek(newItem.StreamInfo)) { await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); @@ -761,6 +797,10 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); + // Send a message to the DLNA device to notify what is the next track in the play list. + var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); + SendNextTrackMessage(newItemIndex, CancellationToken.None); + if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0) { await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); From ec113816aa9d9f973665501c869cab47a13e6125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Senart?= Date: Mon, 29 Mar 2021 23:43:02 +0200 Subject: [PATCH 032/612] [5644] [DLNA] [Music] Next track command from any DLNA device does not do anything. --- Emby.Dlna/PlayTo/PlayToController.cs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 503c2eee2..e0c752c19 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -105,7 +105,7 @@ namespace Emby.Dlna.PlayTo /* * Send a message to the DLNA device to notify what is the next track in the playlist. */ - private async void SendNextTrackMessage(int currentPlayListItemIndex, CancellationToken cancellationToken) + private async Task SendNextTrackMessage(int currentPlayListItemIndex, CancellationToken cancellationToken) { if (currentPlayListItemIndex >= 0 && currentPlayListItemIndex < _playlist.Count - 1) { @@ -169,17 +169,13 @@ namespace Emby.Dlna.PlayTo return; } - // Create the new play list item : mainly to have the normalized StreamUrl and find it in the playlist. - var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; - var newItem = CreatePlaylistItem(streamInfo.Item, user, 0, streamInfo.MediaSourceId, streamInfo.AudioStreamIndex, streamInfo.SubtitleStreamIndex); - - // Send a message to the DLNA device to notify what is the next track in the playlist. - var currentItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - SendNextTrackMessage(currentItemIndex, CancellationToken.None); - var newItemProgress = GetProgressInfo(streamInfo); await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false); + + // Send a message to the DLNA device to notify what is the next track in the playlist. + var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId == streamInfo.ItemId); + await SendNextTrackMessage(currentItemIndex, CancellationToken.None); } catch (Exception ex) { @@ -452,7 +448,7 @@ namespace Emby.Dlna.PlayTo // Send a message to the DLNA device to notify what is the next track in the play list. var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - SendNextTrackMessage(newItemIndex, CancellationToken.None); + await SendNextTrackMessage(newItemIndex, CancellationToken.None); return; } @@ -653,7 +649,7 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false); // Send a message to the DLNA device to notify what is the next track in the play list. - SendNextTrackMessage(index, CancellationToken.None); + await SendNextTrackMessage(index, CancellationToken.None); var streamInfo = currentitem.StreamInfo; if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo)) @@ -770,7 +766,7 @@ namespace Emby.Dlna.PlayTo // Send a message to the DLNA device to notify what is the next track in the play list. var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - SendNextTrackMessage(newItemIndex, CancellationToken.None); + await SendNextTrackMessage(newItemIndex, CancellationToken.None); if (EnableClientSideSeek(newItem.StreamInfo)) { @@ -799,7 +795,7 @@ namespace Emby.Dlna.PlayTo // Send a message to the DLNA device to notify what is the next track in the play list. var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - SendNextTrackMessage(newItemIndex, CancellationToken.None); + await SendNextTrackMessage(newItemIndex, CancellationToken.None); if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0) { From 3164781ce09200547999762ca618d0b67c3cc46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Senart?= Date: Tue, 30 Mar 2021 22:18:32 +0200 Subject: [PATCH 033/612] [5644] [DLNA] [Music] Next track command from any DLNA device does not do anything. --- Emby.Dlna/PlayTo/PlayToController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index e0c752c19..41723bc6c 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -175,6 +175,11 @@ namespace Emby.Dlna.PlayTo // Send a message to the DLNA device to notify what is the next track in the playlist. var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId == streamInfo.ItemId); + if (currentItemIndex >= 0) + { + _currentPlaylistIndex = currentItemIndex; + } + await SendNextTrackMessage(currentItemIndex, CancellationToken.None); } catch (Exception ex) From 16ca8c753618395af2e186744570768e7a76bafc Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 1 Apr 2021 10:42:00 -0400 Subject: [PATCH 034/612] Remove unused SessionManager methods --- .../Session/SessionManager.cs | 16 +--------------- .../Session/ISessionManager.cs | 15 --------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 10e28c33a..e06e48ca6 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1437,11 +1437,6 @@ namespace Emby.Server.Implementations.Session return AuthenticateNewSessionInternal(request, true); } - public Task CreateNewSession(AuthenticationRequest request) - { - return AuthenticateNewSessionInternal(request, false); - } - public Task AuthenticateQuickConnect(AuthenticationRequest request, string token) { var result = _authRepo.Get(new AuthenticationInfoQuery() @@ -1784,18 +1779,9 @@ namespace Emby.Server.Implementations.Session } var item = _libraryManager.GetItemById(new Guid(itemId)); - - var info = GetItemInfo(item, null); - - ReportNowViewingItem(sessionId, info); - } - - /// - public void ReportNowViewingItem(string sessionId, BaseItemDto item) - { var session = GetSession(sessionId); - session.NowViewingItem = item; + session.NowViewingItem = GetItemInfo(item, null); } /// diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 6c06dcad5..9eb486534 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -8,7 +8,6 @@ using Jellyfin.Data.Events; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; @@ -263,13 +262,6 @@ namespace MediaBrowser.Controller.Session /// The item identifier. void ReportNowViewingItem(string sessionId, string itemId); - /// - /// Reports the now viewing item. - /// - /// The session identifier. - /// The item. - void ReportNowViewingItem(string sessionId, BaseItemDto item); - /// /// Authenticates the new session. /// @@ -285,13 +277,6 @@ namespace MediaBrowser.Controller.Session /// Task{SessionInfo}. Task AuthenticateQuickConnect(AuthenticationRequest request, string token); - /// - /// Creates the new session. - /// - /// The request. - /// Task<AuthenticationResult>. - Task CreateNewSession(AuthenticationRequest request); - /// /// Reports the capabilities. /// From 1c501b17d7b6ceeba3450e0be768cfdbf7d581d0 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 1 Apr 2021 11:08:39 -0400 Subject: [PATCH 035/612] Add ApiKey entity and associated relationships --- Jellyfin.Data/Entities/Security/ApiKey.cs | 50 +++++++++++++++++++ Jellyfin.Server.Implementations/JellyfinDb.cs | 7 +++ 2 files changed, 57 insertions(+) create mode 100644 Jellyfin.Data/Entities/Security/ApiKey.cs diff --git a/Jellyfin.Data/Entities/Security/ApiKey.cs b/Jellyfin.Data/Entities/Security/ApiKey.cs new file mode 100644 index 000000000..2a3ad09c4 --- /dev/null +++ b/Jellyfin.Data/Entities/Security/ApiKey.cs @@ -0,0 +1,50 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Jellyfin.Data.Entities.Security +{ + /// + /// An entity representing an API key. + /// + public class ApiKey + { + /// + /// Initializes a new instance of the class. + /// + /// The name. + public ApiKey(string name) + { + Name = name; + + AccessToken = Guid.NewGuid(); + DateCreated = DateTime.UtcNow; + } + + /// + /// Gets the id. + /// + /// + /// Identity, Indexed, Required. + /// + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; private set; } + + /// + /// Gets the date created. + /// + public DateTime DateCreated { get; private set; } + + /// + /// Gets or sets the name. + /// + [MaxLength(64)] + [StringLength(64)] + public string Name { get; set; } + + /// + /// Gets or sets the access token. + /// + public Guid AccessToken { get; set; } + } +} diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index db648472d..0559e5783 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using Jellyfin.Data.Entities; +using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Interfaces; using Microsoft.EntityFrameworkCore; @@ -29,6 +30,8 @@ namespace Jellyfin.Server.Implementations public virtual DbSet ActivityLogs { get; set; } + public virtual DbSet ApiKeys { get; set; } + public virtual DbSet DisplayPreferences { get; set; } public virtual DbSet ImageInfos { get; set; } @@ -196,6 +199,10 @@ namespace Jellyfin.Server.Implementations // Indexes + modelBuilder.Entity() + .HasIndex(entity => entity.AccessToken) + .IsUnique(); + modelBuilder.Entity() .HasIndex(entity => entity.Username) .IsUnique(); From 499785bebb5699c61b211dcb6ea0ee2001effa6f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 1 Apr 2021 17:08:22 -0400 Subject: [PATCH 036/612] Use new entities for API key endpoints --- Jellyfin.Api/Controllers/ApiKeyController.cs | 52 +++++-------- .../Security/AuthenticationManager.cs | 74 +++++++++++++++++++ .../Security/IAuthenticationManager.cs | 34 +++++++++ 3 files changed, 126 insertions(+), 34 deletions(-) create mode 100644 Jellyfin.Server.Implementations/Security/AuthenticationManager.cs create mode 100644 MediaBrowser.Controller/Security/IAuthenticationManager.cs diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs index 8c43d786a..96efde5fb 100644 --- a/Jellyfin.Api/Controllers/ApiKeyController.cs +++ b/Jellyfin.Api/Controllers/ApiKeyController.cs @@ -1,10 +1,8 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Globalization; +using System.Threading.Tasks; using Jellyfin.Api.Constants; -using MediaBrowser.Controller; using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -18,24 +16,15 @@ namespace Jellyfin.Api.Controllers [Route("Auth")] public class ApiKeyController : BaseJellyfinApiController { - private readonly ISessionManager _sessionManager; - private readonly IServerApplicationHost _appHost; - private readonly IAuthenticationRepository _authRepo; + private readonly IAuthenticationManager _authenticationManager; /// /// Initializes a new instance of the class. /// - /// Instance of interface. - /// Instance of interface. - /// Instance of interface. - public ApiKeyController( - ISessionManager sessionManager, - IServerApplicationHost appHost, - IAuthenticationRepository authRepo) + /// Instance of interface. + public ApiKeyController(IAuthenticationManager authenticationManager) { - _sessionManager = sessionManager; - _appHost = appHost; - _authRepo = authRepo; + _authenticationManager = authenticationManager; } /// @@ -46,14 +35,15 @@ namespace Jellyfin.Api.Controllers [HttpGet("Keys")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetKeys() + public async Task>> GetKeys() { - var result = _authRepo.Get(new AuthenticationInfoQuery - { - HasUser = false - }); + var keys = await _authenticationManager.GetApiKeys(); - return result; + return new QueryResult + { + Items = keys, + TotalRecordCount = keys.Count + }; } /// @@ -65,17 +55,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Keys")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult CreateKey([FromQuery, Required] string app) + public async Task CreateKey([FromQuery, Required] string app) { - _authRepo.Create(new AuthenticationInfo - { - AppName = app, - AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), - DateCreated = DateTime.UtcNow, - DeviceId = _appHost.SystemId, - DeviceName = _appHost.FriendlyName, - AppVersion = _appHost.ApplicationVersionString - }); + await _authenticationManager.CreateApiKey(app).ConfigureAwait(false); + return NoContent(); } @@ -88,9 +71,10 @@ namespace Jellyfin.Api.Controllers [HttpDelete("Keys/{key}")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult RevokeKey([FromRoute, Required] string key) + public async Task RevokeKey([FromRoute, Required] Guid key) { - _sessionManager.RevokeToken(key); + await _authenticationManager.DeleteApiKey(key).ConfigureAwait(false); + return NoContent(); } } diff --git a/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs new file mode 100644 index 000000000..37b8ee6e0 --- /dev/null +++ b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Data.Entities.Security; +using MediaBrowser.Controller.Security; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Security +{ + /// + public class AuthenticationManager : IAuthenticationManager + { + private readonly JellyfinDbProvider _dbProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The database provider. + public AuthenticationManager(JellyfinDbProvider dbProvider) + { + _dbProvider = dbProvider; + } + + /// + public async Task CreateApiKey(string name) + { + await using var dbContext = _dbProvider.CreateContext(); + + dbContext.ApiKeys.Add(new ApiKey(name)); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + /// + public async Task> GetApiKeys() + { + await using var dbContext = _dbProvider.CreateContext(); + + return await dbContext.ApiKeys + .AsAsyncEnumerable() + .Select(key => new AuthenticationInfo + { + AppName = key.Name, + AccessToken = key.AccessToken.ToString("N", CultureInfo.InvariantCulture), + DateCreated = key.DateCreated, + DeviceId = string.Empty, + DeviceName = string.Empty, + AppVersion = string.Empty + }).ToListAsync().ConfigureAwait(false); + } + + /// + public async Task DeleteApiKey(Guid id) + { + await using var dbContext = _dbProvider.CreateContext(); + + var key = await dbContext.ApiKeys + .AsQueryable() + .Where(apiKey => apiKey.AccessToken == id) + .FirstOrDefaultAsync(); + + if (key == null) + { + return; + } + + dbContext.Remove(key); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + } +} diff --git a/MediaBrowser.Controller/Security/IAuthenticationManager.cs b/MediaBrowser.Controller/Security/IAuthenticationManager.cs new file mode 100644 index 000000000..46d0c6622 --- /dev/null +++ b/MediaBrowser.Controller/Security/IAuthenticationManager.cs @@ -0,0 +1,34 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Security +{ + /// + /// Handles the retrieval and storage of API keys. + /// + public interface IAuthenticationManager + { + /// + /// Creates an API key. + /// + /// The name of the key. + /// A task representing the creation of the key. + Task CreateApiKey(string name); + + /// + /// Gets the API keys. + /// + /// A task representing the retrieval of the API keys. + Task> GetApiKeys(); + + /// + /// Deletes an API key with the provided access token. + /// + /// The access token. + /// A task representing the deletion of the API key. + Task DeleteApiKey(Guid accessToken); + } +} From 24ac8a12233936955e31a409c1a131473041846e Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sun, 4 Apr 2021 23:34:29 +0200 Subject: [PATCH 037/612] Improve metadata probing to better support music videos --- .../Probing/ProbeResultNormalizer.cs | 95 +++++++++++-------- MediaBrowser.Model/MediaInfo/MediaInfo.cs | 2 + .../MediaInfo/FFProbeAudioInfo.cs | 5 + .../MediaInfo/FFProbeVideoInfo.cs | 11 +++ 4 files changed, 74 insertions(+), 39 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index a87104cd6..22624dfd7 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -121,19 +121,67 @@ namespace MediaBrowser.MediaEncoding.Probing { info.Name = title; } + else + { + title = FFProbeHelpers.GetDictionaryValue(tags, "title-eng"); + if (!string.IsNullOrWhiteSpace(title)) + { + info.Name = title; + } + } + + var titleSort = FFProbeHelpers.GetDictionaryValue(tags, "titlesort"); + if (!string.IsNullOrWhiteSpace(titleSort)) + { + info.ForcedSortName = titleSort; + } info.IndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_sort"); info.ParentIndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "season_number"); info.ShowName = FFProbeHelpers.GetDictionaryValue(tags, "show_name"); info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date"); - // Several different forms of retaildate - info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? + // Several different forms of retail/premiere date + info.PremiereDate = + FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "date_released") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "date"); + // Set common metadata for music (audio) and music videos (video) + info.Album = FFProbeHelpers.GetDictionaryValue(tags, "album"); + + var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists"); + + if (!string.IsNullOrWhiteSpace(artists)) + { + info.Artists = SplitArtists(artists, new[] { '/', ';' }, false) + .DistinctNames() + .ToArray(); + } + else + { + var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist"); + if (string.IsNullOrWhiteSpace(artist)) + { + info.Artists = Array.Empty(); + } + else + { + info.Artists = SplitArtists(artist, _nameDelimiters, true) + .DistinctNames() + .ToArray(); + } + } + + // If we don't have a ProductionYear try and get it from PremiereDate + if (!info.ProductionYear.HasValue && info.PremiereDate.HasValue) + { + info.ProductionYear = info.PremiereDate.Value.Year; + } + + // Set mediaType-specific metadata if (isAudio) { SetAudioRuntimeTicks(data, info); @@ -1076,13 +1124,13 @@ namespace MediaBrowser.MediaEncoding.Probing private void SetAudioInfoFromTags(MediaInfo audio, Dictionary tags) { - var peoples = new List(); + var people = new List(); var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer"); if (!string.IsNullOrWhiteSpace(composer)) { foreach (var person in Split(composer, false)) { - peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer }); + people.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer }); } } @@ -1091,7 +1139,7 @@ namespace MediaBrowser.MediaEncoding.Probing { foreach (var person in Split(conductor, false)) { - peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor }); + people.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor }); } } @@ -1100,46 +1148,21 @@ namespace MediaBrowser.MediaEncoding.Probing { foreach (var person in Split(lyricist, false)) { - peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist }); + people.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist }); } } // Check for writer some music is tagged that way as alternative to composer/lyricist var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer"); - if (!string.IsNullOrWhiteSpace(writer)) { foreach (var person in Split(writer, false)) { - peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer }); + people.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer }); } } - audio.People = peoples.ToArray(); - audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album"); - - var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists"); - - if (!string.IsNullOrWhiteSpace(artists)) - { - audio.Artists = SplitArtists(artists, new[] { '/', ';' }, false) - .DistinctNames() - .ToArray(); - } - else - { - var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist"); - if (string.IsNullOrWhiteSpace(artist)) - { - audio.Artists = Array.Empty(); - } - else - { - audio.Artists = SplitArtists(artist, _nameDelimiters, true) - .DistinctNames() - .ToArray(); - } - } + audio.People = people.ToArray(); var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist"); if (string.IsNullOrWhiteSpace(albumArtist)) @@ -1174,12 +1197,6 @@ namespace MediaBrowser.MediaEncoding.Probing // Disc number audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc"); - // If we don't have a ProductionYear try and get it from PremiereDate - if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue) - { - audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year; - } - // There's several values in tags may or may not be present FetchStudios(audio, tags, "organization"); FetchStudios(audio, tags, "ensemble"); diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs index a268a4fa6..453aeb028 100644 --- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs +++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs @@ -51,6 +51,8 @@ namespace MediaBrowser.Model.MediaInfo public string ShowName { get; set; } + public string ForcedSortName { get; set; } + public int? IndexNumber { get; set; } public int? ParentIndexNumber { get; set; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 945463666..cf271e7db 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -111,6 +111,11 @@ namespace MediaBrowser.Providers.MediaInfo audio.Name = data.Name; } + if (!string.IsNullOrEmpty(data.ForcedSortName)) + { + audio.ForcedSortName = data.ForcedSortName; + } + if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast)) { var people = new List(); diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 74849a522..e7f9cf314 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -394,6 +394,12 @@ namespace MediaBrowser.Providers.MediaInfo } } + if (video is MusicVideo musicVideo) + { + musicVideo.Album = data.Album; + musicVideo.Artists = data.Artists; + } + if (data.ProductionYear.HasValue) { if (!video.ProductionYear.HasValue || isFullRefresh) @@ -436,6 +442,11 @@ namespace MediaBrowser.Providers.MediaInfo video.Name = data.Name; } } + + if (!string.IsNullOrWhiteSpace(data.ForcedSortName)) + { + video.ForcedSortName = data.ForcedSortName; + } } // If we don't have a ProductionYear try and get it from PremiereDate From 07751768f438b7937ede411bb9c65dd6f5035ec4 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Mon, 5 Apr 2021 21:29:46 +0200 Subject: [PATCH 038/612] Add tests for music video metadata --- .../Probing/ProbeResultNormalizerTests.cs | 19 +++ .../Probing/music_video_metadata.json | 111 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_video_metadata.json diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 69e2aa437..c8de78571 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Globalization; using System.IO; using System.Text.Json; using MediaBrowser.Common.Json; @@ -52,5 +54,22 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Empty(res.Chapters); Assert.Equal("Just color bars", res.Overview); } + + [Fact] + public void GetMediaInfo_MusicVideo_Success() + { + var bytes = File.ReadAllBytes("Test Data/Probing/music_video_metadata.json"); + var internalMediaInfoResult = JsonSerializer.Deserialize(bytes, _jsonOptions); + MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/music_video.mkv", MediaProtocol.File); + + Assert.Equal("The Title", res.Name); + Assert.Equal("Title, The", res.ForcedSortName); + Assert.Single(res.Artists); + Assert.Equal("The Artist", res.Artists[0]); + Assert.Equal("Album", res.Album); + Assert.Equal(2021, res.ProductionYear); + Assert.True(res.PremiereDate.HasValue); + Assert.Equal(DateTime.Parse("2021-01-01T00:00Z", DateTimeFormatInfo.CurrentInfo).ToUniversalTime(), res.PremiereDate); + } } } diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_video_metadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_video_metadata.json new file mode 100644 index 000000000..97d6600a4 --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_video_metadata.json @@ -0,0 +1,111 @@ +{ + "streams": [ + { + "index": 0, + "codec_name": "h264", + "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", + "profile": "High", + "codec_type": "video", + "codec_time_base": "1001/48000", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "width": 1920, + "height": 1080, + "coded_width": 1920, + "coded_height": 1088, + "closed_captions": 0, + "has_b_frames": 0, + "sample_aspect_ratio": "1:1", + "display_aspect_ratio": "16:9", + "pix_fmt": "yuv420p", + "level": 42, + "chroma_location": "left", + "field_order": "progressive", + "refs": 1, + "is_avc": "true", + "nal_length_size": "4", + "r_frame_rate": "24000/1001", + "avg_frame_rate": "24000/1001", + "time_base": "1/1000", + "start_pts": 0, + "start_time": "0.000000", + "bits_per_raw_sample": "8", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + }, + "tags": { + "language": "eng" + } + }, + { + "index": 1, + "codec_name": "aac", + "codec_long_name": "AAC (Advanced Audio Coding)", + "profile": "LC", + "codec_type": "audio", + "codec_time_base": "1/48000", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "sample_fmt": "fltp", + "sample_rate": "48000", + "channels": 2, + "channel_layout": "stereo", + "bits_per_sample": 0, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/1000", + "start_pts": 0, + "start_time": "0.000000", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0 + }, + "tags": { + "language": "eng" + } + } + ], + "chapters": [ + ], + "format": { + "filename": "music_video.mkv", + "nb_streams": 2, + "nb_programs": 0, + "format_name": "matroska,webm", + "format_long_name": "Matroska / WebM", + "start_time": "0.000000", + "duration": "180.000000", + "size": "500000000", + "bit_rate": "22222222", + "probe_score": 100, + "tags": { + "TITLE-eng": "The Title", + "TITLESORT": "Title, The", + "ARTIST": "The Artist", + "ARTISTSORT": "Artist, The", + "ALBUM": "Album", + "DATE_RELEASED": "2021-01-01" + } + } +} From a7c8bc632fc3f25b66b80b03c76d7577332e0582 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Mon, 5 Apr 2021 21:31:06 +0200 Subject: [PATCH 039/612] Fix typo in test data filename --- .../Probing/ProbeResultNormalizerTests.cs | 4 ++-- .../Probing/{some_matadata.json => video_metadata.json} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/{some_matadata.json => video_metadata.json} (100%) diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index c8de78571..98fbb00d5 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -19,9 +19,9 @@ namespace Jellyfin.MediaEncoding.Tests.Probing [Fact] public void GetMediaInfo_MetaData_Success() { - var bytes = File.ReadAllBytes("Test Data/Probing/some_matadata.json"); + var bytes = File.ReadAllBytes("Test Data/Probing/video_metadata.json"); var internalMediaInfoResult = JsonSerializer.Deserialize(bytes, _jsonOptions); - MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/some_matadata.mkv", MediaProtocol.File); + MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_metadata.mkv", MediaProtocol.File); Assert.Single(res.MediaStreams); diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_metadata.json similarity index 100% rename from tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json rename to tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_metadata.json From 08ccf2a49cc5c67026b324570a975aa9adab8915 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 9 Apr 2021 13:20:12 +0200 Subject: [PATCH 040/612] Resolve name from episode folder --- .../Library/LibraryManager.cs | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index c18838caf..f92c30093 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -27,6 +27,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -48,6 +49,7 @@ using MediaBrowser.Providers.MediaInfo; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using EpisodeInfo = Emby.Naming.TV.EpisodeInfo; using Genre = MediaBrowser.Controller.Entities.Genre; using Person = MediaBrowser.Controller.Entities.Person; using VideoResolver = Emby.Naming.Video.VideoResolver; @@ -2512,7 +2514,7 @@ namespace Emby.Server.Implementations.Library public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) { var series = episode.Series; - bool? isAbsoluteNaming = series == null ? false : string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); + bool? isAbsoluteNaming = series != null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); if (!isAbsoluteNaming.Value) { // In other words, no filter applied @@ -2524,9 +2526,32 @@ namespace Emby.Server.Implementations.Library var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; // TODO nullable - what are we trying to do there with empty episodeInfo? - var episodeInfo = episode.IsFileProtocol - ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo(episode.Path) - : new Naming.TV.EpisodeInfo(episode.Path); + EpisodeInfo episodeInfo = null; + if (episode.IsFileProtocol) + { + episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new EpisodeInfo(episode.Path); + // Resolve from parent folder if it's not the Season folder + if (!episodeInfo.EpisodeNumber.HasValue && episode.Parent is not Season) + { + var episodeInfoFromFolder = resolver.Resolve(Path.GetDirectoryName(episode.Path)!, true, null, null, isAbsoluteNaming); + // merge the missing information + episodeInfo.SeriesName = episodeInfoFromFolder?.SeriesName; + episodeInfo.EpisodeNumber ??= episodeInfoFromFolder?.EpisodeNumber; + episodeInfo.EndingEpisodeNumber ??= episodeInfoFromFolder?.EndingEpisodeNumber; + episodeInfo.SeasonNumber ??= episodeInfoFromFolder?.SeasonNumber; + episodeInfo.Container ??= episodeInfoFromFolder?.Container; + episodeInfo.Format3D ??= episodeInfoFromFolder?.Format3D; + episodeInfo.Is3D = episodeInfoFromFolder?.Is3D ?? episodeInfo.Is3D; + episodeInfo.IsStub = episodeInfoFromFolder?.IsStub ?? episodeInfo.IsStub; + episodeInfo.StubType = episodeInfoFromFolder?.StubType; + episodeInfo.IsByDate = episodeInfoFromFolder?.IsStub ?? episodeInfo.IsByDate; + episodeInfo.Day ??= episodeInfoFromFolder?.Day; + episodeInfo.Month ??= episodeInfoFromFolder?.Month; + episodeInfo.Year ??= episodeInfoFromFolder?.Year; + } + } + + episodeInfo ??= new EpisodeInfo(episode.Path); try { From e7fc18d0f31c5516889c9dbb65d3cb3421d8b271 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 9 Apr 2021 13:28:27 +0200 Subject: [PATCH 041/612] Fix type check --- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f92c30093..c69b8991b 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2531,7 +2531,7 @@ namespace Emby.Server.Implementations.Library { episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new EpisodeInfo(episode.Path); // Resolve from parent folder if it's not the Season folder - if (!episodeInfo.EpisodeNumber.HasValue && episode.Parent is not Season) + if (!episodeInfo.EpisodeNumber.HasValue && episode.Parent.GetType() == typeof(Folder)) { var episodeInfoFromFolder = resolver.Resolve(Path.GetDirectoryName(episode.Path)!, true, null, null, isAbsoluteNaming); // merge the missing information From 69d2368fbcf734f48341edf73a9e8baae6229343 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 9 Apr 2021 13:31:17 +0200 Subject: [PATCH 042/612] Copy paste error --- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index c69b8991b..aed098e1b 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2544,7 +2544,7 @@ namespace Emby.Server.Implementations.Library episodeInfo.Is3D = episodeInfoFromFolder?.Is3D ?? episodeInfo.Is3D; episodeInfo.IsStub = episodeInfoFromFolder?.IsStub ?? episodeInfo.IsStub; episodeInfo.StubType = episodeInfoFromFolder?.StubType; - episodeInfo.IsByDate = episodeInfoFromFolder?.IsStub ?? episodeInfo.IsByDate; + episodeInfo.IsByDate = episodeInfoFromFolder?.IsByDate ?? episodeInfo.IsByDate; episodeInfo.Day ??= episodeInfoFromFolder?.Day; episodeInfo.Month ??= episodeInfoFromFolder?.Month; episodeInfo.Year ??= episodeInfoFromFolder?.Year; From 457229c56de2bf13af8852611a9c47c4950f1561 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 9 Apr 2021 13:43:40 +0200 Subject: [PATCH 043/612] Simplification --- Emby.Naming/TV/EpisodeResolver.cs | 5 ++++ .../Library/LibraryManager.cs | 25 ++++++------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index f7df58786..eac192be5 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -68,6 +68,11 @@ namespace Emby.Naming.TV var parsingResult = new EpisodePathParser(_options) .Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); + if (!parsingResult.Success) + { + return null; + } + return new EpisodeInfo(path) { Container = container, diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index aed098e1b..f840f3695 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2529,25 +2529,16 @@ namespace Emby.Server.Implementations.Library EpisodeInfo episodeInfo = null; if (episode.IsFileProtocol) { - episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new EpisodeInfo(episode.Path); + episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming); // Resolve from parent folder if it's not the Season folder - if (!episodeInfo.EpisodeNumber.HasValue && episode.Parent.GetType() == typeof(Folder)) + if (episodeInfo == null && episode.Parent.GetType() == typeof(Folder)) { - var episodeInfoFromFolder = resolver.Resolve(Path.GetDirectoryName(episode.Path)!, true, null, null, isAbsoluteNaming); - // merge the missing information - episodeInfo.SeriesName = episodeInfoFromFolder?.SeriesName; - episodeInfo.EpisodeNumber ??= episodeInfoFromFolder?.EpisodeNumber; - episodeInfo.EndingEpisodeNumber ??= episodeInfoFromFolder?.EndingEpisodeNumber; - episodeInfo.SeasonNumber ??= episodeInfoFromFolder?.SeasonNumber; - episodeInfo.Container ??= episodeInfoFromFolder?.Container; - episodeInfo.Format3D ??= episodeInfoFromFolder?.Format3D; - episodeInfo.Is3D = episodeInfoFromFolder?.Is3D ?? episodeInfo.Is3D; - episodeInfo.IsStub = episodeInfoFromFolder?.IsStub ?? episodeInfo.IsStub; - episodeInfo.StubType = episodeInfoFromFolder?.StubType; - episodeInfo.IsByDate = episodeInfoFromFolder?.IsByDate ?? episodeInfo.IsByDate; - episodeInfo.Day ??= episodeInfoFromFolder?.Day; - episodeInfo.Month ??= episodeInfoFromFolder?.Month; - episodeInfo.Year ??= episodeInfoFromFolder?.Year; + episodeInfo = resolver.Resolve(Path.GetDirectoryName(episode.Path)!, true, null, null, isAbsoluteNaming); + if (episodeInfo != null) + { + // add the container + episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.'); + } } } From 53db1a1ffcba7edf9e5da677b6ed7e7e67f0d63a Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 9 Apr 2021 13:47:47 +0200 Subject: [PATCH 044/612] ... --- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f840f3695..dd678eca0 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2533,7 +2533,7 @@ namespace Emby.Server.Implementations.Library // Resolve from parent folder if it's not the Season folder if (episodeInfo == null && episode.Parent.GetType() == typeof(Folder)) { - episodeInfo = resolver.Resolve(Path.GetDirectoryName(episode.Path)!, true, null, null, isAbsoluteNaming); + episodeInfo = resolver.Resolve(episode.Parent.Path, true, null, null, isAbsoluteNaming); if (episodeInfo != null) { // add the container From 5c4be2416d89bf31c6f62042f833e8f91621d8f6 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 9 Apr 2021 13:48:25 +0200 Subject: [PATCH 045/612] Remove unused import --- Emby.Server.Implementations/Library/LibraryManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index dd678eca0..ac5028827 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -27,7 +27,6 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; From 381db60ed37c8d1fa56d57f9e63b750949ad974b Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 9 Apr 2021 15:05:39 +0200 Subject: [PATCH 046/612] fix test --- Emby.Naming/TV/EpisodeResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index eac192be5..c63aec64e 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -68,7 +68,7 @@ namespace Emby.Naming.TV var parsingResult = new EpisodePathParser(_options) .Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); - if (!parsingResult.Success) + if (!parsingResult.Success && !isStub) { return null; } From 271b4cadb75a09a98dc4aa16ad7fd71af2797980 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 9 Apr 2021 23:16:07 -0400 Subject: [PATCH 047/612] Add device entity. --- Jellyfin.Data/Entities/Security/Device.cs | 81 +++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 Jellyfin.Data/Entities/Security/Device.cs diff --git a/Jellyfin.Data/Entities/Security/Device.cs b/Jellyfin.Data/Entities/Security/Device.cs new file mode 100644 index 000000000..8e8b40aa1 --- /dev/null +++ b/Jellyfin.Data/Entities/Security/Device.cs @@ -0,0 +1,81 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Data.Entities.Security +{ + /// + /// An entity representing a device. + /// + public class Device + { + /// + /// Initializes a new instance of the class. + /// + /// The user id. + /// The app name. + /// The app version. + /// The device name. + /// The device id. + public Device(Guid userId, string appName, string appVersion, string deviceName, string deviceId) + { + UserId = userId; + AppName = appName; + AppVersion = appVersion; + DeviceName = deviceName; + DeviceId = deviceId; + + DateCreated = DateTime.UtcNow; + DateLastActivity = DateCreated; + } + + public int Id { get; private set; } + + /// + /// Gets the user id. + /// + public Guid UserId { get; private set; } + + /// + /// Gets or sets the app name. + /// + [MaxLength(64)] + [StringLength(64)] + public string AppName { get; set; } + + /// + /// Gets or sets the app version. + /// + [MaxLength(32)] + [StringLength(32)] + public string AppVersion { get; set; } + + /// + /// Gets or sets the device name. + /// + [MaxLength(64)] + [StringLength(64)] + public string DeviceName { get; set; } + + /// + /// Gets or sets the device id. + /// + [MaxLength(256)] + [StringLength(256)] + public string DeviceId { get; set; } + + /// + /// Gets or sets a value indicating whether this device is active. + /// + public bool IsActive { get; set; } + + /// + /// Gets the date this device was created. + /// + public DateTime DateCreated { get; private set; } + + /// + /// Gets or sets the date of last activity. + /// + public DateTime DateLastActivity { get; set; } + } +} From 1fe26fe352a854523337842b16d6d0be5e68be5a Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 10 Apr 2021 19:44:09 +0100 Subject: [PATCH 048/612] Work through dns failure on test. --- tests/Jellyfin.Networking.Tests/NetworkParseTests.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index caea0d265..b6bbfc5e1 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -382,6 +382,8 @@ namespace Jellyfin.Networking.Tests [InlineData("jellyfin.org", "eth16", false, "eth16")] // User on external network, no binding - so result is the 1st external. [InlineData("jellyfin.org", "", false, "eth11")] + // Dns failure - should skip the test. + [InlineData("ospoakdposkd.abc", "", 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) @@ -414,10 +416,13 @@ namespace Jellyfin.Networking.Tests _ = nm.TryParseInterface(result, out Collection? resultObj); - if (resultObj != null) + // Check to see if dns resolution is working. If not, skip test. + _ = IPHost.TryParse(source, out var host); + + if (resultObj != null && host?.HasAddress == true) { result = ((IPNetAddress)resultObj[0]).ToString(true); - var intf = nm.GetBindInterface(source, out int? _); + var intf = nm.GetBindInterface(source, out _); Assert.Equal(intf, result); } From 9cd53523583b2b3e12ba927ce10e9685ea78d255 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 10 Apr 2021 16:03:20 -0400 Subject: [PATCH 049/612] Add device options entity --- .../Entities/Security/DeviceOptions.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Jellyfin.Data/Entities/Security/DeviceOptions.cs diff --git a/Jellyfin.Data/Entities/Security/DeviceOptions.cs b/Jellyfin.Data/Entities/Security/DeviceOptions.cs new file mode 100644 index 000000000..531f66c62 --- /dev/null +++ b/Jellyfin.Data/Entities/Security/DeviceOptions.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Jellyfin.Data.Entities.Security +{ + /// + /// An entity representing custom options for a device. + /// + public class DeviceOptions + { + /// + /// Initializes a new instance of the class. + /// + /// The device id. + public DeviceOptions(string deviceId) + { + DeviceId = deviceId; + } + + /// + /// Gets the id. + /// + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; private set; } + + /// + /// Gets the device id. + /// + public string DeviceId { get; private set; } + + /// + /// Gets or sets the custom name. + /// + public string? CustomName { get; set; } + } +} From e6f1ffdc8d2d430cbe4ac1d797e455950bb79ed3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 10 Apr 2021 16:15:59 -0400 Subject: [PATCH 050/612] Add device entities to schema. --- Jellyfin.Server.Implementations/JellyfinDb.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 0559e5783..32a857047 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -32,6 +32,10 @@ namespace Jellyfin.Server.Implementations public virtual DbSet ApiKeys { get; set; } + public virtual DbSet Devices { get; set; } + + public virtual DbSet DeviceOptions { get; set; } + public virtual DbSet DisplayPreferences { get; set; } public virtual DbSet ImageInfos { get; set; } From 98e19c9fd34c831cce445a5d4aa0a7f277f16237 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 10 Apr 2021 16:16:08 -0400 Subject: [PATCH 051/612] Add device indexes --- Jellyfin.Server.Implementations/JellyfinDb.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 32a857047..059e884e5 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -211,6 +211,13 @@ namespace Jellyfin.Server.Implementations .HasIndex(entity => entity.Username) .IsUnique(); + modelBuilder.Entity() + .HasIndex(entity => new { entity.DeviceId, entity.DateLastActivity }); + + modelBuilder.Entity() + .HasIndex(entity => entity.DeviceId) + .IsUnique(); + modelBuilder.Entity() .HasIndex(entity => new { entity.UserId, entity.ItemId, entity.Client }) .IsUnique(); From f47fe308b1f5a73f684a8b69754d15d6c2c36b13 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 10 Apr 2021 16:17:02 -0400 Subject: [PATCH 052/612] Add navigation property to device entity --- Jellyfin.Data/Entities/Security/Device.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Jellyfin.Data/Entities/Security/Device.cs b/Jellyfin.Data/Entities/Security/Device.cs index 8e8b40aa1..17d17f594 100644 --- a/Jellyfin.Data/Entities/Security/Device.cs +++ b/Jellyfin.Data/Entities/Security/Device.cs @@ -26,6 +26,9 @@ namespace Jellyfin.Data.Entities.Security DateCreated = DateTime.UtcNow; DateLastActivity = DateCreated; + + // Non-nullable for EF Core, as this is a required relationship. + User = null!; } public int Id { get; private set; } @@ -77,5 +80,10 @@ namespace Jellyfin.Data.Entities.Security /// Gets or sets the date of last activity. /// public DateTime DateLastActivity { get; set; } + + /// + /// Gets the user. + /// + public User User { get; private set; } } } From 44e71774b17942034691d6a2c630cd687b23bceb Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 10 Apr 2021 16:17:36 -0400 Subject: [PATCH 053/612] Rewrite device manager using EF Core --- .../ApplicationHost.cs | 2 - .../Security/AuthenticationRepository.cs | 4 +- .../Session/SessionManager.cs | 2 +- Jellyfin.Api/Controllers/DevicesController.cs | 14 +- .../Devices/DeviceManager.cs | 124 ++++++++++-------- Jellyfin.Server/CoreAppHost.cs | 3 + .../Devices/IDeviceManager.cs | 9 +- .../Security/IAuthenticationRepository.cs | 2 +- MediaBrowser.Model/Devices/DeviceOptions.cs | 9 -- 9 files changed, 89 insertions(+), 80 deletions(-) rename {Emby.Server.Implementations => Jellyfin.Server.Implementations}/Devices/DeviceManager.cs (50%) delete mode 100644 MediaBrowser.Model/Devices/DeviceOptions.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 3846de5fd..f2ed20fbc 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -644,8 +644,6 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 4bc12f44a..0d0a2b1df 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -6,9 +6,9 @@ using System.Globalization; using System.IO; using System.Linq; using Emby.Server.Implementations.Data; +using Jellyfin.Data.Entities.Security; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; @@ -357,7 +357,7 @@ namespace Emby.Server.Implementations.Security { statement.TryBind("@DeviceId", deviceId); - var result = new DeviceOptions(); + var result = new DeviceOptions(deviceId); foreach (var row in statement.ExecuteQuery()) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index e06e48ca6..a47a1f56f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using MediaBrowser.Common.Events; @@ -24,7 +25,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Devices; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index b3e3490c2..3ca6488d9 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -1,6 +1,8 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; using Jellyfin.Api.Constants; +using Jellyfin.Data.Entities.Security; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; @@ -47,10 +49,10 @@ namespace Jellyfin.Api.Controllers /// An containing the list of devices. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) + public async Task>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) { var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; - return _deviceManager.GetDevices(deviceQuery); + return await _deviceManager.GetDevices(deviceQuery); } /// @@ -63,9 +65,9 @@ namespace Jellyfin.Api.Controllers [HttpGet("Info")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetDeviceInfo([FromQuery, Required] string id) + public async Task> GetDeviceInfo([FromQuery, Required] string id) { - var deviceInfo = _deviceManager.GetDevice(id); + var deviceInfo = await _deviceManager.GetDevice(id).ConfigureAwait(false); if (deviceInfo == null) { return NotFound(); @@ -106,7 +108,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Options")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateDeviceOptions( + public async Task UpdateDeviceOptions( [FromQuery, Required] string id, [FromBody, Required] DeviceOptions deviceOptions) { @@ -116,7 +118,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - _deviceManager.UpdateDeviceOptions(id, deviceOptions); + await _deviceManager.UpdateDeviceOptions(id, deviceOptions).ConfigureAwait(false); return NoContent(); } diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs similarity index 50% rename from Emby.Server.Implementations/Devices/DeviceManager.cs rename to Jellyfin.Server.Implementations/Devices/DeviceManager.cs index da5047d24..c942678d9 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -1,78 +1,100 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Security; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; +using Microsoft.EntityFrameworkCore; -namespace Emby.Server.Implementations.Devices +namespace Jellyfin.Server.Implementations.Devices { public class DeviceManager : IDeviceManager { + private readonly JellyfinDbProvider _dbProvider; private readonly IUserManager _userManager; - private readonly IAuthenticationRepository _authRepo; private readonly ConcurrentDictionary _capabilitiesMap = new (); - public DeviceManager(IAuthenticationRepository authRepo, IUserManager userManager) + /// + /// Initializes a new instance of the class. + /// + /// The database provider. + /// The user manager. + public DeviceManager(JellyfinDbProvider dbProvider, IUserManager userManager) { + _dbProvider = dbProvider; _userManager = userManager; - _authRepo = authRepo; } - public event EventHandler>> DeviceOptionsUpdated; + /// + public event EventHandler>>? DeviceOptionsUpdated; + /// public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) { _capabilitiesMap[deviceId] = capabilities; } - public void UpdateDeviceOptions(string deviceId, DeviceOptions options) + /// + public async Task UpdateDeviceOptions(string deviceId, DeviceOptions options) { - _authRepo.UpdateDeviceOptions(deviceId, options); + await using var dbContext = _dbProvider.CreateContext(); + await dbContext.Database + .ExecuteSqlRawAsync($"UPDATE [DeviceOptions] SET [CustomName] = ${options.CustomName}") + .ConfigureAwait(false); DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs>(new Tuple(deviceId, options))); } - public DeviceOptions GetDeviceOptions(string deviceId) + /// + public DeviceOptions? GetDeviceOptions(string deviceId) { - return _authRepo.GetDeviceOptions(deviceId); + using var dbContext = _dbProvider.CreateContext(); + return dbContext.DeviceOptions + .AsQueryable() + .FirstOrDefault(d => d.DeviceId == deviceId); } + /// public ClientCapabilities GetCapabilities(string id) { - return _capabilitiesMap.TryGetValue(id, out ClientCapabilities result) + return _capabilitiesMap.TryGetValue(id, out ClientCapabilities? result) ? result : new ClientCapabilities(); } - public DeviceInfo GetDevice(string id) + /// + public async Task GetDevice(string id) { - var session = _authRepo.Get(new AuthenticationInfoQuery - { - DeviceId = id - }).Items.FirstOrDefault(); + await using var dbContext = _dbProvider.CreateContext(); + var device = await dbContext.Devices + .AsQueryable() + .Where(d => d.DeviceId == id) + .OrderByDescending(d => d.DateLastActivity) + .Include(d => d.User) + .FirstOrDefaultAsync() + .ConfigureAwait(false); - var device = session == null ? null : ToDeviceInfo(session); + var deviceInfo = device == null ? null : ToDeviceInfo(device); - return device; + return deviceInfo; } - public QueryResult GetDevices(DeviceQuery query) + /// + public async Task> GetDevices(DeviceQuery query) { - IEnumerable sessions = _authRepo.Get(new AuthenticationInfoQuery - { - // UserId = query.UserId - HasUser = true - }).Items; + await using var dbContext = _dbProvider.CreateContext(); + var sessions = dbContext.Devices + .AsQueryable() + .OrderBy(d => d.DeviceId) + .ThenByDescending(d => d.DateLastActivity) + .AsAsyncEnumerable(); // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger. if (query.SupportsSync.HasValue) @@ -89,28 +111,12 @@ namespace Emby.Server.Implementations.Devices sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); } - var array = sessions.Select(ToDeviceInfo).ToArray(); + var array = await sessions.Select(ToDeviceInfo).ToArrayAsync(); return new QueryResult(array); } - private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo) - { - var caps = GetCapabilities(authInfo.DeviceId); - - return new DeviceInfo - { - AppName = authInfo.AppName, - AppVersion = authInfo.AppVersion, - Id = authInfo.DeviceId, - LastUserId = authInfo.UserId, - LastUserName = authInfo.UserName, - Name = authInfo.DeviceName, - DateLastActivity = authInfo.DateLastActivity, - IconUrl = caps?.IconUrl - }; - } - + /// public bool CanAccessDevice(User user, string deviceId) { if (user == null) @@ -128,17 +134,25 @@ namespace Emby.Server.Implementations.Devices return true; } - if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase)) + return user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase) + || !GetCapabilities(deviceId).SupportsPersistentIdentifier; + } + + private DeviceInfo ToDeviceInfo(Device authInfo) + { + var caps = GetCapabilities(authInfo.DeviceId); + + return new DeviceInfo { - var capabilities = GetCapabilities(deviceId); - - if (capabilities != null && capabilities.SupportsPersistentIdentifier) - { - return false; - } - } - - return true; + AppName = authInfo.AppName, + AppVersion = authInfo.AppVersion, + Id = authInfo.DeviceId, + LastUserId = authInfo.UserId, + LastUserName = authInfo.User.Username, + Name = authInfo.DeviceName, + DateLastActivity = authInfo.DateLastActivity, + IconUrl = caps.IconUrl + }; } } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 94c3ca4a9..b20acae32 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -9,10 +9,12 @@ using Jellyfin.Api.WebSocketListeners; using Jellyfin.Drawing.Skia; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; +using Jellyfin.Server.Implementations.Devices; using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; using MediaBrowser.Controller.BaseItemManager; +using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; @@ -84,6 +86,7 @@ namespace Jellyfin.Server ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); + ServiceCollection.AddSingleton(); // TODO search the assemblies instead of adding them manually? ServiceCollection.AddSingleton(); diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 8f0872dba..aa05ead8f 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,7 +1,9 @@ #pragma warning disable CS1591 using System; +using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; @@ -18,7 +20,6 @@ namespace MediaBrowser.Controller.Devices /// /// The reported identifier. /// The capabilities. - /// Task. void SaveCapabilities(string reportedId, ClientCapabilities capabilities); /// @@ -33,21 +34,21 @@ namespace MediaBrowser.Controller.Devices /// /// The identifier. /// DeviceInfo. - DeviceInfo GetDevice(string id); + Task GetDevice(string id); /// /// Gets the devices. /// /// The query. /// IEnumerable<DeviceInfo>. - QueryResult GetDevices(DeviceQuery query); + Task> GetDevices(DeviceQuery query); /// /// Determines whether this instance [can access device] the specified user identifier. /// bool CanAccessDevice(User user, string deviceId); - void UpdateDeviceOptions(string deviceId, DeviceOptions options); + Task UpdateDeviceOptions(string deviceId, DeviceOptions options); DeviceOptions GetDeviceOptions(string deviceId); } diff --git a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs index 883b74165..27f281b71 100644 --- a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs +++ b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs @@ -1,6 +1,6 @@ #pragma warning disable CS1591 -using MediaBrowser.Model.Devices; +using Jellyfin.Data.Entities.Security; using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Security diff --git a/MediaBrowser.Model/Devices/DeviceOptions.cs b/MediaBrowser.Model/Devices/DeviceOptions.cs deleted file mode 100644 index 037ffeb5e..000000000 --- a/MediaBrowser.Model/Devices/DeviceOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Devices -{ - public class DeviceOptions - { - public string? CustomName { get; set; } - } -} From 8607b5254142662e79dbf826d43375ce60727cfe Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 10 Apr 2021 16:57:25 -0400 Subject: [PATCH 054/612] Make device/session code async --- Emby.Dlna/PlayTo/PlayToManager.cs | 4 +- .../HttpServer/Security/SessionContext.cs | 11 +-- .../Session/SessionManager.cs | 26 +++--- .../Session/SessionWebSocketListener.cs | 4 +- Jellyfin.Api/Controllers/DevicesController.cs | 4 +- Jellyfin.Api/Controllers/LiveTvController.cs | 26 +++--- .../Controllers/PlaystateController.cs | 20 ++--- Jellyfin.Api/Controllers/SessionController.cs | 72 +++++++++------- .../Controllers/SyncPlayController.cs | 85 ++++++++++--------- Jellyfin.Api/Helpers/RequestHelpers.cs | 14 ++- .../Devices/DeviceManager.cs | 9 +- .../Devices/IDeviceManager.cs | 2 +- .../Net/ISessionContext.cs | 9 +- .../Session/ISessionManager.cs | 6 +- 14 files changed, 160 insertions(+), 132 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 8272e505a..236ea4d57 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -171,7 +171,9 @@ namespace Emby.Dlna.PlayTo uuid = uri.ToString().GetMD5().ToString("N", CultureInfo.InvariantCulture); } - var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null); + var sessionInfo = await _sessionManager + .LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null) + .ConfigureAwait(false); var controller = sessionInfo.SessionControllers.OfType().FirstOrDefault(); diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 040b6b9e4..1b295a92d 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; @@ -23,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer.Security _sessionManager = sessionManager; } - public SessionInfo GetSession(HttpContext requestContext) + public Task GetSession(HttpContext requestContext) { var authorization = _authContext.GetAuthorizationInfo(requestContext); @@ -31,19 +32,19 @@ namespace Emby.Server.Implementations.HttpServer.Security return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp(), user); } - public SessionInfo GetSession(object requestContext) + public Task GetSession(object requestContext) { return GetSession((HttpContext)requestContext); } - public User GetUser(HttpContext requestContext) + public async Task GetUser(HttpContext requestContext) { - var session = GetSession(requestContext); + var session = await GetSession(requestContext).ConfigureAwait(false); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public User GetUser(object requestContext) + public Task GetUser(object requestContext) { return GetUser(((HttpRequest)requestContext).HttpContext); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index a47a1f56f..678a27665 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -254,7 +254,7 @@ namespace Emby.Server.Implementations.Session /// The remote end point. /// The user. /// SessionInfo. - public SessionInfo LogSessionActivity( + public async Task LogSessionActivity( string appName, string appVersion, string deviceId, @@ -280,7 +280,7 @@ namespace Emby.Server.Implementations.Session } var activityDate = DateTime.UtcNow; - var session = GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user); + var session = await GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); var lastActivityDate = session.LastActivityDate; session.LastActivityDate = activityDate; @@ -458,7 +458,7 @@ namespace Emby.Server.Implementations.Session /// The remote end point. /// The user. /// SessionInfo. - private SessionInfo GetSessionInfo( + private async Task GetSessionInfo( string appName, string appVersion, string deviceId, @@ -477,9 +477,11 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); - var sessionInfo = _activeConnections.GetOrAdd( - key, - k => CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user)); + if (!_activeConnections.TryGetValue(key, out var sessionInfo)) + { + _activeConnections[key] = await CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); + sessionInfo = _activeConnections[key]; + } sessionInfo.UserId = user?.Id ?? Guid.Empty; sessionInfo.UserName = user?.Username; @@ -502,7 +504,7 @@ namespace Emby.Server.Implementations.Session return sessionInfo; } - private SessionInfo CreateSession( + private async Task CreateSession( string key, string appName, string appVersion, @@ -532,7 +534,7 @@ namespace Emby.Server.Implementations.Session deviceName = "Network Device"; } - var deviceOptions = _deviceManager.GetDeviceOptions(deviceId); + var deviceOptions = await _deviceManager.GetDeviceOptions(deviceId).ConfigureAwait(false); if (string.IsNullOrEmpty(deviceOptions.CustomName)) { sessionInfo.DeviceName = deviceName; @@ -1507,13 +1509,13 @@ namespace Emby.Server.Implementations.Session var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); - var session = LogSessionActivity( + var session = await LogSessionActivity( request.App, request.AppVersion, request.DeviceId, request.DeviceName, request.RemoteEndPoint, - user); + user).ConfigureAwait(false); var returnResult = new AuthenticationResult { @@ -1811,7 +1813,7 @@ namespace Emby.Server.Implementations.Session } /// - public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) + public Task GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) { if (info == null) { @@ -1844,7 +1846,7 @@ namespace Emby.Server.Implementations.Session } /// - public SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) + public Task GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) { var items = _authRepo.Get(new AuthenticationInfoQuery { diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 39c369a01..54e64cfeb 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.Session /// public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection) { - var session = GetSession(connection.QueryString, connection.RemoteEndPoint.ToString()); + var session = await GetSession(connection.QueryString, connection.RemoteEndPoint.ToString()).ConfigureAwait(false); if (session != null) { EnsureController(session, connection); @@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Session } } - private SessionInfo GetSession(IQueryCollection queryString, string remoteEndpoint) + private Task GetSession(IQueryCollection queryString, string remoteEndpoint) { if (queryString == null) { diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 3ca6488d9..99f8ede3a 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -86,9 +86,9 @@ namespace Jellyfin.Api.Controllers [HttpGet("Options")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetDeviceOptions([FromQuery, Required] string id) + public async Task> GetDeviceOptions([FromQuery, Required] string id) { - var deviceInfo = _deviceManager.GetDeviceOptions(id); + var deviceInfo = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false); if (deviceInfo == null) { return NotFound(); diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 24ee833ef..3e9b8bfa4 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -429,10 +429,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Tuners/{tunerId}/Reset")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.DefaultAuthorization)] - public ActionResult ResetTuner([FromRoute, Required] string tunerId) + public async Task ResetTuner([FromRoute, Required] string tunerId) { - AssertUserCanManageLiveTv(); - _liveTvManager.ResetTuner(tunerId, CancellationToken.None); + await AssertUserCanManageLiveTv().ConfigureAwait(false); + await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -761,9 +761,9 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId) + public async Task DeleteRecording([FromRoute, Required] Guid recordingId) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); var item = _libraryManager.GetItemById(recordingId); if (item == null) @@ -790,7 +790,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CancelTimer([FromRoute, Required] string timerId) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false); return NoContent(); } @@ -808,7 +808,7 @@ namespace Jellyfin.Api.Controllers [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] public async Task UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -824,7 +824,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CreateTimer([FromBody] TimerInfoDto timerInfo) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -882,7 +882,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CancelSeriesTimer([FromRoute, Required] string timerId) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false); return NoContent(); } @@ -900,7 +900,7 @@ namespace Jellyfin.Api.Controllers [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] public async Task UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -916,7 +916,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -1215,9 +1215,9 @@ namespace Jellyfin.Api.Controllers return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container)); } - private void AssertUserCanManageLiveTv() + private async Task AssertUserCanManageLiveTv() { - var user = _sessionContext.GetUser(Request); + var user = await _sessionContext.GetUser(Request).ConfigureAwait(false); if (user == null) { diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index f256c8c25..cc8c630b3 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -72,13 +72,13 @@ namespace Jellyfin.Api.Controllers /// An containing the . [HttpPost("Users/{userId}/PlayedItems/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult MarkPlayedItem( + public async Task> MarkPlayedItem( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed) { var user = _userManager.GetUserById(userId); - var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var session = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false); var dto = UpdatePlayedStatus(user, itemId, true, datePlayed); foreach (var additionalUserInfo in session.AdditionalUsers) { @@ -98,10 +98,10 @@ namespace Jellyfin.Api.Controllers /// A containing the . [HttpDelete("Users/{userId}/PlayedItems/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) + public async Task> MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) { var user = _userManager.GetUserById(userId); - var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var session = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false); var dto = UpdatePlayedStatus(user, itemId, false, null); foreach (var additionalUserInfo in session.AdditionalUsers) { @@ -123,7 +123,7 @@ namespace Jellyfin.Api.Controllers public async Task ReportPlaybackStart([FromBody] PlaybackStartInfo playbackStartInfo) { playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId); - playbackStartInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false); return NoContent(); } @@ -139,7 +139,7 @@ namespace Jellyfin.Api.Controllers public async Task ReportPlaybackProgress([FromBody] PlaybackProgressInfo playbackProgressInfo) { playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId); - playbackProgressInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false); return NoContent(); } @@ -174,7 +174,7 @@ namespace Jellyfin.Api.Controllers await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } - playbackStopInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false); return NoContent(); } @@ -220,7 +220,7 @@ namespace Jellyfin.Api.Controllers }; playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId); - playbackStartInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false); return NoContent(); } @@ -278,7 +278,7 @@ namespace Jellyfin.Api.Controllers }; playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId); - playbackProgressInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false); return NoContent(); } @@ -323,7 +323,7 @@ namespace Jellyfin.Api.Controllers await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } - playbackStopInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index e2269a2ce..14ce75514 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; @@ -124,7 +125,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Viewing")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult DisplayContent( + public async Task DisplayContent( [FromRoute, Required] string sessionId, [FromQuery, Required] string itemType, [FromQuery, Required] string itemId, @@ -137,11 +138,12 @@ namespace Jellyfin.Api.Controllers ItemType = itemType }; - _sessionManager.SendBrowseCommand( - RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, + await _sessionManager.SendBrowseCommand( + await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false), sessionId, command, - CancellationToken.None); + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -158,7 +160,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Playing")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult Play( + public async Task Play( [FromRoute, Required] string sessionId, [FromQuery, Required] PlayCommand playCommand, [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds, @@ -171,11 +173,12 @@ namespace Jellyfin.Api.Controllers PlayCommand = playCommand }; - _sessionManager.SendPlayCommand( - RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, + await _sessionManager.SendPlayCommand( + await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false), sessionId, playRequest, - CancellationToken.None); + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -192,14 +195,14 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Playing/{command}")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendPlaystateCommand( + public async Task SendPlaystateCommand( [FromRoute, Required] string sessionId, [FromRoute, Required] PlaystateCommand command, [FromQuery] long? seekPositionTicks, [FromQuery] string? controllingUserId) { - _sessionManager.SendPlaystateCommand( - RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, + await _sessionManager.SendPlaystateCommand( + await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false), sessionId, new PlaystateRequest() { @@ -207,7 +210,8 @@ namespace Jellyfin.Api.Controllers ControllingUserId = controllingUserId, SeekPositionTicks = seekPositionTicks, }, - CancellationToken.None); + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -222,18 +226,18 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/System/{command}")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendSystemCommand( + public async Task SendSystemCommand( [FromRoute, Required] string sessionId, [FromRoute, Required] GeneralCommandType command) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false); var generalCommand = new GeneralCommand { Name = command, ControllingUserId = currentSession.UserId }; - _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None); + await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None); return NoContent(); } @@ -248,11 +252,11 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Command/{command}")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendGeneralCommand( + public async Task SendGeneralCommand( [FromRoute, Required] string sessionId, [FromRoute, Required] GeneralCommandType command) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false); var generalCommand = new GeneralCommand { @@ -260,7 +264,8 @@ namespace Jellyfin.Api.Controllers ControllingUserId = currentSession.UserId }; - _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None); + await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -275,11 +280,12 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Command")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendFullGeneralCommand( + public async Task SendFullGeneralCommand( [FromRoute, Required] string sessionId, [FromBody, Required] GeneralCommand command) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authContext, Request) + .ConfigureAwait(false); if (command == null) { @@ -288,11 +294,12 @@ namespace Jellyfin.Api.Controllers command.ControllingUserId = currentSession.UserId; - _sessionManager.SendGeneralCommand( + await _sessionManager.SendGeneralCommand( currentSession.Id, sessionId, command, - CancellationToken.None); + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -309,7 +316,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Message")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendMessageCommand( + public async Task SendMessageCommand( [FromRoute, Required] string sessionId, [FromQuery, Required] string text, [FromQuery] string? header, @@ -322,7 +329,12 @@ namespace Jellyfin.Api.Controllers Text = text }; - _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, command, CancellationToken.None); + await _sessionManager.SendMessageCommand( + await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false), + sessionId, + command, + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -377,7 +389,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/Capabilities")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult PostCapabilities( + public async Task PostCapabilities( [FromQuery] string? id, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] playableMediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands, @@ -387,7 +399,7 @@ namespace Jellyfin.Api.Controllers { if (string.IsNullOrWhiteSpace(id)) { - id = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + id = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); } _sessionManager.ReportCapabilities(id, new ClientCapabilities @@ -411,13 +423,13 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/Capabilities/Full")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult PostFullCapabilities( + public async Task PostFullCapabilities( [FromQuery] string? id, [FromBody, Required] ClientCapabilitiesDto capabilities) { if (string.IsNullOrWhiteSpace(id)) { - id = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + id = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); } _sessionManager.ReportCapabilities(id, capabilities.ToClientCapabilities()); @@ -435,11 +447,11 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/Viewing")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult ReportViewing( + public async Task ReportViewing( [FromQuery] string? sessionId, [FromQuery, Required] string? itemId) { - string session = sessionId ?? RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + string session = sessionId ?? await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); _sessionManager.ReportNowViewingItem(session, itemId); return NoContent(); diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index f878f2329..1b3248c0c 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; +using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.SyncPlayDtos; @@ -51,10 +52,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("New")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayCreateGroup)] - public ActionResult SyncPlayCreateGroup( + public async Task SyncPlayCreateGroup( [FromBody, Required] NewGroupRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new NewGroupRequest(requestData.GroupName); _syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -69,10 +70,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Join")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayJoinGroup)] - public ActionResult SyncPlayJoinGroup( + public async Task SyncPlayJoinGroup( [FromBody, Required] JoinGroupRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new JoinGroupRequest(requestData.GroupId); _syncPlayManager.JoinGroup(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -86,9 +87,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Leave")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayLeaveGroup() + public async Task SyncPlayLeaveGroup() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new LeaveGroupRequest(); _syncPlayManager.LeaveGroup(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -102,9 +103,9 @@ namespace Jellyfin.Api.Controllers [HttpGet("List")] [ProducesResponseType(StatusCodes.Status200OK)] [Authorize(Policy = Policies.SyncPlayJoinGroup)] - public ActionResult> SyncPlayGetGroups() + public async Task>> SyncPlayGetGroups() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new ListGroupsRequest(); return Ok(_syncPlayManager.ListGroups(currentSession, syncPlayRequest)); } @@ -118,10 +119,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetNewQueue")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetNewQueue( + public async Task SyncPlaySetNewQueue( [FromBody, Required] PlayRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new PlayGroupRequest( requestData.PlayingQueue, requestData.PlayingItemPosition, @@ -139,10 +140,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetPlaylistItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetPlaylistItem( + public async Task SyncPlaySetPlaylistItem( [FromBody, Required] SetPlaylistItemRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new SetPlaylistItemGroupRequest(requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -157,10 +158,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("RemoveFromPlaylist")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayRemoveFromPlaylist( + public async Task SyncPlayRemoveFromPlaylist( [FromBody, Required] RemoveFromPlaylistRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new RemoveFromPlaylistGroupRequest(requestData.PlaylistItemIds); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -175,10 +176,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("MovePlaylistItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayMovePlaylistItem( + public async Task SyncPlayMovePlaylistItem( [FromBody, Required] MovePlaylistItemRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new MovePlaylistItemGroupRequest(requestData.PlaylistItemId, requestData.NewIndex); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -193,10 +194,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Queue")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayQueue( + public async Task SyncPlayQueue( [FromBody, Required] QueueRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new QueueGroupRequest(requestData.ItemIds, requestData.Mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -210,9 +211,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Unpause")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayUnpause() + public async Task SyncPlayUnpause() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new UnpauseGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -226,9 +227,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Pause")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayPause() + public async Task SyncPlayPause() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new PauseGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -242,9 +243,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Stop")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayStop() + public async Task SyncPlayStop() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new StopGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -259,10 +260,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Seek")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySeek( + public async Task SyncPlaySeek( [FromBody, Required] SeekRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new SeekGroupRequest(requestData.PositionTicks); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -277,10 +278,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Buffering")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayBuffering( + public async Task SyncPlayBuffering( [FromBody, Required] BufferRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new BufferGroupRequest( requestData.When, requestData.PositionTicks, @@ -299,10 +300,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Ready")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayReady( + public async Task SyncPlayReady( [FromBody, Required] ReadyRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new ReadyGroupRequest( requestData.When, requestData.PositionTicks, @@ -321,10 +322,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetIgnoreWait")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetIgnoreWait( + public async Task SyncPlaySetIgnoreWait( [FromBody, Required] IgnoreWaitRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new IgnoreWaitGroupRequest(requestData.IgnoreWait); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -339,10 +340,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("NextItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayNextItem( + public async Task SyncPlayNextItem( [FromBody, Required] NextItemRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new NextItemGroupRequest(requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -357,10 +358,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("PreviousItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayPreviousItem( + public async Task SyncPlayPreviousItem( [FromBody, Required] PreviousItemRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new PreviousItemGroupRequest(requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -375,10 +376,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetRepeatMode")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetRepeatMode( + public async Task SyncPlaySetRepeatMode( [FromBody, Required] SetRepeatModeRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new SetRepeatModeGroupRequest(requestData.Mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -393,10 +394,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetShuffleMode")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetShuffleMode( + public async Task SyncPlaySetShuffleMode( [FromBody, Required] SetShuffleModeRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new SetShuffleModeGroupRequest(requestData.Mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -410,10 +411,10 @@ namespace Jellyfin.Api.Controllers /// A indicating success. [HttpPost("Ping")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SyncPlayPing( + public async Task SyncPlayPing( [FromBody, Required] PingRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new PingGroupRequest(requestData.Ping); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 94856e03e..72b60ee57 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; @@ -75,17 +76,17 @@ namespace Jellyfin.Api.Helpers return true; } - internal static SessionInfo GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request) + internal static async Task GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request) { var authorization = authContext.GetAuthorizationInfo(request); var user = authorization.User; - var session = sessionManager.LogSessionActivity( + var session = await sessionManager.LogSessionActivity( authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, request.HttpContext.GetNormalizedRemoteIp(), - user); + user).ConfigureAwait(false); if (session == null) { @@ -95,6 +96,13 @@ namespace Jellyfin.Api.Helpers return session; } + internal static async Task GetSessionId(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request) + { + var session = await GetSession(sessionManager, authContext, request).ConfigureAwait(false); + + return session.Id; + } + internal static QueryResult CreateQueryResult( QueryResult<(BaseItem, ItemCounts)> result, DtoOptions dtoOptions, diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index c942678d9..0d93ee2bf 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -53,12 +53,13 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public DeviceOptions? GetDeviceOptions(string deviceId) + public async Task GetDeviceOptions(string deviceId) { - using var dbContext = _dbProvider.CreateContext(); - return dbContext.DeviceOptions + await using var dbContext = _dbProvider.CreateContext(); + return await dbContext.DeviceOptions .AsQueryable() - .FirstOrDefault(d => d.DeviceId == deviceId); + .FirstOrDefaultAsync(d => d.DeviceId == deviceId) + .ConfigureAwait(false); } /// diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index aa05ead8f..0df040794 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -50,6 +50,6 @@ namespace MediaBrowser.Controller.Devices Task UpdateDeviceOptions(string deviceId, DeviceOptions options); - DeviceOptions GetDeviceOptions(string deviceId); + Task GetDeviceOptions(string deviceId); } } diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs index a60dc2ea1..57938da1f 100644 --- a/MediaBrowser.Controller/Net/ISessionContext.cs +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Session; using Microsoft.AspNetCore.Http; @@ -8,12 +9,12 @@ namespace MediaBrowser.Controller.Net { public interface ISessionContext { - SessionInfo GetSession(object requestContext); + Task GetSession(object requestContext); - User GetUser(object requestContext); + Task GetUser(object requestContext); - SessionInfo GetSession(HttpContext requestContext); + Task GetSession(HttpContext requestContext); - User GetUser(HttpContext requestContext); + Task GetUser(HttpContext requestContext); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 9eb486534..630037570 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -80,7 +80,7 @@ namespace MediaBrowser.Controller.Session /// Name of the device. /// The remote end point. /// The user. - SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Jellyfin.Data.Entities.User user); + Task LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Jellyfin.Data.Entities.User user); /// /// Used to report that a session controller has connected. @@ -313,7 +313,7 @@ namespace MediaBrowser.Controller.Session /// The device identifier. /// The remote endpoint. /// SessionInfo. - SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint); + Task GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint); /// /// Gets the session by authentication token. @@ -323,7 +323,7 @@ namespace MediaBrowser.Controller.Session /// The remote endpoint. /// The application version. /// Task<SessionInfo>. - SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion); + Task GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion); /// /// Logouts the specified access token. From 3ebc0474343eb07defb6dd6f0e8bed707471e0a0 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 10 Apr 2021 16:59:41 -0400 Subject: [PATCH 055/612] Convert UpdateUser to solely async --- .../LiveTv/Listings/SchedulesDirect.cs | 4 ++-- .../Session/SessionManager.cs | 2 +- .../Users/UserManager.cs | 17 ++++------------- MediaBrowser.Controller/Library/IUserManager.cs | 16 ++++------------ 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1926e738f..dd58ca899 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -457,10 +457,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings } StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13)); - foreach (ReadOnlySpan i in programIds) + foreach (string i in programIds) { str.Append('"') - .Append(i.Slice(0, 10)) + .Append(i[..10]) .Append("\","); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 678a27665..50156d2b6 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -293,7 +293,7 @@ namespace Emby.Server.Implementations.Session try { user.LastActivityDate = activityDate; - _userManager.UpdateUser(user); + await _userManager.UpdateUserAsync(user); } catch (DbUpdateConcurrencyException e) { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index b6b45e69b..4d847ec95 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -163,15 +163,6 @@ namespace Jellyfin.Server.Implementations.Users OnUserUpdated?.Invoke(this, new GenericEventArgs(user)); } - /// - public void UpdateUser(User user) - { - using var dbContext = _dbProvider.CreateContext(); - dbContext.Users.Update(user); - _users[user.Id] = user; - dbContext.SaveChanges(); - } - /// public async Task UpdateUserAsync(User user) { @@ -271,9 +262,9 @@ namespace Jellyfin.Server.Implementations.Users } /// - public void ResetEasyPassword(User user) + public Task ResetEasyPassword(User user) { - ChangeEasyPassword(user, string.Empty, null); + return ChangeEasyPassword(user, string.Empty, null); } /// @@ -291,7 +282,7 @@ namespace Jellyfin.Server.Implementations.Users } /// - public void ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1) + public async Task ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1) { if (newPassword != null) { @@ -304,7 +295,7 @@ namespace Jellyfin.Server.Implementations.Users } user.EasyPassword = newPasswordSha1; - UpdateUser(user); + await UpdateUserAsync(user); _eventManager.Publish(new UserPasswordChangedEventArgs(user)); } diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 6e267834b..41dfe1b96 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -63,14 +63,6 @@ namespace MediaBrowser.Controller.Library /// Task RenameUser(User user, string newName); - /// - /// Updates the user. - /// - /// The user. - /// user - /// - void UpdateUser(User user); - /// /// Updates the user. /// @@ -108,7 +100,7 @@ namespace MediaBrowser.Controller.Library /// /// The user. /// Task. - void ResetEasyPassword(User user); + Task ResetEasyPassword(User user); /// /// Changes the password. @@ -118,7 +110,7 @@ namespace MediaBrowser.Controller.Library /// /// Changes the easy password. /// - void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1); + Task ChangeEasyPassword(User user, string newPassword, string newPasswordSha1); /// /// Gets the user dto. @@ -155,7 +147,7 @@ namespace MediaBrowser.Controller.Library /// /// This method updates the user's configuration. /// This is only included as a stopgap until the new API, using this internally is not recommended. - /// Instead, modify the user object directly, then call . + /// Instead, modify the user object directly, then call . /// /// The user's Id. /// The request containing the new user configuration. @@ -165,7 +157,7 @@ namespace MediaBrowser.Controller.Library /// /// This method updates the user's policy. /// This is only included as a stopgap until the new API, using this internally is not recommended. - /// Instead, modify the user object directly, then call . + /// Instead, modify the user object directly, then call . /// /// The user's Id. /// The request containing the new user policy. From ed0b5ff0171e340544702d99a07c149e01c5bf8a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 10 Apr 2021 17:11:59 -0400 Subject: [PATCH 056/612] Fix builds --- Jellyfin.Data/Entities/Security/Device.cs | 5 +++++ Jellyfin.Server.Implementations/Devices/DeviceManager.cs | 9 ++++++--- .../Security/AuthenticationManager.cs | 7 ++++--- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- MediaBrowser.Controller/Devices/IDeviceManager.cs | 8 ++++---- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Jellyfin.Data/Entities/Security/Device.cs b/Jellyfin.Data/Entities/Security/Device.cs index 17d17f594..bb192e772 100644 --- a/Jellyfin.Data/Entities/Security/Device.cs +++ b/Jellyfin.Data/Entities/Security/Device.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities.Security { @@ -31,6 +32,10 @@ namespace Jellyfin.Data.Entities.Security User = null!; } + /// + /// Gets the id. + /// + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; private set; } /// diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index 0d93ee2bf..4758f24f3 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -15,6 +15,9 @@ using Microsoft.EntityFrameworkCore; namespace Jellyfin.Server.Implementations.Devices { + /// + /// Manages the creation, updating, and retrieval of devices. + /// public class DeviceManager : IDeviceManager { private readonly JellyfinDbProvider _dbProvider; @@ -63,9 +66,9 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public ClientCapabilities GetCapabilities(string id) + public ClientCapabilities GetCapabilities(string deviceId) { - return _capabilitiesMap.TryGetValue(id, out ClientCapabilities? result) + return _capabilitiesMap.TryGetValue(deviceId, out ClientCapabilities? result) ? result : new ClientCapabilities(); } @@ -112,7 +115,7 @@ namespace Jellyfin.Server.Implementations.Devices sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); } - var array = await sessions.Select(ToDeviceInfo).ToArrayAsync(); + var array = await sessions.Select(ToDeviceInfo).ToArrayAsync().ConfigureAwait(false); return new QueryResult(array); } diff --git a/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs index 37b8ee6e0..ab76e2302 100644 --- a/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs +++ b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs @@ -52,14 +52,15 @@ namespace Jellyfin.Server.Implementations.Security } /// - public async Task DeleteApiKey(Guid id) + public async Task DeleteApiKey(Guid accessToken) { await using var dbContext = _dbProvider.CreateContext(); var key = await dbContext.ApiKeys .AsQueryable() - .Where(apiKey => apiKey.AccessToken == id) - .FirstOrDefaultAsync(); + .Where(apiKey => apiKey.AccessToken == accessToken) + .FirstOrDefaultAsync() + .ConfigureAwait(false); if (key == null) { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 4d847ec95..87d33330f 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -295,7 +295,7 @@ namespace Jellyfin.Server.Implementations.Users } user.EasyPassword = newPasswordSha1; - await UpdateUserAsync(user); + await UpdateUserAsync(user).ConfigureAwait(false); _eventManager.Publish(new UserPasswordChangedEventArgs(user)); } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 0df040794..4cdd8575e 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -18,16 +18,16 @@ namespace MediaBrowser.Controller.Devices /// /// Saves the capabilities. /// - /// The reported identifier. + /// The device id. /// The capabilities. - void SaveCapabilities(string reportedId, ClientCapabilities capabilities); + void SaveCapabilities(string deviceId, ClientCapabilities capabilities); /// /// Gets the capabilities. /// - /// The reported identifier. + /// The device id. /// ClientCapabilities. - ClientCapabilities GetCapabilities(string reportedId); + ClientCapabilities GetCapabilities(string deviceId); /// /// Gets the device information. From 7aa53b060e5e7c4315e203034114b9d585d039da Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 11 Apr 2021 14:34:45 -0400 Subject: [PATCH 057/612] Add label commenter workflow Right now the main purpose is to handle instructions for stable backports. Can be expanded in the future. --- .github/workflows/label-commenter-config.yml | 41 ++++++++++++++++++++ .github/workflows/label-commenter.yml | 22 +++++++++++ 2 files changed, 63 insertions(+) create mode 100644 .github/workflows/label-commenter-config.yml create mode 100644 .github/workflows/label-commenter.yml diff --git a/.github/workflows/label-commenter-config.yml b/.github/workflows/label-commenter-config.yml new file mode 100644 index 000000000..6ee967f55 --- /dev/null +++ b/.github/workflows/label-commenter-config.yml @@ -0,0 +1,41 @@ +comment: + header: Hello @{{ issue.user.login }} + footer: "\ + ---\n\n + > This is an automated comment created by the [peaceiris/actions-label-commenter]. \ + Responding to the bot or mentioning it won't have any effect.\n\n + [peaceiris/actions-label-commenter]: https://github.com/peaceiris/actions-label-commenter + " + +labels: + - name: stable backport + labeled: + pr: + body: | + This pull request has been tagged as a stable backport. It will be cherry-picked into the next stable point release. + + Please observe the following: + + * Any dependent PRs that this PR requires **must** be tagged for stable backporting as well. + + * Any issue(s) this PR fixes or closes **should** target the current stable release or a previous stable release to which a fix has not yet entered the current stable release. + + * This PR **must** be test cherry-picked against the current release branch (`release-X.Y.z` where X and Y are numbers). It must apply cleanly, or a diff of the expected change must be provided. + + To do this, run the following commands from your local copy of the Jellyfin repository: + + 1. `git checkout master` + + 1. `git merge --no-ff ` + + 1. `git log` -> `commit xxxxxxxxx`, grab hash + + 1. `git checkout release-X.Y.z` replacing X and Y with the *current* stable version (e.g. `release-10.7.z`) + + 1. `git cherry-pick -sx -m1 ` + + Ensure the `cherry-pick` applies cleanly. If it does not, fix any merge conflicts *preserving as much of the original code as possible*, and make note of the resulting diff. + + **Do not** push your merges to either branch. Use `git reset --hard HEAD~1` to revert both branches to their original state. + + Reply to this PR with a comment beginning "Cherry-pick test completed." and including the merge-conflict-ixing diff(s) if applicable. diff --git a/.github/workflows/label-commenter.yml b/.github/workflows/label-commenter.yml new file mode 100644 index 000000000..be9216cc1 --- /dev/null +++ b/.github/workflows/label-commenter.yml @@ -0,0 +1,22 @@ +name: Label Commenter + +on: + issues: + types: + - labeled + - unlabeled + pull_request_target: + types: + - labeled + - unlabeled + +jobs: + comment: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + with: + ref: master + + - name: Label Commenter + uses: peaceiris/actions-label-commenter@v1 From f381c536342760a53db85a1ed527091da2d3abfc Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 11 Apr 2021 14:51:45 -0400 Subject: [PATCH 058/612] Fix typo --- .github/workflows/label-commenter-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-commenter-config.yml b/.github/workflows/label-commenter-config.yml index 6ee967f55..c12c5cf85 100644 --- a/.github/workflows/label-commenter-config.yml +++ b/.github/workflows/label-commenter-config.yml @@ -38,4 +38,4 @@ labels: **Do not** push your merges to either branch. Use `git reset --hard HEAD~1` to revert both branches to their original state. - Reply to this PR with a comment beginning "Cherry-pick test completed." and including the merge-conflict-ixing diff(s) if applicable. + Reply to this PR with a comment beginning "Cherry-pick test completed." and including the merge-conflict-fixing diff(s) if applicable. From 4deccd451ea09c3c34d9236f3b7426277b32a0bb Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 11 Apr 2021 16:31:10 -0400 Subject: [PATCH 059/612] Mention testing too --- .github/workflows/label-commenter-config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/label-commenter-config.yml b/.github/workflows/label-commenter-config.yml index c12c5cf85..78b75be43 100644 --- a/.github/workflows/label-commenter-config.yml +++ b/.github/workflows/label-commenter-config.yml @@ -36,6 +36,8 @@ labels: Ensure the `cherry-pick` applies cleanly. If it does not, fix any merge conflicts *preserving as much of the original code as possible*, and make note of the resulting diff. + Test your changes with a build to ensure they are successful. If not, adjust the diff accordingly. + **Do not** push your merges to either branch. Use `git reset --hard HEAD~1` to revert both branches to their original state. Reply to this PR with a comment beginning "Cherry-pick test completed." and including the merge-conflict-fixing diff(s) if applicable. From 10d358c8da4f5e323c81a5880625737e025d2ba0 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 12 Apr 2021 10:28:24 +0100 Subject: [PATCH 060/612] Update tests/Jellyfin.Networking.Tests/NetworkParseTests.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Odd Stråbø --- tests/Jellyfin.Networking.Tests/NetworkParseTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index b6bbfc5e1..63182974e 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -383,7 +383,8 @@ namespace Jellyfin.Networking.Tests // User on external network, no binding - so result is the 1st external. [InlineData("jellyfin.org", "", false, "eth11")] // Dns failure - should skip the test. - [InlineData("ospoakdposkd.abc", "", false, "eth11")] + // https://en.wikipedia.org/wiki/.test + [InlineData("invalid.domain.test", "", 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) From b11718a01d7341f37840893398f158e6cbcd5f9b Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 13 Apr 2021 07:12:49 -0600 Subject: [PATCH 061/612] Properly redirect healthcheck endpoint if using BaseUrl --- .../BaseUrlRedirectionMiddleware.cs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs index c23da2fd6..4dda900b0 100644 --- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs +++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs @@ -45,16 +45,27 @@ namespace Jellyfin.Server.Middleware var localPath = httpContext.Request.Path.ToString(); var baseUrlPrefix = serverConfigurationManager.GetNetworkConfiguration().BaseUrl; - if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) - || string.IsNullOrEmpty(localPath) - || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(baseUrlPrefix)) { - // Always redirect back to the default path if the base prefix is invalid or missing - _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); - httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]); - return; + if (!localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + && localPath.EndsWith("/health", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Redirecting /health check"); + httpContext.Response.Redirect(baseUrlPrefix + "/health"); + return; + } + + if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) + || string.IsNullOrEmpty(localPath) + || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) + { + // Always redirect back to the default path if the base prefix is invalid or missing + _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); + httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]); + return; + } } await _next(httpContext).ConfigureAwait(false); From 75df6965a046967dca94912c9490fd4b4af9f0a6 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 13 Apr 2021 20:01:21 -0400 Subject: [PATCH 062/612] Don't use database for QuickConnect --- .../QuickConnect/QuickConnectManager.cs | 71 ++++++++----------- .../Session/SessionManager.cs | 18 ----- .../QuickConnect/IQuickConnect.cs | 8 +++ 3 files changed, 38 insertions(+), 59 deletions(-) diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 7bed06de3..9f639138a 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.Cryptography; -using MediaBrowser.Common; using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; -using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; using MediaBrowser.Model.QuickConnect; using Microsoft.Extensions.Logging; @@ -21,36 +20,26 @@ namespace Emby.Server.Implementations.QuickConnect /// public class QuickConnectManager : IQuickConnect, IDisposable { - private readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider(); - private readonly ConcurrentDictionary _currentRequests = new ConcurrentDictionary(); + private readonly RNGCryptoServiceProvider _rng = new (); + private readonly ConcurrentDictionary _currentRequests = new (); + private readonly ConcurrentDictionary _quickConnectTokens = new (); private readonly IServerConfigurationManager _config; private readonly ILogger _logger; - private readonly IAuthenticationRepository _authenticationRepository; - private readonly IAuthorizationContext _authContext; - private readonly IServerApplicationHost _appHost; + private readonly ISessionManager _sessionManager; /// /// Initializes a new instance of the class. /// Should only be called at server startup when a singleton is created. /// - /// Configuration. - /// Logger. - /// Application host. - /// Authentication context. - /// Authentication repository. - public QuickConnectManager( - IServerConfigurationManager config, - ILogger logger, - IServerApplicationHost appHost, - IAuthorizationContext authContext, - IAuthenticationRepository authenticationRepository) + /// The server configuration manager. + /// The logger. + /// The session manager. + public QuickConnectManager(IServerConfigurationManager config, ILogger logger, ISessionManager sessionManager) { _config = config; _logger = logger; - _appHost = appHost; - _authContext = authContext; - _authenticationRepository = authenticationRepository; + _sessionManager = sessionManager; ReloadConfiguration(); } @@ -138,6 +127,19 @@ namespace Emby.Server.Implementations.QuickConnect return result; } + public void AuthenticateRequest(AuthenticationRequest request, string token) + { + if (!_quickConnectTokens.TryGetValue(token, out var entry)) + { + throw new SecurityException("Unknown quick connect token"); + } + + request.UserId = entry.Item2; + _quickConnectTokens.Remove(token, out _); + + _sessionManager.AuthenticateQuickConnect(request, token); + } + /// public string GenerateCode() { @@ -179,16 +181,7 @@ namespace Emby.Server.Implementations.QuickConnect var added = result.DateAdded ?? DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(Timeout)); result.DateAdded = added.Subtract(TimeSpan.FromMinutes(Timeout - 1)); - _authenticationRepository.Create(new AuthenticationInfo - { - AppName = TokenName, - AccessToken = result.Authentication, - DateCreated = DateTime.UtcNow, - DeviceId = _appHost.SystemId, - DeviceName = _appHost.FriendlyName, - AppVersion = _appHost.ApplicationVersionString, - UserId = userId - }); + _quickConnectTokens[result.Authentication] = (TokenName, userId); _logger.LogDebug("Authorizing device with code {Code} to login as user {userId}", code, userId); @@ -198,19 +191,15 @@ namespace Emby.Server.Implementations.QuickConnect /// public int DeleteAllDevices(Guid user) { - var raw = _authenticationRepository.Get(new AuthenticationInfoQuery() - { - DeviceId = _appHost.SystemId, - UserId = user - }); - - var tokens = raw.Items.Where(x => x.AppName.StartsWith(TokenName, StringComparison.Ordinal)); + var tokens = _quickConnectTokens + .Where(entry => entry.Value.Item1.StartsWith(TokenName, StringComparison.Ordinal) && entry.Value.Item2 == user) + .ToList(); var removed = 0; foreach (var token in tokens) { - _authenticationRepository.Delete(token); - _logger.LogDebug("Deleted token {AccessToken}", token.AccessToken); + _quickConnectTokens.Remove(token.Key, out _); + _logger.LogDebug("Deleted token {AccessToken}", token.Key); removed++; } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 50156d2b6..99d2947f0 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1441,24 +1441,6 @@ namespace Emby.Server.Implementations.Session public Task AuthenticateQuickConnect(AuthenticationRequest request, string token) { - var result = _authRepo.Get(new AuthenticationInfoQuery() - { - AccessToken = token, - DeviceId = _appHost.SystemId, - Limit = 1 - }); - - if (result.TotalRecordCount == 0) - { - throw new SecurityException("Unknown quick connect token"); - } - - var info = result.Items[0]; - request.UserId = info.UserId; - - // There's no need to keep the quick connect token in the database, as AuthenticateNewSessionInternal() issues a long lived token. - _authRepo.Delete(info); - return AuthenticateNewSessionInternal(request, false); } diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs index 959a2d771..ce8999e49 100644 --- a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -1,4 +1,5 @@ using System; +using MediaBrowser.Controller.Session; using MediaBrowser.Model.QuickConnect; namespace MediaBrowser.Controller.QuickConnect @@ -57,6 +58,13 @@ namespace MediaBrowser.Controller.QuickConnect /// Quick connect result. QuickConnectResult CheckRequestStatus(string secret); + /// + /// Authenticates a QuickConnect request. + /// + /// The request. + /// The token. + void AuthenticateRequest(AuthenticationRequest request, string token); + /// /// Authorizes a quick connect request to connect as the calling user. /// From 12fa5c0c41c255f4d74d56eacaa38ed2da420d6f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 13 Apr 2021 20:01:31 -0400 Subject: [PATCH 063/612] Add AccessToken to device --- Jellyfin.Data/Entities/Security/Device.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Data/Entities/Security/Device.cs b/Jellyfin.Data/Entities/Security/Device.cs index bb192e772..9bff7f0cf 100644 --- a/Jellyfin.Data/Entities/Security/Device.cs +++ b/Jellyfin.Data/Entities/Security/Device.cs @@ -13,13 +13,15 @@ namespace Jellyfin.Data.Entities.Security /// Initializes a new instance of the class. /// /// The user id. + /// The access token. /// The app name. /// The app version. /// The device name. /// The device id. - public Device(Guid userId, string appName, string appVersion, string deviceName, string deviceId) + public Device(Guid userId, string accessToken, string appName, string appVersion, string deviceName, string deviceId) { UserId = userId; + AccessToken = accessToken; AppName = appName; AppVersion = appVersion; DeviceName = deviceName; @@ -43,6 +45,11 @@ namespace Jellyfin.Data.Entities.Security /// public Guid UserId { get; private set; } + /// + /// Gets or sets the access token. + /// + public string AccessToken { get; set; } + /// /// Gets or sets the app name. /// From 78791a932f4a83834c1fb58e28a94cce6c8f349c Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 14 Apr 2021 06:44:11 -0600 Subject: [PATCH 064/612] Simplify baseUrl check --- Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs index 4dda900b0..3abf579e2 100644 --- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs +++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs @@ -47,7 +47,9 @@ namespace Jellyfin.Server.Middleware if (!string.IsNullOrEmpty(baseUrlPrefix)) { - if (!localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + var startsWithBaseUrl = localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase); + + if (!startsWithBaseUrl && localPath.EndsWith("/health", StringComparison.OrdinalIgnoreCase)) { _logger.LogDebug("Redirecting /health check"); @@ -59,7 +61,7 @@ namespace Jellyfin.Server.Middleware || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(localPath) - || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) + || !startsWithBaseUrl) { // Always redirect back to the default path if the base prefix is invalid or missing _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); From 3199d1c902f62b1a2697a8361bf810c3766b7f0b Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 15 Apr 2021 18:36:47 +0100 Subject: [PATCH 065/612] Fix: PlayTo using external ip not internal --- Emby.Dlna/PlayTo/PlayToManager.cs | 2 +- Emby.Dlna/Ssdp/DeviceDiscovery.cs | 2 +- MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs | 2 ++ RSSDP/DeviceAvailableEventArgs.cs | 2 +- RSSDP/SsdpDeviceLocator.cs | 26 +++++++++++------------ 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index a6793a708..e9cab17f0 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -183,7 +183,7 @@ namespace Emby.Dlna.PlayTo _sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName); - string serverAddress = _appHost.GetSmartApiUrl(info.LocalIpAddress); + string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIpAddress); controller = new PlayToController( sessionInfo, diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index 8c7d961f3..70223cbdb 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -104,7 +104,7 @@ namespace Emby.Dlna.Ssdp { Location = e.DiscoveredDevice.DescriptionLocation, Headers = headers, - LocalIpAddress = e.LocalIpAddress + RemoteIpAddress = e.RemoteIpAddress }); DeviceDiscoveredInternal?.Invoke(this, args); diff --git a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs index d71013f01..987a3a908 100644 --- a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs +++ b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs @@ -16,5 +16,7 @@ namespace MediaBrowser.Model.Dlna public IPAddress LocalIpAddress { get; set; } public int LocalPort { get; set; } + + public IPAddress RemoteIpAddress { get; set; } } } diff --git a/RSSDP/DeviceAvailableEventArgs.cs b/RSSDP/DeviceAvailableEventArgs.cs index b7d22a7df..04b14c4dc 100644 --- a/RSSDP/DeviceAvailableEventArgs.cs +++ b/RSSDP/DeviceAvailableEventArgs.cs @@ -8,7 +8,7 @@ namespace Rssdp /// public sealed class DeviceAvailableEventArgs : EventArgs { - public IPAddress LocalIpAddress { get; set; } + public IPAddress RemoteIpAddress { get; set; } private readonly DiscoveredSsdpDevice _DiscoveredDevice; diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index bfad6de97..eb99788f0 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -208,7 +208,7 @@ namespace Rssdp.Infrastructure /// Raises the event. /// /// - protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress) + protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IpAddress) { if (this.IsDisposed) { @@ -220,7 +220,7 @@ namespace Rssdp.Infrastructure { handlers(this, new DeviceAvailableEventArgs(device, isNewDevice) { - LocalIpAddress = localIpAddress + RemoteIpAddress = IpAddress }); } } @@ -286,7 +286,7 @@ namespace Rssdp.Infrastructure } } - private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress localIpAddress) + private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress IpAddress) { bool isNewDevice = false; lock (_Devices) @@ -304,17 +304,17 @@ namespace Rssdp.Infrastructure } } - DeviceFound(device, isNewDevice, localIpAddress); + DeviceFound(device, isNewDevice, IpAddress); } - private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress) + private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IpAddress) { if (!NotificationTypeMatchesFilter(device)) { return; } - OnDeviceAvailable(device, isNewDevice, localIpAddress); + OnDeviceAvailable(device, isNewDevice, IpAddress); } private bool NotificationTypeMatchesFilter(DiscoveredSsdpDevice device) @@ -347,7 +347,7 @@ namespace Rssdp.Infrastructure return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken); } - private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress) + private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress IpAddress) { if (!message.IsSuccessStatusCode) { @@ -367,11 +367,11 @@ namespace Rssdp.Infrastructure ResponseHeaders = message.Headers }; - AddOrUpdateDiscoveredDevice(device, localIpAddress); + AddOrUpdateDiscoveredDevice(device, IpAddress); } } - private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress localIpAddress) + private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress IpAddress) { if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0) { @@ -381,7 +381,7 @@ namespace Rssdp.Infrastructure var notificationType = GetFirstHeaderStringValue("NTS", message); if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0) { - ProcessAliveNotification(message, localIpAddress); + ProcessAliveNotification(message, IpAddress); } else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0) { @@ -389,7 +389,7 @@ namespace Rssdp.Infrastructure } } - private void ProcessAliveNotification(HttpRequestMessage message, IPAddress localIpAddress) + private void ProcessAliveNotification(HttpRequestMessage message, IPAddress IpAddress) { var location = GetFirstHeaderUriValue("Location", message); if (location != null) @@ -404,7 +404,7 @@ namespace Rssdp.Infrastructure ResponseHeaders = message.Headers }; - AddOrUpdateDiscoveredDevice(device, localIpAddress); + AddOrUpdateDiscoveredDevice(device, IpAddress); } } @@ -630,7 +630,7 @@ namespace Rssdp.Infrastructure private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e) { - ProcessNotificationMessage(e.Message, e.LocalIpAddress); + ProcessNotificationMessage(e.Message, e.ReceivedFrom.Address); } } } From d7855500c2c16a0b4b7fb7ed72e59ecd6ab0c514 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 15 Apr 2021 14:44:21 -0400 Subject: [PATCH 066/612] Add NextUpCutoffDate to NextUpQuery --- Emby.Server.Implementations/TV/TVSeriesManager.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 5 ++++- MediaBrowser.Model/Querying/NextUpQuery.cs | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index d3f6fa34d..b1b905fc7 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.TV return i.Item1 != DateTime.MinValue; } - if (alwaysEnableFirstEpisode || i.Item1 != DateTime.MinValue) + if (alwaysEnableFirstEpisode || (i.Item1 != DateTime.MinValue && i.Item1.Date > request.NextUpDateCutoff)) { anyFound = true; return true; diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index e1c67f830..59802e2a5 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -65,6 +65,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Optional. Include user data. + /// Starting date of shows to show in Next Up section. /// Whether to enable the total records count. Defaults to true. /// Whether to disable sending the first episode in a series as next up. /// A with the next up episodes. @@ -81,6 +82,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, + [FromQuery] DateTime nextUpDateCutoff, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool disableFirstEpisode = false) { @@ -97,7 +99,8 @@ namespace Jellyfin.Api.Controllers StartIndex = startIndex, UserId = userId ?? Guid.Empty, EnableTotalRecordCount = enableTotalRecordCount, - DisableFirstEpisode = disableFirstEpisode + DisableFirstEpisode = disableFirstEpisode, + NextUpDateCutoff = nextUpDateCutoff }, options); diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index 0555afc00..8a3cb96e1 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -13,6 +13,7 @@ namespace MediaBrowser.Model.Querying EnableImageTypes = Array.Empty(); EnableTotalRecordCount = true; DisableFirstEpisode = false; + NextUpDateCutoff = new DateTime(0001, 01, 01); } /// @@ -75,5 +76,10 @@ namespace MediaBrowser.Model.Querying /// Gets or sets a value indicating whether do disable sending first episode as next up. /// public bool DisableFirstEpisode { get; set; } + + /// + /// Gets or sets a value indicating the oldest date for a show to appear in Next Up. + /// + public DateTime NextUpDateCutoff { get; set; } } } From 198cc6e76af180d0ea3216a928096c4335099fd7 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 16 Apr 2021 13:57:22 -0400 Subject: [PATCH 067/612] Some code cleanup. Allow NextUpDateCutoff to be null --- Emby.Server.Implementations/TV/TVSeriesManager.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 2 +- MediaBrowser.Model/Querying/NextUpQuery.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index b1b905fc7..0ab9c1576 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.TV return i.Item1 != DateTime.MinValue; } - if (alwaysEnableFirstEpisode || (i.Item1 != DateTime.MinValue && i.Item1.Date > request.NextUpDateCutoff)) + if (alwaysEnableFirstEpisode || (i.Item1 != DateTime.MinValue && (request.NextUpDateCutoff is null || i.Item1.Date > request.NextUpDateCutoff))) { anyFound = true; return true; diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 59802e2a5..5e90e37f3 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -82,7 +82,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, - [FromQuery] DateTime nextUpDateCutoff, + [FromQuery] DateTime? nextUpDateCutoff, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool disableFirstEpisode = false) { diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index 8a3cb96e1..c5b39001a 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Model.Querying EnableImageTypes = Array.Empty(); EnableTotalRecordCount = true; DisableFirstEpisode = false; - NextUpDateCutoff = new DateTime(0001, 01, 01); + NextUpDateCutoff = null; } /// @@ -80,6 +80,6 @@ namespace MediaBrowser.Model.Querying /// /// Gets or sets a value indicating the oldest date for a show to appear in Next Up. /// - public DateTime NextUpDateCutoff { get; set; } + public DateTime? NextUpDateCutoff { get; set; } } } From bb6fddde9ac48905b876717806754a052bf0ad24 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 17 Apr 2021 11:19:09 +0100 Subject: [PATCH 068/612] Group Methods --- Emby.Server.Implementations/Playlists/PlaylistManager.cs | 2 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 4 ++-- Jellyfin.Networking/Manager/NetworkManager.cs | 2 +- .../Migrations/Routines/CreateUserLoggingConfigFile.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 2 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 932f721ab..2d1a559f1 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.Playlists // Create a list of the new linked children to add to the playlist var childrenToAdd = newItems - .Select(i => LinkedChild.Create(i)) + .Select(LinkedChild.Create) .ToList(); // Log duplicates that have been ignored, if any diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index e2306aa27..404cb3a46 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -121,14 +121,14 @@ namespace Jellyfin.Api.Helpers if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec)) { state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); - state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToAudioCodec(i)) + state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec) ?? state.SupportedAudioCodecs.FirstOrDefault(); } if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec)) { state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); - state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToSubtitleCodec(i)) + state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleCodec) ?? state.SupportedSubtitleCodecs.FirstOrDefault(); } diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 73e8b2cd7..4078fd126 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -1067,7 +1067,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(IsInLocalNetwork)); } _logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString()); diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 6821630db..ee4f8b0ba 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -75,7 +75,7 @@ namespace Jellyfin.Server.Migrations.Routines { var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath)); return _defaultConfigHistory - .Select(historicalConfigText => JToken.Parse(historicalConfigText)) + .Select(JToken.Parse) .Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson)); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 53d45261e..1b69c6646 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2324,7 +2324,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => i.IsLocalFile) .Select(i => System.IO.Path.GetDirectoryName(i.Path)) .Distinct(StringComparer.OrdinalIgnoreCase) - .SelectMany(i => directoryService.GetFilePaths(i)) + .SelectMany(directoryService.GetFilePaths) .ToList(); var deletedImages = ImageInfos diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index a87104cd6..ee2e5fcde 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.MediaEncoding.Probing .Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec)) .ToList(); - info.MediaAttachments = internalStreams.Select(s => GetMediaAttachment(s)) + info.MediaAttachments = internalStreams.Select(GetMediaAttachment) .Where(i => i != null) .ToList(); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 398d47d5f..f4c69fe8f 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -297,7 +297,7 @@ namespace MediaBrowser.Model.Dlna int? inputAudioSampleRate = audioStream?.SampleRate; int? inputAudioBitDepth = audioStream?.BitDepth; - if (directPlayMethods.Count() > 0) + if (directPlayMethods.Any()) { string audioCodec = audioStream?.Codec; From c68f6163774e4d6534a5a68d2c0f6b7435240ed3 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 19 Apr 2021 12:36:30 +0100 Subject: [PATCH 069/612] Flip fields --- Emby.Dlna/DlnaManager.cs | 2 +- tests/Jellyfin.Dlna.Tests/ProfileTester.cs | 120 +++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 tests/Jellyfin.Dlna.Tests/ProfileTester.cs diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 9ab324038..88b0e1195 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -107,7 +107,7 @@ namespace Emby.Dlna } var profile = GetProfiles() - .FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification)); + .FirstOrDefault(i => i.Identification != null && IsMatch(i.Identification, deviceInfo)); if (profile != null) { diff --git a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs new file mode 100644 index 000000000..83638c7c4 --- /dev/null +++ b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Emby.Dlna.PlayTo; +using MediaBrowser.Model.Dlna; +using Xunit; + +namespace Jellyfin.Dlna.Tests +{ + public class ProfileTester + { + private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) + { + if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) + { + if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) + { + if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) + { + if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) + { + if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(profileInfo.ModelName)) + { + if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) + { + if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) + { + if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) + { + if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) + { + return false; + } + } + + return true; + } + + private bool IsRegexOrSubstringMatch(string input, string pattern) + { + return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + + [Fact] + public void Test_Profile_Matches() + { + var source = new DeviceInfo() + { + Name = "HelloWorld" + }; + + var dest = new DeviceProfile() + { + Name = "Test Subject 1", + FriendlyName = "HelloWorld", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", + Identification = new DeviceIdentification() + { + FriendlyName = "HelloWorld", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", + } + }; + + Assert.True(IsMatch(dest.Identification, source.ToDeviceIdentification())); + } + } +} From 95b733ad4cbbe35b638de51e0b84d2794d28c34d Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 19 Apr 2021 14:07:14 +0100 Subject: [PATCH 070/612] reworked code --- Emby.Dlna/DlnaManager.cs | 112 +++++++++-------- tests/Jellyfin.Dlna.Tests/ProfileTester.cs | 138 +++++++++------------ 2 files changed, 116 insertions(+), 134 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 88b0e1195..4a50136cf 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -106,19 +106,28 @@ namespace Emby.Dlna throw new ArgumentNullException(nameof(deviceInfo)); } - var profile = GetProfiles() - .FirstOrDefault(i => i.Identification != null && IsMatch(i.Identification, deviceInfo)); + try + { + var profile = GetProfiles() + .FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification)); - if (profile != null) - { - _logger.LogDebug("Found matching device profile: {0}", profile.Name); + if (profile != null) + { + _logger.LogDebug("Found matching device profile: {0}", profile.Name); + } + else + { + LogUnmatchedProfile(deviceInfo); + } + + return profile; } - else + catch (ArgumentException ex) { - LogUnmatchedProfile(deviceInfo); + _logger.LogError(ex, "Error in profile comparison."); } - return profile; + return null; } private void LogUnmatchedProfile(DeviceIdentification profile) @@ -138,85 +147,82 @@ namespace Emby.Dlna _logger.LogInformation(builder.ToString()); } - private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) + /// + /// Attempts to match a device with a profile. + /// Rules: + /// - If the profile field has no value, the field matches irregardless of its contents. + /// - the profile field can be an exact match, or a reg exp. + /// + /// The of the device. + /// The of the profile. + /// True if they match. + public static bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) { - if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) + if (!IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) { - if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) - { - return false; - } + return false; } - if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) + if (!IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) { - if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) - { - return false; - } + return false; } - if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) + if (!IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) { - if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) - { - return false; - } + return false; } - if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) + if (!IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) { - if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) - { - return false; - } + return false; } - if (!string.IsNullOrEmpty(profileInfo.ModelName)) + if (!IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)) { - if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)) - { - return false; - } + return false; } - if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) + if (!IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) { - if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) - { - return false; - } + return false; } - if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) + if (!IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) { - if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) - { - return false; - } + return false; } - if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) + if (!IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) { - if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) - { - return false; - } + return false; } return true; } - private bool IsRegexOrSubstringMatch(string input, string pattern) + public static bool IsRegexOrSubstringMatch(string input, string pattern) { + if (string.IsNullOrEmpty(pattern)) + { + // In profile identification: An empty pattern matches anything. + return true; + } + + if (string.IsNullOrEmpty(input)) + { + // The profile contains a value, and the device doesn't. + return false; + } + try { - return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + return input.Equals(pattern, StringComparison.OrdinalIgnoreCase) + || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } catch (ArgumentException ex) { - _logger.LogError(ex, "Error evaluating regex pattern {Pattern}", pattern); - return false; + throw new ArgumentException("Error evaluating regex pattern " + pattern, ex); } } diff --git a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs index 83638c7c4..cc7cf92e7 100644 --- a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs +++ b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Emby.Dlna; using Emby.Dlna.PlayTo; using MediaBrowser.Model.Dlna; using Xunit; @@ -12,92 +13,23 @@ namespace Jellyfin.Dlna.Tests { public class ProfileTester { - private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) - { - if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) - { - if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) - { - if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) - { - if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) - { - if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.ModelName)) - { - if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) - { - if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) - { - if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) - { - if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) - { - return false; - } - } - - return true; - } - - private bool IsRegexOrSubstringMatch(string input, string pattern) - { - return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } - [Fact] public void Test_Profile_Matches() { - var source = new DeviceInfo() + var device = new DeviceInfo() { - Name = "HelloWorld" + Name = "My Device", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", }; - var dest = new DeviceProfile() + var profile = new DeviceProfile() { - Name = "Test Subject 1", - FriendlyName = "HelloWorld", + Name = "Test Profile", + FriendlyName = "My Device", Manufacturer = "LG Electronics", ManufacturerUrl = "http://www.lge.com", ModelDescription = "LG WebOSTV DMRplus", @@ -105,7 +37,7 @@ namespace Jellyfin.Dlna.Tests ModelNumber = "1.0", Identification = new DeviceIdentification() { - FriendlyName = "HelloWorld", + FriendlyName = "My Device", Manufacturer = "LG Electronics", ManufacturerUrl = "http://www.lge.com", ModelDescription = "LG WebOSTV DMRplus", @@ -114,7 +46,51 @@ namespace Jellyfin.Dlna.Tests } }; - Assert.True(IsMatch(dest.Identification, source.ToDeviceIdentification())); + Assert.True(DlnaManager.IsMatch(device.ToDeviceIdentification(), profile.Identification)); + + var profile2 = new DeviceProfile() + { + Name = "Test Profile", + FriendlyName = "My Device", + Identification = new DeviceIdentification() + { + FriendlyName = "My Device", + } + }; + + Assert.True(DlnaManager.IsMatch(device.ToDeviceIdentification(), profile2.Identification)); + } + + [Fact] + public void Test_Profile_NoMatch() + { + var device = new DeviceInfo() + { + Name = "My Device", + Manufacturer = "JVC" + }; + + var profile = new DeviceProfile() + { + Name = "Test Profile", + FriendlyName = "My Device", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", + Identification = new DeviceIdentification() + { + FriendlyName = "My Device", + Manufacturer = "LG Electronics", + ManufacturerUrl = "http://www.lge.com", + ModelDescription = "LG WebOSTV DMRplus", + ModelName = "LG TV", + ModelNumber = "1.0", + } + }; + + Assert.False(DlnaManager.IsMatch(device.ToDeviceIdentification(), profile.Identification)); } } } From 4449217f8ff736e98a50fc686331c5240f6fe33c Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 19 Apr 2021 14:24:58 +0100 Subject: [PATCH 071/612] simplified isMatch --- Emby.Dlna/DlnaManager.cs | 49 +++++++--------------------------------- 1 file changed, 8 insertions(+), 41 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 4a50136cf..374e04210 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -158,47 +158,14 @@ namespace Emby.Dlna /// True if they match. public static bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) { - if (!IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) - { - return false; - } - - if (!IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) - { - return false; - } - - if (!IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) - { - return false; - } - - if (!IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) - { - return false; - } - - if (!IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)) - { - return false; - } - - if (!IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) - { - return false; - } - - if (!IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) - { - return false; - } - - if (!IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) - { - return false; - } - - return true; + return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName) + && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer) + && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl) + && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription) + && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName) + && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber) + && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl) + && IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber); } public static bool IsRegexOrSubstringMatch(string input, string pattern) From 092c610fbf8268d5ff4518c2a6882764b89183d1 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 20 Apr 2021 08:26:41 +0100 Subject: [PATCH 072/612] Update Emby.Dlna/DlnaManager.cs Co-authored-by: Claus Vium --- Emby.Dlna/DlnaManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 374e04210..82dc30fb5 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -113,7 +113,7 @@ namespace Emby.Dlna if (profile != null) { - _logger.LogDebug("Found matching device profile: {0}", profile.Name); + _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name); } else { From a99caa0daae7c47afdf7d0cf5cf182976211ffa2 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 20 Apr 2021 18:04:16 +0100 Subject: [PATCH 073/612] Changed testing --- Emby.Dlna/DlnaManager.cs | 15 +++------- .../Jellyfin.Dlna.Tests.csproj | 1 + tests/Jellyfin.Dlna.Tests/ProfileTester.cs | 29 +++++++++++++------ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 82dc30fb5..e80f1aaee 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -156,7 +156,7 @@ namespace Emby.Dlna /// The of the device. /// The of the profile. /// True if they match. - public static bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) + public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) { return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName) && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer) @@ -168,7 +168,7 @@ namespace Emby.Dlna && IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber); } - public static bool IsRegexOrSubstringMatch(string input, string pattern) + private bool IsRegexOrSubstringMatch(string input, string pattern) { if (string.IsNullOrEmpty(pattern)) { @@ -182,15 +182,8 @@ namespace Emby.Dlna return false; } - try - { - return input.Equals(pattern, StringComparison.OrdinalIgnoreCase) - || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } - catch (ArgumentException ex) - { - throw new ArgumentException("Error evaluating regex pattern " + pattern, ex); - } + return input.Equals(pattern, StringComparison.OrdinalIgnoreCase) + || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } public DeviceProfile GetProfile(IHeaderDictionary headers) diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index 850db1c75..a1255a858 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs index cc7cf92e7..3676cc9ef 100644 --- a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs +++ b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs @@ -1,18 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using Emby.Dlna; using Emby.Dlna.PlayTo; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using Moq; using Xunit; namespace Jellyfin.Dlna.Tests { public class ProfileTester { + private DlnaManager GetManager() + { + var xmlSerializer = new Mock(); + var fileSystem = new Mock(); + var appPaths = new Mock(); + var loggerFactory = new Mock(); + var appHost = new Mock(); + + return new DlnaManager(xmlSerializer.Object, fileSystem.Object, appPaths.Object, loggerFactory.Object, appHost.Object); + } + [Fact] public void Test_Profile_Matches() { @@ -46,7 +57,7 @@ namespace Jellyfin.Dlna.Tests } }; - Assert.True(DlnaManager.IsMatch(device.ToDeviceIdentification(), profile.Identification)); + Assert.True(GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification)); var profile2 = new DeviceProfile() { @@ -58,7 +69,7 @@ namespace Jellyfin.Dlna.Tests } }; - Assert.True(DlnaManager.IsMatch(device.ToDeviceIdentification(), profile2.Identification)); + Assert.True(GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification)); } [Fact] @@ -90,7 +101,7 @@ namespace Jellyfin.Dlna.Tests } }; - Assert.False(DlnaManager.IsMatch(device.ToDeviceIdentification(), profile.Identification)); + Assert.False(GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification)); } } } From 7848ea148438baf8929e1c2939e6979cb84aad1a Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 20 Apr 2021 18:08:19 +0100 Subject: [PATCH 074/612] missed one. --- Emby.Dlna/DlnaManager.cs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index e80f1aaee..7f8dba35f 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -106,28 +106,19 @@ namespace Emby.Dlna throw new ArgumentNullException(nameof(deviceInfo)); } - try + var profile = GetProfiles() + .FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification)); + + if (profile != null) { - var profile = GetProfiles() - .FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification)); - - if (profile != null) - { - _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name); - } - else - { - LogUnmatchedProfile(deviceInfo); - } - - return profile; + _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name); } - catch (ArgumentException ex) + else { - _logger.LogError(ex, "Error in profile comparison."); + LogUnmatchedProfile(deviceInfo); } - return null; + return profile; } private void LogUnmatchedProfile(DeviceIdentification profile) From 41246909dcbf71a85140b5354e5f76e33ea4801e Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 20 Apr 2021 18:14:23 +0100 Subject: [PATCH 075/612] fixed merge --- Emby.Dlna/DlnaManager.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 8aed4a50f..a554a4d5b 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -173,8 +173,16 @@ namespace Emby.Dlna return false; } - return input.Equals(pattern, StringComparison.OrdinalIgnoreCase) + try + { + return input.Equals(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + catch (ArgumentException ex) + { + _logger.LogError(ex, "Error evaluating regex pattern {Pattern}", pattern); + return false; + } } public DeviceProfile GetProfile(IHeaderDictionary headers) From 522d5a71582fa4b610c75b0029812a9c10619002 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 20 Apr 2021 18:17:48 +0100 Subject: [PATCH 076/612] Fixed indent --- Emby.Dlna/DlnaManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index a554a4d5b..57a6c2059 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -176,7 +176,7 @@ namespace Emby.Dlna try { return input.Equals(pattern, StringComparison.OrdinalIgnoreCase) - || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } catch (ArgumentException ex) { From 2559ceffab778933a6122d2989cfa4d58f699839 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 21 Apr 2021 10:09:05 +0100 Subject: [PATCH 077/612] Update tests/Jellyfin.Dlna.Tests/ProfileTester.cs Co-authored-by: Claus Vium --- tests/Jellyfin.Dlna.Tests/ProfileTester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs index 3676cc9ef..74868bc0e 100644 --- a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs +++ b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs @@ -11,7 +11,7 @@ using Xunit; namespace Jellyfin.Dlna.Tests { - public class ProfileTester + public class DlnaManagerTests { private DlnaManager GetManager() { From 740e5ec167fc8a147cfaca2904220baee6ccc509 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 21 Apr 2021 10:09:14 +0100 Subject: [PATCH 078/612] Update tests/Jellyfin.Dlna.Tests/ProfileTester.cs Co-authored-by: Claus Vium --- tests/Jellyfin.Dlna.Tests/ProfileTester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs index 74868bc0e..2f27a0534 100644 --- a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs +++ b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs @@ -73,7 +73,7 @@ namespace Jellyfin.Dlna.Tests } [Fact] - public void Test_Profile_NoMatch() + public void IsMatch_GivenNamesAndManufacturersDoNotMatch_ReturnsFalse() { var device = new DeviceInfo() { From 39eb5da44f8e931aa6a89f9bb3d0785ca5e84465 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 21 Apr 2021 10:09:21 +0100 Subject: [PATCH 079/612] Update tests/Jellyfin.Dlna.Tests/ProfileTester.cs Co-authored-by: Claus Vium --- tests/Jellyfin.Dlna.Tests/ProfileTester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs index 2f27a0534..004933ca9 100644 --- a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs +++ b/tests/Jellyfin.Dlna.Tests/ProfileTester.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Dlna.Tests } [Fact] - public void Test_Profile_Matches() + public void IsMatch_GivenMatchingName_ReturnsTrue() { var device = new DeviceInfo() { From 53e1b302cc08181c7909719df0cd837fd4b182f4 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 21 Apr 2021 10:18:29 +0100 Subject: [PATCH 080/612] Changes as requested --- Emby.Dlna/DlnaManager.cs | 14 ++++----- .../{ProfileTester.cs => DlnaManagerTests.cs} | 31 ++++++++++++++++--- 2 files changed, 34 insertions(+), 11 deletions(-) rename tests/Jellyfin.Dlna.Tests/{ProfileTester.cs => DlnaManagerTests.cs} (78%) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 57a6c2059..5f2be07cf 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -150,13 +150,13 @@ namespace Emby.Dlna public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) { return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName) - && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer) - && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl) - && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription) - && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName) - && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber) - && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl) - && IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber); + && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer) + && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl) + && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription) + && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName) + && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber) + && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl) + && IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber); } private bool IsRegexOrSubstringMatch(string input, string pattern) diff --git a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs similarity index 78% rename from tests/Jellyfin.Dlna.Tests/ProfileTester.cs rename to tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs index 004933ca9..087d43a77 100644 --- a/tests/Jellyfin.Dlna.Tests/ProfileTester.cs +++ b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs @@ -46,7 +46,7 @@ namespace Jellyfin.Dlna.Tests ModelDescription = "LG WebOSTV DMRplus", ModelName = "LG TV", ModelNumber = "1.0", - Identification = new DeviceIdentification() + Identification = new () { FriendlyName = "My Device", Manufacturer = "LG Electronics", @@ -69,7 +69,8 @@ namespace Jellyfin.Dlna.Tests } }; - Assert.True(GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification)); + var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification); + Assert.True(deviceMatch); } [Fact] @@ -90,7 +91,7 @@ namespace Jellyfin.Dlna.Tests ModelDescription = "LG WebOSTV DMRplus", ModelName = "LG TV", ModelNumber = "1.0", - Identification = new DeviceIdentification() + Identification = new () { FriendlyName = "My Device", Manufacturer = "LG Electronics", @@ -101,7 +102,29 @@ namespace Jellyfin.Dlna.Tests } }; - Assert.False(GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification)); + var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification); + + Assert.False(deviceMatch); + } + + [Fact] + public void IsMatch_GivenNamesAndRegExMatch_ReturnsTrue() + { + var device = new DeviceInfo() + { + Name = "My Device" + }; + + var profile = new DeviceProfile() + { + Name = "Test Profile", + FriendlyName = "My .*", + Identification = new () + }; + + var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification); + + Assert.True(deviceMatch); } } } From 20e19ae9b3a89e4c9aac00f2352eb6e3ec52230a Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 21 Apr 2021 12:08:02 +0100 Subject: [PATCH 081/612] Moved Assert --- tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs index 087d43a77..668bd8f87 100644 --- a/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs +++ b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs @@ -57,8 +57,6 @@ namespace Jellyfin.Dlna.Tests } }; - Assert.True(GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification)); - var profile2 = new DeviceProfile() { Name = "Test Profile", @@ -70,7 +68,10 @@ namespace Jellyfin.Dlna.Tests }; var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification); + var deviceMatch2 = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification); + Assert.True(deviceMatch); + Assert.True(deviceMatch2); } [Fact] From 831e768ee7ae8e750202ff54395edade2761a90e Mon Sep 17 00:00:00 2001 From: artiume Date: Wed, 21 Apr 2021 13:15:03 -0400 Subject: [PATCH 082/612] Request more logs --- .github/ISSUE_TEMPLATE/bug_report.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d67e1c98b..12f1f5ed5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -33,7 +33,13 @@ assignees: '' **Expected behavior** -**Logs** +**Server Logs** + + +**FFmpeg Logs** + + +**Browser Console Logs** **Screenshots** From b323044139fd7a0b63a717101f7ccb7f03f3f125 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 22 Apr 2021 01:23:24 +0200 Subject: [PATCH 083/612] Reduce string allocations/fs lookups in resolve code --- .../Library/LibraryManager.cs | 1 - .../Library/ResolverHelper.cs | 58 +++++-------------- .../Entities/AggregateFolder.cs | 3 +- .../Entities/CollectionFolder.cs | 1 - .../Library/ItemResolveArgs.cs | 6 +- .../Library/EpisodeResolverTest.cs | 11 +++- 6 files changed, 29 insertions(+), 51 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6a9f4174d..d869c7e39 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -558,7 +558,6 @@ namespace Emby.Server.Implementations.Library var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService) { Parent = parent, - Path = fullPath, FileInfo = fileInfo, CollectionType = collectionType, LibraryOptions = libraryOptions diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 4e4cac75b..8be80d726 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.IO; using System.Linq; @@ -21,8 +23,8 @@ namespace Emby.Server.Implementations.Library /// The file system. /// The library manager. /// The directory service. - /// Item must have a path - public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService) + /// Item must have a path. + public static void SetInitialItemValues(BaseItem item, Folder? parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService) { // This version of the below method has no ItemResolveArgs, so we have to require the path already being set if (string.IsNullOrEmpty(item.Path)) @@ -43,9 +45,9 @@ namespace Emby.Server.Implementations.Library // Make sure DateCreated and DateModified have values var fileInfo = directoryService.GetFile(item.Path); - SetDateCreated(item, fileSystem, fileInfo); + SetDateCreated(item, fileInfo); - EnsureName(item, item.Path, fileInfo); + EnsureName(item, fileInfo); } /// @@ -72,9 +74,9 @@ namespace Emby.Server.Implementations.Library item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); // Make sure the item has a name - EnsureName(item, item.Path, args.FileInfo); + EnsureName(item, args.FileInfo); - item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || + item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) || item.GetParents().Any(i => i.IsLocked); // Make sure DateCreated and DateModified have values @@ -84,28 +86,15 @@ namespace Emby.Server.Implementations.Library /// /// Ensures the name. /// - private static void EnsureName(BaseItem item, string fullPath, FileSystemMetadata fileInfo) + private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo) { // If the subclass didn't supply a name, add it here - if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(fullPath)) + if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path)) { - var fileName = fileInfo == null ? Path.GetFileName(fullPath) : fileInfo.Name; - - item.Name = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory); + item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name); } } - /// - /// Gets the display name. - /// - /// The path. - /// if set to true [is directory]. - /// System.String. - private static string GetDisplayName(string path, bool isDirectory) - { - return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path); - } - /// /// Ensures DateCreated and DateModified have values. /// @@ -114,21 +103,6 @@ namespace Emby.Server.Implementations.Library /// The args. private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args) { - if (fileSystem == null) - { - throw new ArgumentNullException(nameof(fileSystem)); - } - - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - // See if a different path came out of the resolver than what went in if (!fileSystem.AreEqual(args.Path, item.Path)) { @@ -136,7 +110,7 @@ namespace Emby.Server.Implementations.Library if (childData != null) { - SetDateCreated(item, fileSystem, childData); + SetDateCreated(item, childData); } else { @@ -144,17 +118,17 @@ namespace Emby.Server.Implementations.Library if (fileData.Exists) { - SetDateCreated(item, fileSystem, fileData); + SetDateCreated(item, fileData); } } } else { - SetDateCreated(item, fileSystem, args.FileInfo); + SetDateCreated(item, args.FileInfo); } } - private static void SetDateCreated(BaseItem item, IFileSystem fileSystem, FileSystemMetadata info) + private static void SetDateCreated(BaseItem item, FileSystemMetadata? info) { var config = BaseItem.ConfigurationManager.GetMetadataConfiguration(); @@ -163,7 +137,7 @@ namespace Emby.Server.Implementations.Library // directoryService.getFile may return null if (info != null) { - var dateCreated = fileSystem.GetCreationTimeUtc(info); + var dateCreated = info.CreationTimeUtc; if (dateCreated.Equals(DateTime.MinValue)) { diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 6ebea5f44..6a92200dd 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -120,8 +120,7 @@ namespace MediaBrowser.Controller.Entities var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) { - FileInfo = FileSystem.GetDirectoryInfo(path), - Path = path + FileInfo = FileSystem.GetDirectoryInfo(path) }; // Gather child folder and files diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 76b6d39a9..16a2c77e9 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -271,7 +271,6 @@ namespace MediaBrowser.Controller.Entities var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) { FileInfo = FileSystem.GetDirectoryInfo(path), - Path = path, Parent = GetParent() as Folder, CollectionType = CollectionType }; diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index 12a311dc3..df8842237 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -60,10 +60,10 @@ namespace MediaBrowser.Controller.Library public FileSystemMetadata FileInfo { get; set; } /// - /// Gets or sets the path. + /// Gets the path. /// /// The path. - public string Path { get; set; } + public string Path => FileInfo.FullName; /// /// Gets a value indicating whether this instance is directory. @@ -87,7 +87,7 @@ namespace MediaBrowser.Controller.Library return false; } - var parentDir = System.IO.Path.GetDirectoryName(Path) ?? string.Empty; + var parentDir = FileInfo.DirectoryName ?? string.Empty; return parentDir.Length > _appPaths.RootFolderPath.Length && parentDir.StartsWith(_appPaths.RootFolderPath, StringComparison.OrdinalIgnoreCase); diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs index 876519215..c393742eb 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; using Moq; using Xunit; @@ -28,7 +29,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library { Parent = parent, CollectionType = CollectionType.TvShows, - Path = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv" + FileInfo = new FileSystemMetadata() + { + FullName = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv" + } }; Assert.Null(episodeResolver.Resolve(itemResolveArgs)); @@ -48,7 +52,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library { Parent = series, CollectionType = CollectionType.TvShows, - Path = "Extras/Extras S01E01.mkv" + FileInfo = new FileSystemMetadata() + { + FullName = "Extras/Extras S01E01.mkv" + } }; Assert.NotNull(episodeResolver.Resolve(itemResolveArgs)); } From 81209258abeade8614d848f7e7cceeb6bb37a7d7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 22 Apr 2021 03:11:21 +0200 Subject: [PATCH 084/612] ManagedFileSystem: Rewrite GetValidFilename and more improvements --- .../IO/ManagedFileSystem.cs | 62 ++++++++++--------- .../IO/ManagedFileSystemTests.cs | 10 +++ 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 3893a1577..0d72cb00f 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -2,11 +2,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -using System.Text; +using System.Runtime.InteropServices; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; @@ -24,7 +23,7 @@ namespace Emby.Server.Implementations.IO private readonly List _shortcutHandlers = new List(); private readonly string _tempPath; - private readonly bool _isEnvironmentCaseInsensitive; + private static readonly bool _isEnvironmentCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); public ManagedFileSystem( ILogger logger, @@ -32,8 +31,6 @@ namespace Emby.Server.Implementations.IO { Logger = logger; _tempPath = applicationPaths.TempDirectory; - - _isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows; } public virtual void AddShortcutHandler(IShortcutHandler handler) @@ -55,7 +52,7 @@ namespace Emby.Server.Implementations.IO } var extension = Path.GetExtension(filename); - return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); + return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); } /// @@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.IO } var extension = Path.GetExtension(filename); - var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); + var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); return handler?.Resolve(filename); } @@ -303,16 +300,38 @@ namespace Emby.Server.Implementations.IO /// The filename. /// System.String. /// The filename is null. - public virtual string GetValidFilename(string filename) + public string GetValidFilename(string filename) { - var builder = new StringBuilder(filename); + return string.Create( + filename.Length, + filename, + (chars, state) => + { + state.AsSpan().CopyTo(chars); - foreach (var c in Path.GetInvalidFileNameChars()) - { - builder = builder.Replace(c, ' '); - } + var invalid = Path.GetInvalidFileNameChars(); - return builder.ToString(); + var first = state.AsSpan().IndexOfAny(invalid); + if (first == -1) + { + // Fast path for clean strings + return; + } + + chars[first++] = ' '; + + var len = chars.Length; + foreach (var c in invalid) + { + for (int i = first; i < len; i++) + { + if (chars[i] == c) + { + chars[i] = ' '; + } + } + } + }); } /// @@ -684,20 +703,5 @@ namespace Emby.Server.Implementations.IO AttributesToSkip = 0 }; } - - private static void RunProcess(string path, string args, string workingDirectory) - { - using (var process = Process.Start(new ProcessStartInfo - { - Arguments = args, - FileName = path, - CreateNoWindow = true, - WorkingDirectory = workingDirectory, - WindowStyle = ProcessWindowStyle.Normal - })) - { - process.WaitForExit(); - } - } } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs index 614a68975..30e6542f9 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs @@ -42,6 +42,16 @@ namespace Jellyfin.Server.Implementations.Tests.IO } } + [Theory] + [InlineData("ValidFileName", "ValidFileName")] + [InlineData("AC/DC", "AC DC")] + [InlineData("Invalid\0", "Invalid ")] + [InlineData("AC/DC\0KD/A", "AC DC KD A")] + public void GetValidFilename_ReturnsValidFilename(string filename, string expectedFileName) + { + Assert.Equal(expectedFileName, _sut.GetValidFilename(filename)); + } + [SkippableFact] public void GetFileInfo_DanglingSymlink_ExistsFalse() { From 33327aa1a951a1d0717b39f8f71356f09eb7bd6e Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 22 Apr 2021 12:31:47 +0200 Subject: [PATCH 085/612] Improve fast path of ManagedFileSystem.GetValidFilename | Method | Data | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Allocated | |---------------------------- |-------------- |------------:|----------:|----------:|------------:|-------:|------:|------:|----------:| | GetValidFilenameBench | AC/DCKD/A | 52.29 ns | 0.537 ns | 0.448 ns | 52.35 ns | 0.0255 | - | - | 80 B | | GetValidFilenameOldBench | AC/DCKD/A | 86.68 ns | 1.205 ns | 1.127 ns | 86.33 ns | 0.0587 | - | - | 184 B | | GetValidFilenameWinBench | AC/DCKD/A | 448.55 ns | 1.228 ns | 1.088 ns | 448.33 ns | 0.0505 | - | - | 160 B | | GetValidFilenameOldWinBench | AC/DCKD/A | 865.21 ns | 5.734 ns | 5.083 ns | 866.60 ns | 0.0839 | - | - | 264 B | | GetValidFilenameBench | ValidFileName | 16.00 ns | 0.234 ns | 0.207 ns | 16.02 ns | 0.0102 | - | - | 32 B | | GetValidFilenameOldBench | ValidFileName | 100.66 ns | 1.255 ns | 1.174 ns | 101.21 ns | 0.0587 | - | - | 184 B | | GetValidFilenameWinBench | ValidFileName | 116.60 ns | 1.624 ns | 1.519 ns | 116.88 ns | 0.0356 | - | - | 112 B | | GetValidFilenameOldWinBench | ValidFileName | 1,052.66 ns | 18.077 ns | 33.056 ns | 1,037.25 ns | 0.0839 | - | - | 264 B | --- .../IO/ManagedFileSystem.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 0d72cb00f..df973f971 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -302,28 +302,27 @@ namespace Emby.Server.Implementations.IO /// The filename is null. public string GetValidFilename(string filename) { + var invalid = Path.GetInvalidFileNameChars(); + var first = filename.IndexOfAny(invalid); + if (first == -1) + { + // Fast path for clean strings + return filename; + } + return string.Create( filename.Length, - filename, + (filename, invalid, first), (chars, state) => { - state.AsSpan().CopyTo(chars); + state.filename.AsSpan().CopyTo(chars); - var invalid = Path.GetInvalidFileNameChars(); - - var first = state.AsSpan().IndexOfAny(invalid); - if (first == -1) - { - // Fast path for clean strings - return; - } - - chars[first++] = ' '; + chars[state.first++] = ' '; var len = chars.Length; - foreach (var c in invalid) + foreach (var c in state.invalid) { - for (int i = first; i < len; i++) + for (int i = state.first; i < len; i++) { if (chars[i] == c) { From a02e37daa009bebc3c5076b0e407dffe41e84c55 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 22 Apr 2021 15:28:24 +0200 Subject: [PATCH 086/612] SqliteItemRepository: remove redundant operations removed: * nameof -> FullName lookup * IndexOf before Replace * Enum.GetNames -> Enum.Parse roundtrip --- .../Data/SqliteItemRepository.cs | 185 +++++++----------- .../Library/LibraryManager.cs | 2 +- .../Persistence/IItemRepository.cs | 3 +- 3 files changed, 68 insertions(+), 122 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 694805ebe..28e59913c 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2116,9 +2116,7 @@ namespace Emby.Server.Implementations.Data || query.IsLiked.HasValue; } - private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToArray(); + private readonly ItemFields[] _allFields = Enum.GetValues(); private string[] GetColumnNamesFromField(ItemFields field) { @@ -2721,87 +2719,22 @@ namespace Emby.Server.Implementations.Data private string FixUnicodeChars(string buffer) { - if (buffer.IndexOf('\u2013', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2013', '-'); // en dash - } - - if (buffer.IndexOf('\u2014', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2014', '-'); // em dash - } - - if (buffer.IndexOf('\u2015', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2015', '-'); // horizontal bar - } - - if (buffer.IndexOf('\u2017', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2017', '_'); // double low line - } - - if (buffer.IndexOf('\u2018', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2018', '\''); // left single quotation mark - } - - if (buffer.IndexOf('\u2019', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2019', '\''); // right single quotation mark - } - - if (buffer.IndexOf('\u201a', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark - } - - if (buffer.IndexOf('\u201b', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark - } - - if (buffer.IndexOf('\u201c', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark - } - - if (buffer.IndexOf('\u201d', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark - } - - if (buffer.IndexOf('\u201e', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark - } - - if (buffer.IndexOf('\u2026', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis - } - - if (buffer.IndexOf('\u2032', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2032', '\''); // prime - } - - if (buffer.IndexOf('\u2033', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2033', '\"'); // double prime - } - - if (buffer.IndexOf('\u0060', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u0060', '\''); // grave accent - } - - if (buffer.IndexOf('\u00B4', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u00B4', '\''); // acute accent - } - - return buffer; + buffer = buffer.Replace('\u2013', '-'); // en dash + buffer = buffer.Replace('\u2014', '-'); // em dash + buffer = buffer.Replace('\u2015', '-'); // horizontal bar + buffer = buffer.Replace('\u2017', '_'); // double low line + buffer = buffer.Replace('\u2018', '\''); // left single quotation mark + buffer = buffer.Replace('\u2019', '\''); // right single quotation mark + buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark + buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark + buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark + buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark + buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark + buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis + buffer = buffer.Replace('\u2032', '\''); // prime + buffer = buffer.Replace('\u2033', '\"'); // double prime + buffer = buffer.Replace('\u0060', '\''); // grave accent + return buffer.Replace('\u00B4', '\''); // acute accent } private void AddItem(List items, BaseItem newItem) @@ -3584,11 +3517,11 @@ namespace Emby.Server.Implementations.Data statement?.TryBind("@IsFolder", query.IsFolder); } - var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); + var includeTypes = query.IncludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray(); // Only specify excluded types if no included types are specified if (includeTypes.Length == 0) { - var excludeTypes = query.ExcludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); + var excludeTypes = query.ExcludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray(); if (excludeTypes.Length == 1) { whereClauses.Add("type<>@type"); @@ -4532,7 +4465,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb")); } - var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList(); + var includedItemByNameTypes = GetItemByNameTypesInQuery(query); var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; var queryTopParentIds = query.TopParentIds; @@ -4790,27 +4723,27 @@ namespace Emby.Server.Implementations.Data if (IsTypeInQuery(nameof(Person), query)) { - list.Add(nameof(Person)); + list.Add(typeof(Person).FullName); } if (IsTypeInQuery(nameof(Genre), query)) { - list.Add(nameof(Genre)); + list.Add(typeof(Genre).FullName); } if (IsTypeInQuery(nameof(MusicGenre), query)) { - list.Add(nameof(MusicGenre)); + list.Add(typeof(MusicGenre).FullName); } if (IsTypeInQuery(nameof(MusicArtist), query)) { - list.Add(nameof(MusicArtist)); + list.Add(typeof(MusicArtist).FullName); } if (IsTypeInQuery(nameof(Studio), query)) { - list.Add(nameof(Studio)); + list.Add(typeof(Studio).FullName); } return list; @@ -4915,15 +4848,10 @@ namespace Emby.Server.Implementations.Data typeof(AggregateFolder) }; - public void UpdateInheritedValues(CancellationToken cancellationToken) - { - UpdateInheritedTags(cancellationToken); - } - - private void UpdateInheritedTags(CancellationToken cancellationToken) + public void UpdateInheritedValues() { string sql = string.Join( - ";", + ';', new string[] { "delete from itemvalues where type = 6", @@ -4946,37 +4874,38 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - private static Dictionary GetTypeMapDictionary() + private static Dictionary GetTypeMapDictionary() { - var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); + var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var t in _knownTypes) { - dict[t.Name] = new[] { t.FullName }; + dict[t.Name] = t.FullName ; } - dict["Program"] = new[] { typeof(LiveTvProgram).FullName }; - dict["TvChannel"] = new[] { typeof(LiveTvChannel).FullName }; + dict["Program"] = typeof(LiveTvProgram).FullName; + dict["TvChannel"] = typeof(LiveTvChannel).FullName; return dict; } // Not crazy about having this all the way down here, but at least it's in one place - private readonly Dictionary _types = GetTypeMapDictionary(); + private readonly Dictionary _types = GetTypeMapDictionary(); - private string[] MapIncludeItemTypes(string value) + private string MapIncludeItemTypes(string value) { - if (_types.TryGetValue(value, out string[] result)) + if (_types.TryGetValue(value, out string result)) { return result; } if (IsValidType(value)) { - return new[] { value }; + return value; } - return Array.Empty(); + Logger.LogWarning("Unknown item type: {ItemType}", value); + return null; } public void DeleteItem(Guid id) @@ -5279,31 +5208,46 @@ AND Type = @InternalPersonType)"); public List GetStudioNames() { - return GetItemValueNames(new[] { 3 }, new List(), new List()); + return GetItemValueNames(new[] { 3 }, Array.Empty(), Array.Empty()); } public List GetAllArtistNames() { - return GetItemValueNames(new[] { 0, 1 }, new List(), new List()); + return GetItemValueNames(new[] { 0, 1 }, Array.Empty(), Array.Empty()); } public List GetMusicGenreNames() { - return GetItemValueNames(new[] { 2 }, new List { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }, new List()); + return GetItemValueNames( + new[] { 2 }, + new string[] + { + typeof(Audio).FullName, + typeof(MusicVideo).FullName, + typeof(MusicAlbum).FullName, + typeof(MusicArtist).FullName + }, + Array.Empty()); } public List GetGenreNames() { - return GetItemValueNames(new[] { 2 }, new List(), new List { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }); + return GetItemValueNames( + new[] { 2 }, + Array.Empty(), + new string[] + { + typeof(Audio).FullName, + typeof(MusicVideo).FullName, + typeof(MusicAlbum).FullName, + typeof(MusicArtist).FullName + }); } - private List GetItemValueNames(int[] itemValueTypes, List withItemTypes, List excludeItemTypes) + private List GetItemValueNames(int[] itemValueTypes, IReadOnlyList withItemTypes, IReadOnlyList excludeItemTypes) { CheckDisposed(); - withItemTypes = withItemTypes.SelectMany(MapIncludeItemTypes).ToList(); - excludeItemTypes = excludeItemTypes.SelectMany(MapIncludeItemTypes).ToList(); - var now = DateTime.UtcNow; var typeClause = itemValueTypes.Length == 1 ? @@ -5809,7 +5753,10 @@ AND Type = @InternalPersonType)"); var endIndex = Math.Min(people.Count, startIndex + Limit); for (var i = startIndex; i < endIndex; i++) { - insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture)); + insertText.AppendFormat( + CultureInfo.InvariantCulture, + "(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", + i.ToString(CultureInfo.InvariantCulture)); } // Remove last comma @@ -6261,7 +6208,7 @@ AND Type = @InternalPersonType)"); CheckDisposed(); if (id == Guid.Empty) { - throw new ArgumentException(nameof(id)); + throw new ArgumentException("Guid can't be empty.", nameof(id)); } if (attachments == null) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6a9f4174d..9c5172fb4 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1163,7 +1163,7 @@ namespace Emby.Server.Implementations.Library progress.Report(percent * 100); } - _itemRepository.UpdateInheritedValues(cancellationToken); + _itemRepository.UpdateInheritedValues(); progress.Report(100); } diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 45c6805f0..ed473c749 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -153,8 +153,7 @@ namespace MediaBrowser.Controller.Persistence /// /// Updates the inherited values. /// - /// The cancellation token. - void UpdateInheritedValues(CancellationToken cancellationToken); + void UpdateInheritedValues(); int GetCount(InternalItemsQuery query); From e4691d45f5ffccded535a683a6c2f76a2c8536cb Mon Sep 17 00:00:00 2001 From: Ian Walton Date: Sat, 24 Apr 2021 10:54:42 -0400 Subject: [PATCH 087/612] Leave SyncPlay group on session disconnect. --- .../SyncPlay/SyncPlayManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index aee959c53..315277985 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -87,7 +87,7 @@ namespace Emby.Server.Implementations.SyncPlay _sessionManager = sessionManager; _libraryManager = libraryManager; _logger = loggerFactory.CreateLogger(); - _sessionManager.SessionControllerConnected += OnSessionControllerConnected; + _sessionManager.SessionEnded += OnSessionEnded; } /// @@ -352,18 +352,18 @@ namespace Emby.Server.Implementations.SyncPlay return; } - _sessionManager.SessionControllerConnected -= OnSessionControllerConnected; + _sessionManager.SessionEnded -= OnSessionEnded; _disposed = true; } - private void OnSessionControllerConnected(object sender, SessionEventArgs e) + private void OnSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; if (_sessionToGroupMap.TryGetValue(session.Id, out var group)) { - var request = new JoinGroupRequest(group.GroupId); - JoinGroup(session, request, CancellationToken.None); + var leaveGroupRequest = new LeaveGroupRequest(); + LeaveGroup(session, leaveGroupRequest, CancellationToken.None); } } From 77261a844541efdff96088c047908109d6063133 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 24 Apr 2021 20:22:23 +0200 Subject: [PATCH 088/612] add UpdatePeopleAsync and add people to both tables --- .../Library/LibraryManager.cs | 60 +++++++++++++++++++ .../Library/ILibraryManager.cs | 9 +++ .../Manager/MetadataService.cs | 59 +----------------- 3 files changed, 72 insertions(+), 56 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6a9f4174d..7d030418a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2880,6 +2880,12 @@ namespace Emby.Server.Implementations.Library } public void UpdatePeople(BaseItem item, List people) + { + UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult(); + } + + /// + public async Task UpdatePeopleAsync(BaseItem item, List people, CancellationToken cancellationToken) { if (!item.SupportsPeople) { @@ -2887,6 +2893,8 @@ namespace Emby.Server.Implementations.Library } _itemRepository.UpdatePeople(item.Id, people); + + await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false); } public async Task ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex) @@ -2990,6 +2998,58 @@ namespace Emby.Server.Implementations.Library } } + private async Task SavePeopleMetadataAsync(IEnumerable people, CancellationToken cancellationToken) + { + var personsToSave = new List(); + + foreach (var person in people) + { + cancellationToken.ThrowIfCancellationRequested(); + + var itemUpdateType = ItemUpdateType.MetadataDownload; + var saveEntity = false; + var personEntity = GetPerson(person.Name); + + // if PresentationUniqueKey is empty it's likely a new item. + if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey)) + { + personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey(); + saveEntity = true; + } + + foreach (var id in person.ProviderIds) + { + if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase)) + { + personEntity.SetProviderId(id.Key, id.Value); + saveEntity = true; + } + } + + if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) + { + personEntity.SetImage( + new ItemImageInfo + { + Path = person.ImageUrl, + Type = ImageType.Primary + }, + 0); + + saveEntity = true; + itemUpdateType = ItemUpdateType.ImageUpdate; + } + + if (saveEntity) + { + personsToSave.Add(personEntity); + await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); + } + } + + CreateItems(personsToSave, null, CancellationToken.None); + } + private void StartScanInBackground() { Task.Run(() => diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 80fcf71f3..6d9b568da 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -466,6 +466,15 @@ namespace MediaBrowser.Controller.Library /// The people. void UpdatePeople(BaseItem item, List people); + /// + /// Asynchronously updates the people. + /// + /// The item. + /// The people. + /// The cancellation token. + /// The async task. + Task UpdatePeopleAsync(BaseItem item, List people, CancellationToken cancellationToken); + /// /// Gets the item ids. /// diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index f12586665..6b778a090 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Manager } // Save to database - await SaveItemAsync(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false); + await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false); } await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false); @@ -216,71 +216,18 @@ namespace MediaBrowser.Providers.Manager lookupInfo.Year = result.ProductionYear; } - protected async Task SaveItemAsync(MetadataResult result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken) + protected async Task SaveItemAsync(MetadataResult result, ItemUpdateType reason, CancellationToken cancellationToken) { if (result.Item.SupportsPeople && result.People != null) { var baseItem = result.Item; - LibraryManager.UpdatePeople(baseItem, result.People); - await SavePeopleMetadataAsync(result.People, cancellationToken).ConfigureAwait(false); + await LibraryManager.UpdatePeopleAsync(baseItem, result.People, cancellationToken).ConfigureAwait(false); } await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false); } - private async Task SavePeopleMetadataAsync(IEnumerable people, CancellationToken cancellationToken) - { - var personsToSave = new List(); - - foreach (var person in people) - { - cancellationToken.ThrowIfCancellationRequested(); - - var itemUpdateType = ItemUpdateType.MetadataDownload; - var saveEntity = false; - var personEntity = LibraryManager.GetPerson(person.Name); - - // if PresentationUniqueKey is empty it's likely a new item. - if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey)) - { - personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey(); - saveEntity = true; - } - - foreach (var id in person.ProviderIds) - { - if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase)) - { - personEntity.SetProviderId(id.Key, id.Value); - saveEntity = true; - } - } - - if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) - { - personEntity.SetImage( - new ItemImageInfo - { - Path = person.ImageUrl, - Type = ImageType.Primary - }, - 0); - - saveEntity = true; - itemUpdateType = ItemUpdateType.ImageUpdate; - } - - if (saveEntity) - { - personsToSave.Add(personEntity); - await LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); - } - } - - LibraryManager.CreateItems(personsToSave, null, CancellationToken.None); - } - protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { item.AfterMetadataRefresh(); From eee3b385daae376efb6f05af19692bc8b64e9c96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 12:00:42 +0000 Subject: [PATCH 089/612] Bump AutoFixture from 4.16.0 to 4.17.0 Bumps [AutoFixture](https://github.com/AutoFixture/AutoFixture) from 4.16.0 to 4.17.0. - [Release notes](https://github.com/AutoFixture/AutoFixture/releases) - [Commits](https://github.com/AutoFixture/AutoFixture/compare/v4.16.0...v4.17.0) Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 050d4c040..0071cda6e 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 486899f4f..7a4ab9b26 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 0de92249a..8646b60b1 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 9e60dbcd9..64383a2d9 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -10,7 +10,7 @@ - + From d5037a8988df1e4790b45ed55696f7afa7f43b5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 12:00:48 +0000 Subject: [PATCH 090/612] Bump Swashbuckle.AspNetCore from 6.0.7 to 6.1.3 Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.0.7 to 6.1.3. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.0.7...v6.1.3) Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index d6dc5a2dc..01341d8b1 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -17,7 +17,7 @@ - + From 24a05bc9f83236a2e52b5c754c2a775ac1e6c1c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 12:00:57 +0000 Subject: [PATCH 091/612] Bump sharpcompress from 0.28.1 to 0.28.2 Bumps [sharpcompress](https://github.com/adamhathcock/sharpcompress) from 0.28.1 to 0.28.2. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.28.1...0.28.2) Signed-off-by: dependabot[bot] --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 9248053f5..4ee23127e 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -30,7 +30,7 @@ - + From e409d89138b21daef34dd64afb4d6434956b56fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 12:03:24 +0000 Subject: [PATCH 092/612] Bump alex-page/github-project-automation-plus from v0.5.1 to v0.6.0 Bumps [alex-page/github-project-automation-plus](https://github.com/alex-page/github-project-automation-plus) from v0.5.1 to v0.6.0. - [Release notes](https://github.com/alex-page/github-project-automation-plus/releases) - [Commits](https://github.com/alex-page/github-project-automation-plus/compare/v0.5.1...4230e39aec629f1b622666350cdbdf29ff149aca) Signed-off-by: dependabot[bot] --- .github/workflows/automation.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index db34693cc..d5d308185 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -16,7 +16,7 @@ jobs: label: stable backport - name: Remove from 'Current Release' project - uses: alex-page/github-project-automation-plus@v0.5.1 + uses: alex-page/github-project-automation-plus@v0.6.0 if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel continue-on-error: true with: @@ -25,7 +25,7 @@ jobs: repo-token: ${{ secrets.GH_TOKEN }} - name: Add to 'Release Next' project - uses: alex-page/github-project-automation-plus@v0.5.1 + uses: alex-page/github-project-automation-plus@v0.6.0 if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened' continue-on-error: true with: @@ -34,7 +34,7 @@ jobs: repo-token: ${{ secrets.GH_TOKEN }} - name: Add to 'Current Release' project - uses: alex-page/github-project-automation-plus@v0.5.1 + uses: alex-page/github-project-automation-plus@v0.6.0 if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel continue-on-error: true with: @@ -48,7 +48,7 @@ jobs: run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)" - name: Move issue to needs triage - uses: alex-page/github-project-automation-plus@v0.5.1 + uses: alex-page/github-project-automation-plus@v0.6.0 if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1 continue-on-error: true with: @@ -57,7 +57,7 @@ jobs: repo-token: ${{ secrets.GH_TOKEN }} - name: Add issue to triage project - uses: alex-page/github-project-automation-plus@v0.5.1 + uses: alex-page/github-project-automation-plus@v0.6.0 if: github.event.issue.pull_request == '' && github.event.action == 'opened' continue-on-error: true with: From 47c54166e14a89a6895f6ceda3ab48591c097fad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 14:43:14 +0000 Subject: [PATCH 093/612] Bump prometheus-net.DotNetRuntime from 3.4.1 to 4.0.0 Bumps [prometheus-net.DotNetRuntime](https://github.com/djluck/prometheus-net.DotNetRuntime) from 3.4.1 to 4.0.0. - [Release notes](https://github.com/djluck/prometheus-net.DotNetRuntime/releases) - [Commits](https://github.com/djluck/prometheus-net.DotNetRuntime/compare/3.4.1...4.0.0) Signed-off-by: dependabot[bot] --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 4ee23127e..adbfe52c4 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,7 +29,7 @@ - + From a3cb8dbc9de4b8aa9c5bb4f6c5e0bd1ee53f60a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 21:47:14 +0000 Subject: [PATCH 094/612] Bump Swashbuckle.AspNetCore.ReDoc from 6.0.7 to 6.1.3 Bumps [Swashbuckle.AspNetCore.ReDoc](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.0.7 to 6.1.3. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.0.7...v6.1.3) Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 01341d8b1..3868882e5 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -18,7 +18,7 @@ - + From c1563d9303da386fb37c78cfa4976917fb649b5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Apr 2021 13:35:13 +0000 Subject: [PATCH 095/612] Bump AutoFixture.Xunit2 from 4.16.0 to 4.17.0 Bumps [AutoFixture.Xunit2](https://github.com/AutoFixture/AutoFixture) from 4.16.0 to 4.17.0. - [Release notes](https://github.com/AutoFixture/AutoFixture/releases) - [Commits](https://github.com/AutoFixture/AutoFixture/compare/v4.16.0...v4.17.0) Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 0071cda6e..9a5ffaa55 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 8646b60b1..81878eafa 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 64383a2d9..f25998252 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -12,7 +12,7 @@ - + From d27ca993a5670d69bb40dd9879137c56eefe00e8 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 28 Apr 2021 18:33:30 -0600 Subject: [PATCH 096/612] Add ability to sort on Genre, MusicGenre, Artist --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 4 +++- Jellyfin.Api/Controllers/ArtistsController.cs | 7 ++++++- Jellyfin.Api/Controllers/GenresController.cs | 8 +++++++- Jellyfin.Api/Controllers/MusicGenresController.cs | 7 ++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 694805ebe..a9815ae36 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -5469,7 +5469,9 @@ AND Type = @InternalPersonType)"); commandText += whereText + " group by PresentationUniqueKey"; - if (query.SimilarTo != null || !string.IsNullOrEmpty(query.SearchTerm)) + if (query.OrderBy.Count != 0 + || query.SimilarTo != null + || !string.IsNullOrEmpty(query.SearchTerm)) { commandText += GetOrderByText(query); } diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 4b2e5e7ea..85d7c50d3 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -77,6 +77,8 @@ namespace Jellyfin.Api.Controllers /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. + /// Optional. Specify one or more sort orders, comma delimited. + /// Sort Order - Ascending,Descending. /// Optional, include image information in output. /// Total record count. /// Artists returned. @@ -112,6 +114,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { @@ -150,7 +154,8 @@ namespace Jellyfin.Api.Controllers MinCommunityRating = minCommunityRating, DtoOptions = dtoOptions, SearchTerm = searchTerm, - EnableTotalRecordCount = enableTotalRecordCount + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) }; if (parentId.HasValue) diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 7bcf4674c..2c67e82f2 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -63,12 +63,15 @@ namespace Jellyfin.Api.Controllers /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. + /// Optional. Specify one or more sort orders, comma delimited. + /// Sort Order - Ascending,Descending. /// Optional, include image information in output. /// Optional. Include total record count. /// Genres returned. /// An containing the queryresult of genres. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] + [AllowAnonymous] public ActionResult> GetGenres( [FromQuery] int? startIndex, [FromQuery] int? limit, @@ -84,6 +87,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { @@ -107,7 +112,8 @@ namespace Jellyfin.Api.Controllers NameStartsWithOrGreater = nameStartsWithOrGreater, DtoOptions = dtoOptions, SearchTerm = searchTerm, - EnableTotalRecordCount = enableTotalRecordCount + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) }; if (parentId.HasValue) diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 7f7058b5e..27eec2b9a 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -63,6 +63,8 @@ namespace Jellyfin.Api.Controllers /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. + /// Optional. Specify one or more sort orders, comma delimited. + /// Sort Order - Ascending,Descending. /// Optional, include image information in output. /// Optional. Include total record count. /// Music genres returned. @@ -84,6 +86,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { @@ -107,7 +111,8 @@ namespace Jellyfin.Api.Controllers NameStartsWithOrGreater = nameStartsWithOrGreater, DtoOptions = dtoOptions, SearchTerm = searchTerm, - EnableTotalRecordCount = enableTotalRecordCount + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) }; if (parentId.HasValue) From 69e91c33d8af168d12ba02c1a3e4762131ded952 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Thu, 29 Apr 2021 04:10:26 -0600 Subject: [PATCH 097/612] Update Jellyfin.Api/Controllers/GenresController.cs --- Jellyfin.Api/Controllers/GenresController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 2c67e82f2..5aa457153 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -71,7 +71,6 @@ namespace Jellyfin.Api.Controllers /// An containing the queryresult of genres. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - [AllowAnonymous] public ActionResult> GetGenres( [FromQuery] int? startIndex, [FromQuery] int? limit, From 34313ef2165b4889454eaac92b563fe1998854c0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 29 Apr 2021 16:23:29 +0200 Subject: [PATCH 098/612] SqliteItemRepository: Parse ChannelId directly from utf-8 data --- .../Data/SqliteItemRepository.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 694805ebe..8d220c807 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Buffers.Text; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -1311,7 +1312,14 @@ namespace Emby.Server.Implementations.Data if (!reader.IsDBNull(index)) { - item.ChannelId = new Guid(reader.GetString(index)); + if (!Utf8Parser.TryParse(reader[index].ToBlob(), out Guid value, out _, standardFormat: 'N')) + { + var str = reader.GetString(index); + Logger.LogWarning("{ChannelId} isn't in the expected format", str); + value = new Guid(str); + } + + item.ChannelId = value; } index++; From bdab8d1edbbad79ccecd4e0325c090ebf6ded2bc Mon Sep 17 00:00:00 2001 From: Nathan Mascitelli Date: Thu, 29 Apr 2021 22:35:57 -0400 Subject: [PATCH 099/612] Add ResumeBook section --- Jellyfin.Data/Enums/HomeSectionType.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Data/Enums/HomeSectionType.cs b/Jellyfin.Data/Enums/HomeSectionType.cs index e597c9431..9bcd097dc 100644 --- a/Jellyfin.Data/Enums/HomeSectionType.cs +++ b/Jellyfin.Data/Enums/HomeSectionType.cs @@ -48,6 +48,11 @@ /// /// Live TV. /// - LiveTv = 8 + LiveTv = 8, + + /// + /// Continue Reading. + /// + ResumeBook = 9 } } From 182117d0a7c60a00e03f69bc8cd237ddd3e73f34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Apr 2021 08:47:38 +0000 Subject: [PATCH 100/612] Bump AutoFixture.AutoMoq from 4.16.0 to 4.17.0 Bumps [AutoFixture.AutoMoq](https://github.com/AutoFixture/AutoFixture) from 4.16.0 to 4.17.0. - [Release notes](https://github.com/AutoFixture/AutoFixture/releases) - [Commits](https://github.com/AutoFixture/AutoFixture/compare/v4.16.0...v4.17.0) Signed-off-by: dependabot[bot] --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Implementations.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 9a5ffaa55..397b863b7 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 7a4ab9b26..27713d58a 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -23,7 +23,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 81878eafa..c1d871126 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index f25998252..72e40ebcb 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -11,7 +11,7 @@ - + From e90fbe90f9dc9603b6d9e07cc2c8ee096062c57b Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Fri, 30 Apr 2021 15:07:27 +0200 Subject: [PATCH 101/612] Remove extraneous 'stream' parameter The argument isn't passed to the method but causes the API generator to include an unnecessary parameter. Also fixes some typos in the documentation comments. --- Jellyfin.Api/Controllers/VideosController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 8dbb6aaa5..e544d001e 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -527,7 +527,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The dlna device profile id to utilize. /// The play session id. /// The segment container. - /// The segment lenght. + /// The segment length. /// The minimum number of segments. /// The media version id, if playing an alternate version. /// The device id of the client requesting. Used to stop encoding processes when needed. @@ -556,7 +556,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum video bit depth. /// Optional. Whether to require avc. /// Optional. Whether to deinterlace the video. - /// Optional. Whether to require a non anamporphic stream. + /// Optional. Whether to require a non anamorphic stream. /// Optional. The maximum number of audio channels to transcode. /// Optional. The limit of how many cpu cores to use. /// The live stream id. @@ -570,8 +570,8 @@ namespace Jellyfin.Api.Controllers /// Optional. The streaming options. /// Video stream returned. /// A containing the audio file. - [HttpGet("{itemId}/{stream=stream}.{container}")] - [HttpHead("{itemId}/{stream=stream}.{container}", Name = "HeadVideoStreamByContainer")] + [HttpGet("{itemId}/stream.{container}")] + [HttpHead("{itemId}/stream.{container}", Name = "HeadVideoStreamByContainer")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesVideoFile] public Task GetVideoStreamByContainer( From 608cba817c54df60959079719bafca4d7d54269a Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 30 Apr 2021 15:09:36 +0200 Subject: [PATCH 102/612] Reduce some allocations with the magic of spans etc. --- .../Data/SqliteItemRepository.cs | 155 ++++++++++++------ .../Library/MediaSourceManager.cs | 13 +- .../LiveTv/EmbyTV/EmbyTV.cs | 14 +- .../Localization/LocalizationManager.cs | 3 +- .../QuickConnect/QuickConnectManager.cs | 15 +- .../SyncPlay/SyncPlayManager.cs | 13 +- .../Extensions/SplitLinesStringExtensions.cs | 102 ++++++++++++ MediaBrowser.Controller/Entities/Folder.cs | 17 +- .../Entities/ProviderIdsExtensions.cs | 30 ++++ 9 files changed, 267 insertions(+), 95 deletions(-) create mode 100644 MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 28e59913c..bd5a3e30b 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1007,15 +1007,12 @@ namespace Emby.Server.Implementations.Data return; } - var parts = value.Split('|', StringSplitOptions.RemoveEmptyEntries); - - foreach (var part in parts) + foreach (var part in value.SpanSplit('|')) { - var idParts = part.Split('='); - - if (idParts.Length == 2) + var providerDelimiterIndex = part.IndexOf('='); + if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('=')) { - item.SetProviderId(idParts[0], idParts[1]); + item.SetProviderId(part.Slice(0, providerDelimiterIndex), part.Slice(providerDelimiterIndex + 1)); } } } @@ -1057,9 +1054,8 @@ namespace Emby.Server.Implementations.Data return; } - var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries); var list = new List(); - foreach (var part in parts) + foreach (var part in value.SpanSplit('|')) { var image = ItemImageInfoFromValueString(part); @@ -1094,41 +1090,89 @@ namespace Emby.Server.Implementations.Data .Append(hash.Replace('*', '/').Replace('|', '\\')); } - public ItemImageInfo ItemImageInfoFromValueString(string value) + private ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan value) { - var parts = value.Split('*', StringSplitOptions.None); - - if (parts.Length < 3) + var nextSegment = value.IndexOf('*'); + if (nextSegment == -1) { return null; } - var image = new ItemImageInfo(); + ReadOnlySpan path = value[..nextSegment]; + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf('*'); + if (nextSegment == -1) + { + return null; + } - image.Path = RestorePath(parts[0]); + ReadOnlySpan dateModified = value[..nextSegment]; + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf('*'); + if (nextSegment == -1) + { + return null; + } - if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)) + ReadOnlySpan imageType = value[..nextSegment]; + + var image = new ItemImageInfo + { + Path = RestorePath(path.ToString()) + }; + + if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)) { image.DateModified = new DateTime(ticks, DateTimeKind.Utc); } - if (Enum.TryParse(parts[2], true, out ImageType type)) + if (Enum.TryParse(imageType.ToString(), true, out ImageType type)) { image.Type = type; } - if (parts.Length >= 5) + // Optional parameters: width*height*blurhash + if (nextSegment + 1 < value.Length - 1) { - if (int.TryParse(parts[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) - && int.TryParse(parts[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf('*'); + ReadOnlySpan widthSpan = value[..nextSegment]; + + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf('*'); + if (nextSegment == -1) + { + return image; + } + + ReadOnlySpan heightSpan = value[..nextSegment]; + + if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) + && int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) { image.Width = width; image.Height = height; } - if (parts.Length >= 6) + nextSegment += 1; + if (nextSegment < value.Length - 1) { - image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|'); + value = value[nextSegment..]; + var length = value.Length; + + Span blurHashSpan = stackalloc char[length]; + for (int i = 0; i < length; i++) + { + var c = value[i]; + blurHashSpan[i] = c switch + { + '/' => '*', + '\\' => '|', + _ => c + }; + } + + image.BlurHash = new string(blurHashSpan); } } @@ -2118,27 +2162,6 @@ namespace Emby.Server.Implementations.Data private readonly ItemFields[] _allFields = Enum.GetValues(); - private string[] GetColumnNamesFromField(ItemFields field) - { - switch (field) - { - case ItemFields.Settings: - return new[] { "IsLocked", "PreferredMetadataCountryCode", "PreferredMetadataLanguage", "LockedFields" }; - case ItemFields.ServiceName: - return new[] { "ExternalServiceId" }; - case ItemFields.SortName: - return new[] { "ForcedSortName" }; - case ItemFields.Taglines: - return new[] { "Tagline" }; - case ItemFields.Tags: - return new[] { "Tags" }; - case ItemFields.IsHD: - return Array.Empty(); - default: - return new[] { field.ToString() }; - } - } - private bool HasField(InternalItemsQuery query, ItemFields name) { switch (name) @@ -2327,9 +2350,32 @@ namespace Emby.Server.Implementations.Data { if (!HasField(query, field)) { - foreach (var fieldToRemove in GetColumnNamesFromField(field)) + switch (field) { - list.Remove(fieldToRemove); + case ItemFields.Settings: + list.Remove("IsLocked"); + list.Remove("PreferredMetadataCountryCode"); + list.Remove("PreferredMetadataLanguage"); + list.Remove("LockedFields"); + break; + case ItemFields.ServiceName: + list.Remove("ExternalServiceId"); + break; + case ItemFields.SortName: + list.Remove("ForcedSortName"); + break; + case ItemFields.Taglines: + list.Remove("Tagline"); + break; + case ItemFields.Tags: + list.Remove("Tags"); + break; + case ItemFields.IsHD: + // do nothing + break; + default: + list.Remove(field.ToString()); + break; } } } @@ -2575,10 +2621,21 @@ namespace Emby.Server.Implementations.Data query.Limit = query.Limit.Value + 4; } - var commandText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) - + GetFromText() - + GetJoinUserDataText(query); + var commandText = "select "; + if (EnableGroupByPresentationUniqueKey(query)) + { + commandText += "count (distinct PresentationUniqueKey)"; + } + else if (query.GroupBySeriesPresentationUniqueKey) + { + commandText += "count (distinct SeriesPresentationUniqueKey)"; + } + else + { + commandText += "count (guid)"; + } + + commandText += GetFromText() + GetJoinUserDataText(query); var whereClauses = GetWhereClauses(query, null); if (whereClauses.Count != 0) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index d0b85f07d..85d6d3043 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -590,18 +590,9 @@ namespace Emby.Server.Implementations.Library public Task GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) { - var info = _openStreams.Values.FirstOrDefault(i => - { - var liveStream = i as ILiveStream; - if (liveStream != null) - { - return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase); - } + var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase)); - return false; - }); - - return Task.FromResult(info as IDirectStreamProvider); + return Task.FromResult(info.Value as IDirectStreamProvider); } public async Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index c9d9cc49a..665fbfa0f 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -801,22 +801,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public ActiveRecordingInfo GetActiveRecordingInfo(string path) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrWhiteSpace(path) || _activeRecordings.IsEmpty) { return null; } - foreach (var recording in _activeRecordings.Values) + foreach (var (_, recordingInfo) in _activeRecordings) { - if (string.Equals(recording.Path, path, StringComparison.Ordinal) && !recording.CancellationTokenSource.IsCancellationRequested) + if (string.Equals(recordingInfo.Path, path, StringComparison.Ordinal) && !recordingInfo.CancellationTokenSource.IsCancellationRequested) { - var timer = recording.Timer; + var timer = recordingInfo.Timer; if (timer.Status != RecordingStatus.InProgress) { return null; } - return recording; + return recordingInfo; } } @@ -1621,9 +1621,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } return _activeRecordings - .Values - .ToList() - .Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase)); + .Any(i => string.Equals(i.Value.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Value.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase)); } private IRecorder GetRecorder(MediaSourceInfo mediaSource) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 2fdc2b4d9..46858b4fb 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -315,10 +315,9 @@ namespace Emby.Server.Implementations.Localization } const string Prefix = "Core"; - var key = Prefix + culture; return _dictionaries.GetOrAdd( - key, + culture, f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); } diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 22739a008..0259dc436 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -257,20 +257,17 @@ namespace Emby.Server.Implementations.QuickConnect } // Expire stale connection requests - var code = string.Empty; - var values = _currentRequests.Values.ToList(); - - for (int i = 0; i < values.Count; i++) + foreach (var (_, currentRequest) in _currentRequests) { - var added = values[i].DateAdded ?? DateTime.UnixEpoch; - if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll) + var added = currentRequest.DateAdded ?? DateTime.UnixEpoch; + if (expireAll || DateTime.UtcNow > added.AddMinutes(Timeout)) { - code = values[i].Code; - _logger.LogDebug("Removing expired request {code}", code); + var code = currentRequest.Code; + _logger.LogDebug("Removing expired request {Code}", code); if (!_currentRequests.TryRemove(code, out _)) { - _logger.LogWarning("Request {code} already expired", code); + _logger.LogWarning("Request {Code} already expired", code); } } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 315277985..72c0a838e 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -269,14 +269,17 @@ namespace Emby.Server.Implementations.SyncPlay var user = _userManager.GetUserById(session.UserId); List list = new List(); - foreach (var group in _groups.Values) + lock (_groupsLock) { - // Locking required as group is not thread-safe. - lock (group) + foreach (var (_, group) in _groups) { - if (group.HasAccessToPlayQueue(user)) + // Locking required as group is not thread-safe. + lock (group) { - list.Add(group.GetInfo()); + if (group.HasAccessToPlayQueue(user)) + { + list.Add(group.GetInfo()); + } } } } diff --git a/MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs b/MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs new file mode 100644 index 000000000..5332aba9f --- /dev/null +++ b/MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs @@ -0,0 +1,102 @@ +/* +MIT License + +Copyright (c) 2019 Gérald Barré + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +#nullable enable +#pragma warning disable CS1591 +#pragma warning disable CA1034 +using System; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Extension class for splitting lines without unnecessary allocations. + /// + public static class SplitLinesStringExtensions + { + /// + /// Creates a new line split enumerator. + /// + /// The string to split. + /// The separator to split on. + /// The enumerator struct. + [Pure] + public static LineSplitEnumerator SpanSplit(this string str, char separator) => new (str.AsSpan(), separator); + + /// + /// Creates a new line split enumerator. + /// + /// The span to split. + /// The separator to split on. + /// The enumerator struct. + [Pure] + public static LineSplitEnumerator Split(this ReadOnlySpan str, char separator) => new (str, separator); + + [StructLayout(LayoutKind.Auto)] + public ref struct LineSplitEnumerator + { + private readonly char _separator; + private ReadOnlySpan _str; + + public LineSplitEnumerator(ReadOnlySpan str, char separator) + { + _str = str; + _separator = separator; + Current = default; + } + + public ReadOnlySpan Current { get; private set; } + + public readonly LineSplitEnumerator GetEnumerator() => this; + + public bool MoveNext() + { + if (_str.Length == 0) + { + return false; + } + + var span = _str; + var index = span.IndexOf(_separator); + if (index == -1) + { + _str = ReadOnlySpan.Empty; + Current = span; + return true; + } + + if (index < span.Length - 1 && span[index] == _separator) + { + Current = span.Slice(0, index); + _str = span[(index + 1)..]; + return true; + } + + Current = span.Slice(0, index); + _str = span[(index + 1)..]; + return true; + } + } + } +} diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index d45f8758c..d74e6f9d8 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1768,20 +1768,15 @@ namespace MediaBrowser.Controller.Entities { EnableImages = false } - }); + }).TotalRecordCount; - double unplayedCount = unplayedQueryResult.TotalRecordCount; + dto.UnplayedItemCount = unplayedQueryResult; - dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount; - - if (itemDto != null && itemDto.RecursiveItemCount.HasValue) + if (itemDto?.RecursiveItemCount > 0) { - if (itemDto.RecursiveItemCount.Value > 0) - { - var unplayedPercentage = (unplayedCount / itemDto.RecursiveItemCount.Value) * 100; - dto.PlayedPercentage = 100 - unplayedPercentage; - dto.Played = dto.PlayedPercentage.Value >= 100; - } + var unplayedPercentage = ((double)unplayedQueryResult / itemDto.RecursiveItemCount.Value) * 100; + dto.PlayedPercentage = 100 - unplayedPercentage; + dto.Played = dto.PlayedPercentage.Value >= 100; } else { diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 09d14dc6a..bc14da7f2 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -132,6 +132,36 @@ namespace MediaBrowser.Model.Entities } } + /// + /// Sets a provider id. + /// + /// The instance. + /// The name. + /// The value. + public static void SetProviderId(this IHasProviderIds instance, ReadOnlySpan name, ReadOnlySpan value) + { + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + // If it's null remove the key from the dictionary + if (value.IsEmpty) + { + instance.ProviderIds?.Remove(name.ToString()); + } + else + { + // Ensure it exists + if (instance.ProviderIds == null) + { + instance.ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + instance.ProviderIds[name.ToString()] = value.ToString(); + } + } + /// /// Sets a provider id. /// From a6726730fc181599db0ba68545adce9b88c11f7b Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 30 Apr 2021 15:11:16 +0200 Subject: [PATCH 103/612] revert the last bits of the getcount experiment --- .../Data/SqliteItemRepository.cs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index bd5a3e30b..7cee309d0 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2621,21 +2621,10 @@ namespace Emby.Server.Implementations.Data query.Limit = query.Limit.Value + 4; } - var commandText = "select "; - if (EnableGroupByPresentationUniqueKey(query)) - { - commandText += "count (distinct PresentationUniqueKey)"; - } - else if (query.GroupBySeriesPresentationUniqueKey) - { - commandText += "count (distinct SeriesPresentationUniqueKey)"; - } - else - { - commandText += "count (guid)"; - } - - commandText += GetFromText() + GetJoinUserDataText(query); + var commandText = "select " + + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) + + GetFromText() + + GetJoinUserDataText(query); var whereClauses = GetWhereClauses(query, null); if (whereClauses.Count != 0) From ba2e346d1246cc51e186a64d38a9b7385492ca4f Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 30 Apr 2021 15:27:07 +0200 Subject: [PATCH 104/612] prevent cancellationtoken leakage --- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 4 +++- Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs | 3 ++- Jellyfin.Api/Helpers/ProgressiveFileCopier.cs | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 1dcc78687..c32ca2fb6 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -661,7 +661,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _modelCache.Clear(); } - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token; + using var timedCancellationToken = new CancellationTokenSource(discoveryDurationMs); + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timedCancellationToken.Token, cancellationToken); + cancellationToken = linkedCancellationTokenSource.Token; var list = new List(); // Create udp broadcast discovery message diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 78e62ff0a..f8baf55da 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -150,7 +150,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token; + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token); + cancellationToken = linkedCancellationTokenSource.Token; // use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039 var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT; diff --git a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs index 8bddf00d5..963e17724 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs @@ -71,7 +71,8 @@ namespace Jellyfin.Api.Helpers /// A . public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) { - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token; + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken); + cancellationToken = linkedCancellationTokenSource.Token; try { From 716cbb06958da987ab917c1f6408396d4ace7a0c Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 30 Apr 2021 23:35:29 +0200 Subject: [PATCH 105/612] remove span based setproviderid --- .../Data/SqliteItemRepository.cs | 2 +- .../Entities/ProviderIdsExtensions.cs | 30 ------------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 7cee309d0..e78ebbc50 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1012,7 +1012,7 @@ namespace Emby.Server.Implementations.Data var providerDelimiterIndex = part.IndexOf('='); if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('=')) { - item.SetProviderId(part.Slice(0, providerDelimiterIndex), part.Slice(providerDelimiterIndex + 1)); + item.SetProviderId(part.Slice(0, providerDelimiterIndex).ToString(), part.Slice(providerDelimiterIndex + 1).ToString()); } } } diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index bc14da7f2..09d14dc6a 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -132,36 +132,6 @@ namespace MediaBrowser.Model.Entities } } - /// - /// Sets a provider id. - /// - /// The instance. - /// The name. - /// The value. - public static void SetProviderId(this IHasProviderIds instance, ReadOnlySpan name, ReadOnlySpan value) - { - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - // If it's null remove the key from the dictionary - if (value.IsEmpty) - { - instance.ProviderIds?.Remove(name.ToString()); - } - else - { - // Ensure it exists - if (instance.ProviderIds == null) - { - instance.ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - instance.ProviderIds[name.ToString()] = value.ToString(); - } - } - /// /// Sets a provider id. /// From 70771fdcd60ec5d8a9f13713662778c7e57d0633 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sat, 1 May 2021 13:06:10 +0200 Subject: [PATCH 106/612] Nullability handling for device profile classes --- Emby.Dlna/Didl/DidlBuilder.cs | 26 +++-- .../Controllers/UniversalAudioController.cs | 6 +- MediaBrowser.Model/Dlna/ContainerProfile.cs | 63 ++++------- MediaBrowser.Model/Dlna/DeviceProfile.cs | 104 ++++++++---------- MediaBrowser.Model/Dlna/DirectPlayProfile.cs | 11 +- MediaBrowser.Model/Dlna/TranscodingProfile.cs | 27 ++++- 6 files changed, 114 insertions(+), 123 deletions(-) diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 66ae07329..c66bdbf22 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -976,15 +976,28 @@ namespace Emby.Dlna.Didl return; } - var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg"); + // TODO: Remove these default values + var albumArtUrlInfo = GetImageUrl( + imageInfo, + _profile.MaxAlbumArtWidth ?? 10000, + _profile.MaxAlbumArtHeight ?? 10000, + "jpg"); writer.WriteStartElement("upnp", "albumArtURI", NsUpnp); - writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn); - writer.WriteString(albumartUrlInfo.url); + if (!string.IsNullOrEmpty(_profile.AlbumArtPn)) + { + writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn); + } + + writer.WriteString(albumArtUrlInfo.url); writer.WriteFullEndElement(); - // TOOD: Remove these default values - var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg"); + // TODO: Remove these default values + var iconUrlInfo = GetImageUrl( + imageInfo, + _profile.MaxIconWidth ?? 48, + _profile.MaxIconHeight ?? 48, + "jpg"); writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url); if (!_profile.EnableAlbumArtInDidl) @@ -1207,8 +1220,7 @@ namespace Emby.Dlna.Didl if (width.HasValue && height.HasValue) { - var newSize = DrawingUtils.Resize( - new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight); + var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight); width = newSize.Width; height = newSize.Height; diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index dcdd8b367..679f055bc 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -298,9 +298,9 @@ namespace Jellyfin.Api.Controllers { Type = DlnaProfileType.Audio, Context = EncodingContext.Streaming, - Container = transcodingContainer, - AudioCodec = audioCodec, - Protocol = transcodingProtocol, + Container = transcodingContainer ?? "mp3", + AudioCodec = audioCodec ?? "mp3", + Protocol = transcodingProtocol ?? "http", BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture) } diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index d83c8f2f3..54d4f7f38 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -1,7 +1,7 @@ -#nullable disable #pragma warning disable CS1591 using System; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Xml.Serialization; @@ -9,25 +9,17 @@ namespace MediaBrowser.Model.Dlna { public class ContainerProfile { - public ContainerProfile() - { - Conditions = Array.Empty(); - } - + [Required] [XmlAttribute("type")] public DlnaProfileType Type { get; set; } - public ProfileCondition[] Conditions { get; set; } + public ProfileCondition[]? Conditions { get; set; } = Array.Empty(); + [Required] [XmlAttribute("container")] - public string Container { get; set; } + public string Container { get; set; } = string.Empty; - public string[] GetContainers() - { - return SplitValue(Container); - } - - public static string[] SplitValue(string value) + public static string[] SplitValue(string? value) { if (string.IsNullOrEmpty(value)) { @@ -37,14 +29,14 @@ namespace MediaBrowser.Model.Dlna return value.Split(',', StringSplitOptions.RemoveEmptyEntries); } - public bool ContainsContainer(string container) + public bool ContainsContainer(string? container) { - var containers = GetContainers(); + var containers = SplitValue(Container); return ContainsContainer(containers, container); } - public static bool ContainsContainer(string profileContainers, string inputContainer) + public static bool ContainsContainer(string? profileContainers, string? inputContainer) { var isNegativeList = false; if (profileContainers != null && profileContainers.StartsWith('-')) @@ -56,46 +48,29 @@ namespace MediaBrowser.Model.Dlna return ContainsContainer(SplitValue(profileContainers), isNegativeList, inputContainer); } - public static bool ContainsContainer(string[] profileContainers, string inputContainer) + public static bool ContainsContainer(string[]? profileContainers, string? inputContainer) { return ContainsContainer(profileContainers, false, inputContainer); } - public static bool ContainsContainer(string[] profileContainers, bool isNegativeList, string inputContainer) + public static bool ContainsContainer(string[]? profileContainers, bool isNegativeList, string? inputContainer) { - if (profileContainers.Length == 0) + if (profileContainers == null || profileContainers.Length == 0) { - return true; + return isNegativeList; } - if (isNegativeList) - { - var allInputContainers = SplitValue(inputContainer); + var allInputContainers = SplitValue(inputContainer); - foreach (var container in allInputContainers) + foreach (var container in allInputContainers) + { + if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) { - if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) - { - return false; - } + return !isNegativeList; } - - return true; } - else - { - var allInputContainers = SplitValue(inputContainer); - foreach (var container in allInputContainers) - { - if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } + return isNegativeList; } } } diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index ff5186658..6ec4cb547 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,6 +1,6 @@ -#nullable disable #pragma warning disable CA1819 // Properties should not return arrays using System; +using System.ComponentModel; using System.Linq; using System.Xml.Serialization; using MediaBrowser.Model.MediaInfo; @@ -13,121 +13,104 @@ namespace MediaBrowser.Model.Dlna [XmlRoot("Profile")] public class DeviceProfile { - /// - /// Initializes a new instance of the class. - /// - public DeviceProfile() - { - DirectPlayProfiles = Array.Empty(); - TranscodingProfiles = Array.Empty(); - ResponseProfiles = Array.Empty(); - CodecProfiles = Array.Empty(); - ContainerProfiles = Array.Empty(); - SubtitleProfiles = Array.Empty(); - - XmlRootAttributes = Array.Empty(); - - SupportedMediaTypes = "Audio,Photo,Video"; - MaxStreamingBitrate = 8000000; - MaxStaticBitrate = 8000000; - MusicStreamingTranscodingBitrate = 128000; - } - /// /// Gets or sets the Name. /// - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the Id. /// [XmlIgnore] - public string Id { get; set; } + public string? Id { get; set; } /// /// Gets or sets the Identification. /// - public DeviceIdentification Identification { get; set; } + public DeviceIdentification? Identification { get; set; } /// /// Gets or sets the FriendlyName. /// - public string FriendlyName { get; set; } + public string? FriendlyName { get; set; } /// /// Gets or sets the Manufacturer. /// - public string Manufacturer { get; set; } + public string? Manufacturer { get; set; } /// /// Gets or sets the ManufacturerUrl. /// - public string ManufacturerUrl { get; set; } + public string? ManufacturerUrl { get; set; } /// /// Gets or sets the ModelName. /// - public string ModelName { get; set; } + public string? ModelName { get; set; } /// /// Gets or sets the ModelDescription. /// - public string ModelDescription { get; set; } + public string? ModelDescription { get; set; } /// /// Gets or sets the ModelNumber. /// - public string ModelNumber { get; set; } + public string? ModelNumber { get; set; } /// /// Gets or sets the ModelUrl. /// - public string ModelUrl { get; set; } + public string? ModelUrl { get; set; } /// /// Gets or sets the SerialNumber. /// - public string SerialNumber { get; set; } + public string? SerialNumber { get; set; } /// /// Gets or sets a value indicating whether EnableAlbumArtInDidl. /// + [DefaultValue(false)] public bool EnableAlbumArtInDidl { get; set; } /// /// Gets or sets a value indicating whether EnableSingleAlbumArtLimit. /// + [DefaultValue(false)] public bool EnableSingleAlbumArtLimit { get; set; } /// /// Gets or sets a value indicating whether EnableSingleSubtitleLimit. /// + [DefaultValue(false)] public bool EnableSingleSubtitleLimit { get; set; } /// /// Gets or sets the SupportedMediaTypes. /// - public string SupportedMediaTypes { get; set; } + public string SupportedMediaTypes { get; set; } = "Audio,Photo,Video"; /// /// Gets or sets the UserId. /// - public string UserId { get; set; } + public string? UserId { get; set; } /// /// Gets or sets the AlbumArtPn. /// - public string AlbumArtPn { get; set; } + public string? AlbumArtPn { get; set; } /// /// Gets or sets the MaxAlbumArtWidth. /// - public int MaxAlbumArtWidth { get; set; } + public int? MaxAlbumArtWidth { get; set; } /// /// Gets or sets the MaxAlbumArtHeight. /// - public int MaxAlbumArtHeight { get; set; } + public int? MaxAlbumArtHeight { get; set; } /// /// Gets or sets the MaxIconWidth. @@ -142,92 +125,97 @@ namespace MediaBrowser.Model.Dlna /// /// Gets or sets the MaxStreamingBitrate. /// - public int? MaxStreamingBitrate { get; set; } + public int? MaxStreamingBitrate { get; set; } = 8000000; /// /// Gets or sets the MaxStaticBitrate. /// - public int? MaxStaticBitrate { get; set; } + public int? MaxStaticBitrate { get; set; } = 8000000; /// /// Gets or sets the MusicStreamingTranscodingBitrate. /// - public int? MusicStreamingTranscodingBitrate { get; set; } + public int? MusicStreamingTranscodingBitrate { get; set; } = 128000; /// /// Gets or sets the MaxStaticMusicBitrate. /// - public int? MaxStaticMusicBitrate { get; set; } + public int? MaxStaticMusicBitrate { get; set; } = 8000000; /// /// Gets or sets the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace. /// - public string SonyAggregationFlags { get; set; } + public string? SonyAggregationFlags { get; set; } /// /// Gets or sets the ProtocolInfo. /// - public string ProtocolInfo { get; set; } + public string? ProtocolInfo { get; set; } /// /// Gets or sets the TimelineOffsetSeconds. /// + [DefaultValue(0)] public int TimelineOffsetSeconds { get; set; } /// /// Gets or sets a value indicating whether RequiresPlainVideoItems. /// + [DefaultValue(false)] public bool RequiresPlainVideoItems { get; set; } /// /// Gets or sets a value indicating whether RequiresPlainFolders. /// + [DefaultValue(false)] public bool RequiresPlainFolders { get; set; } /// /// Gets or sets a value indicating whether EnableMSMediaReceiverRegistrar. /// + [DefaultValue(false)] public bool EnableMSMediaReceiverRegistrar { get; set; } /// /// Gets or sets a value indicating whether IgnoreTranscodeByteRangeRequests. /// + [DefaultValue(false)] public bool IgnoreTranscodeByteRangeRequests { get; set; } /// /// Gets or sets the XmlRootAttributes. /// - public XmlAttribute[] XmlRootAttributes { get; set; } + public XmlAttribute[] XmlRootAttributes { get; set; } = Array.Empty(); /// /// Gets or sets the direct play profiles. /// - public DirectPlayProfile[] DirectPlayProfiles { get; set; } + public DirectPlayProfile[] DirectPlayProfiles { get; set; } = Array.Empty(); /// /// Gets or sets the transcoding profiles. /// - public TranscodingProfile[] TranscodingProfiles { get; set; } + public TranscodingProfile[] TranscodingProfiles { get; set; } = Array.Empty(); /// /// Gets or sets the ContainerProfiles. /// - public ContainerProfile[] ContainerProfiles { get; set; } + public ContainerProfile[] ContainerProfiles { get; set; } = Array.Empty(); /// /// Gets or sets the CodecProfiles. /// - public CodecProfile[] CodecProfiles { get; set; } + public CodecProfile[] CodecProfiles { get; set; } = Array.Empty(); /// /// Gets or sets the ResponseProfiles. /// - public ResponseProfile[] ResponseProfiles { get; set; } + public ResponseProfile[] ResponseProfiles { get; set; } = Array.Empty(); /// /// Gets or sets the SubtitleProfiles. /// - public SubtitleProfile[] SubtitleProfiles { get; set; } + public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty(); /// /// The GetSupportedMediaTypes. @@ -244,13 +232,13 @@ namespace MediaBrowser.Model.Dlna /// The container. /// The audio Codec. /// A . - public TranscodingProfile GetAudioTranscodingProfile(string container, string audioCodec) + public TranscodingProfile? GetAudioTranscodingProfile(string? container, string? audioCodec) { container = (container ?? string.Empty).TrimStart('.'); foreach (var i in TranscodingProfiles) { - if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Audio) + if (i.Type != DlnaProfileType.Audio) { continue; } @@ -278,13 +266,13 @@ namespace MediaBrowser.Model.Dlna /// The audio Codec. /// The video Codec. /// The . - public TranscodingProfile GetVideoTranscodingProfile(string container, string audioCodec, string videoCodec) + public TranscodingProfile? GetVideoTranscodingProfile(string? container, string? audioCodec, string? videoCodec) { container = (container ?? string.Empty).TrimStart('.'); foreach (var i in TranscodingProfiles) { - if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Video) + if (i.Type != DlnaProfileType.Video) { continue; } @@ -299,7 +287,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!string.Equals(videoCodec, i.VideoCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(videoCodec, i.VideoCodec, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -320,7 +308,7 @@ namespace MediaBrowser.Model.Dlna /// The audio sample rate. /// The audio bit depth. /// The . - public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth) + public ResponseProfile? GetAudioMediaProfile(string container, string? audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth) { foreach (var i in ResponseProfiles) { @@ -384,7 +372,7 @@ namespace MediaBrowser.Model.Dlna /// The width. /// The height. /// The . - public ResponseProfile GetImageMediaProfile(string container, int? width, int? height) + public ResponseProfile? GetImageMediaProfile(string container, int? width, int? height) { foreach (var i in ResponseProfiles) { @@ -442,7 +430,7 @@ namespace MediaBrowser.Model.Dlna /// The video Codec tag. /// True if Avc. /// The . - public ResponseProfile GetVideoMediaProfile( + public ResponseProfile? GetVideoMediaProfile( string container, string audioCodec, string videoCodec, diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index 88cb83991..417d915b5 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -1,6 +1,6 @@ -#nullable disable #pragma warning disable CS1591 +using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna @@ -8,14 +8,15 @@ namespace MediaBrowser.Model.Dlna public class DirectPlayProfile { [XmlAttribute("container")] - public string Container { get; set; } + public string? Container { get; set; } [XmlAttribute("audioCodec")] - public string AudioCodec { get; set; } + public string? AudioCodec { get; set; } [XmlAttribute("videoCodec")] - public string VideoCodec { get; set; } + public string? VideoCodec { get; set; } + [Required] [XmlAttribute("type")] public DlnaProfileType Type { get; set; } @@ -31,7 +32,7 @@ namespace MediaBrowser.Model.Dlna public bool SupportsAudioCodec(string codec) { - return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec); + return (Type is DlnaProfileType.Audio or DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec); } } } diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index f05e31047..ea446b733 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -1,54 +1,69 @@ -#nullable disable #pragma warning disable CS1591 +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna { public class TranscodingProfile { + [Required] [XmlAttribute("container")] - public string Container { get; set; } + public string Container { get; set; } = string.Empty; + [Required] [XmlAttribute("type")] public DlnaProfileType Type { get; set; } + [Required] [XmlAttribute("videoCodec")] - public string VideoCodec { get; set; } + public string VideoCodec { get; set; } = string.Empty; + [Required] [XmlAttribute("audioCodec")] - public string AudioCodec { get; set; } + public string AudioCodec { get; set; } = string.Empty; + [Required] [XmlAttribute("protocol")] - public string Protocol { get; set; } + public string Protocol { get; set; } = string.Empty; + [DefaultValue(false)] [XmlAttribute("estimateContentLength")] public bool EstimateContentLength { get; set; } + [DefaultValue(false)] [XmlAttribute("enableMpegtsM2TsMode")] public bool EnableMpegtsM2TsMode { get; set; } + [DefaultValue(TranscodeSeekInfo.Auto)] [XmlAttribute("transcodeSeekInfo")] public TranscodeSeekInfo TranscodeSeekInfo { get; set; } + [DefaultValue(false)] [XmlAttribute("copyTimestamps")] public bool CopyTimestamps { get; set; } + [DefaultValue(EncodingContext.Streaming)] [XmlAttribute("context")] public EncodingContext Context { get; set; } + [DefaultValue(false)] [XmlAttribute("enableSubtitlesInManifest")] public bool EnableSubtitlesInManifest { get; set; } [XmlAttribute("maxAudioChannels")] - public string MaxAudioChannels { get; set; } + public string? MaxAudioChannels { get; set; } + [DefaultValue(0)] [XmlAttribute("minSegments")] public int MinSegments { get; set; } + [DefaultValue(0)] [XmlAttribute("segmentLength")] public int SegmentLength { get; set; } + [DefaultValue(false)] [XmlAttribute("breakOnNonKeyFrames")] public bool BreakOnNonKeyFrames { get; set; } From c608d5104df7b7281e35595552734d77e42b5036 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 1 May 2021 15:56:16 +0200 Subject: [PATCH 107/612] Fix scanning --- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 2 -- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- Emby.Server.Implementations/Library/ResolverHelper.cs | 3 +-- MediaBrowser.Controller/Library/ItemResolveArgs.cs | 2 +- MediaBrowser.Model/IO/FileSystemMetadata.cs | 6 ------ 5 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index df973f971..27096ed33 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -260,8 +260,6 @@ namespace Emby.Server.Implementations.IO result.Exists = false; } } - - result.DirectoryName = fileInfo.DirectoryName; } result.CreationTimeUtc = GetCreationTimeUtc(info); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index ec59ca9b9..7629676f5 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -683,7 +683,7 @@ namespace Emby.Server.Implementations.Library foreach (var item in items) { - ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService); + ResolverHelper.SetInitialItemValues(item, parent, this, directoryService); } items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 8be80d726..b1a2e9284 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -20,11 +20,10 @@ namespace Emby.Server.Implementations.Library /// /// The item. /// The parent. - /// The file system. /// The library manager. /// The directory service. /// Item must have a path. - public static void SetInitialItemValues(BaseItem item, Folder? parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService) + public static void SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService) { // This version of the below method has no ItemResolveArgs, so we have to require the path already being set if (string.IsNullOrEmpty(item.Path)) diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index df8842237..f86f7df25 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -87,7 +87,7 @@ namespace MediaBrowser.Controller.Library return false; } - var parentDir = FileInfo.DirectoryName ?? string.Empty; + var parentDir = System.IO.Path.GetDirectoryName(Path) ?? string.Empty; return parentDir.Length > _appPaths.RootFolderPath.Length && parentDir.StartsWith(_appPaths.RootFolderPath, StringComparison.OrdinalIgnoreCase); diff --git a/MediaBrowser.Model/IO/FileSystemMetadata.cs b/MediaBrowser.Model/IO/FileSystemMetadata.cs index 118c78e80..fb74886bf 100644 --- a/MediaBrowser.Model/IO/FileSystemMetadata.cs +++ b/MediaBrowser.Model/IO/FileSystemMetadata.cs @@ -37,12 +37,6 @@ namespace MediaBrowser.Model.IO /// The length. public long Length { get; set; } - /// - /// Gets or sets the name of the directory. - /// - /// The name of the directory. - public string DirectoryName { get; set; } - /// /// Gets or sets the last write time UTC. /// From e128b6d9978dc219060fe27bc4de0e73495822c2 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 1 May 2021 15:59:21 +0200 Subject: [PATCH 108/612] TmdbUtils: Use ordinal string compare --- MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 15a44c7ed..2498ce9c4 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -63,19 +63,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The Jellyfin person type. public static string MapCrewToPersonType(Crew crew) { - if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) - && crew.Job.Contains("director", StringComparison.InvariantCultureIgnoreCase)) + if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) + && crew.Job.Contains("director", StringComparison.OrdinalIgnoreCase)) { return PersonType.Director; } - if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) - && crew.Job.Contains("producer", StringComparison.InvariantCultureIgnoreCase)) + if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) + && crew.Job.Contains("producer", StringComparison.OrdinalIgnoreCase)) { return PersonType.Producer; } - if (crew.Department.Equals("writing", StringComparison.InvariantCultureIgnoreCase)) + if (crew.Department.Equals("writing", StringComparison.OrdinalIgnoreCase)) { return PersonType.Writer; } From 8a6b9e1fb6d89ac77bd8d62da66bd05abd75ca65 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 2 May 2021 00:26:30 +0200 Subject: [PATCH 109/612] Add tests for SqliteItemRepository.(De)SerializeImages --- .../Data/SqliteItemRepository.cs | 41 ++--- .../Data/SqliteItemRepositoryTests.cs | 172 ++++++++++++++++++ 2 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 28e59913c..9b558d34b 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -502,7 +502,7 @@ namespace Emby.Server.Implementations.Data using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) { saveImagesStatement.TryBind("@Id", item.Id.ToByteArray()); - saveImagesStatement.TryBind("@Images", SerializeImages(item)); + saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos)); saveImagesStatement.MoveNext(); } @@ -898,7 +898,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@Tagline", item.Tagline); saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item)); - saveItemStatement.TryBind("@Images", SerializeImages(item)); + saveItemStatement.TryBind("@Images", SerializeImages(item.ImageInfos)); if (item.ProductionLocations.Length > 0) { @@ -1020,10 +1020,8 @@ namespace Emby.Server.Implementations.Data } } - private string SerializeImages(BaseItem item) + internal string SerializeImages(ItemImageInfo[] images) { - var images = item.ImageInfos; - if (images.Length == 0) { return null; @@ -1045,16 +1043,11 @@ namespace Emby.Server.Implementations.Data return str.ToString(); } - private void DeserializeImages(string value, BaseItem item) + internal ItemImageInfo[] DeserializeImages(string value) { if (string.IsNullOrWhiteSpace(value)) { - return; - } - - if (item.ImageInfos.Length > 0) - { - return; + return Array.Empty(); } var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries); @@ -1069,15 +1062,14 @@ namespace Emby.Server.Implementations.Data } } - item.ImageInfos = list.ToArray(); + return list.ToArray(); } - public void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image) + private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image) { const char Delimiter = '*'; var path = image.Path ?? string.Empty; - var hash = image.BlurHash ?? string.Empty; bldr.Append(GetPathToSave(path)) .Append(Delimiter) @@ -1087,11 +1079,16 @@ namespace Emby.Server.Implementations.Data .Append(Delimiter) .Append(image.Width) .Append(Delimiter) - .Append(image.Height) - .Append(Delimiter) - // Replace delimiters with other characters. - // This can be removed when we migrate to a proper DB. - .Append(hash.Replace('*', '/').Replace('|', '\\')); + .Append(image.Height); + + var hash = image.BlurHash; + if (!string.IsNullOrEmpty(hash)) + { + bldr.Append(Delimiter) + // Replace delimiters with other characters. + // This can be removed when we migrate to a proper DB. + .Append(hash.Replace('*', '/').Replace('|', '\\')); + } } public ItemImageInfo ItemImageInfoFromValueString(string value) @@ -1797,11 +1794,11 @@ namespace Emby.Server.Implementations.Data index++; - if (query.DtoOptions.EnableImages) + if (query.DtoOptions.EnableImages && item.ImageInfos.Length == 0) { if (!reader.IsDBNull(index)) { - DeserializeImages(reader.GetString(index), item); + item.ImageInfos = DeserializeImages(reader.GetString(index)); } index++; diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs new file mode 100644 index 000000000..e9cfd5b12 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using AutoFixture; +using AutoFixture.AutoMoq; +using Emby.Server.Implementations.Data; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Data +{ + public class SqliteItemRepositoryTests + { + public const string VirtualMetaDataPath = "%MetadataPath%"; + public const string MetaDataPath = "/meta/data/path"; + + private readonly IFixture _fixture; + private readonly SqliteItemRepository _sqliteItemRepository; + + public SqliteItemRepositoryTests() + { + var appHost = new Mock(); + appHost.Setup(x => x.ExpandVirtualPath(It.IsAny())) + .Returns((string x) => x.Replace(VirtualMetaDataPath, MetaDataPath, StringComparison.Ordinal)); + appHost.Setup(x => x.ReverseVirtualPath(It.IsAny())) + .Returns((string x) => x.Replace(MetaDataPath, VirtualMetaDataPath, StringComparison.Ordinal)); + + _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true }); + _fixture.Inject(appHost); + _sqliteItemRepository = _fixture.Create(); + } + + public static IEnumerable ItemImageInfoFromValueString_Valid_TestData() + { + yield return new object[] + { + "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN", + new ItemImageInfo() + { + Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg", + Type = ImageType.Primary, + DateModified = new DateTime(637452096478512963, DateTimeKind.Utc), + Width = 1920, + Height = 1080, + BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN" + } + }; + + yield return new object[] + { + "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*0*0", + new ItemImageInfo() + { + Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg", + Type = ImageType.Primary, + } + }; + + yield return new object[] + { + "%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336", + new ItemImageInfo() + { + Path = "/meta/data/path/library/68/68578562b96c80a7ebd530848801f645/poster.jpg", + Type = ImageType.Primary, + DateModified = new DateTime(637264380567586027, DateTimeKind.Utc), + Width = 600, + Height = 336 + } + }; + } + + [Theory] + [MemberData(nameof(ItemImageInfoFromValueString_Valid_TestData))] + public void ItemImageInfoFromValueString_Valid_Success(string value, ItemImageInfo expected) + { + var result = _sqliteItemRepository.ItemImageInfoFromValueString(value); + Assert.Equal(expected.Path, result.Path); + Assert.Equal(expected.Type, result.Type); + Assert.Equal(expected.DateModified, result.DateModified); + Assert.Equal(expected.Width, result.Width); + Assert.Equal(expected.Height, result.Height); + Assert.Equal(expected.BlurHash, result.BlurHash); + } + + [Theory] + [InlineData("")] + [InlineData("*")] + public void ItemImageInfoFromValueString_Invalid_Null(string value) + { + Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value)); + } + + public static IEnumerable DeserializeImages_Valid_TestData() + { + yield return new object[] + { + "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN", + new ItemImageInfo[] + { + new ItemImageInfo() + { + Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg", + Type = ImageType.Primary, + DateModified = new DateTime(637452096478512963, DateTimeKind.Utc), + Width = 1920, + Height = 1080, + BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN" + } + } + }; + + yield return new object[] + { + "%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg*637261226720645297*Primary*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png*637261226720805297*Logo*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg*637261226721285297*Thumb*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg*637261226721685297*Backdrop*0*0", + new ItemImageInfo[] + { + new ItemImageInfo() + { + Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg", + Type = ImageType.Primary, + DateModified = new DateTime(637261226720645297, DateTimeKind.Utc), + }, + new ItemImageInfo() + { + Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png", + Type = ImageType.Logo, + DateModified = new DateTime(637261226720805297, DateTimeKind.Utc), + }, + new ItemImageInfo() + { + Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg", + Type = ImageType.Thumb, + DateModified = new DateTime(637261226721285297, DateTimeKind.Utc), + }, + new ItemImageInfo() + { + Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg", + Type = ImageType.Backdrop, + DateModified = new DateTime(637261226721685297, DateTimeKind.Utc), + } + } + }; + } + + [Theory] + [MemberData(nameof(DeserializeImages_Valid_TestData))] + public void DeserializeImages_Valid_Success(string value, ItemImageInfo[] expected) + { + var result = _sqliteItemRepository.DeserializeImages(value); + Assert.Equal(expected.Length, result.Length); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i].Path, result[i].Path); + Assert.Equal(expected[i].Type, result[i].Type); + Assert.Equal(expected[i].DateModified, result[i].DateModified); + Assert.Equal(expected[i].Width, result[i].Width); + Assert.Equal(expected[i].Height, result[i].Height); + Assert.Equal(expected[i].BlurHash, result[i].BlurHash); + } + } + + [Theory] + [MemberData(nameof(DeserializeImages_Valid_TestData))] + public void SerializeImages_Valid_Success(string expected, ItemImageInfo[] value) + { + Assert.Equal(expected, _sqliteItemRepository.SerializeImages(value)); + } + } +} From dc81d576ab78cc6e666f768343cb6d29e5820670 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 2 May 2021 01:20:58 -0400 Subject: [PATCH 110/612] Remove /Images/Remote API endpoint --- .../Controllers/RemoteImageController.cs | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index e226adc64..ec836f43e 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -145,58 +145,6 @@ namespace Jellyfin.Api.Controllers return Ok(_providerManager.GetRemoteImageProviderInfo(item)); } - /// - /// Gets a remote image. - /// - /// The image url. - /// Remote image returned. - /// Remote image not found. - /// Image Stream. - [HttpGet("Images/Remote")] - [Produces(MediaTypeNames.Application.Octet)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesImageFile] - public async Task GetRemoteImage([FromQuery, Required] Uri imageUrl) - { - var urlHash = imageUrl.ToString().GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); - - string? contentPath = null; - var hasFile = false; - - try - { - contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - if (System.IO.File.Exists(contentPath)) - { - hasFile = true; - } - } - catch (FileNotFoundException) - { - // The file isn't cached yet - } - catch (IOException) - { - // The file isn't cached yet - } - - if (!hasFile) - { - await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - } - - if (string.IsNullOrEmpty(contentPath)) - { - return NotFound(); - } - - var contentType = MimeTypes.GetMimeType(contentPath); - return PhysicalFile(contentPath, contentType); - } - /// /// Downloads a remote image for an item. /// From 3e4c86098613d92eb2fc34ee158459c0d063f910 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 2 May 2021 01:22:52 -0400 Subject: [PATCH 111/612] Remove /Items/RemoteSearch/Image API endpoint --- .../Controllers/ItemLookupController.cs | 91 ------------------- 1 file changed, 91 deletions(-) diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index dabd4deb7..9fa307858 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -237,48 +237,6 @@ namespace Jellyfin.Api.Controllers return Ok(results); } - /// - /// Gets a remote image. - /// - /// The image url. - /// The provider name. - /// Remote image retrieved. - /// - /// A that represents the asynchronous operation to get the remote search results. - /// The task result contains an containing the images file stream. - /// - [HttpGet("Items/RemoteSearch/Image")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesImageFile] - public async Task GetRemoteSearchImage( - [FromQuery, Required] string imageUrl, - [FromQuery, Required] string providerName) - { - var urlHash = imageUrl.GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); - - try - { - var contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - if (System.IO.File.Exists(contentPath)) - { - return PhysicalFile(contentPath, MimeTypes.GetMimeType(contentPath)); - } - } - catch (FileNotFoundException) - { - // Means the file isn't cached yet - } - catch (IOException) - { - // Means the file isn't cached yet - } - - await DownloadImage(providerName, imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - var updatedContentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - return PhysicalFile(updatedContentPath, MimeTypes.GetMimeType(updatedContentPath)); - } - /// /// Applies search criteria to an item and refreshes metadata. /// @@ -320,54 +278,5 @@ namespace Jellyfin.Api.Controllers return NoContent(); } - - /// - /// Downloads the image. - /// - /// Name of the provider. - /// The URL. - /// The URL hash. - /// The pointer cache path. - /// Task. - private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath) - { - using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false); - if (result.Content.Headers.ContentType?.MediaType == null) - { - throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType)); - } - - var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1]; - var fullCachePath = GetFullCachePath(urlHash + "." + ext); - - var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); - Directory.CreateDirectory(directory); - using (var stream = result.Content) - { - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var fileStream = new FileStream( - fullCachePath, - FileMode.Create, - FileAccess.Write, - FileShare.None, - IODefaults.FileStreamBufferSize, - true); - - await stream.CopyToAsync(fileStream).ConfigureAwait(false); - } - - var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath)); - - Directory.CreateDirectory(pointerCacheDirectory); - await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false); - } - - /// - /// Gets the full cache path. - /// - /// The filename. - /// System.String. - private string GetFullCachePath(string filename) - => Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); } } From 874f92e93a1411fbba7097f0665db68234441dfa Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 2 May 2021 12:45:02 +0200 Subject: [PATCH 112/612] Add tests for SqliteItemRepository.(De)SerializeProviderIds --- .../Data/SqliteItemRepository.cs | 19 +++---- .../Data/SqliteItemRepositoryTests.cs | 57 +++++++++++++++++++ 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 9b558d34b..786e3b6fa 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -897,7 +897,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId); saveItemStatement.TryBind("@Tagline", item.Tagline); - saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item)); + saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item.ProviderIds)); saveItemStatement.TryBind("@Images", SerializeImages(item.ImageInfos)); if (item.ProductionLocations.Length > 0) @@ -968,10 +968,10 @@ namespace Emby.Server.Implementations.Data saveItemStatement.MoveNext(); } - private static string SerializeProviderIds(BaseItem item) + internal static string SerializeProviderIds(Dictionary providerIds) { StringBuilder str = new StringBuilder(); - foreach (var i in item.ProviderIds) + foreach (var i in providerIds) { // Ideally we shouldn't need this IsNullOrWhiteSpace check, // but we're seeing some cases of bad data slip through @@ -995,18 +995,13 @@ namespace Emby.Server.Implementations.Data return str.ToString(); } - private static void DeserializeProviderIds(string value, BaseItem item) + internal static void DeserializeProviderIds(string value, IHasProviderIds item) { if (string.IsNullOrWhiteSpace(value)) { return; } - if (item.ProviderIds.Count > 0) - { - return; - } - var parts = value.Split('|', StringSplitOptions.RemoveEmptyEntries); foreach (var part in parts) @@ -1787,16 +1782,16 @@ namespace Emby.Server.Implementations.Data index++; } - if (!reader.IsDBNull(index)) + if (item.ProviderIds.Count == 0 && !reader.IsDBNull(index)) { DeserializeProviderIds(reader.GetString(index), item); } index++; - if (query.DtoOptions.EnableImages && item.ImageInfos.Length == 0) + if (query.DtoOptions.EnableImages) { - if (!reader.IsDBNull(index)) + if (item.ImageInfos.Length == 0 && !reader.IsDBNull(index)) { item.ImageInfos = DeserializeImages(reader.GetString(index)); } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs index e9cfd5b12..af6ec3245 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs @@ -168,5 +168,62 @@ namespace Jellyfin.Server.Implementations.Tests.Data { Assert.Equal(expected, _sqliteItemRepository.SerializeImages(value)); } + + public static IEnumerable DeserializeProviderIds_Valid_TestData() + { + yield return new object[] + { + "Imdb=tt0119567", + new Dictionary() + { + { "Imdb", "tt0119567" }, + } + }; + + yield return new object[] + { + "Imdb=tt0119567|Tmdb=330|TmdbCollection=328", + new Dictionary() + { + { "Imdb", "tt0119567" }, + { "Tmdb", "330" }, + { "TmdbCollection", "328" }, + } + }; + + yield return new object[] + { + "MusicBrainzAlbum=9d363e43-f24f-4b39-bc5a-7ef305c677c7|MusicBrainzReleaseGroup=63eba062-847c-3b73-8b0f-6baf27bba6fa|AudioDbArtist=111352|AudioDbAlbum=2116560|MusicBrainzAlbumArtist=20244d07-534f-4eff-b4d4-930878889970", + new Dictionary() + { + { "MusicBrainzAlbum", "9d363e43-f24f-4b39-bc5a-7ef305c677c7" }, + { "MusicBrainzReleaseGroup", "63eba062-847c-3b73-8b0f-6baf27bba6fa" }, + { "AudioDbArtist", "111352" }, + { "AudioDbAlbum", "2116560" }, + { "MusicBrainzAlbumArtist", "20244d07-534f-4eff-b4d4-930878889970" }, + } + }; + } + + [Theory] + [MemberData(nameof(DeserializeProviderIds_Valid_TestData))] + public void DeserializeProviderIds_Valid_Success(string value, Dictionary expected) + { + var result = new ProviderIdsExtensionsTestsObject(); + SqliteItemRepository.DeserializeProviderIds(value, result); + Assert.Equal(expected, result.ProviderIds); + } + + [Theory] + [MemberData(nameof(DeserializeProviderIds_Valid_TestData))] + public void SerializeProviderIds_Valid_Success(string expected, Dictionary values) + { + Assert.Equal(expected, SqliteItemRepository.SerializeProviderIds(values)); + } + + private class ProviderIdsExtensionsTestsObject : IHasProviderIds + { + public Dictionary ProviderIds { get; set; } = new Dictionary(); + } } } From bdd7a37794518b0487b191e7dbbbd3beb2397458 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 2 May 2021 13:04:35 +0200 Subject: [PATCH 113/612] Don't run integration tests in parallel * Easier to debug failing tests when the logs aren't scrambled * Static properties could cause issues --- .../Jellyfin.Server.Integration.Tests.csproj | 7 +++++++ tests/Jellyfin.Server.Integration.Tests/xunit.runner.json | 4 ++++ 2 files changed, 11 insertions(+) create mode 100644 tests/Jellyfin.Server.Integration.Tests/xunit.runner.json diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index c1d871126..938385a2a 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -22,6 +22,13 @@ + + + + PreserveNewest + + + diff --git a/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json b/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json new file mode 100644 index 000000000..809e880c7 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "parallelizeAssembly": false, + "parallelizeTestCollections": false +} From bcba501dfbe1e0f422829f26159d9a9625530231 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 2 May 2021 19:25:04 +0100 Subject: [PATCH 114/612] minor optimization. --- Emby.Server.Implementations/Plugins/PluginManager.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 3a8296455..fd2ee6b7a 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -166,9 +166,7 @@ namespace Emby.Server.Implementations.Plugins /// public void CreatePlugins() { - _ = _appHost.GetExports(CreatePluginInstance) - .Where(i => i != null) - .ToArray(); + _ = _appHost.GetExports(CreatePluginInstance); } /// From 6f722bac0f5b3fb019a8313753b198f34afa714c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 May 2021 12:00:39 +0000 Subject: [PATCH 115/612] Bump Swashbuckle.AspNetCore from 6.1.3 to 6.1.4 Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.1.3 to 6.1.4. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.1.3...v6.1.4) Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 3868882e5..d6d07cd27 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -17,7 +17,7 @@ - + From 9b1243cf5ef21bffdd31054e35121d811688ba3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 May 2021 12:00:50 +0000 Subject: [PATCH 116/612] Bump SQLitePCL.pretty.netstandard from 2.1.0 to 2.2.0 Bumps [SQLitePCL.pretty.netstandard](https://github.com/jellyfin/SQLitePCL.pretty.netstandard) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/jellyfin/SQLitePCL.pretty.netstandard/releases) - [Commits](https://github.com/jellyfin/SQLitePCL.pretty.netstandard/commits) Signed-off-by: dependabot[bot] --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index adbfe52c4..b8a544b8c 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -31,7 +31,7 @@ - + From 2ee33bd602fb17411774b278a3f104e5121fab13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 May 2021 12:03:25 +0000 Subject: [PATCH 117/612] Bump alex-page/github-project-automation-plus from v0.6.0 to v0.7.1 Bumps [alex-page/github-project-automation-plus](https://github.com/alex-page/github-project-automation-plus) from v0.6.0 to v0.7.1. - [Release notes](https://github.com/alex-page/github-project-automation-plus/releases) - [Commits](https://github.com/alex-page/github-project-automation-plus/compare/v0.6.0...50502d399cbb98cefe7ce1f99f93f78c6756562e) Signed-off-by: dependabot[bot] --- .github/workflows/automation.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index d5d308185..a203e6695 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -16,7 +16,7 @@ jobs: label: stable backport - name: Remove from 'Current Release' project - uses: alex-page/github-project-automation-plus@v0.6.0 + uses: alex-page/github-project-automation-plus@v0.7.1 if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel continue-on-error: true with: @@ -25,7 +25,7 @@ jobs: repo-token: ${{ secrets.GH_TOKEN }} - name: Add to 'Release Next' project - uses: alex-page/github-project-automation-plus@v0.6.0 + uses: alex-page/github-project-automation-plus@v0.7.1 if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened' continue-on-error: true with: @@ -34,7 +34,7 @@ jobs: repo-token: ${{ secrets.GH_TOKEN }} - name: Add to 'Current Release' project - uses: alex-page/github-project-automation-plus@v0.6.0 + uses: alex-page/github-project-automation-plus@v0.7.1 if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel continue-on-error: true with: @@ -48,7 +48,7 @@ jobs: run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)" - name: Move issue to needs triage - uses: alex-page/github-project-automation-plus@v0.6.0 + uses: alex-page/github-project-automation-plus@v0.7.1 if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1 continue-on-error: true with: @@ -57,7 +57,7 @@ jobs: repo-token: ${{ secrets.GH_TOKEN }} - name: Add issue to triage project - uses: alex-page/github-project-automation-plus@v0.6.0 + uses: alex-page/github-project-automation-plus@v0.7.1 if: github.event.issue.pull_request == '' && github.event.action == 'opened' continue-on-error: true with: From c0feb3694b86ccc242a637468070daff9120e631 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 3 May 2021 23:51:45 +0200 Subject: [PATCH 118/612] rename to SplitEnumerator and fix test --- .../Data/SqliteItemRepository.cs | 14 ++++++--- ...Extensions.cs => SplitStringExtensions.cs} | 16 +++++----- .../Data/SqliteItemRepositoryTests.cs | 31 ++++++++++++++++--- 3 files changed, 43 insertions(+), 18 deletions(-) rename MediaBrowser.Common/Extensions/{SplitLinesStringExtensions.cs => SplitStringExtensions.cs} (84%) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 292ff48fa..4b9b0bed0 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1082,7 +1082,7 @@ namespace Emby.Server.Implementations.Data } } - private ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan value) + internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan value) { var nextSegment = value.IndexOf('*'); if (nextSegment == -1) @@ -1103,7 +1103,7 @@ namespace Emby.Server.Implementations.Data nextSegment = value.IndexOf('*'); if (nextSegment == -1) { - return null; + nextSegment = value.Length; } ReadOnlySpan imageType = value[..nextSegment]; @@ -1128,13 +1128,18 @@ namespace Emby.Server.Implementations.Data { value = value[(nextSegment + 1)..]; nextSegment = value.IndexOf('*'); + if (nextSegment == -1 || nextSegment == value.Length) + { + return image; + } + ReadOnlySpan widthSpan = value[..nextSegment]; value = value[(nextSegment + 1)..]; nextSegment = value.IndexOf('*'); if (nextSegment == -1) { - return image; + nextSegment = value.Length; } ReadOnlySpan heightSpan = value[..nextSegment]; @@ -1146,10 +1151,9 @@ namespace Emby.Server.Implementations.Data image.Height = height; } - nextSegment += 1; if (nextSegment < value.Length - 1) { - value = value[nextSegment..]; + value = value[(nextSegment + 1)..]; var length = value.Length; Span blurHashSpan = stackalloc char[length]; diff --git a/MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs b/MediaBrowser.Common/Extensions/SplitStringExtensions.cs similarity index 84% rename from MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs rename to MediaBrowser.Common/Extensions/SplitStringExtensions.cs index 5332aba9f..6c1c45013 100644 --- a/MediaBrowser.Common/Extensions/SplitLinesStringExtensions.cs +++ b/MediaBrowser.Common/Extensions/SplitStringExtensions.cs @@ -33,33 +33,33 @@ namespace MediaBrowser.Common.Extensions /// /// Extension class for splitting lines without unnecessary allocations. /// - public static class SplitLinesStringExtensions + public static class SplitStringExtensions { /// - /// Creates a new line split enumerator. + /// Creates a new string split enumerator. /// /// The string to split. /// The separator to split on. /// The enumerator struct. [Pure] - public static LineSplitEnumerator SpanSplit(this string str, char separator) => new (str.AsSpan(), separator); + public static SplitEnumerator SpanSplit(this string str, char separator) => new (str.AsSpan(), separator); /// - /// Creates a new line split enumerator. + /// Creates a new span split enumerator. /// /// The span to split. /// The separator to split on. /// The enumerator struct. [Pure] - public static LineSplitEnumerator Split(this ReadOnlySpan str, char separator) => new (str, separator); + public static SplitEnumerator Split(this ReadOnlySpan str, char separator) => new (str, separator); [StructLayout(LayoutKind.Auto)] - public ref struct LineSplitEnumerator + public ref struct SplitEnumerator { private readonly char _separator; private ReadOnlySpan _str; - public LineSplitEnumerator(ReadOnlySpan str, char separator) + public SplitEnumerator(ReadOnlySpan str, char separator) { _str = str; _separator = separator; @@ -68,7 +68,7 @@ namespace MediaBrowser.Common.Extensions public ReadOnlySpan Current { get; private set; } - public readonly LineSplitEnumerator GetEnumerator() => this; + public readonly SplitEnumerator GetEnumerator() => this; public bool MoveNext() { diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs index af6ec3245..c0f34afa7 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs @@ -37,7 +37,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data yield return new object[] { "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN", - new ItemImageInfo() + new ItemImageInfo { Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg", Type = ImageType.Primary, @@ -51,7 +51,27 @@ namespace Jellyfin.Server.Implementations.Tests.Data yield return new object[] { "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*0*0", - new ItemImageInfo() + new ItemImageInfo + { + Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg", + Type = ImageType.Primary, + } + }; + + yield return new object[] + { + "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary", + new ItemImageInfo + { + Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg", + Type = ImageType.Primary, + } + }; + + yield return new object[] + { + "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*600", + new ItemImageInfo { Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg", Type = ImageType.Primary, @@ -61,7 +81,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data yield return new object[] { "%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336", - new ItemImageInfo() + new ItemImageInfo { Path = "/meta/data/path/library/68/68578562b96c80a7ebd530848801f645/poster.jpg", Type = ImageType.Primary, @@ -76,7 +96,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data [MemberData(nameof(ItemImageInfoFromValueString_Valid_TestData))] public void ItemImageInfoFromValueString_Valid_Success(string value, ItemImageInfo expected) { - var result = _sqliteItemRepository.ItemImageInfoFromValueString(value); + var result = _sqliteItemRepository.ItemImageInfoFromValueString(value.AsSpan()); Assert.Equal(expected.Path, result.Path); Assert.Equal(expected.Type, result.Type); Assert.Equal(expected.DateModified, result.DateModified); @@ -88,9 +108,10 @@ namespace Jellyfin.Server.Implementations.Tests.Data [Theory] [InlineData("")] [InlineData("*")] + [InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")] public void ItemImageInfoFromValueString_Invalid_Null(string value) { - Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value)); + Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value.AsSpan())); } public static IEnumerable DeserializeImages_Valid_TestData() From ee92dffa82bf2cc5eb6e8e088a391c3e22c03345 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 May 2021 09:01:23 +0000 Subject: [PATCH 119/612] Bump Swashbuckle.AspNetCore.ReDoc from 6.1.3 to 6.1.4 Bumps [Swashbuckle.AspNetCore.ReDoc](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.1.3 to 6.1.4. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.1.3...v6.1.4) Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index d6d07cd27..c10c34b59 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -18,7 +18,7 @@ - + From ad3e835bcf7e800b483495af74adc36e10139be5 Mon Sep 17 00:00:00 2001 From: cvium Date: Tue, 4 May 2021 19:57:03 +0200 Subject: [PATCH 120/612] remove redundant code --- MediaBrowser.Common/Extensions/SplitStringExtensions.cs | 7 ------- .../Data/SqliteItemRepositoryTests.cs | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/MediaBrowser.Common/Extensions/SplitStringExtensions.cs b/MediaBrowser.Common/Extensions/SplitStringExtensions.cs index 6c1c45013..e78fa78d6 100644 --- a/MediaBrowser.Common/Extensions/SplitStringExtensions.cs +++ b/MediaBrowser.Common/Extensions/SplitStringExtensions.cs @@ -86,13 +86,6 @@ namespace MediaBrowser.Common.Extensions return true; } - if (index < span.Length - 1 && span[index] == _separator) - { - Current = span.Slice(0, index); - _str = span[(index + 1)..]; - return true; - } - Current = span.Slice(0, index); _str = span[(index + 1)..]; return true; diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs index c0f34afa7..71f8c5181 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs @@ -96,7 +96,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data [MemberData(nameof(ItemImageInfoFromValueString_Valid_TestData))] public void ItemImageInfoFromValueString_Valid_Success(string value, ItemImageInfo expected) { - var result = _sqliteItemRepository.ItemImageInfoFromValueString(value.AsSpan()); + var result = _sqliteItemRepository.ItemImageInfoFromValueString(value); Assert.Equal(expected.Path, result.Path); Assert.Equal(expected.Type, result.Type); Assert.Equal(expected.DateModified, result.DateModified); @@ -111,7 +111,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data [InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")] public void ItemImageInfoFromValueString_Invalid_Null(string value) { - Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value.AsSpan())); + Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value)); } public static IEnumerable DeserializeImages_Valid_TestData() From 244ad5b22577efa4dc737db549e62e422f42449e Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Tue, 4 May 2021 22:57:27 +0200 Subject: [PATCH 121/612] Apply review feedback --- MediaBrowser.Model/Dlna/DeviceProfile.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 6ec4cb547..47d101613 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -432,8 +432,8 @@ namespace MediaBrowser.Model.Dlna /// The . public ResponseProfile? GetVideoMediaProfile( string container, - string audioCodec, - string videoCodec, + string? audioCodec, + string? videoCodec, int? width, int? height, int? bitDepth, From b2bb062ced57870eb0a116a9518685a8bae48131 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Tue, 4 May 2021 23:38:17 +0200 Subject: [PATCH 122/612] Revert shortened 'is ... or' check --- MediaBrowser.Model/Dlna/DirectPlayProfile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index 417d915b5..7b8cbe181 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Model.Dlna public bool SupportsAudioCodec(string codec) { - return (Type is DlnaProfileType.Audio or DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec); + return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec); } } } From 031a5c122d6e874fab5f8e4c3b27de5c03ac7217 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sat, 1 May 2021 00:43:43 +0200 Subject: [PATCH 123/612] Improve documentation for DeviceProfile --- MediaBrowser.Model/Dlna/DeviceProfile.cs | 41 +++++++++++++----------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 47d101613..feb3d880e 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -8,13 +8,18 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna { /// - /// Defines the . + /// A represents a set of metadata which determines which content a certain device is able to play. + ///
+ /// Specifically, it defines the supported containers and + /// codecs (video and/or audio, including codec profiles and levels) + /// the device is able to direct play (without transcoding or remuxing), + /// as well as which containers/codecs to transcode to in case it isn't. ///
[XmlRoot("Profile")] public class DeviceProfile { /// - /// Gets or sets the Name. + /// Gets or sets the name of this device profile. /// public string? Name { get; set; } @@ -30,32 +35,32 @@ namespace MediaBrowser.Model.Dlna public DeviceIdentification? Identification { get; set; } /// - /// Gets or sets the FriendlyName. + /// Gets or sets the friendly name of the device profile, which can be shown to users. /// public string? FriendlyName { get; set; } /// - /// Gets or sets the Manufacturer. + /// Gets or sets the manufacturer of the device which this profile represents. /// public string? Manufacturer { get; set; } /// - /// Gets or sets the ManufacturerUrl. + /// Gets or sets an url for the manufacturer of the device which this profile represents. /// public string? ManufacturerUrl { get; set; } /// - /// Gets or sets the ModelName. + /// Gets or sets the model name of the device which this profile represents. /// public string? ModelName { get; set; } /// - /// Gets or sets the ModelDescription. + /// Gets or sets the model description of the device which this profile represents. /// public string? ModelDescription { get; set; } /// - /// Gets or sets the ModelNumber. + /// Gets or sets the model number of the device which this profile represents. /// public string? ModelNumber { get; set; } @@ -65,7 +70,7 @@ namespace MediaBrowser.Model.Dlna public string? ModelUrl { get; set; } /// - /// Gets or sets the SerialNumber. + /// Gets or sets the serial number of the device which this profile represents. /// public string? SerialNumber { get; set; } @@ -113,32 +118,32 @@ namespace MediaBrowser.Model.Dlna public int? MaxAlbumArtHeight { get; set; } /// - /// Gets or sets the MaxIconWidth. + /// Gets or sets the maximum allowed width of embedded icons. /// public int? MaxIconWidth { get; set; } /// - /// Gets or sets the MaxIconHeight. + /// Gets or sets the maximum allowed height of embedded icons. /// public int? MaxIconHeight { get; set; } /// - /// Gets or sets the MaxStreamingBitrate. + /// Gets or sets the maximum allowed bitrate for all streamed content. /// public int? MaxStreamingBitrate { get; set; } = 8000000; /// - /// Gets or sets the MaxStaticBitrate. + /// Gets or sets the maximum allowed bitrate for statically streamed content (= direct played files). /// public int? MaxStaticBitrate { get; set; } = 8000000; /// - /// Gets or sets the MusicStreamingTranscodingBitrate. + /// Gets or sets the maximum allowed bitrate for transcoded music streams. /// public int? MusicStreamingTranscodingBitrate { get; set; } = 128000; /// - /// Gets or sets the MaxStaticMusicBitrate. + /// Gets or sets the maximum allowed bitrate for statically streamed (= direct played) music files. /// public int? MaxStaticMusicBitrate { get; set; } = 8000000; @@ -198,12 +203,12 @@ namespace MediaBrowser.Model.Dlna public TranscodingProfile[] TranscodingProfiles { get; set; } = Array.Empty(); /// - /// Gets or sets the ContainerProfiles. + /// Gets or sets the container profiles. /// public ContainerProfile[] ContainerProfiles { get; set; } = Array.Empty(); /// - /// Gets or sets the CodecProfiles. + /// Gets or sets the codec profiles. /// public CodecProfile[] CodecProfiles { get; set; } = Array.Empty(); @@ -213,7 +218,7 @@ namespace MediaBrowser.Model.Dlna public ResponseProfile[] ResponseProfiles { get; set; } = Array.Empty(); /// - /// Gets or sets the SubtitleProfiles. + /// Gets or sets the subtitle profiles. /// public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty(); From 31cf9f4b76a258babec0838a6e114979dd23b93c Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 4 May 2021 18:21:08 -0600 Subject: [PATCH 124/612] Remove legacy apiclient generation --- .ci/azure-pipelines-api-client.yml | 59 ------------------- .../Properties/launchSettings.json | 3 +- apiclient/.openapi-generator-ignore | 2 - .../templates/typescript/axios/generate.sh | 11 ---- .../typescript/axios/package.mustache | 30 ---------- 5 files changed, 2 insertions(+), 103 deletions(-) delete mode 100644 .ci/azure-pipelines-api-client.yml delete mode 100644 apiclient/.openapi-generator-ignore delete mode 100644 apiclient/templates/typescript/axios/generate.sh delete mode 100644 apiclient/templates/typescript/axios/package.mustache diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml deleted file mode 100644 index 0e944e6f4..000000000 --- a/.ci/azure-pipelines-api-client.yml +++ /dev/null @@ -1,59 +0,0 @@ -parameters: - - name: LinuxImage - type: string - default: "ubuntu-latest" - - name: GeneratorVersion - type: string - default: "5.0.1" - -jobs: -- job: GenerateApiClients - displayName: 'Generate Api Clients' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') - dependsOn: Test - - pool: - vmImage: "${{ parameters.LinuxImage }}" - - steps: - - task: DownloadPipelineArtifact@2 - displayName: 'Download OpenAPI Spec Artifact' - inputs: - source: 'current' - artifact: "OpenAPI Spec" - path: "$(System.ArtifactsDirectory)/openapispec" - runVersion: "latest" - - - task: CmdLine@2 - displayName: 'Download OpenApi Generator' - inputs: - script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar" - -## Authenticate with npm registry - - task: npmAuthenticate@0 - inputs: - workingFile: ./.npmrc - customEndpoint: 'jellyfin-bot for NPM' - -## Generate npm api client - - task: CmdLine@2 - displayName: 'Build stable typescript axios client' - inputs: - script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)" - -## Run npm install - - task: Npm@1 - displayName: 'Install npm dependencies' - inputs: - command: install - workingDir: ./apiclient/generated/typescript/axios - -## Publish npm packages - - task: Npm@1 - displayName: 'Publish stable typescript axios client' - inputs: - command: custom - customCommand: publish --access public - publishRegistry: useExternalRegistry - publishEndpoint: 'jellyfin-bot for NPM' - workingDir: ./apiclient/generated/typescript/axios diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json index 20d432afc..61a1723bd 100644 --- a/Jellyfin.Server/Properties/launchSettings.json +++ b/Jellyfin.Server/Properties/launchSettings.json @@ -6,7 +6,8 @@ "applicationUrl": "http://localhost:8096", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "commandLineArgs": "--webdir C:\\Users\\Cody\\Code\\Jellyfin\\jellyfin-web\\dist" }, "Jellyfin.Server (nowebclient)": { "commandName": "Project", diff --git a/apiclient/.openapi-generator-ignore b/apiclient/.openapi-generator-ignore deleted file mode 100644 index f3802cf54..000000000 --- a/apiclient/.openapi-generator-ignore +++ /dev/null @@ -1,2 +0,0 @@ -# Prevent generator from creating these files: -git_push.sh diff --git a/apiclient/templates/typescript/axios/generate.sh b/apiclient/templates/typescript/axios/generate.sh deleted file mode 100644 index 9599f85db..000000000 --- a/apiclient/templates/typescript/axios/generate.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -artifactsDirectory="${1}" - -java -jar openapi-generator-cli.jar generate \ - --input-spec ${artifactsDirectory}/openapispec/openapi.json \ - --generator-name typescript-axios \ - --output ./apiclient/generated/typescript/axios \ - --template-dir ./apiclient/templates/typescript/axios \ - --ignore-file-override ./apiclient/.openapi-generator-ignore \ - --additional-properties=useSingleRequestParameter="true",withSeparateModelsAndApi="true",modelPackage="models",apiPackage="api",npmName="axios" diff --git a/apiclient/templates/typescript/axios/package.mustache b/apiclient/templates/typescript/axios/package.mustache deleted file mode 100644 index 7bfab08cb..000000000 --- a/apiclient/templates/typescript/axios/package.mustache +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@jellyfin/client-axios", - "version": "10.7.0{{snapshotVersion}}", - "description": "Jellyfin api client using axios", - "author": "Jellyfin Contributors", - "keywords": [ - "axios", - "typescript", - "jellyfin" - ], - "license": "GPL-3.0-only", - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", - "scripts": { - "build": "tsc --outDir dist/", - "prepublishOnly": "npm run build" - }, - "dependencies": { - "axios": "^0.19.2" - }, - "devDependencies": { - "@types/node": "^12.11.5", - "typescript": "^3.6.4" - }{{#npmRepository}},{{/npmRepository}} -{{#npmRepository}} - "publishConfig": { - "registry": "{{npmRepository}}" - } -{{/npmRepository}} -} From 1a178e84905c776766c49551f52dc163b93c81b0 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 4 May 2021 19:11:01 -0600 Subject: [PATCH 125/612] Remove Required attributes --- MediaBrowser.Model/Dlna/ContainerProfile.cs | 2 -- MediaBrowser.Model/Dlna/DirectPlayProfile.cs | 1 - MediaBrowser.Model/Dlna/TranscodingProfile.cs | 5 ----- 3 files changed, 8 deletions(-) diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index 54d4f7f38..c66ec8bc3 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -9,13 +9,11 @@ namespace MediaBrowser.Model.Dlna { public class ContainerProfile { - [Required] [XmlAttribute("type")] public DlnaProfileType Type { get; set; } public ProfileCondition[]? Conditions { get; set; } = Array.Empty(); - [Required] [XmlAttribute("container")] public string Container { get; set; } = string.Empty; diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index 7b8cbe181..fa3ad098f 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -16,7 +16,6 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("videoCodec")] public string? VideoCodec { get; set; } - [Required] [XmlAttribute("type")] public DlnaProfileType Type { get; set; } diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index ea446b733..214578a85 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -8,23 +8,18 @@ namespace MediaBrowser.Model.Dlna { public class TranscodingProfile { - [Required] [XmlAttribute("container")] public string Container { get; set; } = string.Empty; - [Required] [XmlAttribute("type")] public DlnaProfileType Type { get; set; } - [Required] [XmlAttribute("videoCodec")] public string VideoCodec { get; set; } = string.Empty; - [Required] [XmlAttribute("audioCodec")] public string AudioCodec { get; set; } = string.Empty; - [Required] [XmlAttribute("protocol")] public string Protocol { get; set; } = string.Empty; From 08d07656237fd53668796e0d6a2d78bbba5f706f Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Tue, 4 May 2021 19:17:16 -0600 Subject: [PATCH 126/612] Restore launchSettings.json --- Jellyfin.Server/Properties/launchSettings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json index 61a1723bd..20d432afc 100644 --- a/Jellyfin.Server/Properties/launchSettings.json +++ b/Jellyfin.Server/Properties/launchSettings.json @@ -6,8 +6,7 @@ "applicationUrl": "http://localhost:8096", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "commandLineArgs": "--webdir C:\\Users\\Cody\\Code\\Jellyfin\\jellyfin-web\\dist" + } }, "Jellyfin.Server (nowebclient)": { "commandName": "Project", From 08213f2368aed728e2e12e92e5d799952f7b412a Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 4 May 2021 19:30:51 -0600 Subject: [PATCH 127/612] Fully remove reference. --- .ci/azure-pipelines.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 6430503f9..c028b6e3e 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -61,6 +61,3 @@ jobs: - ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}: - template: azure-pipelines-package.yml - -- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}: - - template: azure-pipelines-api-client.yml From 260782349c7a800a576f0a7d3ff2b2ccc205013a Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 5 May 2021 00:58:12 -0400 Subject: [PATCH 128/612] Fix web build in dockerfiles --- Dockerfile | 6 +++--- Dockerfile.arm | 6 +++--- Dockerfile.arm64 | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index ebe5eb00c..4e2d06b82 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ ARG DOTNET_VERSION=5.0 -FROM node:alpine as web-builder +FROM node:lts-alpine as web-builder ARG JELLYFIN_WEB_VERSION=master -RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ +RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ - && npm ci --no-audit \ + && npm ci --no-audit --unsafe-perm \ && mv dist /dist FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder diff --git a/Dockerfile.arm b/Dockerfile.arm index d63dbee75..25a0de7db 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -5,12 +5,12 @@ ARG DOTNET_VERSION=5.0 -FROM node:alpine as web-builder +FROM node:lts-alpine as web-builder ARG JELLYFIN_WEB_VERSION=master -RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ +RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ - && npm ci --no-audit \ + && npm ci --no-audit --unsafe-perm \ && mv dist /dist diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index e95999f2a..c9f19c5a3 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -5,12 +5,12 @@ ARG DOTNET_VERSION=5.0 -FROM node:alpine as web-builder +FROM node:lts-alpine as web-builder ARG JELLYFIN_WEB_VERSION=master -RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ +RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ - && npm ci --no-audit \ + && npm ci --no-audit --unsafe-perm \ && mv dist /dist From e57b80b8e2c094c27eef835a335d4c7386bff968 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Wed, 5 May 2021 10:10:51 +0200 Subject: [PATCH 129/612] Add support for fanart aspect in thumb tag --- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 1 + .../Parsers/MovieNfoParserTests.cs | 6 +++++- .../Test Data/Justice League.nfo | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 317dc0bf6..302c93f0b 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -1333,6 +1333,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers "discart" => ImageType.Disc, "landscape" => ImageType.Thumb, "clearart" => ImageType.Art, + "fanart" => ImageType.Backdrop, // unknown type (including "poster") --> primary _ => ImageType.Primary, }; diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index b58151b3b..30a48857a 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -156,7 +156,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers Assert.Equal("Justice League Collection", item.CollectionName); // Images - Assert.Equal(6, result.RemoteImages.Count); + Assert.Equal(7, result.RemoteImages.Count); var posters = result.RemoteImages.Where(x => x.type == ImageType.Primary).ToList(); Assert.Single(posters); @@ -182,6 +182,10 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers Assert.Single(discArt); Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].url); + var backdrop = result.RemoteImages.Where(x => x.type == ImageType.Backdrop).ToList(); + Assert.Single(backdrop); + Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg", backdrop[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); diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo index b0c5e3c57..4e8c79dca 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo +++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo @@ -82,8 +82,8 @@ https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a0b913c233be.png https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-59dc595362ef1.png + https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg - https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg From 65a9a4771afc5b6b7c956f7b799586400b23af8b Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Wed, 5 May 2021 12:25:54 +0200 Subject: [PATCH 130/612] Fix direct play for DirectPlayProfiles without any codecs set 70771fdcd60ec5d8a9f13713662778c7e57d0633 broke direct play by treating empty container/codec strings as unsupported in `ContainerProfile.ContainsContainer()`` (which is also used for video and audio codec checks). Instead, they should be treated as supported, for both the positive and negative list option. --- MediaBrowser.Model/Dlna/ContainerProfile.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index c66ec8bc3..b059e43f3 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -55,7 +55,8 @@ namespace MediaBrowser.Model.Dlna { if (profileContainers == null || profileContainers.Length == 0) { - return isNegativeList; + // Empty profiles always support all containers/codecs + return true; } var allInputContainers = SplitValue(inputContainer); From 91c2a57b284011a21c926e9238dbc7edb5eaab13 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 5 May 2021 12:57:01 +0200 Subject: [PATCH 131/612] Enable nullable reference types for MediaBrowser.Common --- Jellyfin.Api/Auth/BaseAuthorizationHandler.cs | 5 +++-- .../Controllers/ConfigurationController.cs | 6 ++++++ .../Configuration/ConfigurationStore.cs | 2 ++ .../ConfigurationUpdateEventArgs.cs | 1 + .../Configuration/IApplicationPaths.cs | 2 ++ .../Cryptography/PasswordHash.cs | 1 - MediaBrowser.Common/Events/EventHelper.cs | 4 ++-- .../Extensions/BaseExtensions.cs | 2 -- .../Extensions/CopyToExtensions.cs | 2 -- .../Extensions/HttpContextExtensions.cs | 2 +- .../Extensions/MethodNotAllowedException.cs | 2 -- .../Extensions/ProcessExtensions.cs | 2 -- .../Extensions/RateLimitExceededException.cs | 1 - .../Extensions/ResourceNotFoundException.cs | 2 -- .../Extensions/ShuffleExtensions.cs | 2 -- .../Extensions/SplitStringExtensions.cs | 2 +- .../Extensions/StreamExtensions.cs | 2 -- MediaBrowser.Common/IApplicationHost.cs | 2 ++ .../JsonCommaDelimitedArrayConverter.cs | 14 ++++++++++---- .../JsonCommaDelimitedArrayConverterFactory.cs | 4 ++-- .../JsonNullableStructConverterFactory.cs | 6 +++--- .../JsonOmdbNotAvailableStringConverter.cs | 18 ++++++++++++------ .../JsonPipeDelimitedArrayConverter.cs | 13 ++++++++++--- .../JsonPipeDelimitedArrayConverterFactory.cs | 4 ++-- .../Json/Converters/JsonStringConverter.cs | 8 ++++---- .../Json/Converters/JsonVersionConverter.cs | 2 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 1 + MediaBrowser.Common/Net/INetworkManager.cs | 1 - MediaBrowser.Common/Net/IPHost.cs | 1 - MediaBrowser.Common/Net/IPNetAddress.cs | 1 - MediaBrowser.Common/Net/IPObject.cs | 1 - MediaBrowser.Common/Plugins/BasePlugin.cs | 2 ++ MediaBrowser.Common/Plugins/BasePluginOfT.cs | 2 ++ MediaBrowser.Common/Plugins/IPlugin.cs | 2 ++ MediaBrowser.Common/Plugins/IPluginManager.cs | 2 -- MediaBrowser.Common/Plugins/LocalPlugin.cs | 1 - MediaBrowser.Common/Plugins/PluginManifest.cs | 2 -- .../Progress/ActionableProgress.cs | 4 ++-- MediaBrowser.Common/Progress/SimpleProgress.cs | 2 +- .../Providers/ProviderIdParsers.cs | 4 +--- .../Updates/IInstallationManager.cs | 2 -- .../Updates/InstallationEventArgs.cs | 2 ++ .../Updates/InstallationFailedEventArgs.cs | 1 + 43 files changed, 78 insertions(+), 64 deletions(-) diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs index 7d68aecf9..392498c53 100644 --- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs @@ -77,8 +77,9 @@ namespace Jellyfin.Api.Auth return false; } - var ip = _httpContextAccessor.HttpContext.GetNormalizedRemoteIp(); - var isInLocalNetwork = _networkManager.IsInLocalNetwork(ip); + var isInLocalNetwork = _httpContextAccessor.HttpContext != null + && _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIp()); + // User cannot access remotely and user is remote if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !isInLocalNetwork) { diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs index 049a4bed7..b6309baab 100644 --- a/Jellyfin.Api/Controllers/ConfigurationController.cs +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel.DataAnnotations; using System.Net.Mime; using System.Text.Json; @@ -94,6 +95,11 @@ namespace Jellyfin.Api.Controllers { var configurationType = _configurationManager.GetConfigurationType(key); var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false); + if (configuration == null) + { + throw new ArgumentException("Body doesn't contain a valid configuration"); + } + _configurationManager.SaveConfiguration(key, configuration); return NoContent(); } diff --git a/MediaBrowser.Common/Configuration/ConfigurationStore.cs b/MediaBrowser.Common/Configuration/ConfigurationStore.cs index d31d45e4c..050ab1ab5 100644 --- a/MediaBrowser.Common/Configuration/ConfigurationStore.cs +++ b/MediaBrowser.Common/Configuration/ConfigurationStore.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace MediaBrowser.Common.Configuration diff --git a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs index 344aecf53..2df87d879 100644 --- a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs +++ b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index 57c654667..1370e6d79 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace MediaBrowser.Common.Configuration { /// diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index ec21d0580..0e2065302 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#nullable enable using System; using System.Collections.Generic; diff --git a/MediaBrowser.Common/Events/EventHelper.cs b/MediaBrowser.Common/Events/EventHelper.cs index c9d3226ac..a9cf86fbc 100644 --- a/MediaBrowser.Common/Events/EventHelper.cs +++ b/MediaBrowser.Common/Events/EventHelper.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Common.Events /// The sender. /// The instance containing the event data. /// The logger. - public static void QueueEventIfNotNull(EventHandler handler, object sender, EventArgs args, ILogger logger) + public static void QueueEventIfNotNull(EventHandler? handler, object sender, EventArgs args, ILogger logger) { if (handler != null) { @@ -43,7 +43,7 @@ namespace MediaBrowser.Common.Events /// The sender. /// The args. /// The logger. - public static void QueueEventIfNotNull(EventHandler handler, object sender, T args, ILogger logger) + public static void QueueEventIfNotNull(EventHandler? handler, object sender, T args, ILogger logger) { if (handler != null) { diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs index 40020093b..08964420e 100644 --- a/MediaBrowser.Common/Extensions/BaseExtensions.cs +++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Security.Cryptography; using System.Text; diff --git a/MediaBrowser.Common/Extensions/CopyToExtensions.cs b/MediaBrowser.Common/Extensions/CopyToExtensions.cs index 94bf7c740..2ecbc6539 100644 --- a/MediaBrowser.Common/Extensions/CopyToExtensions.cs +++ b/MediaBrowser.Common/Extensions/CopyToExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Collections.Generic; namespace MediaBrowser.Common.Extensions diff --git a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs index e51ad42d1..1e5877c84 100644 --- a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs +++ b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Common.Extensions { return (context.Connection.LocalIpAddress == null && context.Connection.RemoteIpAddress == null) - || context.Connection.LocalIpAddress.Equals(context.Connection.RemoteIpAddress); + || Equals(context.Connection.LocalIpAddress, context.Connection.RemoteIpAddress); } /// diff --git a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs index 258bd6662..48e758ee4 100644 --- a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs +++ b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; namespace MediaBrowser.Common.Extensions diff --git a/MediaBrowser.Common/Extensions/ProcessExtensions.cs b/MediaBrowser.Common/Extensions/ProcessExtensions.cs index 2f52ba196..c74787122 100644 --- a/MediaBrowser.Common/Extensions/ProcessExtensions.cs +++ b/MediaBrowser.Common/Extensions/ProcessExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Diagnostics; using System.Threading; diff --git a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs index 7c7bdaa92..95802a462 100644 --- a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs +++ b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs index ebac9d8e6..22130c5a1 100644 --- a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs +++ b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; namespace MediaBrowser.Common.Extensions diff --git a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs b/MediaBrowser.Common/Extensions/ShuffleExtensions.cs index 6f0ea9bd5..2604abf85 100644 --- a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs +++ b/MediaBrowser.Common/Extensions/ShuffleExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; diff --git a/MediaBrowser.Common/Extensions/SplitStringExtensions.cs b/MediaBrowser.Common/Extensions/SplitStringExtensions.cs index e78fa78d6..9c9108495 100644 --- a/MediaBrowser.Common/Extensions/SplitStringExtensions.cs +++ b/MediaBrowser.Common/Extensions/SplitStringExtensions.cs @@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#nullable enable + #pragma warning disable CS1591 #pragma warning disable CA1034 using System; diff --git a/MediaBrowser.Common/Extensions/StreamExtensions.cs b/MediaBrowser.Common/Extensions/StreamExtensions.cs index cd77be7b2..08d81ffc3 100644 --- a/MediaBrowser.Common/Extensions/StreamExtensions.cs +++ b/MediaBrowser.Common/Extensions/StreamExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index c3e4ed6db..46d93e494 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Reflection; diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs index 2ec702165..55c4665e8 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Common.Json.Converters /// Convert comma delimited string to array of type. /// /// Type to convert to. - public class JsonCommaDelimitedArrayConverter : JsonConverter + public class JsonCommaDelimitedArrayConverter : JsonConverter { private readonly TypeConverter _typeConverter; @@ -22,11 +22,17 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + if (reader.TokenType == JsonTokenType.String) { - var stringEntries = reader.GetString().Split(',', StringSplitOptions.RemoveEmptyEntries); + // GetString can't return null here because we already handled it above + var stringEntries = reader.GetString()!.Split(',', StringSplitOptions.RemoveEmptyEntries); if (stringEntries.Length == 0) { return Array.Empty(); @@ -67,7 +73,7 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options) { throw new NotImplementedException(); } diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs index 24ed3ea19..de41348dd 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs @@ -19,10 +19,10 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0]; - return (JsonConverter)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType)); + return (JsonConverter?)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType)); } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs index d5b54e3ca..e2a3d798a 100644 --- a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs +++ b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs @@ -18,10 +18,10 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { var structType = typeToConvert.GenericTypeArguments[0]; - return (JsonConverter)Activator.CreateInstance(typeof(JsonNullableStructConverter<>).MakeGenericType(structType)); + return (JsonConverter?)Activator.CreateInstance(typeof(JsonNullableStructConverter<>).MakeGenericType(structType)); } } -} \ No newline at end of file +} diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs index 6a8790374..77cf46b70 100644 --- a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs @@ -7,15 +7,21 @@ namespace MediaBrowser.Common.Json.Converters /// /// Converts a string N/A to string.Empty. /// - public class JsonOmdbNotAvailableStringConverter : JsonConverter + public class JsonOmdbNotAvailableStringConverter : JsonConverter { /// - public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + if (reader.TokenType == JsonTokenType.String) { - var str = reader.GetString(); - if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) + // GetString can't return null here because we already handled it above + var str = reader.GetString()!; + if (str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) { return null; } @@ -23,11 +29,11 @@ namespace MediaBrowser.Common.Json.Converters return str; } - return JsonSerializer.Deserialize(ref reader, options); + return JsonSerializer.Deserialize(ref reader, options); } /// - public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) { writer.WriteStringValue(value); } diff --git a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs index c408a3be1..c83657b5f 100644 --- a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs @@ -24,10 +24,16 @@ namespace MediaBrowser.Common.Json.Converters /// public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType == JsonTokenType.Null) + { + return Array.Empty(); + } + if (reader.TokenType == JsonTokenType.String) { - var stringEntries = reader.GetString()?.Split('|', StringSplitOptions.RemoveEmptyEntries); - if (stringEntries == null || stringEntries.Length == 0) + // GetString can't return null here because we already handled it above + var stringEntries = reader.GetString()!.Split('|', StringSplitOptions.RemoveEmptyEntries); + if (stringEntries.Length == 0) { return Array.Empty(); } @@ -63,7 +69,8 @@ namespace MediaBrowser.Common.Json.Converters return typedValues; } - return JsonSerializer.Deserialize(ref reader, options); + // can't return null here because we already handled it above + return JsonSerializer.Deserialize(ref reader, options)!; } /// diff --git a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs index 5e77223ef..1bebc49ec 100644 --- a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs +++ b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs @@ -19,10 +19,10 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0]; - return (JsonConverter)Activator.CreateInstance(typeof(JsonPipeDelimitedArrayConverter<>).MakeGenericType(structType)); + return (JsonConverter?)Activator.CreateInstance(typeof(JsonPipeDelimitedArrayConverter<>).MakeGenericType(structType)); } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs index 669b3cd07..6cd980e48 100644 --- a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs @@ -9,10 +9,10 @@ namespace MediaBrowser.Common.Json.Converters /// /// Converter to allow the serializer to read strings. /// - public class JsonStringConverter : JsonConverter + public class JsonStringConverter : JsonConverter { /// - public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.TokenType switch { @@ -23,7 +23,7 @@ namespace MediaBrowser.Common.Json.Converters } /// - public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) { writer.WriteStringValue(value); } @@ -36,4 +36,4 @@ namespace MediaBrowser.Common.Json.Converters return Encoding.UTF8.GetString(utf8Bytes); } } -} \ No newline at end of file +} diff --git a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs index f69e868cc..81c093c54 100644 --- a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Common.Json.Converters { /// public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => new Version(reader.GetString()); + => new Version(reader.GetString()!); // Will throw ArgumentNullException on null /// public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 0d9f78704..0299a8456 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -33,6 +33,7 @@ false true true + enable AllEnabledByDefault ../jellyfin.ruleset true diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 185df5b77..b93939730 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index fb3ef9b12..72197db07 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Diagnostics; using System.Linq; diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs index 589aad4b0..f6e3971bf 100644 --- a/MediaBrowser.Common/Net/IPNetAddress.cs +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Net; using System.Net.Sockets; diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs index 3542dcd75..2612268fd 100644 --- a/MediaBrowser.Common/Net/IPObject.cs +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Net; using System.Net.Sockets; diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index ad5a7338d..8972089a8 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using System.Reflection; diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index 99c226f50..361f4803b 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -1,4 +1,6 @@ +#nullable disable #pragma warning disable SA1649 // File name should match first type name + using System; using System.IO; using System.Runtime.InteropServices; diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs index b2ba1179c..01e0a536d 100644 --- a/MediaBrowser.Common/Plugins/IPlugin.cs +++ b/MediaBrowser.Common/Plugins/IPlugin.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Model.Plugins; diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index 0e2e814cb..176bcbbd5 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Reflection; diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index 12a1ad1ec..4c8e2d504 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Collections.Generic; using MediaBrowser.Model.Plugins; diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index 4c724f694..2910dbe14 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Text.Json.Serialization; using MediaBrowser.Model.Plugins; diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs index fe7cb1078..0ba46ea3b 100644 --- a/MediaBrowser.Common/Progress/ActionableProgress.cs +++ b/MediaBrowser.Common/Progress/ActionableProgress.cs @@ -14,9 +14,9 @@ namespace MediaBrowser.Common.Progress /// /// The _actions. /// - private Action _action; + private Action? _action; - public event EventHandler ProgressChanged; + public event EventHandler? ProgressChanged; /// /// Registers the action. diff --git a/MediaBrowser.Common/Progress/SimpleProgress.cs b/MediaBrowser.Common/Progress/SimpleProgress.cs index 988d8ad34..7071f2bc3 100644 --- a/MediaBrowser.Common/Progress/SimpleProgress.cs +++ b/MediaBrowser.Common/Progress/SimpleProgress.cs @@ -7,7 +7,7 @@ namespace MediaBrowser.Common.Progress { public class SimpleProgress : IProgress { - public event EventHandler ProgressChanged; + public event EventHandler? ProgressChanged; public void Report(T value) { diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs index 64c2e1976..33d09ed38 100644 --- a/MediaBrowser.Common/Providers/ProviderIdParsers.cs +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -1,6 +1,4 @@ -#nullable enable - -using System; +using System; using System.Diagnostics.CodeAnalysis; namespace MediaBrowser.Common.Providers diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 0844c2d79..c2a28e0a2 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs index adf336313..f4f759955 100644 --- a/MediaBrowser.Common/Updates/InstallationEventArgs.cs +++ b/MediaBrowser.Common/Updates/InstallationEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Model.Updates; diff --git a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs index 46f10c84f..d37146195 100644 --- a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs +++ b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; From 39931fe3ade3ad10e758b3dbb5acf80c37bc05fa Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 5 May 2021 13:33:34 +0200 Subject: [PATCH 132/612] Add regression test for ContainerProfile.ContainsContainer --- MediaBrowser.Model/Dlna/ContainerProfile.cs | 1 - .../Dlna/ContainerProfileTests.cs | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index c66ec8bc3..530ffed43 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -1,7 +1,6 @@ #pragma warning disable CS1591 using System; -using System.ComponentModel.DataAnnotations; using System.Linq; using System.Xml.Serialization; diff --git a/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs b/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs new file mode 100644 index 000000000..cca056c28 --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs @@ -0,0 +1,19 @@ +using MediaBrowser.Model.Dlna; +using Xunit; + +namespace Jellyfin.Model.Tests.Dlna +{ + public class ContainerProfileTests + { + private readonly ContainerProfile _emptyContainerProfile = new ContainerProfile(); + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("mp4")] + public void ContainsContainer_EmptyContainerProfile_True(string? containers) + { + Assert.True(_emptyContainerProfile.ContainsContainer(containers)); + } + } +} From f2c10471bf00263adc6411b38db60ba931d0ec15 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 5 May 2021 12:37:36 +0100 Subject: [PATCH 133/612] Code Clean up: Use Pattern Matching (#5838) Co-authored-by: Cody Robibero Co-authored-by: Patrick Barron <18354464+barronpm@users.noreply.github.com> --- .../Images/PlaylistImageProvider.cs | 4 +--- .../Library/MusicManager.cs | 3 +-- .../ScheduledTasks/ScheduledTaskWorker.cs | 4 +--- .../TV/TVSeriesManager.cs | 8 ++------ .../Entities/CollectionFolder.cs | 4 +--- .../Entities/Movies/BoxSet.cs | 3 +-- MediaBrowser.Controller/Entities/Photo.cs | 3 +-- MediaBrowser.Controller/Entities/TV/Series.cs | 11 ++--------- MediaBrowser.Controller/Providers/ItemInfo.cs | 3 +-- .../Parsers/BaseItemXmlParser.cs | 3 +-- .../Savers/BaseXmlSaver.cs | 6 ++---- .../BdInfo/BdInfoExaminer.cs | 16 ++++------------ .../Manager/MetadataService.cs | 6 ++---- .../MediaInfo/AudioImageProvider.cs | 4 +--- .../MediaInfo/SubtitleDownloader.cs | 4 +--- .../MediaInfo/VideoImageProvider.cs | 4 +--- 16 files changed, 23 insertions(+), 63 deletions(-) diff --git a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs index 0ce1b91e8..a4c106e87 100644 --- a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs @@ -29,9 +29,7 @@ namespace Emby.Server.Implementations.Images { var subItem = i.Item2; - var episode = subItem as Episode; - - if (episode != null) + if (subItem is Episode episode) { var series = episode.Series; if (series != null && series.HasImage(ImageType.Primary)) diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 658c53f28..f8bae4fd1 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -100,8 +100,7 @@ namespace Emby.Server.Implementations.Library public List GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions) { - var genre = item as MusicGenre; - if (genre != null) + if (item is MusicGenre genre) { return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions); } diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 9c0e92705..61dccaa19 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -348,9 +348,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { var trigger = (ITaskTrigger)sender; - var configurableTask = ScheduledTask as IConfigurableScheduledTask; - - if (configurableTask != null && !configurableTask.IsEnabled) + if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled) { return; } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index d3f6fa34d..829df64bf 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -43,9 +43,7 @@ namespace Emby.Server.Implementations.TV string presentationUniqueKey = null; if (!string.IsNullOrEmpty(request.SeriesId)) { - var series = _libraryManager.GetItemById(request.SeriesId) as Series; - - if (series != null) + if (_libraryManager.GetItemById(request.SeriesId) is Series series) { presentationUniqueKey = GetUniqueSeriesKey(series); } @@ -95,9 +93,7 @@ namespace Emby.Server.Implementations.TV int? limit = null; if (!string.IsNullOrEmpty(request.SeriesId)) { - var series = _libraryManager.GetItemById(request.SeriesId) as Series; - - if (series != null) + if (_libraryManager.GetItemById(request.SeriesId) is Series series) { presentationUniqueKey = GetUniqueSeriesKey(series); limit = 1; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 16a2c77e9..347d5b73c 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -354,9 +354,7 @@ namespace MediaBrowser.Controller.Entities if (result.Count == 0) { - var folder = LibraryManager.FindByPath(path, true) as Folder; - - if (folder != null) + if (LibraryManager.FindByPath(path, true) is Folder folder) { result.Add(folder); } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 05e4229ca..507f400f1 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -217,8 +217,7 @@ namespace MediaBrowser.Controller.Entities.Movies private IEnumerable FlattenItems(BaseItem item, List expandedFolders) { - var boxset = item as BoxSet; - if (boxset != null) + if (item is BoxSet boxset) { if (!expandedFolders.Contains(item.Id)) { diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index 2fc66176f..0f82f742f 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -24,8 +24,7 @@ namespace MediaBrowser.Controller.Entities var parents = GetParents(); foreach (var parent in parents) { - var photoAlbum = parent as PhotoAlbum; - if (photoAlbum != null) + if (parent is PhotoAlbum photoAlbum) { return photoAlbum; } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 9f9a2ad50..06a405121 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -316,20 +316,13 @@ namespace MediaBrowser.Controller.Entities.TV cancellationToken.ThrowIfCancellationRequested(); - var skipItem = false; - - var episode = item as Episode; - - if (episode != null + bool skipItem = item is Episode episode && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh && !refreshOptions.ReplaceAllMetadata && episode.IsMissingEpisode && episode.LocationType == LocationType.Virtual && episode.PremiereDate.HasValue - && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30) - { - skipItem = true; - } + && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30; if (!skipItem) { diff --git a/MediaBrowser.Controller/Providers/ItemInfo.cs b/MediaBrowser.Controller/Providers/ItemInfo.cs index b50def043..3a97127ea 100644 --- a/MediaBrowser.Controller/Providers/ItemInfo.cs +++ b/MediaBrowser.Controller/Providers/ItemInfo.cs @@ -14,8 +14,7 @@ namespace MediaBrowser.Controller.Providers ContainingFolderPath = item.ContainingFolderPath; IsInMixedFolder = item.IsInMixedFolder; - var video = item as Video; - if (video != null) + if (item is Video video) { VideoType = video.VideoType; IsPlaceHolder = video.IsPlaceHolder; diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 5f620634f..32e5ac761 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -468,8 +468,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { var val = reader.ReadElementContentAsString(); - var hasDisplayOrder = item as IHasDisplayOrder; - if (hasDisplayOrder != null) + if (item is IHasDisplayOrder hasDisplayOrder) { if (!string.IsNullOrWhiteSpace(val)) { diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index dfbce5f49..98ed3dcf7 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -296,8 +296,7 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteEndElement(); } - var hasDisplayOrder = item as IHasDisplayOrder; - if (hasDisplayOrder != null && !string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder)) + if (item is IHasDisplayOrder hasDisplayOrder && !string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder)) { writer.WriteElementString("DisplayOrder", hasDisplayOrder.DisplayOrder); } @@ -312,8 +311,7 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(_usCulture)); } - var hasAspectRatio = item as IHasAspectRatio; - if (hasAspectRatio != null) + if (item is IHasAspectRatio hasAspectRatio) { if (!string.IsNullOrEmpty(hasAspectRatio.AspectRatio)) { diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs index 9108d9649..6ebaa4fff 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs @@ -61,33 +61,25 @@ namespace MediaBrowser.MediaEncoding.BdInfo foreach (var stream in playlist.SortedStreams) { - var videoStream = stream as TSVideoStream; - - if (videoStream != null) + if (stream is TSVideoStream videoStream) { AddVideoStream(mediaStreams, videoStream); continue; } - var audioStream = stream as TSAudioStream; - - if (audioStream != null) + if (stream is TSAudioStream audioStream) { AddAudioStream(mediaStreams, audioStream); continue; } - var textStream = stream as TSTextStream; - - if (textStream != null) + if (stream is TSTextStream textStream) { AddSubtitleStream(mediaStreams, textStream); continue; } - var graphicsStream = stream as TSGraphicsStream; - - if (graphicsStream != null) + if (stream is TSGraphicsStream graphicsStream) { AddSubtitleStream(mediaStreams, graphicsStream); } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 6b778a090..401c7e99f 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -281,8 +281,7 @@ namespace MediaBrowser.Providers.Manager return true; } - var folder = item as Folder; - if (folder != null) + if (item is Folder folder) { return folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks; } @@ -336,8 +335,7 @@ namespace MediaBrowser.Providers.Manager private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IList children) { - var folder = item as Folder; - if (folder != null && folder.SupportsCumulativeRunTimeTicks) + if (item is Folder folder && folder.SupportsCumulativeRunTimeTicks) { long ticks = 0; diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 64ad1bddf..03e45fb86 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -137,9 +137,7 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - var audio = item as Audio; - - return audio != null; + return item is Audio; } } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index 912aedb0d..44ab5aa5b 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -172,9 +172,7 @@ namespace MediaBrowser.Providers.MediaInfo SubtitleFetcherOrder = subtitleFetcherOrder }; - var episode = video as Episode; - - if (episode != null) + if (video is Episode episode) { request.IndexNumberEnd = episode.IndexNumberEnd; request.SeriesName = episode.SeriesName; diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index c36c3af6a..30af6710a 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -154,9 +154,7 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - var video = item as Video; - - if (video != null && !video.IsPlaceHolder && video.IsCompleteMedia) + if (item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia) { return true; } From 2e98de90628e9a4e42fb182f2d5a2a296acfd827 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 5 May 2021 12:51:14 +0100 Subject: [PATCH 134/612] Code Clean up: Convert to null-coalescing operator ?? (#5845) Co-authored-by: Cody Robibero Co-authored-by: Patrick Barron <18354464+barronpm@users.noreply.github.com> --- .../ApplicationHost.cs | 10 +---- Emby.Server.Implementations/Dto/DtoService.cs | 15 ++----- .../Library/LibraryManager.cs | 5 +-- .../Library/Resolvers/TV/EpisodeResolver.cs | 14 ++---- .../LiveTv/EmbyTV/EmbyTV.cs | 10 ++--- .../LiveTv/Listings/SchedulesDirect.cs | 7 +-- .../LiveTv/LiveTvManager.cs | 15 ++----- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 5 +-- .../Plugins/PluginManager.cs | 13 +----- .../ScheduledTasks/ScheduledTaskWorker.cs | 7 +-- .../Session/SessionManager.cs | 5 +-- Jellyfin.Api/Controllers/PluginsController.cs | 7 +-- Jellyfin.Api/Controllers/SearchController.cs | 5 +-- Jellyfin.Api/Helpers/StreamingHelpers.cs | 5 +-- MediaBrowser.Common/Net/IPHost.cs | 5 +-- MediaBrowser.Common/Plugins/BasePluginOfT.cs | 5 +-- MediaBrowser.Controller/Entities/BaseItem.cs | 26 ++++------- MediaBrowser.Controller/Entities/UserView.cs | 5 +-- .../Library/ItemResolveArgs.cs | 5 +-- MediaBrowser.Controller/Playlists/Playlist.cs | 5 +-- .../Providers/MetadataRefreshOptions.cs | 5 +-- .../Providers/MetadataResult.cs | 16 +++---- .../Probing/ProbeResultNormalizer.cs | 45 +++++-------------- .../Entities/ProviderIdsExtensions.cs | 5 +-- .../MediaInfo/FFProbeVideoInfo.cs | 5 +-- .../Subtitles/SubtitleManager.cs | 5 +-- RSSDP/SsdpCommunicationsServer.cs | 5 +-- 27 files changed, 60 insertions(+), 200 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 703f8d20d..75d8fc113 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -335,10 +335,7 @@ namespace Emby.Server.Implementations { get { - if (_deviceId == null) - { - _deviceId = new DeviceId(ApplicationPaths, LoggerFactory); - } + _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory); return _deviceId.Value; } @@ -370,10 +367,7 @@ namespace Emby.Server.Implementations /// System.Object. protected object CreateInstanceSafe(Type type) { - if (_creatingInstances == null) - { - _creatingInstances = new List(); - } + _creatingInstances ??= new List(); if (_creatingInstances.IndexOf(type) != -1) { diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 54b18a8c8..4ae35039a 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -665,10 +665,7 @@ namespace Emby.Server.Implementations.Dto var tag = GetImageCacheTag(item, image); if (!string.IsNullOrEmpty(image.BlurHash)) { - if (dto.ImageBlurHashes == null) - { - dto.ImageBlurHashes = new Dictionary>(); - } + dto.ImageBlurHashes ??= new Dictionary>(); if (!dto.ImageBlurHashes.ContainsKey(image.Type)) { @@ -702,10 +699,7 @@ namespace Emby.Server.Implementations.Dto if (hashes.Count > 0) { - if (dto.ImageBlurHashes == null) - { - dto.ImageBlurHashes = new Dictionary>(); - } + dto.ImageBlurHashes ??= new Dictionary>(); dto.ImageBlurHashes[imageType] = hashes; } @@ -898,10 +892,7 @@ namespace Emby.Server.Implementations.Dto dto.Taglines = new string[] { item.Tagline }; } - if (dto.Taglines == null) - { - dto.Taglines = Array.Empty(); - } + dto.Taglines ??= Array.Empty(); } dto.Type = item.GetBaseItemKind(); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index a44edad16..4d207471a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -176,10 +176,7 @@ namespace Emby.Server.Implementations.Library { lock (_rootFolderSyncLock) { - if (_rootFolder == null) - { - _rootFolder = CreateRootFolder(); - } + _rootFolder ??= CreateRootFolder(); } } diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 9b4cd7a3d..6f29bc649 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -35,14 +35,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return null; } - var season = parent as Season; - // Just in case the user decided to nest episodes. // Not officially supported but in some cases we can handle it. - if (season == null) - { - season = parent.GetParents().OfType().FirstOrDefault(); - } + + var season = parent as Season ?? parent.GetParents().OfType().FirstOrDefault(); // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // Also handle flat tv folders @@ -55,11 +51,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV if (episode != null) { - var series = parent as Series; - if (series == null) - { - series = parent.GetParents().OfType().FirstOrDefault(); - } + var series = parent as Series ?? parent.GetParents().OfType().FirstOrDefault(); if (series != null) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 665fbfa0f..28a2095e1 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2237,14 +2237,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var enabledTimersForSeries = new List(); foreach (var timer in allTimers) { - var existingTimer = _timerProvider.GetTimer(timer.Id); - - if (existingTimer == null) - { - existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) + var existingTimer = _timerProvider.GetTimer(timer.Id) + ?? (string.IsNullOrWhiteSpace(timer.ProgramId) ? null - : _timerProvider.GetTimerByProgramId(timer.ProgramId); - } + : _timerProvider.GetTimerByProgramId(timer.ProgramId)); if (existingTimer == null) { diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1926e738f..9af65cabb 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -787,14 +787,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var channelNumber = GetChannelNumber(channel); - var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)); - if (station == null) - { - station = new ScheduleDirect.Station + var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)) + ?? new ScheduleDirect.Station { stationID = channel.stationID }; - } var channelInfo = new ChannelInfo { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 63a3146aa..1145d8aa1 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -987,10 +987,7 @@ namespace Emby.Server.Implementations.LiveTv var externalProgramId = programTuple.Item2; string externalSeriesId = programTuple.Item3; - if (timerList == null) - { - timerList = (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; - } + timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase)); var foundSeriesTimer = false; @@ -1018,10 +1015,7 @@ namespace Emby.Server.Implementations.LiveTv continue; } - if (seriesTimerList == null) - { - seriesTimerList = (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items; - } + seriesTimerList ??= (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items; var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase)); @@ -1974,10 +1968,7 @@ namespace Emby.Server.Implementations.LiveTv }; } - if (service == null) - { - service = _services[0]; - } + service ??= _services[0]; var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index c32ca2fb6..4aa5832b1 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -421,10 +421,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun string audioCodec = channelInfo.AudioCodec; - if (!videoBitrate.HasValue) - { - videoBitrate = isHd ? 15000000 : 2000000; - } + videoBitrate ??= isHd ? 15000000 : 2000000; int? audioBitrate = isHd ? 448000 : 192000; diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index fd2ee6b7a..14df20936 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -44,12 +44,7 @@ namespace Emby.Server.Implementations.Plugins { get { - if (_httpClientFactory == null) - { - _httpClientFactory = _appHost.Resolve(); - } - - return _httpClientFactory; + return _httpClientFactory ?? (_httpClientFactory = _appHost.Resolve()); } } @@ -276,11 +271,7 @@ namespace Emby.Server.Implementations.Plugins // If no version is given, return the current instance. var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); - plugin = plugins.FirstOrDefault(p => p.Instance != null); - if (plugin == null) - { - plugin = plugins.OrderByDescending(p => p.Version).FirstOrDefault(); - } + plugin = plugins.FirstOrDefault(p => p.Instance != null) ?? plugins.OrderByDescending(p => p.Version).FirstOrDefault(); } else { diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 61dccaa19..101d9b537 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -301,12 +301,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { get { - if (_id == null) - { - _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); - } - - return _id; + return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); } } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6f21ec31e..6844152ea 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1475,10 +1475,7 @@ namespace Emby.Server.Implementations.Session user = _userManager.GetUserById(request.UserId); } - if (user == null) - { - user = _userManager.GetUserByName(request.Username); - } + user ??= _userManager.GetUserByName(request.Username); if (enforcePassword) { diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index adec86a10..7a6130719 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -207,12 +207,7 @@ namespace Jellyfin.Api.Controllers var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)); // Select the un-instanced one first. - var plugin = plugins.FirstOrDefault(p => p.Instance == null); - if (plugin == null) - { - // Then by the status. - plugin = plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); - } + var plugin = plugins.FirstOrDefault(p => p.Instance == null) ?? plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); if (plugin != null) { diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 6c22050a7..73bdf9018 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -228,10 +228,7 @@ namespace Jellyfin.Api.Controllers itemWithImage = GetParentWithImage(item, ImageType.Thumb); } - if (itemWithImage == null) - { - itemWithImage = GetParentWithImage(item, ImageType.Thumb); - } + itemWithImage ??= GetParentWithImage(item, ImageType.Thumb); if (itemWithImage != null) { diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 583e613b4..8cffe9c4c 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -292,10 +292,7 @@ namespace Jellyfin.Api.Helpers } } - if (profile == null) - { - profile = dlnaManager.GetDefaultProfile(); - } + profile ??= dlnaManager.GetDefaultProfile(); var audioCodec = state.ActualOutputAudioCodec; diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index fb3ef9b12..7156ce618 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -400,10 +400,7 @@ namespace MediaBrowser.Common.Net private bool ResolveHost() { // When was the last time we resolved? - if (_lastResolved == null) - { - _lastResolved = DateTime.UtcNow; - } + _lastResolved ??= DateTime.UtcNow; // If we haven't resolved before, or our timer has run out... if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout))) diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index 99c226f50..e074cc6a0 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -105,10 +105,7 @@ namespace MediaBrowser.Common.Plugins { lock (_configurationSyncLock) { - if (_configuration == null) - { - _configuration = LoadConfiguration(); - } + _configuration ??= LoadConfiguration(); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 1b69c6646..32ae15498 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -106,15 +106,10 @@ namespace MediaBrowser.Controller.Entities { get { - if (_themeSongIds == null) - { - _themeSongIds = GetExtras() - .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong) - .Select(song => song.Id) - .ToArray(); - } - - return _themeSongIds; + return _themeSongIds ??= GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong) + .Select(song => song.Id) + .ToArray(); } private set @@ -128,15 +123,10 @@ namespace MediaBrowser.Controller.Entities { get { - if (_themeVideoIds == null) - { - _themeVideoIds = GetExtras() - .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo) - .Select(song => song.Id) - .ToArray(); - } - - return _themeVideoIds; + return _themeVideoIds ??= GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo) + .Select(song => song.Id) + .ToArray(); } private set diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index b1da4d64c..fec83dd94 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -75,10 +75,7 @@ namespace MediaBrowser.Controller.Entities public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { - if (query == null) - { - query = new InternalItemsQuery(user); - } + query ??= new InternalItemsQuery(user); query.EnableTotalRecordCount = false; var result = GetItemList(query); diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index f86f7df25..f9086066d 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -147,10 +147,7 @@ namespace MediaBrowser.Controller.Library throw new ArgumentException("The path was empty or null.", nameof(path)); } - if (AdditionalLocations == null) - { - AdditionalLocations = new List(); - } + AdditionalLocations ??= new List(); AdditionalLocations.Add(path); } diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index c9c168c4c..3c93cfc79 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -126,10 +126,7 @@ namespace MediaBrowser.Controller.Playlists private List GetPlayableItems(User user, InternalItemsQuery query) { - if (query == null) - { - query = new InternalItemsQuery(user); - } + query ??= new InternalItemsQuery(user); query.IsFolder = false; diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index b92b83701..db0ef7072 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -45,10 +45,7 @@ namespace MediaBrowser.Controller.Providers if (copy.RefreshPaths != null && copy.RefreshPaths.Length > 0) { - if (RefreshPaths == null) - { - RefreshPaths = Array.Empty(); - } + RefreshPaths ??= Array.Empty(); RefreshPaths = copy.RefreshPaths.ToArray(); } diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index 864cb3050..98c7eadfe 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -37,10 +37,7 @@ namespace MediaBrowser.Controller.Providers public void AddPerson(PersonInfo p) { - if (People == null) - { - People = new List(); - } + People ??= new List(); PeopleHelper.AddPerson(People, p); } @@ -54,16 +51,15 @@ namespace MediaBrowser.Controller.Providers { People = new List(); } - - People.Clear(); + else + { + People.Clear(); + } } public UserItemData GetOrAddUserData(string userId) { - if (UserDataList == null) - { - UserDataList = new List(); - } + UserDataList ??= new List(); UserItemData userData = null; diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index ee2e5fcde..2e96f8cb0 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1187,43 +1187,28 @@ namespace MediaBrowser.MediaEncoding.Probing FetchStudios(audio, tags, "label"); // These support mulitple values, but for now we only store the first. - var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); - } + var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); audio.SetProviderId(MetadataProvider.MusicBrainzArtist, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); audio.SetProviderId(MetadataProvider.MusicBrainzTrack, mb); } @@ -1290,15 +1275,7 @@ namespace MediaBrowser.MediaEncoding.Probing private IEnumerable GetSplitWhitelist() { - if (_splitWhiteList == null) - { - _splitWhiteList = new List - { - "AC/DC" - }; - } - - return _splitWhiteList; + return _splitWhiteList ??= new List { "AC/DC" }; } /// diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 09d14dc6a..ce4b0ec92 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -123,10 +123,7 @@ namespace MediaBrowser.Model.Entities else { // Ensure it exists - if (instance.ProviderIds == null) - { - instance.ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + instance.ProviderIds ??= new Dictionary(StringComparer.OrdinalIgnoreCase); instance.ProviderIds[name] = value; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 74849a522..f049cc81f 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -111,10 +111,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (streamFileNames == null) - { - streamFileNames = Array.Empty(); - } + streamFileNames ??= Array.Empty(); mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 1f3d9acff..8d62343cb 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -256,10 +256,7 @@ namespace MediaBrowser.Providers.Subtitles } catch (Exception ex) { - if (exceptionToThrow == null) - { - exceptionToThrow = ex; - } + exceptionToThrow ??= ex; } finally { diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 8f1f0fa61..f448ad38b 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -415,10 +415,7 @@ namespace Rssdp.Infrastructure { lock (_SendSocketSynchroniser) { - if (_sendSockets == null) - { - _sendSockets = CreateSocketAndListenForResponsesAsync(); - } + _sendSockets ??= CreateSocketAndListenForResponsesAsync(); } } } From e432796f6f0f500830b1c90c233c4e4c07287190 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 5 May 2021 14:39:50 +0200 Subject: [PATCH 135/612] Minor improvements --- .../Probing/ProbeResultNormalizer.cs | 11 ++++------- MediaBrowser.Providers/Subtitles/SubtitleManager.cs | 12 ++++++------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 2e96f8cb0..884ec0a29 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.MediaEncoding.Probing private readonly ILogger _logger; private readonly ILocalizationManager _localization; - private List _splitWhiteList = null; + private string[] _splitWhiteList; public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization) { @@ -37,6 +37,8 @@ namespace MediaBrowser.MediaEncoding.Probing _localization = localization; } + private IReadOnlyList SplitWhitelist => _splitWhiteList ??= new string[] { "AC/DC" }; + public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol) { var info = new MediaInfo @@ -1254,7 +1256,7 @@ namespace MediaBrowser.MediaEncoding.Probing var artistsFound = new List(); - foreach (var whitelistArtist in GetSplitWhitelist()) + foreach (var whitelistArtist in SplitWhitelist) { var originalVal = val; val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase); @@ -1273,11 +1275,6 @@ namespace MediaBrowser.MediaEncoding.Probing return artistsFound; } - private IEnumerable GetSplitWhitelist() - { - return _splitWhiteList ??= new List { "AC/DC" }; - } - /// /// Gets the studios from the tags collection. /// diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 8d62343cb..bf0c853ae 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -207,7 +207,7 @@ namespace MediaBrowser.Providers.Subtitles { var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName)); // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path."); - if (mediaFolderPath.StartsWith(video.ContainingFolderPath)) + if (mediaFolderPath.StartsWith(video.ContainingFolderPath, StringComparison.Ordinal)) { savePaths.Add(mediaFolderPath); } @@ -216,7 +216,7 @@ namespace MediaBrowser.Providers.Subtitles var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path."); - if (internalPath.StartsWith(video.GetInternalMetadataPath())) + if (internalPath.StartsWith(video.GetInternalMetadataPath(), StringComparison.Ordinal)) { savePaths.Add(internalPath); } @@ -234,7 +234,7 @@ namespace MediaBrowser.Providers.Subtitles private async Task TrySaveToFiles(Stream stream, List savePaths) { - Exception exceptionToThrow = null; + List exs = null; foreach (var savePath in savePaths) { @@ -256,7 +256,7 @@ namespace MediaBrowser.Providers.Subtitles } catch (Exception ex) { - exceptionToThrow ??= ex; + (exs ??= new List()).Add(ex); } finally { @@ -266,9 +266,9 @@ namespace MediaBrowser.Providers.Subtitles stream.Position = 0; } - if (exceptionToThrow != null) + if (exs != null) { - throw exceptionToThrow; + throw new AggregateException(exs); } } From 4479713e047f0e51450ed343472d8c43ab57e00f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 5 May 2021 14:44:53 +0200 Subject: [PATCH 136/612] MediaStream: Replace string.IndexOf with string.Contains where possible --- MediaBrowser.Model/Entities/MediaStream.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index ade9d7e8d..e644c9ba7 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -163,7 +163,7 @@ namespace MediaBrowser.Model.Entities foreach (var tag in attributes) { // Keep Tags that are not already in Title. - if (Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) + if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) { result.Append(" - ").Append(tag); } @@ -202,7 +202,7 @@ namespace MediaBrowser.Model.Entities foreach (var tag in attributes) { // Keep Tags that are not already in Title. - if (Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) + if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) { result.Append(" - ").Append(tag); } @@ -522,9 +522,9 @@ namespace MediaBrowser.Model.Entities // sub = external .sub file - return codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) == -1 && - codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) == -1 && - codec.IndexOf("dvbsub", StringComparison.OrdinalIgnoreCase) == -1 && + return !codec.Contains("pgs", StringComparison.OrdinalIgnoreCase) && + !codec.Contains("dvd", StringComparison.OrdinalIgnoreCase) && + !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase) && !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase); } From 787bcd4a83ea212e6ba4f93dcc4ce6788b950410 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 5 May 2021 14:45:08 +0200 Subject: [PATCH 137/612] Remove dead code --- .../Entities/PeopleHelper.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/MediaBrowser.Controller/Entities/PeopleHelper.cs b/MediaBrowser.Controller/Entities/PeopleHelper.cs index 1f3758a73..687ce1ec8 100644 --- a/MediaBrowser.Controller/Entities/PeopleHelper.cs +++ b/MediaBrowser.Controller/Entities/PeopleHelper.cs @@ -100,23 +100,5 @@ namespace MediaBrowser.Controller.Entities existing.SetProviderId(id.Key, id.Value); } } - - public static bool ContainsPerson(List people, string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - foreach (var i in people) - { - if (string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } } } From bcb4010db615b7c732856629553cba4e7ccc3358 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 5 May 2021 15:30:32 +0200 Subject: [PATCH 138/612] More improvements --- .../Library/Resolvers/ItemResolver.cs | 12 ++++++------ .../Library/Resolvers/Movies/MovieResolver.cs | 2 +- .../Library/Resolvers/PhotoAlbumResolver.cs | 12 ++++++------ .../Library/Resolvers/PhotoResolver.cs | 2 +- .../Library/Resolvers/TV/SeasonResolver.cs | 2 +- .../Library/Resolvers/TV/SeriesResolver.cs | 18 ++---------------- .../Entities/CollectionFolder.cs | 1 - .../Library/ItemResolveArgs.cs | 18 +++++++++--------- .../Resolvers/BaseItemResolver.cs | 12 ++++++------ 9 files changed, 32 insertions(+), 47 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs index 9ca76095b..92fb2a753 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs @@ -11,6 +11,12 @@ namespace Emby.Server.Implementations.Library.Resolvers public abstract class ItemResolver : IItemResolver where T : BaseItem, new() { + /// + /// Gets the priority. + /// + /// The priority. + public virtual ResolverPriority Priority => ResolverPriority.First; + /// /// Resolves the specified args. /// @@ -21,12 +27,6 @@ namespace Emby.Server.Implementations.Library.Resolvers return null; } - /// - /// Gets the priority. - /// - /// The priority. - public virtual ResolverPriority Priority => ResolverPriority.First; - /// /// Sets initial values on the newly resolved item. /// diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 714bc3a84..16bf4dc4a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -376,7 +376,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies { var multiDiscFolders = new List(); - var libraryOptions = args.GetLibraryOptions(); + var libraryOptions = args.LibraryOptions; var supportPhotos = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && libraryOptions.EnablePhotos; var photos = new List(); diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 3ac837057..204c8a62e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Library.Resolvers public class PhotoAlbumResolver : FolderResolver { private readonly IImageProcessor _imageProcessor; - private ILibraryManager _libraryManager; + private readonly ILibraryManager _libraryManager; /// /// Initializes a new instance of the class. @@ -26,6 +26,9 @@ namespace Emby.Server.Implementations.Library.Resolvers _libraryManager = libraryManager; } + /// + public override ResolverPriority Priority => ResolverPriority.Second; + /// /// Resolves the specified args. /// @@ -39,8 +42,8 @@ namespace Emby.Server.Implementations.Library.Resolvers // Must be an image file within a photo collection var collectionType = args.GetCollectionType(); - if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) || - (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) + if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) + || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos)) { if (HasPhotos(args)) { @@ -84,8 +87,5 @@ namespace Emby.Server.Implementations.Library.Resolvers return false; } - - /// - public override ResolverPriority Priority => ResolverPriority.Second; } } diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index bcfcee9c6..3cb6542cf 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library.Resolvers var collectionType = args.CollectionType; if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) - || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) + || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos)) { if (IsImageFile(args.Path, _imageProcessor)) { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 3332e1806..768e2e4f5 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV CultureInfo.InvariantCulture, _localization.GetLocalizedString("NameSeasonNumber"), seasonNumber, - args.GetLibraryOptions().PreferredMetadataLanguage); + args.LibraryOptions.PreferredMetadataLanguage); } return season; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 4a9d2cf8c..8fc3e3e75 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -19,19 +19,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// public class SeriesResolver : FolderResolver { - private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; /// /// Initializes a new instance of the class. /// - /// The file system. /// The logger. /// The library manager. - public SeriesResolver(IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager) + public SeriesResolver(ILogger logger, ILibraryManager libraryManager) { - _fileSystem = fileSystem; _logger = logger; _libraryManager = libraryManager; } @@ -59,15 +56,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var collectionType = args.GetCollectionType(); if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { - // if (args.ContainsFileSystemEntryByName("tvshow.nfo")) - //{ - // return new Series - // { - // Path = args.Path, - // Name = Path.GetFileName(args.Path) - // }; - //} - var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path); if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { @@ -100,7 +88,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return null; } - if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, false)) + if (IsSeriesFolder(args.Path, args.FileSystemChildren, _logger, _libraryManager, false)) { return new Series { @@ -117,8 +105,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV public static bool IsSeriesFolder( string path, IEnumerable fileSystemChildren, - IDirectoryService directoryService, - IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, bool isTvContentType) diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 347d5b73c..bc5f38256 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -61,7 +61,6 @@ namespace MediaBrowser.Controller.Entities try { var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) as LibraryOptions; - if (result == null) { return new LibraryOptions(); diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index f9086066d..5f9aed341 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -14,14 +14,14 @@ namespace MediaBrowser.Controller.Library /// These are arguments relating to the file system that are collected once and then referred to /// whenever needed. Primarily for entity resolution. /// - public class ItemResolveArgs : EventArgs + public class ItemResolveArgs { /// /// The _app paths. /// private readonly IServerApplicationPaths _appPaths; - public IDirectoryService DirectoryService { get; private set; } + private LibraryOptions _libraryOptions; /// /// Initializes a new instance of the class. @@ -34,17 +34,18 @@ namespace MediaBrowser.Controller.Library DirectoryService = directoryService; } + public IDirectoryService DirectoryService { get; } + /// /// Gets the file system children. /// /// The file system children. public FileSystemMetadata[] FileSystemChildren { get; set; } - public LibraryOptions LibraryOptions { get; set; } - - public LibraryOptions GetLibraryOptions() + public LibraryOptions LibraryOptions { - return LibraryOptions ?? (LibraryOptions = Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent)); + get => _libraryOptions ??= Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent); + set => _libraryOptions = value; } /// @@ -139,7 +140,7 @@ namespace MediaBrowser.Controller.Library /// Adds the additional location. /// /// The path. - /// + /// is null or empty. public void AddAdditionalLocation(string path) { if (string.IsNullOrEmpty(path)) @@ -148,7 +149,6 @@ namespace MediaBrowser.Controller.Library } AdditionalLocations ??= new List(); - AdditionalLocations.Add(path); } @@ -172,7 +172,7 @@ namespace MediaBrowser.Controller.Library /// /// The name. /// FileSystemInfo. - /// + /// is null or empty. public FileSystemMetadata GetFileSystemEntryByName(string name) { if (string.IsNullOrEmpty(name)) diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs index 25128a5cd..a904c7424 100644 --- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs @@ -10,6 +10,12 @@ namespace MediaBrowser.Controller.Resolvers public abstract class ItemResolver : IItemResolver where T : BaseItem, new() { + /// + /// Gets the priority. + /// + /// The priority. + public virtual ResolverPriority Priority => ResolverPriority.First; + /// /// Resolves the specified args. /// @@ -20,12 +26,6 @@ namespace MediaBrowser.Controller.Resolvers return null; } - /// - /// Gets the priority. - /// - /// The priority. - public virtual ResolverPriority Priority => ResolverPriority.First; - /// /// Sets initial values on the newly resolved item. /// From 47e7c1356c1364e5e834ce37cd4a9ace5e2d838e Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 5 May 2021 16:43:20 +0200 Subject: [PATCH 139/612] PathExtensions: Fix index out of bounds in TryReplaceSubPath Fixes #5977 --- Emby.Server.Implementations/Library/PathExtensions.cs | 10 ++++++++-- .../Library/PathExtensionsTests.cs | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 770cf6bb0..0de4edb7e 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -96,8 +96,14 @@ 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 var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar; - if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase) - || (!oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar)) + if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (path.Length > subPath.Length + && !oldSubPathEndsWithSeparator + && path[subPath.Length] != newDirectorySeparatorChar) { return false; } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index e5508243f..c5cc056f5 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -33,6 +33,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library [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")] + [InlineData("/o", "/o", "/s", "/s")] // regression test for #5977 public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, string? expectedResult) { Assert.True(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result)); From 81d675990f87586061bdce5d585dad28f7e181fa Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 5 May 2021 22:52:39 +0100 Subject: [PATCH 140/612] Enable automatic url decoding --- .../ApiApplicationBuilderExtensions.cs | 10 +++ .../QueryStringDecodingMiddleware.cs | 42 +++++++++++ .../Middleware/UrlDecodeQueryFeature.cs | 75 +++++++++++++++++++ Jellyfin.Server/Startup.cs | 1 + 4 files changed, 128 insertions(+) create mode 100644 Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 88e2b4152..e29167747 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -78,6 +78,16 @@ namespace Jellyfin.Server.Extensions return appBuilder.UseMiddleware(); } + /// + /// Enables url decoding before binding to the application pipeline. + /// + /// The . + /// The updated application builder. + public static IApplicationBuilder UseQueryStringDecoding(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware(); + } + /// /// Adds base url redirection to the application pipeline. /// diff --git a/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs b/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs new file mode 100644 index 000000000..954dc4ccd --- /dev/null +++ b/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Middleware +{ + /// + /// URL decodes the querystring before binding. + /// + public class QueryStringDecodingMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + /// The logger. + public QueryStringDecodingMiddleware( + RequestDelegate next, + ILogger logger) + { + _next = next; + _logger = logger; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The async task. + public async Task Invoke(HttpContext httpContext) + { + httpContext.Features.Set(new UrlDecodeQueryFeature(httpContext.Features.Get())); + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs new file mode 100644 index 000000000..0232b89ce --- /dev/null +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Primitives; + +namespace Jellyfin.Server.Middleware +{ + /// + /// Defines the . + /// + public class UrlDecodeQueryFeature : IQueryFeature + { + private IQueryCollection? _store; + + /// + /// Initializes a new instance of the class. + /// + /// The instance. + public UrlDecodeQueryFeature(IQueryFeature feature) + { + Query = feature.Query; + } + + /// + /// Gets or sets a value indicating the url decoded . + /// + public IQueryCollection Query + { + get + { + return _store ?? QueryCollection.Empty; + } + + set + { + // Only interested in where the querystring is encoded which shows up as one key with everything else in the value. + if (value.Count != 1) + { + _store = value; + return; + } + + // Encoded querystrings have no value, so don't process anything if a values is present. + var kvp = value.First(); + if (!string.IsNullOrEmpty(kvp.Value)) + { + _store = value; + return; + } + + // Unencode and re-parse querystring. + var unencodedKey = HttpUtility.UrlDecode(kvp.Key); + + if (string.Equals(unencodedKey, kvp.Key, System.StringComparison.Ordinal)) + { + _store = value; + return; + } + + var pairs = new Dictionary(); + var queryString = unencodedKey.Split('&', System.StringSplitOptions.RemoveEmptyEntries); + + foreach (var pair in queryString) + { + var item = pair.Split('=', System.StringSplitOptions.RemoveEmptyEntries); + pairs.Add(item[0], new StringValues(item.Length == 2 ? item[1] : string.Empty)); + } + + _store = new QueryCollection(pairs); + } + } + } +} diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index e56e61092..f27d28658 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -150,6 +150,7 @@ namespace Jellyfin.Server mainApp.UseAuthentication(); mainApp.UseJellyfinApiSwagger(_serverConfigurationManager); + mainApp.UseQueryStringDecoding(); mainApp.UseRouting(); mainApp.UseAuthorization(); From a7bccd4fe0a1c046e335a91e85b8e806b50d58bc Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 5 May 2021 23:09:04 +0100 Subject: [PATCH 141/612] removed unneeded logger. --- Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs b/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs index 954dc4ccd..d6d955c4f 100644 --- a/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs +++ b/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs @@ -12,19 +12,15 @@ namespace Jellyfin.Server.Middleware public class QueryStringDecodingMiddleware { private readonly RequestDelegate _next; - private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The next delegate in the pipeline. - /// The logger. public QueryStringDecodingMiddleware( - RequestDelegate next, - ILogger logger) + RequestDelegate next) { _next = next; - _logger = logger; } /// From dabeabc553afc4551356566f9184e6659f604cee Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 5 May 2021 23:14:05 +0100 Subject: [PATCH 142/612] corrected comments --- Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs index 0232b89ce..b18ba7051 100644 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -35,14 +35,14 @@ namespace Jellyfin.Server.Middleware set { - // Only interested in where the querystring is encoded which shows up as one key with everything else in the value. + // Only interested in where the querystring is encoded which shows up as one key with nothing in the value. if (value.Count != 1) { _store = value; return; } - // Encoded querystrings have no value, so don't process anything if a values is present. + // Encoded querystrings have no value, so don't process anything if a value is present. var kvp = value.First(); if (!string.IsNullOrEmpty(kvp.Value)) { @@ -55,6 +55,7 @@ namespace Jellyfin.Server.Middleware if (string.Equals(unencodedKey, kvp.Key, System.StringComparison.Ordinal)) { + // Don't do anything if it's not encoded. _store = value; return; } From c8061f92bec0d1e12e2b57012c6c72c629374327 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 5 May 2021 23:15:24 +0100 Subject: [PATCH 143/612] slight format correction. --- Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs b/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs index d6d955c4f..08fbbce0b 100644 --- a/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs +++ b/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs @@ -17,8 +17,7 @@ namespace Jellyfin.Server.Middleware /// Initializes a new instance of the class. /// /// The next delegate in the pipeline. - public QueryStringDecodingMiddleware( - RequestDelegate next) + public QueryStringDecodingMiddleware(RequestDelegate next) { _next = next; } From ddb04dc12b8bdf33c2020cb1c539664463e61bc3 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 8 Jan 2021 23:57:27 +0100 Subject: [PATCH 144/612] Use new ReadAllLines extensions --- .../LiveTv/EmbyTV/EncodedRecorder.cs | 7 ++-- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 14 ++++---- .../LiveTv/TunerHosts/M3uParser.cs | 34 +++++++------------ .../Localization/LocalizationManager.cs | 10 +++--- Jellyfin.Api/Helpers/HlsHelpers.cs | 5 +-- .../Extensions/StreamExtensions.cs | 20 +++++++++-- .../Studios/StudiosImageProvider.cs | 9 +++-- 7 files changed, 48 insertions(+), 51 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 44a8cdee4..9372b0f6c 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -10,6 +10,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; @@ -307,13 +308,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { using (var reader = new StreamReader(source)) { - while (!reader.EndOfStream) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); - await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await target.WriteAsync(bytes.AsMemory()).ConfigureAwait(false); await target.FlushAsync().ConfigureAwait(false); } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 4aa5832b1..324109bcf 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -182,16 +182,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var sr = new StreamReader(stream, System.Text.Encoding.UTF8); var tuners = new List(); - while (!sr.EndOfStream) + await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false)) { - string line = StripXML(sr.ReadLine()); - if (line.Contains("Channel", StringComparison.Ordinal)) + string stripedLine = StripXML(line); + if (stripedLine.Contains("Channel", StringComparison.Ordinal)) { LiveTvTunerStatus status; - var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); - var name = line.Substring(0, index - 1); - var currentChannel = line.Substring(index + 7); - if (currentChannel != "none") + var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); + var name = stripedLine.Substring(0, index - 1); + var currentChannel = stripedLine.Substring(index + 7); + if (string.Equals(currentChannel, "none", StringComparison.Ordinal)) { status = LiveTvTunerStatus.LiveTv; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index cc30a516d..84d416149 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -35,16 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts // Read the file and display it line by line. using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false))) { - return GetChannels(reader, channelIdPrefix, info.Id); - } - } - - public List ParseString(string text, string channelIdPrefix, string tunerHostId) - { - // Read the file and display it line by line. - using (var reader = new StringReader(text)) - { - return GetChannels(reader, channelIdPrefix, tunerHostId); + return await GetChannelsAsync(reader, channelIdPrefix, info.Id).ConfigureAwait(false); } } @@ -70,43 +61,42 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private const string ExtInfPrefix = "#EXTINF:"; - private List GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId) + private async Task> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId) { var channels = new List(); - string line; string extInf = string.Empty; - while ((line = reader.ReadLine()) != null) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { - line = line.Trim(); - if (string.IsNullOrWhiteSpace(line)) + var trimmedLine = line.Trim(); + if (string.IsNullOrWhiteSpace(trimmedLine)) { continue; } - if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase)) + if (trimmedLine.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase)) { continue; } - if (line.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase)) + if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase)) { - extInf = line.Substring(ExtInfPrefix.Length).Trim(); + extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim(); _logger.LogInformation("Found m3u channel: {0}", extInf); } - else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith('#')) + else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#')) { - var channel = GetChannelnfo(extInf, tunerHostId, line); + var channel = GetChannelnfo(extInf, tunerHostId, trimmedLine); if (string.IsNullOrWhiteSpace(channel.Id)) { - channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture); + channel.Id = channelIdPrefix + trimmedLine.GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture); } - channel.Path = line; + channel.Path = trimmedLine; channels.Add(channel); extInf = string.Empty; } diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 46858b4fb..220e423bf 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reflection; using System.Text.Json; using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; @@ -72,8 +73,7 @@ namespace Emby.Server.Implementations.Localization using (var str = _assembly.GetManifestResourceStream(resource)) using (var reader = new StreamReader(str)) { - string line; - while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { if (string.IsNullOrWhiteSpace(line)) { @@ -118,10 +118,8 @@ namespace Emby.Server.Implementations.Localization using (var stream = _assembly.GetManifestResourceStream(ResourcePath)) using (var reader = new StreamReader(stream)) { - while (!reader.EndOfStream) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(line)) { continue; @@ -179,7 +177,7 @@ namespace Emby.Server.Implementations.Localization /// public IEnumerable GetCountries() { - StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json")); + using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json")); return JsonSerializer.Deserialize>(reader.ReadToEnd(), _jsonOptions); } diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index 18e23fb5c..d0666034e 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -118,10 +118,7 @@ namespace Jellyfin.Api.Helpers /// The playlist text as a string. public static string GetLivePlaylistText(string path, StreamState state) { - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var reader = new StreamReader(stream); - - var text = reader.ReadToEnd(); + var text = File.ReadAllText(path); var segmentFormat = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Common/Extensions/StreamExtensions.cs b/MediaBrowser.Common/Extensions/StreamExtensions.cs index cd77be7b2..d49bf1d46 100644 --- a/MediaBrowser.Common/Extensions/StreamExtensions.cs +++ b/MediaBrowser.Common/Extensions/StreamExtensions.cs @@ -35,11 +35,11 @@ namespace MediaBrowser.Common.Extensions } /// - /// Reads all lines in the . + /// Reads all lines in the . /// - /// The to read from. + /// The to read from. /// All lines in the stream. - public static IEnumerable ReadAllLines(this StreamReader reader) + public static IEnumerable ReadAllLines(this TextReader reader) { string? line; while ((line = reader.ReadLine()) != null) @@ -47,5 +47,19 @@ namespace MediaBrowser.Common.Extensions yield return line; } } + + /// + /// Reads all lines in the . + /// + /// The to read from. + /// All lines in the stream. + public static async IAsyncEnumerable ReadAllLinesAsync(this TextReader reader) + { + string? line; + while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) + { + yield return line; + } + } } } diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 5fcf6d9aa..5ec9a02cb 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -177,13 +178,11 @@ namespace MediaBrowser.Providers.Studios { var lines = new List(); - while (!reader.EndOfStream) + foreach (var line in reader.ReadAllLines()) { - var text = reader.ReadLine(); - - if (!string.IsNullOrWhiteSpace(text)) + if (!string.IsNullOrWhiteSpace(line)) { - lines.Add(text); + lines.Add(line); } } From 8eee32c8c2835382c2f7c7d959dd034a50f86e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Kr=C3=B6nke?= Date: Thu, 6 May 2021 18:56:13 +0200 Subject: [PATCH 145/612] Respect configured JELLYFIN_USER in Debian's postinst In my setup I configured a different user. But updates keep "stealing" file permissions for my `$PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA` directories. --- debian/postinst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/debian/postinst b/debian/postinst index 860222e05..2f9c4cffb 100644 --- a/debian/postinst +++ b/debian/postinst @@ -9,6 +9,8 @@ if [[ -f $DEFAULT_FILE ]]; then . $DEFAULT_FILE fi +JELLYFIN_USER=${JELLYFIN_USER:-jellyfin} + # Data directories for program data (cache, db), configs, and logs PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} @@ -18,12 +20,12 @@ CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} case "$1" in configure) # create jellyfin group if it does not exist - if [[ -z "$(getent group jellyfin)" ]]; then - addgroup --quiet --system jellyfin > /dev/null 2>&1 + if [[ -z "$(getent group ${JELLYFIN_USER})" ]]; then + addgroup --quiet --system ${JELLYFIN_USER} > /dev/null 2>&1 fi # create jellyfin user if it does not exist - if [[ -z "$(getent passwd jellyfin)" ]]; then - adduser --system --ingroup jellyfin --shell /bin/false jellyfin --no-create-home --home ${PROGRAMDATA} \ + if [[ -z "$(getent passwd ${JELLYFIN_USER})" ]]; then + adduser --system --ingroup ${JELLYFIN_USER} --shell /bin/false ${JELLYFIN_USER} --no-create-home --home ${PROGRAMDATA} \ --gecos "Jellyfin default user" > /dev/null 2>&1 fi # ensure $PROGRAMDATA exists @@ -43,7 +45,7 @@ case "$1" in mkdir $CACHEDATA fi # Ensure permissions are correct on all config directories - chown -R jellyfin $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA + chown -R ${JELLYFIN_USER} $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA chgrp adm $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA chmod 0750 $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA From 3cd57cb28703a078cc4c03e0af53a6b2e166a679 Mon Sep 17 00:00:00 2001 From: Jake King Date: Thu, 6 May 2021 19:50:00 +0100 Subject: [PATCH 146/612] Fixes for Book Progress - Ignore Books when checking for minium progress --- Emby.Server.Implementations/Library/UserDataManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 827e3c64b..d7e4e2af7 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -220,7 +220,7 @@ namespace Emby.Server.Implementations.Library var hasRuntime = runtimeTicks > 0; // If a position has been reported, and if we know the duration - if (positionTicks > 0 && hasRuntime && !(item is AudioBook)) + if (positionTicks > 0 && hasRuntime && !(item is AudioBook) && !(item is Book)) { var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100; @@ -239,7 +239,7 @@ namespace Emby.Server.Implementations.Library { // Enforce MinResumeDuration var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds; - if (durationSeconds < _config.Configuration.MinResumeDurationSeconds && !(item is Book)) + if (durationSeconds < _config.Configuration.MinResumeDurationSeconds) { positionTicks = 0; data.Played = playedToCompletion = true; From ad1d9d9a23c4bff7a485a61fe41639a7d1b3491e Mon Sep 17 00:00:00 2001 From: wehrstedt Date: Thu, 6 May 2021 23:07:32 +0200 Subject: [PATCH 147/612] fixed no channel icons when using tvheadend (#5996) Co-authored-by: Cody Robibero Co-authored-by: Maximilian Wehrstedt --- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 08c92e15a..dd497845d 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -167,7 +167,7 @@ namespace MediaBrowser.Providers.Manager throw new HttpRequestException("Invalid image received.", null, response.StatusCode); } - var contentType = response.Content.Headers.ContentType.MediaType; + var contentType = response.Content.Headers.ContentType?.MediaType; // Workaround for tvheadend channel icons // TODO: Isolate this hack into the tvh plugin From 4b9a64c18cff58938dab0c0770147a5e48c833f9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 6 May 2021 23:16:52 +0200 Subject: [PATCH 148/612] Abstract JsonDelimitedArrayConverter --- .../JsonCommaDelimitedArrayConverter.cs | 63 +-------------- .../Converters/JsonDelimitedArrayConverter.cs | 81 +++++++++++++++++++ .../JsonPipeDelimitedArrayConverter.cs | 64 +-------------- 3 files changed, 87 insertions(+), 121 deletions(-) create mode 100644 MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs index 55c4665e8..127a41a06 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -9,73 +9,16 @@ namespace MediaBrowser.Common.Json.Converters /// Convert comma delimited string to array of type. /// /// Type to convert to. - public class JsonCommaDelimitedArrayConverter : JsonConverter + public sealed class JsonCommaDelimitedArrayConverter : JsonDelimitedArrayConverter { - private readonly TypeConverter _typeConverter; - /// /// Initializes a new instance of the class. /// - public JsonCommaDelimitedArrayConverter() + public JsonCommaDelimitedArrayConverter() : base() { - _typeConverter = TypeDescriptor.GetConverter(typeof(T)); } /// - public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - return null; - } - - if (reader.TokenType == JsonTokenType.String) - { - // GetString can't return null here because we already handled it above - var stringEntries = reader.GetString()!.Split(',', StringSplitOptions.RemoveEmptyEntries); - if (stringEntries.Length == 0) - { - return Array.Empty(); - } - - var parsedValues = new object[stringEntries.Length]; - var convertedCount = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - try - { - parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()); - convertedCount++; - } - catch (FormatException) - { - // TODO log when upgraded to .Net6 - // https://github.com/dotnet/runtime/issues/42975 - // _logger.LogDebug(e, "Error converting value."); - } - } - - var typedValues = new T[convertedCount]; - var typedValueIndex = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - if (parsedValues[i] != null) - { - typedValues.SetValue(parsedValues[i], typedValueIndex); - typedValueIndex++; - } - } - - return typedValues; - } - - return JsonSerializer.Deserialize(ref reader, options); - } - - /// - public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } + protected override char Delimiter => ','; } } diff --git a/MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs new file mode 100644 index 000000000..b691798c9 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs @@ -0,0 +1,81 @@ +using System; +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Convert delimited string to array of type. + /// + /// Type to convert to. + public abstract class JsonDelimitedArrayConverter : JsonConverter + { + private readonly TypeConverter _typeConverter; + + /// + /// Initializes a new instance of the class. + /// + protected JsonDelimitedArrayConverter() + { + _typeConverter = TypeDescriptor.GetConverter(typeof(T)); + } + + /// + /// Gets the array delimiter. + /// + protected virtual char Delimiter { get; } + + /// + public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + // GetString can't return null here because we already handled it above + var stringEntries = reader.GetString()?.Split(Delimiter, StringSplitOptions.RemoveEmptyEntries); + if (stringEntries == null || stringEntries.Length == 0) + { + return Array.Empty(); + } + + var parsedValues = new object[stringEntries.Length]; + var convertedCount = 0; + for (var i = 0; i < stringEntries.Length; i++) + { + try + { + parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()); + convertedCount++; + } + catch (FormatException) + { + // TODO log when upgraded to .Net6 + // https://github.com/dotnet/runtime/issues/42975 + // _logger.LogDebug(e, "Error converting value."); + } + } + + var typedValues = new T[convertedCount]; + var typedValueIndex = 0; + for (var i = 0; i < stringEntries.Length; i++) + { + if (parsedValues[i] != null) + { + typedValues.SetValue(parsedValues[i], typedValueIndex); + typedValueIndex++; + } + } + + return typedValues; + } + + return JsonSerializer.Deserialize(ref reader, options); + } + + /// + public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs index c83657b5f..a8f6cfbec 100644 --- a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs @@ -9,74 +9,16 @@ namespace MediaBrowser.Common.Json.Converters /// Convert Pipe delimited string to array of type. /// /// Type to convert to. - public class JsonPipeDelimitedArrayConverter : JsonConverter + public sealed class JsonPipeDelimitedArrayConverter : JsonDelimitedArrayConverter { - private readonly TypeConverter _typeConverter; - /// /// Initializes a new instance of the class. /// - public JsonPipeDelimitedArrayConverter() + public JsonPipeDelimitedArrayConverter() : base() { - _typeConverter = TypeDescriptor.GetConverter(typeof(T)); } /// - public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - return Array.Empty(); - } - - if (reader.TokenType == JsonTokenType.String) - { - // GetString can't return null here because we already handled it above - var stringEntries = reader.GetString()!.Split('|', StringSplitOptions.RemoveEmptyEntries); - if (stringEntries.Length == 0) - { - return Array.Empty(); - } - - var parsedValues = new object[stringEntries.Length]; - var convertedCount = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - try - { - parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()); - convertedCount++; - } - catch (FormatException) - { - // TODO log when upgraded to .Net6 - // https://github.com/dotnet/runtime/issues/42975 - // _logger.LogDebug(e, "Error converting value."); - } - } - - var typedValues = new T[convertedCount]; - var typedValueIndex = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - if (parsedValues[i] != null) - { - typedValues.SetValue(parsedValues[i], typedValueIndex); - typedValueIndex++; - } - } - - return typedValues; - } - - // can't return null here because we already handled it above - return JsonSerializer.Deserialize(ref reader, options)!; - } - - /// - public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } + protected override char Delimiter => '|'; } } From ead4e1e977b086ea70db0214a3714268547e8102 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 6 May 2021 23:54:29 +0200 Subject: [PATCH 149/612] Add support for legacy HDhomerun DUAl --- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 2 +- .../LiveTv/HdHomerunHostTests.cs | 47 +++++++++++++++---- .../LiveTv/10.10.10.100/discover.json | 1 + .../Test Data/LiveTv/10.10.10.100/lineup.json | 1 + .../LiveTv/{ => 192.168.1.182}/discover.json | 0 .../LiveTv/{ => 192.168.1.182}/lineup.json | 0 6 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/discover.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/lineup.json rename tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/{ => 192.168.1.182}/discover.json (100%) rename tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/{ => 192.168.1.182}/lineup.json (100%) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 4aa5832b1..b6124753f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -74,7 +74,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var lineup = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken) .ConfigureAwait(false) ?? new List(); diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs index 8847239d9..c859d11c6 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Net.Http; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using AutoFixture; @@ -15,8 +16,6 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv { public class HdHomerunHostTests { - private const string TestIp = "http://192.168.1.182"; - private readonly Fixture _fixture; private readonly HdHomerunHost _hdHomerunHost; @@ -30,7 +29,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv { return Task.FromResult(new HttpResponseMessage() { - Content = new StreamContent(File.OpenRead("Test Data/LiveTv/" + m.RequestUri?.Segments[^1])) + Content = new StreamContent(File.OpenRead(Path.Combine("Test Data/LiveTv", m.RequestUri!.Host, m.RequestUri.Segments[^1]))) }); }); @@ -50,7 +49,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv { var host = new TunerHostInfo() { - Url = TestIp + Url = "192.168.1.182" }; var modelInfo = await _hdHomerunHost.GetModelInfo(host, true, CancellationToken.None).ConfigureAwait(false); @@ -65,6 +64,26 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Assert.Equal("http://192.168.1.182:80/lineup.json", modelInfo.LineupURL); } + [Fact] + public async Task GetModelInfo_Legacy_Success() + { + var host = new TunerHostInfo() + { + Url = "10.10.10.100" + }; + + var modelInfo = await _hdHomerunHost.GetModelInfo(host, true, CancellationToken.None).ConfigureAwait(false); + Assert.Equal("HDHomeRun DUAL", modelInfo.FriendlyName); + Assert.Equal("HDHR3-US", modelInfo.ModelNumber); + Assert.Equal("hdhomerun3_atsc", modelInfo.FirmwareName); + Assert.Equal("20200225", modelInfo.FirmwareVersion); + Assert.Equal("10xxxxx5", modelInfo.DeviceID); + Assert.Null(modelInfo.DeviceAuth); + Assert.Equal(2, modelInfo.TunerCount); + Assert.Equal("http://10.10.10.100:80", modelInfo.BaseURL); + Assert.Null(modelInfo.LineupURL); + } + [Fact] public async Task GetModelInfo_EmptyUrl_ArgumentException() { @@ -81,7 +100,7 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv { var host = new TunerHostInfo() { - Url = TestIp + Url = "192.168.1.182" }; var channels = await _hdHomerunHost.GetLineup(host, CancellationToken.None).ConfigureAwait(false); @@ -93,12 +112,24 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv Assert.Equal("http://192.168.1.111:5004/auto/v4.1", channels[0].URL); } + [Fact] + public async Task GetLineup_Legacy_Success() + { + var host = new TunerHostInfo() + { + Url = "10.10.10.100" + }; + + // Placeholder json is invalid, just need to make sure we can reach it + await Assert.ThrowsAsync(() => _hdHomerunHost.GetLineup(host, CancellationToken.None)); + } + [Fact] public async Task GetLineup_ImportFavoritesOnly_Success() { var host = new TunerHostInfo() { - Url = TestIp, + Url = "192.168.1.182", ImportFavoritesOnly = true }; @@ -114,9 +145,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv [Fact] public async Task TryGetTunerHostInfo_Valid_Success() { - var host = await _hdHomerunHost.TryGetTunerHostInfo(TestIp, CancellationToken.None).ConfigureAwait(false); + var host = await _hdHomerunHost.TryGetTunerHostInfo("192.168.1.182", CancellationToken.None).ConfigureAwait(false); Assert.Equal(_hdHomerunHost.Type, host.Type); - Assert.Equal(TestIp, host.Url); + Assert.Equal("192.168.1.182", host.Url); Assert.Equal("HDHomeRun PRIME", host.FriendlyName); Assert.Equal("FFFFFFFF", host.DeviceId); Assert.Equal(3, host.TunerCount); diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/discover.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/discover.json new file mode 100644 index 000000000..a4ad4ed44 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/discover.json @@ -0,0 +1 @@ +{"FriendlyName":"HDHomeRun DUAL","ModelNumber":"HDHR3-US","Legacy":1,"FirmwareName":"hdhomerun3_atsc","FirmwareVersion":"20200225","DeviceID":"10xxxxx5","TunerCount":2,"BaseURL":"http://10.10.10.100:80"} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/lineup.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/lineup.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/10.10.10.100/lineup.json @@ -0,0 +1 @@ +{} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/192.168.1.182/discover.json similarity index 100% rename from tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json rename to tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/192.168.1.182/discover.json diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/192.168.1.182/lineup.json similarity index 100% rename from tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json rename to tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/192.168.1.182/lineup.json From fb090df0b59b71d7f143d2181d46f18943bbc35e Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 7 May 2021 00:39:20 +0200 Subject: [PATCH 150/612] Enable nullable reference types for MediaBrowser.Controller --- Jellyfin.Api/Controllers/LibraryController.cs | 2 +- .../Authentication/AuthenticationResult.cs | 2 ++ .../Authentication/IAuthenticationProvider.cs | 2 ++ .../Authentication/IPasswordResetProvider.cs | 2 ++ .../BaseItemManager/BaseItemManager.cs | 2 ++ .../BaseItemManager/IBaseItemManager.cs | 2 ++ MediaBrowser.Controller/Channels/Channel.cs | 2 ++ .../Channels/ChannelItemInfo.cs | 2 ++ .../Channels/ChannelItemResult.cs | 2 ++ .../Channels/ChannelSearchInfo.cs | 2 ++ MediaBrowser.Controller/Channels/IChannel.cs | 2 ++ .../Channels/IChannelManager.cs | 2 ++ .../Channels/IHasCacheKey.cs | 2 ++ .../Channels/ISearchableChannel.cs | 2 ++ .../Channels/InternalChannelFeatures.cs | 2 ++ .../Channels/InternalChannelItemQuery.cs | 2 ++ .../Collections/CollectionCreationOptions.cs | 2 ++ .../Collections/CollectionEvents.cs | 2 ++ .../Collections/ICollectionManager.cs | 2 ++ .../IServerConfigurationManager.cs | 2 ++ .../Devices/IDeviceManager.cs | 2 ++ MediaBrowser.Controller/Dlna/IDlnaManager.cs | 2 ++ .../Drawing/IImageEncoder.cs | 2 ++ .../Drawing/IImageProcessor.cs | 2 ++ .../Drawing/ImageCollageOptions.cs | 2 ++ .../Drawing/ImageHelper.cs | 2 ++ .../Drawing/ImageProcessingOptions.cs | 2 ++ .../Drawing/ImageProcessorExtensions.cs | 2 ++ .../Drawing/ImageStream.cs | 2 +- MediaBrowser.Controller/Dto/DtoOptions.cs | 2 ++ MediaBrowser.Controller/Dto/IDtoService.cs | 2 ++ .../Entities/AggregateFolder.cs | 2 ++ .../Entities/Audio/Audio.cs | 2 ++ .../Entities/Audio/IHasAlbumArtist.cs | 2 ++ .../Entities/Audio/IHasMusicGenres.cs | 2 ++ .../Entities/Audio/MusicAlbum.cs | 2 ++ .../Entities/Audio/MusicArtist.cs | 2 ++ .../Entities/Audio/MusicGenre.cs | 2 ++ MediaBrowser.Controller/Entities/AudioBook.cs | 2 ++ MediaBrowser.Controller/Entities/BaseItem.cs | 2 ++ .../Entities/BaseItemExtensions.cs | 2 ++ .../Entities/BasePluginFolder.cs | 2 ++ MediaBrowser.Controller/Entities/Book.cs | 2 ++ .../Entities/CollectionFolder.cs | 2 ++ .../Entities/Extensions.cs | 2 ++ MediaBrowser.Controller/Entities/Folder.cs | 2 ++ MediaBrowser.Controller/Entities/Genre.cs | 2 ++ .../Entities/ICollectionFolder.cs | 2 ++ .../Entities/IHasAspectRatio.cs | 2 ++ .../Entities/IHasDisplayOrder.cs | 2 ++ .../Entities/IHasMediaSources.cs | 2 ++ .../Entities/IHasProgramAttributes.cs | 2 ++ .../Entities/IHasSeries.cs | 2 ++ .../Entities/IHasSpecialFeatures.cs | 2 ++ .../Entities/IHasTrailers.cs | 2 ++ .../Entities/InternalItemsQuery.cs | 2 ++ .../Entities/InternalPeopleQuery.cs | 2 ++ .../Entities/ItemImageInfo.cs | 2 ++ .../Entities/LinkedChild.cs | 2 ++ .../Entities/Movies/BoxSet.cs | 2 ++ .../Entities/Movies/Movie.cs | 2 ++ .../Entities/MusicVideo.cs | 2 ++ MediaBrowser.Controller/Entities/Person.cs | 2 ++ .../Entities/PersonInfo.cs | 2 ++ MediaBrowser.Controller/Entities/Photo.cs | 2 ++ MediaBrowser.Controller/Entities/Share.cs | 2 ++ MediaBrowser.Controller/Entities/Studio.cs | 2 ++ .../Entities/TV/Episode.cs | 2 ++ MediaBrowser.Controller/Entities/TV/Season.cs | 2 ++ MediaBrowser.Controller/Entities/TV/Series.cs | 2 ++ MediaBrowser.Controller/Entities/Trailer.cs | 2 ++ .../Entities/UserItemData.cs | 2 ++ .../Entities/UserRootFolder.cs | 2 ++ MediaBrowser.Controller/Entities/UserView.cs | 2 ++ .../Entities/UserViewBuilder.cs | 2 ++ MediaBrowser.Controller/Entities/Video.cs | 2 ++ MediaBrowser.Controller/Entities/Year.cs | 2 ++ .../Events/IEventConsumer.cs | 2 +- .../Events/IEventManager.cs | 2 +- .../Events/Session/SessionEndedEventArgs.cs | 2 +- .../Events/Session/SessionStartedEventArgs.cs | 2 +- .../PluginInstallationCancelledEventArgs.cs | 2 +- .../Updates/PluginInstalledEventArgs.cs | 2 +- .../Updates/PluginInstallingEventArgs.cs | 2 +- .../Events/Updates/PluginUpdatedEventArgs.cs | 2 +- .../Extensions/StringExtensions.cs | 1 - .../IDisplayPreferencesManager.cs | 2 ++ .../IServerApplicationHost.cs | 2 ++ .../IServerApplicationPaths.cs | 2 ++ .../Library/IIntroProvider.cs | 2 ++ .../Library/ILibraryManager.cs | 2 ++ .../Library/ILiveStream.cs | 2 ++ .../Library/IMediaSourceManager.cs | 2 ++ .../Library/IMetadataSaver.cs | 2 ++ .../Library/IMusicManager.cs | 2 ++ .../Library/IUserDataManager.cs | 2 ++ .../Library/IUserManager.cs | 2 ++ .../Library/IUserViewManager.cs | 2 ++ MediaBrowser.Controller/Library/IntroInfo.cs | 2 ++ .../Library/ItemChangeEventArgs.cs | 2 ++ .../Library/ItemResolveArgs.cs | 2 ++ .../Library/LibraryManagerExtensions.cs | 2 ++ .../MetadataConfigurationExtensions.cs | 2 ++ .../Library/NameExtensions.cs | 1 - .../Library/PlaybackProgressEventArgs.cs | 2 ++ .../Library/PlaybackStartEventArgs.cs | 2 +- MediaBrowser.Controller/Library/Profiler.cs | 2 ++ .../Library/SearchHintInfo.cs | 2 ++ MediaBrowser.Controller/Library/TVUtils.cs | 4 +++- .../Library/UserDataSaveEventArgs.cs | 2 ++ MediaBrowser.Controller/LiveTv/ChannelInfo.cs | 2 ++ .../LiveTv/IListingsProvider.cs | 2 ++ .../LiveTv/ILiveTvManager.cs | 2 ++ .../LiveTv/ILiveTvService.cs | 2 ++ MediaBrowser.Controller/LiveTv/ITunerHost.cs | 2 ++ .../LiveTv/LiveTvChannel.cs | 2 ++ .../LiveTv/LiveTvProgram.cs | 2 ++ .../LiveTv/LiveTvServiceStatusInfo.cs | 2 ++ .../LiveTv/LiveTvTunerInfo.cs | 2 ++ MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 2 ++ .../LiveTv/RecordingInfo.cs | 2 ++ .../LiveTv/RecordingStatusChangedEventArgs.cs | 2 ++ .../LiveTv/SeriesTimerInfo.cs | 2 ++ .../LiveTv/TimerEventInfo.cs | 2 ++ MediaBrowser.Controller/LiveTv/TimerInfo.cs | 2 ++ .../LiveTv/TunerChannelMapping.cs | 2 ++ .../MediaBrowser.Controller.csproj | 1 + .../MediaEncoding/EncodingHelper.cs | 2 ++ .../MediaEncoding/EncodingJobInfo.cs | 2 ++ .../MediaEncoding/EncodingJobOptions.cs | 2 ++ .../MediaEncoding/IAttachmentExtractor.cs | 2 ++ .../MediaEncoding/IEncodingManager.cs | 2 ++ .../MediaEncoding/IMediaEncoder.cs | 2 ++ .../MediaEncoding/ISubtitleEncoder.cs | 2 ++ .../MediaEncoding/ImageEncodingOptions.cs | 2 ++ .../MediaEncoding/JobLogger.cs | 2 ++ .../MediaEncoding/MediaEncoderHelpers.cs | 1 - .../MediaEncoding/MediaInfoRequest.cs | 2 ++ .../Net/AuthorizationInfo.cs | 2 ++ .../Net/BasePeriodicWebSocketListener.cs | 2 ++ MediaBrowser.Controller/Net/IAuthService.cs | 2 -- .../Net/IWebSocketConnection.cs | 2 ++ .../Net/SecurityException.cs | 2 -- .../Net/WebSocketMessageInfo.cs | 2 ++ .../Notifications/INotificationManager.cs | 2 +- .../Notifications/INotificationService.cs | 2 ++ .../Notifications/UserNotification.cs | 2 ++ .../Persistence/IItemRepository.cs | 2 ++ .../Persistence/IUserDataRepository.cs | 2 ++ MediaBrowser.Controller/Playlists/Playlist.cs | 2 ++ .../Plugins/ILocalizablePlugin.cs | 22 ------------------- MediaBrowser.Controller/Providers/BookInfo.cs | 2 ++ .../Providers/DynamicImageResponse.cs | 2 ++ .../Providers/EpisodeInfo.cs | 2 ++ .../Providers/IExternalId.cs | 2 ++ .../Providers/IProviderManager.cs | 2 ++ .../Providers/ImageRefreshOptions.cs | 2 ++ MediaBrowser.Controller/Providers/ItemInfo.cs | 2 ++ .../Providers/ItemLookupInfo.cs | 2 ++ .../Providers/LocalImageInfo.cs | 2 ++ .../Providers/MetadataRefreshOptions.cs | 2 ++ .../Providers/MetadataResult.cs | 2 ++ .../Providers/MusicVideoInfo.cs | 2 ++ .../Providers/RemoteSearchQuery.cs | 2 ++ MediaBrowser.Controller/Providers/SongInfo.cs | 2 ++ .../QuickConnect/IQuickConnect.cs | 2 ++ .../Resolvers/BaseItemResolver.cs | 2 ++ .../Security/AuthenticationInfo.cs | 2 ++ .../Security/AuthenticationInfoQuery.cs | 2 ++ .../Security/IAuthenticationRepository.cs | 2 ++ .../Session/AuthenticationRequest.cs | 2 ++ .../Session/ISessionController.cs | 2 ++ .../Session/ISessionManager.cs | 2 ++ .../Session/SessionEventArgs.cs | 2 ++ .../Session/SessionInfo.cs | 2 ++ .../Sorting/AlphanumComparator.cs | 2 -- .../Sorting/IUserBaseItemComparer.cs | 2 ++ .../Sorting/SortExtensions.cs | 2 ++ .../Subtitles/ISubtitleManager.cs | 2 ++ .../Subtitles/ISubtitleProvider.cs | 2 ++ .../SubtitleDownloadFailureEventArgs.cs | 2 ++ .../Subtitles/SubtitleResponse.cs | 2 ++ .../Subtitles/SubtitleSearchRequest.cs | 2 ++ .../Sync/IHasDynamicAccess.cs | 2 ++ .../Sync/IServerSyncProvider.cs | 2 ++ MediaBrowser.Controller/Sync/ISyncProvider.cs | 2 ++ .../Sync/SyncedFileInfo.cs | 2 ++ .../SyncPlay/GroupMember.cs | 2 ++ .../GroupStates/AbstractGroupState.cs | 2 ++ .../SyncPlay/GroupStates/IdleGroupState.cs | 2 ++ .../SyncPlay/GroupStates/PausedGroupState.cs | 2 ++ .../SyncPlay/GroupStates/PlayingGroupState.cs | 2 ++ .../SyncPlay/GroupStates/WaitingGroupState.cs | 2 ++ .../SyncPlay/IGroupPlaybackRequest.cs | 2 ++ .../SyncPlay/IGroupState.cs | 2 ++ .../SyncPlay/IGroupStateContext.cs | 2 ++ .../SyncPlay/ISyncPlayManager.cs | 2 ++ .../AbstractPlaybackRequest.cs | 2 ++ .../PlaybackRequests/BufferGroupRequest.cs | 2 ++ .../IgnoreWaitGroupRequest.cs | 2 ++ .../MovePlaylistItemGroupRequest.cs | 2 ++ .../PlaybackRequests/NextItemGroupRequest.cs | 2 ++ .../PlaybackRequests/PauseGroupRequest.cs | 2 ++ .../PlaybackRequests/PingGroupRequest.cs | 2 ++ .../PlaybackRequests/PlayGroupRequest.cs | 2 ++ .../PreviousItemGroupRequest.cs | 2 ++ .../PlaybackRequests/QueueGroupRequest.cs | 2 ++ .../PlaybackRequests/ReadyGroupRequest.cs | 2 ++ .../RemoveFromPlaylistGroupRequest.cs | 2 ++ .../PlaybackRequests/SeekGroupRequest.cs | 2 ++ .../SetPlaylistItemGroupRequest.cs | 2 ++ .../SetRepeatModeGroupRequest.cs | 2 ++ .../SetShuffleModeGroupRequest.cs | 2 ++ .../SyncPlay/Queue/PlayQueueManager.cs | 2 ++ .../Images/EpisodeLocalImageProvider.cs | 4 ++++ 215 files changed, 406 insertions(+), 44 deletions(-) delete mode 100644 MediaBrowser.Controller/Plugins/ILocalizablePlugin.cs diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 1d4bbe61e..4ed15e1d5 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -600,7 +600,7 @@ namespace Jellyfin.Api.Controllers { foreach (var item in dto.Updates) { - _libraryMonitor.ReportFileSystemChanged(item.Path); + _libraryMonitor.ReportFileSystemChanged(item.Path ?? throw new ArgumentException("Item path can't be null.")); } return NoContent(); diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs index 4249a9a66..635e4eb3d 100644 --- a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs +++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index ecdffa2eb..a56d3c822 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Threading.Tasks; diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs index 6729b9115..8c9d1baf8 100644 --- a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index a233c358e..68119cfed 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading; diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs index 8a8736427..b2b36c040 100644 --- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index b2315bda4..26c64e0da 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs index 476992cbd..fa7aff647 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Channels/ChannelItemResult.cs b/MediaBrowser.Controller/Channels/ChannelItemResult.cs index cee7b2003..8e937852f 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemResult.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemResult.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs index 32469d4d7..53a73d62a 100644 --- a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Channels diff --git a/MediaBrowser.Controller/Channels/IChannel.cs b/MediaBrowser.Controller/Channels/IChannel.cs index 2c0eadf95..01bf8d5c8 100644 --- a/MediaBrowser.Controller/Channels/IChannel.cs +++ b/MediaBrowser.Controller/Channels/IChannel.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index ddae7dbd3..4c5626338 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Channels/IHasCacheKey.cs b/MediaBrowser.Controller/Channels/IHasCacheKey.cs index bf895a0ec..9fae43033 100644 --- a/MediaBrowser.Controller/Channels/IHasCacheKey.cs +++ b/MediaBrowser.Controller/Channels/IHasCacheKey.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Channels diff --git a/MediaBrowser.Controller/Channels/ISearchableChannel.cs b/MediaBrowser.Controller/Channels/ISearchableChannel.cs index b627ca1c2..b58446fc4 100644 --- a/MediaBrowser.Controller/Channels/ISearchableChannel.cs +++ b/MediaBrowser.Controller/Channels/ISearchableChannel.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs index 137f5d095..152c653dc 100644 --- a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs +++ b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs index 7e9bb28ed..0d837faca 100644 --- a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs +++ b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index f6037d05e..94e7541f8 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Collections/CollectionEvents.cs b/MediaBrowser.Controller/Collections/CollectionEvents.cs index ce59b4ada..821318ffc 100644 --- a/MediaBrowser.Controller/Collections/CollectionEvents.cs +++ b/MediaBrowser.Controller/Collections/CollectionEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index a6991e2ea..46bc37e7f 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index 43ad04dba..44e2c45dd 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 8f0872dba..ef17c8fb3 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index dc2d5a356..b51dc255c 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 770c6dc2d..800f7a8bb 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 #nullable enable diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 142cebd0c..9bfead8b3 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 #nullable enable diff --git a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs index fe0465d0d..f06bbe4d0 100644 --- a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Drawing diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index 596fcbc8c..204175ed5 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 #nullable enable diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 230a0af60..11e663301 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs index d3a2b4dbf..b036425ab 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Drawing/ImageStream.cs b/MediaBrowser.Controller/Drawing/ImageStream.cs index 46f58ec15..591cc53d1 100644 --- a/MediaBrowser.Controller/Drawing/ImageStream.cs +++ b/MediaBrowser.Controller/Drawing/ImageStream.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Drawing /// Gets or sets the stream. ///
/// The stream. - public Stream Stream { get; set; } + public Stream? Stream { get; set; } /// /// Gets or sets the format. diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index 356783750..758e841a7 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 988557f42..7f4bbead0 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 6a92200dd..f1944a7d3 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 8220464b3..4c2b7cb7c 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs index 20fad4cb0..1625c748a 100644 --- a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs index ac4dd1688..db60c3071 100644 --- a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs +++ b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Entities.Audio diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 9a33ad9d7..610bce4f5 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 8a9bb12c7..6101d3016 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index f0c076108..b07d47ffd 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index f4bd851e1..405284622 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 32ae15498..ca5213273 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs index 157ed8332..c39b18891 100644 --- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs +++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #nullable enable #pragma warning disable CS1591 diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index ef5a5a734..1bd25042f 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Text.Json.Serialization; diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 55945283c..3d0370248 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index bc5f38256..a86da29ce 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs index 3a34c668c..244cc00be 100644 --- a/MediaBrowser.Controller/Entities/Extensions.cs +++ b/MediaBrowser.Controller/Entities/Extensions.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using MediaBrowser.Common.Extensions; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index d74e6f9d8..a59f5c6e4 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 74a170204..7987f38a0 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/ICollectionFolder.cs b/MediaBrowser.Controller/Entities/ICollectionFolder.cs index b84a9fa6f..2304570fd 100644 --- a/MediaBrowser.Controller/Entities/ICollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/ICollectionFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/IHasAspectRatio.cs b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs index d7d007668..3aeb7468f 100644 --- a/MediaBrowser.Controller/Entities/IHasAspectRatio.cs +++ b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace MediaBrowser.Controller.Entities { /// diff --git a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs index 13226b234..14459624e 100644 --- a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs +++ b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace MediaBrowser.Controller.Entities { /// diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs index 0f612262a..98c3b3edf 100644 --- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs +++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs index f747b5149..f80f7c304 100644 --- a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs +++ b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.LiveTv; diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index 5444f1f52..64d769d5b 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs b/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs index 6a350212b..f317a02ff 100644 --- a/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs +++ b/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs index d1f6f2b7e..2bd9ded33 100644 --- a/MediaBrowser.Controller/Entities/IHasTrailers.cs +++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 270217356..c06021029 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs index 5b96a5af6..b2d6a4609 100644 --- a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index 570d8eec0..ea8555dbf 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index 8e0f721e7..01c0a9339 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 507f400f1..74e84288d 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 8b67aaccc..64d60c2e9 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index b278a0142..f42e7723c 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index c4fcb0267..d9ff55362 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/PersonInfo.cs b/MediaBrowser.Controller/Entities/PersonInfo.cs index 4ff9b0955..fb79323f8 100644 --- a/MediaBrowser.Controller/Entities/PersonInfo.cs +++ b/MediaBrowser.Controller/Entities/PersonInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index 0f82f742f..3312a0e3e 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Text.Json.Serialization; diff --git a/MediaBrowser.Controller/Entities/Share.cs b/MediaBrowser.Controller/Entities/Share.cs index 50f1655f3..7e4ec1830 100644 --- a/MediaBrowser.Controller/Entities/Share.cs +++ b/MediaBrowser.Controller/Entities/Share.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Entities diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 9018ddb75..ae1d10447 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 70663ef47..2724bd9b3 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 5b8168d3d..ad3e0fe8d 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 06a405121..ded825abc 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 9ae8ad708..b086b5906 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index db63c42e4..f60359c01 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 7f7224ae0..e492740ed 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index fec83dd94..0dfde2766 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 78a64d8c9..15a4573c2 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 6320b01b8..723027a88 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index b2e4d307a..4d84a151a 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Events/IEventConsumer.cs b/MediaBrowser.Controller/Events/IEventConsumer.cs index 5c4ab5d8d..93005134a 100644 --- a/MediaBrowser.Controller/Events/IEventConsumer.cs +++ b/MediaBrowser.Controller/Events/IEventConsumer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace MediaBrowser.Controller.Events diff --git a/MediaBrowser.Controller/Events/IEventManager.cs b/MediaBrowser.Controller/Events/IEventManager.cs index a1f40b3a6..074e3f1fe 100644 --- a/MediaBrowser.Controller/Events/IEventManager.cs +++ b/MediaBrowser.Controller/Events/IEventManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace MediaBrowser.Controller.Events diff --git a/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs b/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs index 46d7e5a17..3a331ad00 100644 --- a/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs +++ b/MediaBrowser.Controller/Events/Session/SessionEndedEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Session; namespace MediaBrowser.Controller.Events.Session diff --git a/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs b/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs index aab19cc46..deeaaf55d 100644 --- a/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs +++ b/MediaBrowser.Controller/Events/Session/SessionStartedEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Session; namespace MediaBrowser.Controller.Events.Session diff --git a/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs index b06046c05..0dd8b0dbf 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginInstallationCancelledEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Updates; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs index dfadc9f61..c1d503a7e 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginInstalledEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Updates; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs index 045a60027..7a9866834 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginInstallingEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Updates; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs b/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs index 661ca066a..b078e06dc 100644 --- a/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs +++ b/MediaBrowser.Controller/Events/Updates/PluginUpdatedEventArgs.cs @@ -1,4 +1,4 @@ -using Jellyfin.Data.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Updates; namespace MediaBrowser.Controller.Events.Updates diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs index 182c8ef65..8441a3171 100644 --- a/MediaBrowser.Controller/Extensions/StringExtensions.cs +++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/IDisplayPreferencesManager.cs b/MediaBrowser.Controller/IDisplayPreferencesManager.cs index be1d974a4..1678d5067 100644 --- a/MediaBrowser.Controller/IDisplayPreferencesManager.cs +++ b/MediaBrowser.Controller/IDisplayPreferencesManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using Jellyfin.Data.Entities; diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 6a65a8e47..094923842 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs index be57d6bca..1890dbb36 100644 --- a/MediaBrowser.Controller/IServerApplicationPaths.cs +++ b/MediaBrowser.Controller/IServerApplicationPaths.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Common.Configuration; diff --git a/MediaBrowser.Controller/Library/IIntroProvider.cs b/MediaBrowser.Controller/Library/IIntroProvider.cs index d45493d40..3bb1bd9a0 100644 --- a/MediaBrowser.Controller/Library/IIntroProvider.cs +++ b/MediaBrowser.Controller/Library/IIntroProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 6d9b568da..782e15398 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/ILiveStream.cs b/MediaBrowser.Controller/Library/ILiveStream.cs index ff25be657..85d866de5 100644 --- a/MediaBrowser.Controller/Library/ILiveStream.cs +++ b/MediaBrowser.Controller/Library/ILiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Threading; diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 21c6ef2af..d3d85a056 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/IMetadataSaver.cs b/MediaBrowser.Controller/Library/IMetadataSaver.cs index 027cc5b40..5fbfad881 100644 --- a/MediaBrowser.Controller/Library/IMetadataSaver.cs +++ b/MediaBrowser.Controller/Library/IMetadataSaver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Library/IMusicManager.cs b/MediaBrowser.Controller/Library/IMusicManager.cs index d12f008e7..5329841bf 100644 --- a/MediaBrowser.Controller/Library/IMusicManager.cs +++ b/MediaBrowser.Controller/Library/IMusicManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index c6a83e4dc..58499e853 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 6e267834b..c95b0ea32 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/IUserViewManager.cs b/MediaBrowser.Controller/Library/IUserViewManager.cs index 8d541e8b6..46004e42f 100644 --- a/MediaBrowser.Controller/Library/IUserViewManager.cs +++ b/MediaBrowser.Controller/Library/IUserViewManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/IntroInfo.cs b/MediaBrowser.Controller/Library/IntroInfo.cs index 283cc631c..90786786b 100644 --- a/MediaBrowser.Controller/Library/IntroInfo.cs +++ b/MediaBrowser.Controller/Library/IntroInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs b/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs index 1798a4fad..a37dc7af1 100644 --- a/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs +++ b/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index 5f9aed341..0e2d8fb02 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs b/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs index 9581603f0..7bc8fa5ab 100644 --- a/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs +++ b/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs b/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs index 884f9e773..41cfcae16 100644 --- a/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Common.Configuration; diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs index 6e79dc8dd..29bfeca09 100644 --- a/MediaBrowser.Controller/Library/NameExtensions.cs +++ b/MediaBrowser.Controller/Library/NameExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index a2be3a42a..609336ec4 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs index ac372bceb..2138fef58 100644 --- a/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackStartEventArgs.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Controller.Library +namespace MediaBrowser.Controller.Library { /// /// An event that occurs when playback is started. diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs index 5efdc6a48..8f42d3706 100644 --- a/MediaBrowser.Controller/Library/Profiler.cs +++ b/MediaBrowser.Controller/Library/Profiler.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics; using System.Globalization; diff --git a/MediaBrowser.Controller/Library/SearchHintInfo.cs b/MediaBrowser.Controller/Library/SearchHintInfo.cs index 897c2b7f4..de7806adc 100644 --- a/MediaBrowser.Controller/Library/SearchHintInfo.cs +++ b/MediaBrowser.Controller/Library/SearchHintInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Library diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index a3aa6019e..8cbfc78aa 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace MediaBrowser.Controller.Library { @@ -12,7 +13,8 @@ namespace MediaBrowser.Controller.Library /// /// The day. /// List{DayOfWeek}. - public static DayOfWeek[] GetAirDays(string day) + [return: NotNullIfNotNull("day")] + public static DayOfWeek[] GetAirDays(string? day) { if (!string.IsNullOrEmpty(day)) { diff --git a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs index cd9109753..bfe433c97 100644 --- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs +++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index 166c4d77c..a55fd670d 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.LiveTv; diff --git a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs index 038ff2eae..2bd4b20e8 100644 --- a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs +++ b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 54495c1c4..c28e0426b 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index 3ca1d165e..897f263f3 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index abca8f239..7dced9f5e 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index ec933caf3..51e56f4b5 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 43af495dd..d9634a731 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs index b62974904..eb3babc18 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs index 739978e7c..aa5eb59d1 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index f9f559ee9..4a977c5cc 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs index 69190694f..00135afa8 100644 --- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs +++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs b/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs index 847c0ea8c..0b943c939 100644 --- a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs +++ b/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index 1343ecd98..1bb649a99 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs index 1b8f41db6..728387c56 100644 --- a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #nullable enable #pragma warning disable CS1591 diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index aa5170617..e54dc967c 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs index 2759b314f..1c1a4417d 100644 --- a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs +++ b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.LiveTv diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 8c68b47dd..37ce35fc2 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -34,6 +34,7 @@ false true true + enable AllEnabledByDefault ../jellyfin.ruleset true diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2b5364775..97cb8d63b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index d47a689f4..1e13382b7 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index 1f3abe8f4..88de5b292 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs index fbc827534..c38e7ec3b 100644 --- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs +++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs index 15a2580af..773547872 100644 --- a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs +++ b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 05dd1a69b..d3260280a 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs index 6ebf7f159..3fb2c47e1 100644 --- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs index e7b4c8c15..044ba6d33 100644 --- a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.MediaEncoding diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index 227c5f258..aa5e2c403 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 89e01c08b..841e7b287 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 - namespace MediaBrowser.Controller.MediaEncoding { /// diff --git a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs index 2cb04bdc4..1dd8bcf31 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.Dlna; diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs index 93573e08e..2452b25ab 100644 --- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Jellyfin.Data.Entities; diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 163a9c8f8..855467e8e 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index 04b2e13e8..d15c6d318 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index e87f3bca6..5e9fce550 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 #nullable enable diff --git a/MediaBrowser.Controller/Net/SecurityException.cs b/MediaBrowser.Controller/Net/SecurityException.cs index c6347133a..f0d0b45a0 100644 --- a/MediaBrowser.Controller/Net/SecurityException.cs +++ b/MediaBrowser.Controller/Net/SecurityException.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; namespace MediaBrowser.Controller.Net diff --git a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs index be0b3ddc3..6f7ebf156 100644 --- a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs +++ b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Model.Net; namespace MediaBrowser.Controller.Net diff --git a/MediaBrowser.Controller/Notifications/INotificationManager.cs b/MediaBrowser.Controller/Notifications/INotificationManager.cs index 08d9bc12a..7caba1097 100644 --- a/MediaBrowser.Controller/Notifications/INotificationManager.cs +++ b/MediaBrowser.Controller/Notifications/INotificationManager.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Notifications /// Task. Task SendNotification(NotificationRequest request, CancellationToken cancellationToken); - Task SendNotification(NotificationRequest request, BaseItem relatedItem, CancellationToken cancellationToken); + Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken); /// /// Adds the parts. diff --git a/MediaBrowser.Controller/Notifications/INotificationService.cs b/MediaBrowser.Controller/Notifications/INotificationService.cs index fa947220a..535c08795 100644 --- a/MediaBrowser.Controller/Notifications/INotificationService.cs +++ b/MediaBrowser.Controller/Notifications/INotificationService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Threading; diff --git a/MediaBrowser.Controller/Notifications/UserNotification.cs b/MediaBrowser.Controller/Notifications/UserNotification.cs index d768abfe7..4be0e09ae 100644 --- a/MediaBrowser.Controller/Notifications/UserNotification.cs +++ b/MediaBrowser.Controller/Notifications/UserNotification.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index ed473c749..56fb36af2 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index 81ba513ce..6f5f02123 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Threading; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 3c93cfc79..a80c11643 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Plugins/ILocalizablePlugin.cs b/MediaBrowser.Controller/Plugins/ILocalizablePlugin.cs deleted file mode 100644 index bf15fe040..000000000 --- a/MediaBrowser.Controller/Plugins/ILocalizablePlugin.cs +++ /dev/null @@ -1,22 +0,0 @@ -#pragma warning disable CS1591 - -using System.IO; -using System.Reflection; - -namespace MediaBrowser.Controller.Plugins -{ - public interface ILocalizablePlugin - { - Stream GetDictionary(string culture); - } - - public static class LocalizablePluginHelper - { - public static Stream GetDictionary(Assembly assembly, string manifestPrefix, string culture) - { - // Find all dictionaries using GetManifestResourceNames, start start with the prefix - // Return the one for the culture if exists, otherwise return the default - return null; - } - } -} diff --git a/MediaBrowser.Controller/Providers/BookInfo.cs b/MediaBrowser.Controller/Providers/BookInfo.cs index cce0a25fc..3055c5d87 100644 --- a/MediaBrowser.Controller/Providers/BookInfo.cs +++ b/MediaBrowser.Controller/Providers/BookInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Providers diff --git a/MediaBrowser.Controller/Providers/DynamicImageResponse.cs b/MediaBrowser.Controller/Providers/DynamicImageResponse.cs index 006174be8..66fddb0e3 100644 --- a/MediaBrowser.Controller/Providers/DynamicImageResponse.cs +++ b/MediaBrowser.Controller/Providers/DynamicImageResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/EpisodeInfo.cs b/MediaBrowser.Controller/Providers/EpisodeInfo.cs index a4c8dab7e..341bf6936 100644 --- a/MediaBrowser.Controller/Providers/EpisodeInfo.cs +++ b/MediaBrowser.Controller/Providers/EpisodeInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index 5e38446bc..e2dbef2bc 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 7bc56c82a..b4d91f396 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs index 9fc379f04..81a22affb 100644 --- a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/ItemInfo.cs b/MediaBrowser.Controller/Providers/ItemInfo.cs index 3a97127ea..b8dd416a2 100644 --- a/MediaBrowser.Controller/Providers/ItemInfo.cs +++ b/MediaBrowser.Controller/Providers/ItemInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/ItemLookupInfo.cs b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs index b777cc1d3..f16669a78 100644 --- a/MediaBrowser.Controller/Providers/ItemLookupInfo.cs +++ b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/LocalImageInfo.cs b/MediaBrowser.Controller/Providers/LocalImageInfo.cs index 41801862f..a8e70e6d0 100644 --- a/MediaBrowser.Controller/Providers/LocalImageInfo.cs +++ b/MediaBrowser.Controller/Providers/LocalImageInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index db0ef7072..5afc358ba 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index 98c7eadfe..8b0967a6e 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/MusicVideoInfo.cs b/MediaBrowser.Controller/Providers/MusicVideoInfo.cs index 0b927f6eb..322320abd 100644 --- a/MediaBrowser.Controller/Providers/MusicVideoInfo.cs +++ b/MediaBrowser.Controller/Providers/MusicVideoInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs b/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs index 9653bc1c4..d830231cf 100644 --- a/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs +++ b/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Providers/SongInfo.cs b/MediaBrowser.Controller/Providers/SongInfo.cs index 58f76dca9..c90717a2e 100644 --- a/MediaBrowser.Controller/Providers/SongInfo.cs +++ b/MediaBrowser.Controller/Providers/SongInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs index 959a2d771..c4e709c24 100644 --- a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Model.QuickConnect; diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs index a904c7424..e77593a03 100644 --- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/MediaBrowser.Controller/Security/AuthenticationInfo.cs b/MediaBrowser.Controller/Security/AuthenticationInfo.cs index efac9273e..b4b242f1b 100644 --- a/MediaBrowser.Controller/Security/AuthenticationInfo.cs +++ b/MediaBrowser.Controller/Security/AuthenticationInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs index c5f3da0b1..3af6a525c 100644 --- a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs +++ b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs index 883b74165..1dd69ccd8 100644 --- a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs +++ b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.Devices; diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs index 8c3ac58f2..647c75e66 100644 --- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index bc4ccd44c..6bc39d6f4 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 6c06dcad5..7eda49c60 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Session/SessionEventArgs.cs b/MediaBrowser.Controller/Session/SessionEventArgs.cs index 097e32eae..269fe7dc4 100644 --- a/MediaBrowser.Controller/Session/SessionEventArgs.cs +++ b/MediaBrowser.Controller/Session/SessionEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index d09852870..5da3783bf 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs index 70cb9eebe..4d9b98889 100644 --- a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs +++ b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs @@ -1,7 +1,5 @@ #pragma warning disable CS1591 -#nullable enable - using System; using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs index 6d03d97ae..bd47db39a 100644 --- a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.Sorting diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs index 88467814c..aa6ec513f 100644 --- a/MediaBrowser.Controller/Sorting/SortExtensions.cs +++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 6d63286ef..9e661cbe4 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs b/MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs index a633262de..326348d58 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs b/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs index ce8141219..c782f5796 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleDownloadFailureEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs index a86b05778..85b3e6fbd 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs index 7d3c20e8f..0f7c47e76 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs b/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs index e7395b136..3d3e44da0 100644 --- a/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs +++ b/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Threading; diff --git a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs index c97fd7044..3891ac0a6 100644 --- a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs +++ b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Controller/Sync/ISyncProvider.cs b/MediaBrowser.Controller/Sync/ISyncProvider.cs index 950cc73e8..ea20014c7 100644 --- a/MediaBrowser.Controller/Sync/ISyncProvider.cs +++ b/MediaBrowser.Controller/Sync/ISyncProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs index a626738fb..7eac52299 100644 --- a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs +++ b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs index 5fb982e85..7e7e759a5 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupMember.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Session; namespace MediaBrowser.Controller.SyncPlay diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs index e3de22db3..91a13fb28 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay.PlaybackRequests; diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs index 12ce6c8f8..6b5a7cdf9 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay.PlaybackRequests; diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs index fba8ba9e2..b9786ddb0 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs index 9797b247c..cb1cadf0b 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs index 507573653..a0c38b309 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs b/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs index 201f29952..9045063ee 100644 --- a/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/IGroupState.cs b/MediaBrowser.Controller/SyncPlay/IGroupState.cs index 95ee09985..0666a62a8 100644 --- a/MediaBrowser.Controller/SyncPlay/IGroupState.cs +++ b/MediaBrowser.Controller/SyncPlay/IGroupState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay.PlaybackRequests; diff --git a/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs index aa263638a..de26c7d9e 100644 --- a/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs +++ b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index 1c954828c..a6999a12c 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/AbstractPlaybackRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/AbstractPlaybackRequest.cs index 4090f65b9..ef496c103 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/AbstractPlaybackRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/AbstractPlaybackRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs index 11cc99fcd..d188114c3 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs index 64ef791ed..464c81dfd 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs index 9cd8da566..be314e807 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextItemGroupRequest.cs index e0ae0deb7..679076239 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextItemGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextItemGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs index 2869b35f7..7ee18a366 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs index 8ef3b2030..beab655c5 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs index 16f9b4087..05ff262c1 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousItemGroupRequest.cs index 166ee0800..3e34b6ce4 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousItemGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousItemGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs index d4af63b6d..0f91476de 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs index 74f01cbea..b1f0bd360 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs index 47c06c222..689145293 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Threading; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs index ecaa689ae..196113374 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs index c3451703e..44df127a6 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs index 51011672e..d250eab56 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs index d7b2504b4..5034e992e 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs index fdec29417..b8ae9f3ff 100644 --- a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs +++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs index 2d3b2d889..bc62ca4d5 100644 --- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs @@ -42,6 +42,10 @@ namespace MediaBrowser.LocalMetadata.Images public IEnumerable GetImages(BaseItem item, IDirectoryService directoryService) { var parentPath = Path.GetDirectoryName(item.Path); + if (parentPath == null) + { + return Enumerable.Empty(); + } var parentPathFiles = directoryService.GetFiles(parentPath); From 4367b97a54f31233e242ef1afe6714b934ecf4d9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 7 May 2021 00:52:06 +0200 Subject: [PATCH 151/612] Fix build --- .../Library/ResolverHelper.cs | 5 +++++ MediaBrowser.Controller/Library/TVUtils.cs | 2 +- .../Providers/DirectoryService.cs | 19 +++++++++---------- .../Providers/IDirectoryService.cs | 2 +- MediaBrowser.LocalMetadata/BaseXmlProvider.cs | 2 +- .../Providers/BoxSetXmlProvider.cs | 2 +- .../Providers/PlaylistXmlProvider.cs | 2 +- .../Providers/AlbumNfoProvider.cs | 2 +- .../Providers/ArtistNfoProvider.cs | 2 +- .../Providers/EpisodeNfoProvider.cs | 2 +- .../Providers/SeasonNfoProvider.cs | 2 +- .../Providers/SeriesNfoProvider.cs | 2 +- 12 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index b1a2e9284..1d9b44874 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -44,6 +44,11 @@ namespace Emby.Server.Implementations.Library // Make sure DateCreated and DateModified have values var fileInfo = directoryService.GetFile(item.Path); + if (fileInfo == null) + { + throw new FileNotFoundException("Can't find item path.", item.Path); + } + SetDateCreated(item, fileInfo); EnsureName(item, fileInfo); diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 8cbfc78aa..968338dc6 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Library /// The day. /// List{DayOfWeek}. [return: NotNullIfNotNull("day")] - public static DayOfWeek[] GetAirDays(string? day) + public static DayOfWeek[]? GetAirDays(string? day) { if (!string.IsNullOrEmpty(day)) { diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 5c92069b4..291a26883 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -43,18 +43,17 @@ namespace MediaBrowser.Controller.Providers return list; } - public FileSystemMetadata GetFile(string path) + public FileSystemMetadata? GetFile(string path) { - var result = _fileCache.GetOrAdd(path, p => + if (!_fileCache.TryGetValue(path, out var result)) { - var file = _fileSystem.GetFileInfo(p); - return file != null && file.Exists ? file : null; - }); - - if (result == null) - { - // lets not store null results in the cache - _fileCache.TryRemove(path, out _); + var file = _fileSystem.GetFileInfo(path); + var res = file != null && file.Exists ? file : null; + if (res != null) + { + result = res; + _fileCache.TryAdd(path, result); + } } return result; diff --git a/MediaBrowser.Controller/Providers/IDirectoryService.cs b/MediaBrowser.Controller/Providers/IDirectoryService.cs index f06481c7a..9cee06a4c 100644 --- a/MediaBrowser.Controller/Providers/IDirectoryService.cs +++ b/MediaBrowser.Controller/Providers/IDirectoryService.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Providers List GetFiles(string path); - FileSystemMetadata GetFile(string path); + FileSystemMetadata? GetFile(string path); IReadOnlyList GetFilePaths(string path); diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index b036a2cc8..9f8e921b4 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -91,7 +91,7 @@ namespace MediaBrowser.LocalMetadata /// Item inf. /// Instance of the interface. /// The file system metadata. - protected abstract FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService); + protected abstract FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService); /// public bool HasChanged(BaseItem item, IDirectoryService directoryService) diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs index cc705a9df..ea123bbf2 100644 --- a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.LocalMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "collection.xml")); } diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs index 36f3048ad..899035652 100644 --- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.LocalMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(PlaylistXmlSaver.GetSavePath(info.Path)); } diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs index bd557d783..f7a85a07e 100644 --- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) => directoryService.GetFile(Path.Combine(info.Path, "album.nfo")); } } diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs index 54bb83114..2b563b7fa 100644 --- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) => directoryService.GetFile(Path.Combine(info.Path, "artist.nfo")); } } diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs index 64b208345..f6c5877b4 100644 --- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) { var path = Path.ChangeExtension(info.Path, ".nfo"); diff --git a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs index 97220cf7e..d01abadf6 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) => directoryService.GetFile(Path.Combine(info.Path, "season.nfo")); } } diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs index 9a9b94123..002b94463 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers } /// - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemMetadata? GetXmlFile(ItemInfo info, IDirectoryService directoryService) => directoryService.GetFile(Path.Combine(info.Path, "tvshow.nfo")); } } From bc017737e678c62a1573fa9c8820fc4fd2b97405 Mon Sep 17 00:00:00 2001 From: Jake King Date: Fri, 7 May 2021 10:35:03 +0100 Subject: [PATCH 152/612] Changed condition for readbility - Refactored check if item is not audio book or book to be more readable --- Emby.Server.Implementations/Library/UserDataManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index d7e4e2af7..47f881f73 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -220,7 +220,7 @@ namespace Emby.Server.Implementations.Library var hasRuntime = runtimeTicks > 0; // If a position has been reported, and if we know the duration - if (positionTicks > 0 && hasRuntime && !(item is AudioBook) && !(item is Book)) + if (positionTicks > 0 && hasRuntime && item is not AudioBook && item is not Book) { var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100; From 06caee28b7f81fb6898a771df5a5064fdf94891d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 7 May 2021 14:43:50 +0200 Subject: [PATCH 153/612] Enable nullable reference types for Emby.Dlna --- Emby.Dlna/Configuration/DlnaOptions.cs | 2 ++ Emby.Dlna/ConfigurationExtension.cs | 1 - .../ContentDirectory/ContentDirectoryService.cs | 2 ++ Emby.Dlna/ContentDirectory/ControlHandler.cs | 2 ++ Emby.Dlna/ControlRequest.cs | 2 ++ Emby.Dlna/ControlResponse.cs | 2 ++ Emby.Dlna/Didl/DidlBuilder.cs | 2 ++ Emby.Dlna/Didl/StringWriterWithEncoding.cs | 2 +- Emby.Dlna/DlnaConfigurationFactory.cs | 1 - Emby.Dlna/DlnaManager.cs | 2 ++ Emby.Dlna/Emby.Dlna.csproj | 1 + Emby.Dlna/EventSubscriptionResponse.cs | 2 ++ Emby.Dlna/Eventing/DlnaEventManager.cs | 2 ++ Emby.Dlna/Eventing/EventSubscription.cs | 2 ++ Emby.Dlna/Main/DlnaEntryPoint.cs | 2 ++ Emby.Dlna/PlayTo/Device.cs | 2 ++ Emby.Dlna/PlayTo/DeviceInfo.cs | 2 ++ Emby.Dlna/PlayTo/MediaChangedEventArgs.cs | 2 ++ Emby.Dlna/PlayTo/PlayToController.cs | 2 ++ Emby.Dlna/PlayTo/PlayToManager.cs | 2 ++ Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs | 2 ++ Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs | 2 ++ Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs | 2 ++ Emby.Dlna/PlayTo/PlaylistItem.cs | 2 ++ Emby.Dlna/PlayTo/PlaylistItemFactory.cs | 2 ++ Emby.Dlna/PlayTo/SsdpHttpClient.cs | 2 ++ Emby.Dlna/PlayTo/TransportCommands.cs | 14 +++++++------- Emby.Dlna/PlayTo/uBaseObject.cs | 2 ++ Emby.Dlna/Server/DescriptionXmlBuilder.cs | 3 ++- Emby.Dlna/Service/BaseControlHandler.cs | 4 ++-- Emby.Dlna/Ssdp/DeviceDiscovery.cs | 2 ++ Emby.Dlna/Ssdp/SsdpExtensions.cs | 6 +++--- 32 files changed, 64 insertions(+), 16 deletions(-) diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs index e63a85860..5ceeb5530 100644 --- a/Emby.Dlna/Configuration/DlnaOptions.cs +++ b/Emby.Dlna/Configuration/DlnaOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace Emby.Dlna.Configuration diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs index fc02e1751..3ca43052a 100644 --- a/Emby.Dlna/ConfigurationExtension.cs +++ b/Emby.Dlna/ConfigurationExtension.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 using Emby.Dlna.Configuration; diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs index 2f3107450..7b8c50440 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 90ba601b4..27c5b2268 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs index 4ea4e4e48..8ee6325e9 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/Emby.Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs index d827eef26..a7f2d4a73 100644 --- a/Emby.Dlna/ControlResponse.cs +++ b/Emby.Dlna/ControlResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index c66bdbf22..2982ce97e 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/Didl/StringWriterWithEncoding.cs b/Emby.Dlna/Didl/StringWriterWithEncoding.cs index 2b86ea333..b66f53ece 100644 --- a/Emby.Dlna/Didl/StringWriterWithEncoding.cs +++ b/Emby.Dlna/Didl/StringWriterWithEncoding.cs @@ -9,7 +9,7 @@ namespace Emby.Dlna.Didl { public class StringWriterWithEncoding : StringWriter { - private readonly Encoding _encoding; + private readonly Encoding? _encoding; public StringWriterWithEncoding() { diff --git a/Emby.Dlna/DlnaConfigurationFactory.cs b/Emby.Dlna/DlnaConfigurationFactory.cs index 4c6ca869a..6cc6b73a0 100644 --- a/Emby.Dlna/DlnaConfigurationFactory.cs +++ b/Emby.Dlna/DlnaConfigurationFactory.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 5f2be07cf..a1b106704 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 480621dd7..a40578e40 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -21,6 +21,7 @@ false true true + enable diff --git a/Emby.Dlna/EventSubscriptionResponse.cs b/Emby.Dlna/EventSubscriptionResponse.cs index 1b1bd426c..8c82dcbf6 100644 --- a/Emby.Dlna/EventSubscriptionResponse.cs +++ b/Emby.Dlna/EventSubscriptionResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index ff81e83b5..2e672b886 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs index 40d73ee0e..4fd7f8169 100644 --- a/Emby.Dlna/Eventing/EventSubscription.cs +++ b/Emby.Dlna/Eventing/EventSubscription.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index bdfe430cf..0309926ab 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index abd99bbc3..5fa1fd589 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/DeviceInfo.cs b/Emby.Dlna/PlayTo/DeviceInfo.cs index d3daab9e0..2acfff4eb 100644 --- a/Emby.Dlna/PlayTo/DeviceInfo.cs +++ b/Emby.Dlna/PlayTo/DeviceInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs index dabd079af..2bc4d8cc2 100644 --- a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs +++ b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index ee09cc65a..1e6a5fadb 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index f2f526221..35bf5927c 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs index d14617c8a..c7d2b28df 100644 --- a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs index 3f8d55263..f8a14f411 100644 --- a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs index deeb47918..6661f92ac 100644 --- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlaylistItem.cs b/Emby.Dlna/PlayTo/PlaylistItem.cs index 85846166c..5056e69ae 100644 --- a/Emby.Dlna/PlayTo/PlaylistItem.cs +++ b/Emby.Dlna/PlayTo/PlaylistItem.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs index e28840a89..657491303 100644 --- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index d9f1ce490..f14f73bb6 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs index cbcf66e45..b58669355 100644 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ b/Emby.Dlna/PlayTo/TransportCommands.cs @@ -46,7 +46,7 @@ namespace Emby.Dlna.PlayTo { var serviceAction = new ServiceAction { - Name = container.GetValue(UPnpNamespaces.Svc + "name"), + Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty, }; var argumentList = serviceAction.ArgumentList; @@ -68,9 +68,9 @@ namespace Emby.Dlna.PlayTo return new Argument { - Name = container.GetValue(UPnpNamespaces.Svc + "name"), - Direction = container.GetValue(UPnpNamespaces.Svc + "direction"), - RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") + Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty, + Direction = container.GetValue(UPnpNamespaces.Svc + "direction") ?? string.Empty, + RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") ?? string.Empty }; } @@ -89,8 +89,8 @@ namespace Emby.Dlna.PlayTo return new StateVariable { - Name = container.GetValue(UPnpNamespaces.Svc + "name"), - DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"), + Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty, + DataType = container.GetValue(UPnpNamespaces.Svc + "dataType") ?? string.Empty, AllowedValues = allowedValues }; } @@ -166,7 +166,7 @@ namespace Emby.Dlna.PlayTo return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString); } - private string BuildArgumentXml(Argument argument, string value, string commandParameter = "") + private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "") { var state = StateVariables.FirstOrDefault(a => string.Equals(a.Name, argument.RelatedStateVariable, StringComparison.OrdinalIgnoreCase)); diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs index 0d9478e42..02d2da58d 100644 --- a/Emby.Dlna/PlayTo/uBaseObject.cs +++ b/Emby.Dlna/PlayTo/uBaseObject.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 09525aae4..3f3dfccd3 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -250,7 +250,8 @@ namespace Emby.Dlna.Server url = _serverAddress.TrimEnd('/') + "/dlna/" + _serverUdn + "/" + url.TrimStart('/'); - return SecurityElement.Escape(url); + // TODO: @bond remove null-coalescing operator when https://github.com/dotnet/runtime/pull/52442 is merged/released + return SecurityElement.Escape(url) ?? string.Empty; } private IEnumerable GetIcons() diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index fda8346f9..904c23d99 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -47,7 +47,7 @@ namespace Emby.Dlna.Service private async Task ProcessControlRequestInternalAsync(ControlRequest request) { - ControlRequestInfo requestInfo = null; + ControlRequestInfo? requestInfo = null; using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8)) { @@ -151,7 +151,7 @@ namespace Emby.Dlna.Service private async Task ParseBodyTagAsync(XmlReader reader) { - string namespaceURI = null, localName = null; + string? namespaceURI = null, localName = null; await reader.MoveToContentAsync().ConfigureAwait(false); await reader.ReadAsync().ConfigureAwait(false); diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index d9c6a93c7..391dda147 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/Ssdp/SsdpExtensions.cs b/Emby.Dlna/Ssdp/SsdpExtensions.cs index e7a52f168..d00eb02b4 100644 --- a/Emby.Dlna/Ssdp/SsdpExtensions.cs +++ b/Emby.Dlna/Ssdp/SsdpExtensions.cs @@ -7,21 +7,21 @@ namespace Emby.Dlna.Ssdp { public static class SsdpExtensions { - public static string GetValue(this XElement container, XName name) + public static string? GetValue(this XElement container, XName name) { var node = container.Element(name); return node?.Value; } - public static string GetAttributeValue(this XElement container, XName name) + public static string? GetAttributeValue(this XElement container, XName name) { var node = container.Attribute(name); return node?.Value; } - public static string GetDescendantValue(this XElement container, XName name) + public static string? GetDescendantValue(this XElement container, XName name) => container.Descendants(name).FirstOrDefault()?.Value; } } From 4f5c9e95041a39ae549bfa3f36bbeda054bce3ca Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 7 May 2021 14:02:42 +0100 Subject: [PATCH 154/612] tests and small fix. --- Jellyfin.Api/Controllers/SystemController.cs | 8 +++- .../Middleware/UrlDecodeQueryFeature.cs | 9 +++- .../Controllers/EncodedQueryStringTest.cs | 47 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index bbbe5fb8d..5c64d731b 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -84,13 +84,19 @@ namespace Jellyfin.Api.Controllers /// /// Pings the system. /// + /// Optional: Parameters to echo back in the response. /// Information retrieved. /// The server name. [HttpGet("Ping", Name = "GetPingSystem")] [HttpPost("Ping", Name = "PostPingSystem")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult PingSystem() + public ActionResult PingSystem([FromQuery]Dictionary? @params = null) { + if (@params != null && @params.Count > 0) + { + Response.Headers.Add("querystring", string.Join("&", @params.Select(x => x.Key + "=" + x.Value))); + } + return _appHost.Name; } diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs index b18ba7051..44b30baac 100644 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -66,7 +66,14 @@ namespace Jellyfin.Server.Middleware foreach (var pair in queryString) { var item = pair.Split('=', System.StringSplitOptions.RemoveEmptyEntries); - pairs.Add(item[0], new StringValues(item.Length == 2 ? item[1] : string.Empty)); + if (item.Length > 0) + { + pairs.Add(item[0], new StringValues(item.Length == 2 ? item[1] : string.Empty)); + } + else + { + pairs.Add(pair, string.Empty); + } } _store = new QueryCollection(pairs); diff --git a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs new file mode 100644 index 000000000..ce5ac11ea --- /dev/null +++ b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs @@ -0,0 +1,47 @@ +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Jellyfin.Api.Tests.Controllers +{ + /// + /// Defines the test for encoded querystrings in the url. + /// + public class EncodedQueryStringTest : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + + public EncodedQueryStringTest(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task Ensure_Ping_Working() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("system/ping").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Theory] + [InlineData("a=1&b=2&c=3", "a=1&b=2&c=3")] // won't be processed as there is more than 1. + [InlineData("a=1", "a=1")] // won't be processed as it has a value + [InlineData("%3D", "==")] // will decode with an empty string value '=' = ''. + [InlineData("a%3D1%26b%3D2%26c%3D3", "a=1&b=2&c=3")] // will be processed. + + public async Task Ensure_Decoding_Of_Urls_Is_Working(string sourceUrl, string unencodedUrl) + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("system/ping?" + sourceUrl).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(unencodedUrl, response.Headers.GetValues("querystring").First()); + } + } +} From af1fe1af6fee7f4e5cf73d92266df21b291169d9 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 7 May 2021 14:37:26 +0100 Subject: [PATCH 155/612] Moved into test controller. --- Jellyfin.Api/Controllers/SystemController.cs | 8 +--- Jellyfin.Api/Controllers/TestController.cs | 41 +++++++++++++++++++ .../Controllers/EncodedQueryStringTest.cs | 2 +- 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 Jellyfin.Api/Controllers/TestController.cs diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 5c64d731b..bbbe5fb8d 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -84,19 +84,13 @@ namespace Jellyfin.Api.Controllers /// /// Pings the system. /// - /// Optional: Parameters to echo back in the response. /// Information retrieved. /// The server name. [HttpGet("Ping", Name = "GetPingSystem")] [HttpPost("Ping", Name = "PostPingSystem")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult PingSystem([FromQuery]Dictionary? @params = null) + public ActionResult PingSystem() { - if (@params != null && @params.Count > 0) - { - Response.Headers.Add("querystring", string.Join("&", @params.Select(x => x.Key + "=" + x.Value))); - } - return _appHost.Name; } diff --git a/Jellyfin.Api/Controllers/TestController.cs b/Jellyfin.Api/Controllers/TestController.cs new file mode 100644 index 000000000..b76895696 --- /dev/null +++ b/Jellyfin.Api/Controllers/TestController.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Api.Constants; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Controller for testing. + /// + [Route("Tests")] + public class TestController : BaseJellyfinApiController + { + /// + /// Initializes a new instance of the class. + /// + public TestController() + { + } + + /// + /// Tests the url decoding. + /// + /// Parameters to echo back in the response. + /// An . + /// Information retrieved. + [HttpGet("Decoding", Name = "TestUrlDecoding")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult TestUrlDecoding([FromQuery]Dictionary? @params = null) + { + if (@params != null && @params.Count > 0) + { + Response.Headers.Add("querystring", string.Join("&", @params.Select(x => x.Key + "=" + x.Value))); + } + + return Ok(); + } + } +} diff --git a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs index ce5ac11ea..212fb118c 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs @@ -39,7 +39,7 @@ namespace Jellyfin.Api.Tests.Controllers { var client = _factory.CreateClient(); - var response = await client.GetAsync("system/ping?" + sourceUrl).ConfigureAwait(false); + var response = await client.GetAsync("Tests/Decoding?" + sourceUrl).ConfigureAwait(false); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(unencodedUrl, response.Headers.GetValues("querystring").First()); } From 043e69d252d5862ec5466ad037fea05f5aaef40f Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 7 May 2021 18:49:57 +0100 Subject: [PATCH 156/612] Update tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs Co-authored-by: Cody Robibero --- tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs index 212fb118c..dd81ad134 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs @@ -34,7 +34,6 @@ namespace Jellyfin.Api.Tests.Controllers [InlineData("a=1", "a=1")] // won't be processed as it has a value [InlineData("%3D", "==")] // will decode with an empty string value '=' = ''. [InlineData("a%3D1%26b%3D2%26c%3D3", "a=1&b=2&c=3")] // will be processed. - public async Task Ensure_Decoding_Of_Urls_Is_Working(string sourceUrl, string unencodedUrl) { var client = _factory.CreateClient(); From 5f70b4ead16965acf4dbaf7bb486ecf072fb2a1b Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 7 May 2021 18:50:38 +0100 Subject: [PATCH 157/612] Update tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs Co-authored-by: Cody Robibero --- .../Controllers/EncodedQueryStringTest.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs index dd81ad134..80175039e 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs @@ -20,15 +20,6 @@ namespace Jellyfin.Api.Tests.Controllers _factory = factory; } - [Fact] - public async Task Ensure_Ping_Working() - { - var client = _factory.CreateClient(); - - var response = await client.GetAsync("system/ping").ConfigureAwait(false); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - [Theory] [InlineData("a=1&b=2&c=3", "a=1&b=2&c=3")] // won't be processed as there is more than 1. [InlineData("a=1", "a=1")] // won't be processed as it has a value From 5ad8b53f5d1a540453d897cddf924aa6fee46570 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 7 May 2021 18:51:04 +0100 Subject: [PATCH 158/612] Update Jellyfin.Api/Controllers/TestController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/TestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/TestController.cs b/Jellyfin.Api/Controllers/TestController.cs index b76895696..a960bb24f 100644 --- a/Jellyfin.Api/Controllers/TestController.cs +++ b/Jellyfin.Api/Controllers/TestController.cs @@ -26,7 +26,7 @@ namespace Jellyfin.Api.Controllers /// Parameters to echo back in the response. /// An . /// Information retrieved. - [HttpGet("Decoding", Name = "TestUrlDecoding")] + [HttpGet("UrlDecode")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult TestUrlDecoding([FromQuery]Dictionary? @params = null) { From 4c8cfaa61c513e51aab4440244a067ca39175dd5 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 7 May 2021 18:51:15 +0100 Subject: [PATCH 159/612] Update Jellyfin.Api/Controllers/TestController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/TestController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/TestController.cs b/Jellyfin.Api/Controllers/TestController.cs index a960bb24f..168504575 100644 --- a/Jellyfin.Api/Controllers/TestController.cs +++ b/Jellyfin.Api/Controllers/TestController.cs @@ -10,8 +10,7 @@ namespace Jellyfin.Api.Controllers /// /// Controller for testing. /// - [Route("Tests")] - public class TestController : BaseJellyfinApiController + public class TestsController : BaseJellyfinApiController { /// /// Initializes a new instance of the class. From 8b34f76b63dca54d59b4f3db2f5b485c32bd8e36 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 7 May 2021 18:51:31 +0100 Subject: [PATCH 160/612] Update Jellyfin.Api/Controllers/TestController.cs Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/TestController.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Jellyfin.Api/Controllers/TestController.cs b/Jellyfin.Api/Controllers/TestController.cs index 168504575..487a99fcb 100644 --- a/Jellyfin.Api/Controllers/TestController.cs +++ b/Jellyfin.Api/Controllers/TestController.cs @@ -12,13 +12,6 @@ namespace Jellyfin.Api.Controllers /// public class TestsController : BaseJellyfinApiController { - /// - /// Initializes a new instance of the class. - /// - public TestController() - { - } - /// /// Tests the url decoding. /// From d21d1978a3fce65a0af144d4293ce92bbf1f5b86 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Fri, 7 May 2021 21:09:05 +0200 Subject: [PATCH 161/612] Disable automation CI on issues --- .github/workflows/automation.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index a203e6695..2529d8099 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -2,8 +2,6 @@ name: Automation on: pull_request: - issues: - issue_comment: jobs: main: From dca02987106d0433ee4139fb380dd78d92921dae Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 7 May 2021 20:11:32 +0100 Subject: [PATCH 162/612] changed --- .../{TestController.cs => TestsController.cs} | 14 ++++++++------ .../Controllers/EncodedQueryStringTest.cs | 6 ++++-- 2 files changed, 12 insertions(+), 8 deletions(-) rename Jellyfin.Api/Controllers/{TestController.cs => TestsController.cs} (64%) diff --git a/Jellyfin.Api/Controllers/TestController.cs b/Jellyfin.Api/Controllers/TestsController.cs similarity index 64% rename from Jellyfin.Api/Controllers/TestController.cs rename to Jellyfin.Api/Controllers/TestsController.cs index 487a99fcb..1d1e1899f 100644 --- a/Jellyfin.Api/Controllers/TestController.cs +++ b/Jellyfin.Api/Controllers/TestsController.cs @@ -20,14 +20,16 @@ namespace Jellyfin.Api.Controllers /// Information retrieved. [HttpGet("UrlDecode")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult TestUrlDecoding([FromQuery]Dictionary? @params = null) + public ContentResult TestUrlDecoding([FromQuery]Dictionary? @params = null) { - if (@params != null && @params.Count > 0) + return new ContentResult() { - Response.Headers.Add("querystring", string.Join("&", @params.Select(x => x.Key + "=" + x.Value))); - } - - return Ok(); + Content = (@params != null && @params.Count > 0) + ? string.Join("&", @params.Select(x => x.Key + "=" + x.Value)) + : string.Empty, + ContentType = "text/plain; charset=utf-8", + StatusCode = 200 + }; } } } diff --git a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs index 80175039e..d6a423dcd 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Linq; using System.Net; @@ -29,9 +30,10 @@ namespace Jellyfin.Api.Tests.Controllers { var client = _factory.CreateClient(); - var response = await client.GetAsync("Tests/Decoding?" + sourceUrl).ConfigureAwait(false); + var response = await client.GetAsync("Tests/UrlDecode?" + sourceUrl).ConfigureAwait(false); Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(unencodedUrl, response.Headers.GetValues("querystring").First()); + string reply = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + Assert.Equal(unencodedUrl, reply); } } } From ce0e890d8d365e2b2f73645156adb9730caf1ce2 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 7 May 2021 13:39:38 -0600 Subject: [PATCH 163/612] Mooooove the label commenter config --- .github/{workflows => }/label-commenter-config.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => }/label-commenter-config.yml (100%) diff --git a/.github/workflows/label-commenter-config.yml b/.github/label-commenter-config.yml similarity index 100% rename from .github/workflows/label-commenter-config.yml rename to .github/label-commenter-config.yml From 56ac64e70a5cd4b4b4de78a0fc22c8bfe96a3677 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 8 May 2021 00:33:24 +0200 Subject: [PATCH 164/612] Minor improvements * properly dispose CancellationTokenSource * rewrite DynamicHlsController.GetSegmentLengths * remove dead code --- .../Controllers/DynamicHlsController.cs | 56 +++++++++--------- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 1 - .../MediaEncoding/EncodingJobOptions.cs | 49 ---------------- .../Controllers/DynamicHlsControllerTests.cs | 58 +++++++++++++++++++ 4 files changed, 85 insertions(+), 79 deletions(-) create mode 100644 tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index b4154b361..9f3216de3 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -28,7 +28,6 @@ using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -545,7 +544,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { - var cancellationTokenSource = new CancellationTokenSource(); + using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new VideoRequestDto { Id = itemId, @@ -710,7 +709,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions) { - var cancellationTokenSource = new CancellationTokenSource(); + using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new StreamingRequestDto { Id = itemId, @@ -1138,7 +1137,7 @@ namespace Jellyfin.Api.Controllers var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase); var hlsVersion = isHlsInFmp4 ? "7" : "3"; - var builder = new StringBuilder(); + var builder = new StringBuilder(128); builder.AppendLine("#EXTM3U") .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD") @@ -1191,7 +1190,7 @@ namespace Jellyfin.Api.Controllers throw new ArgumentException("StartTimeTicks is not allowed."); } - var cancellationTokenSource = new CancellationTokenSource(); + using var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; using var state = await StreamingHelpers.GetStreamingState( @@ -1208,7 +1207,7 @@ namespace Jellyfin.Api.Controllers _deviceManager, _transcodingJobHelper, TranscodingJobType, - cancellationTokenSource.Token) + cancellationToken) .ConfigureAwait(false); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); @@ -1227,7 +1226,7 @@ namespace Jellyfin.Api.Controllers } var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath); - await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false); var released = false; var startTranscoding = false; @@ -1323,24 +1322,28 @@ namespace Jellyfin.Api.Controllers return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); } - private double[] GetSegmentLengths(StreamState state) + private static double[] GetSegmentLengths(StreamState state) + => GetSegmentLengthsInternal(state.RunTimeTicks ?? 0, state.SegmentLength); + + internal static double[] GetSegmentLengthsInternal(long runtimeTicks, int segmentlength) { - var result = new List(); + var segmentLengthTicks = TimeSpan.FromSeconds(segmentlength).Ticks; + var wholeSegments = runtimeTicks / segmentLengthTicks; + var remainingTicks = runtimeTicks % segmentLengthTicks; - var ticks = state.RunTimeTicks ?? 0; - - var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks; - - while (ticks > 0) + var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1); + var segments = new double[segmentsLen]; + for (int i = 0; i < wholeSegments; i++) { - var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks; - - result.Add(TimeSpan.FromTicks(length).TotalSeconds); - - ticks -= length; + segments[i] = segmentlength; } - return result.ToArray(); + if (remainingTicks != 0) + { + segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds; + } + + return segments; } private string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding, int startNumber) @@ -1376,18 +1379,13 @@ namespace Jellyfin.Api.Controllers } else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)) { - var outputFmp4HeaderArg = string.Empty; - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - if (isWindows) + var outputFmp4HeaderArg = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) switch { // on Windows, the path of fmp4 header file needs to be configured - outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\""; - } - else - { + true => " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"", // on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder - outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""; - } + false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\"" + }; segmentFormat = "fmp4" + outputFmp4HeaderArg; } diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 0879cbd18..5770c13d7 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -22,7 +22,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Helpers diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index 88de5b292..61f3bc771 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -4,59 +4,10 @@ using System; using System.Collections.Generic; -using System.Linq; using MediaBrowser.Model.Dlna; namespace MediaBrowser.Controller.MediaEncoding { - public class EncodingJobOptions : BaseEncodingJobOptions - { - public string OutputDirectory { get; set; } - - public string ItemId { get; set; } - - public string TempDirectory { get; set; } - - public bool ReadInputAtNativeFramerate { get; set; } - - /// - /// Gets a value indicating whether this instance has fixed resolution. - /// - /// true if this instance has fixed resolution; otherwise, false. - public bool HasFixedResolution => Width.HasValue || Height.HasValue; - - public DeviceProfile DeviceProfile { get; set; } - - public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile) - { - Container = info.Container; - StartTimeTicks = info.StartPositionTicks; - MaxWidth = info.MaxWidth; - MaxHeight = info.MaxHeight; - MaxFramerate = info.MaxFramerate; - Id = info.ItemId; - MediaSourceId = info.MediaSourceId; - AudioCodec = info.TargetAudioCodec.FirstOrDefault(); - MaxAudioChannels = info.GlobalMaxAudioChannels; - AudioBitRate = info.AudioBitrate; - AudioSampleRate = info.TargetAudioSampleRate; - DeviceProfile = deviceProfile; - VideoCodec = info.TargetVideoCodec.FirstOrDefault(); - VideoBitRate = info.VideoBitrate; - AudioStreamIndex = info.AudioStreamIndex; - SubtitleMethod = info.SubtitleDeliveryMethod; - Context = info.Context; - TranscodingMaxAudioChannels = info.TranscodingMaxAudioChannels; - - if (info.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) - { - SubtitleStreamIndex = info.SubtitleStreamIndex; - } - - StreamOptions = info.StreamOptions; - } - } - // For now until api and media encoding layers are unified public class BaseEncodingJobOptions { diff --git a/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs new file mode 100644 index 000000000..117083815 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using AutoFixture; +using AutoFixture.AutoMoq; +using Jellyfin.Api.Controllers; +using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using Moq; +using Xunit; + +namespace Jellyfin.Api.Tests.Controllers +{ + public class DynamicHlsControllerTests + { + [Theory] + [MemberData(nameof(GetSegmentLengths_Success_TestData))] + public void GetSegmentLengths_Success(long runtimeTicks, int segmentlength, double[] expected) + { + var res = DynamicHlsController.GetSegmentLengthsInternal(runtimeTicks, segmentlength); + Assert.Equal(expected.Length, res.Length); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], res[i]); + } + } + + public static IEnumerable GetSegmentLengths_Success_TestData() + { + yield return new object[] { 0, 6, Array.Empty() }; + yield return new object[] + { + TimeSpan.FromSeconds(3).Ticks, + 6, + new double[] { 3 } + }; + yield return new object[] + { + TimeSpan.FromSeconds(6).Ticks, + 6, + new double[] { 6 } + }; + yield return new object[] + { + TimeSpan.FromSeconds(3.3333333).Ticks, + 6, + new double[] { 3.3333333 } + }; + yield return new object[] + { + TimeSpan.FromSeconds(9.3333333).Ticks, + 6, + new double[] { 6, 3.3333333 } + }; + } + } +} From bd71de131c384765b9240b7a54649e2c9258b133 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 8 May 2021 12:52:25 +0100 Subject: [PATCH 165/612] Changed to use span --- .../Middleware/UrlDecodeQueryFeature.cs | 21 ++++++++++++------- .../Jellyfin.Api.Tests.csproj | 1 + ...llyfin.Server.Implementations.Tests.csproj | 1 + .../Middleware}/EncodedQueryStringTest.cs | 3 ++- 4 files changed, 17 insertions(+), 9 deletions(-) rename tests/{Jellyfin.Api.Tests/Controllers => Jellyfin.Server.Implementations.Tests/Middleware}/EncodedQueryStringTest.cs (93%) diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs index 44b30baac..c89a318e1 100644 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Web; +using MediaBrowser.Common.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Primitives; @@ -61,19 +62,23 @@ namespace Jellyfin.Server.Middleware } var pairs = new Dictionary(); - var queryString = unencodedKey.Split('&', System.StringSplitOptions.RemoveEmptyEntries); + var queryString = unencodedKey.SpanSplit('&'); foreach (var pair in queryString) { - var item = pair.Split('=', System.StringSplitOptions.RemoveEmptyEntries); - if (item.Length > 0) + var item = pair.Split('='); + item.MoveNext(); + + var key = item.Current; + var val = item.MoveNext() ? item.Current : string.Empty; + if (key.Length == 0 && val.Length == 0) { - pairs.Add(item[0], new StringValues(item.Length == 2 ? item[1] : string.Empty)); - } - else - { - pairs.Add(pair, string.Empty); + // encoded is an equals. + pairs.Add(pair.ToString(), new StringValues(string.Empty)); + continue; } + + pairs.Add(key.ToString(), new StringValues(val.ToString())); } _store = new QueryCollection(pairs); diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 397b863b7..e3577caee 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -37,6 +37,7 @@ + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 27713d58a..ccc18d686 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -42,6 +42,7 @@ + diff --git a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Middleware/EncodedQueryStringTest.cs similarity index 93% rename from tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs rename to tests/Jellyfin.Server.Implementations.Tests/Middleware/EncodedQueryStringTest.cs index d6a423dcd..e5865fb5a 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/EncodedQueryStringTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Middleware/EncodedQueryStringTest.cs @@ -5,9 +5,10 @@ using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; +using Jellyfin.Server.Integration.Tests; using Xunit; -namespace Jellyfin.Api.Tests.Controllers +namespace Jellyfin.Server.Implementations.Tests.Middleware { /// /// Defines the test for encoded querystrings in the url. From cb74a8697554008d37ae9359794b132c4945746b Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 8 May 2021 13:33:47 +0100 Subject: [PATCH 166/612] Moved test --- .../EncodedQueryStringTest.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) rename tests/{Jellyfin.Server.Implementations.Tests/Middleware => Jellyfin.Server.Integration.Tests}/EncodedQueryStringTest.cs (87%) diff --git a/tests/Jellyfin.Server.Implementations.Tests/Middleware/EncodedQueryStringTest.cs b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs similarity index 87% rename from tests/Jellyfin.Server.Implementations.Tests/Middleware/EncodedQueryStringTest.cs rename to tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs index e5865fb5a..a89d6e86f 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Middleware/EncodedQueryStringTest.cs +++ b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs @@ -1,14 +1,8 @@ -using System; -using System.IO; -using System.Linq; using System.Net; -using System.Net.Http; -using System.Text; using System.Threading.Tasks; -using Jellyfin.Server.Integration.Tests; using Xunit; -namespace Jellyfin.Server.Implementations.Tests.Middleware +namespace Jellyfin.Server.Integration.Tests { /// /// Defines the test for encoded querystrings in the url. From 903bf2a086c49266c010f947180bd660b2c58931 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 8 May 2021 16:00:41 +0100 Subject: [PATCH 167/612] changed to use index --- Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs index c89a318e1..dd05f7bc5 100644 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -66,19 +66,17 @@ namespace Jellyfin.Server.Middleware foreach (var pair in queryString) { - var item = pair.Split('='); - item.MoveNext(); + var section = pair.ToString(); + var i = section.IndexOf('=', System.StringComparison.Ordinal); - var key = item.Current; - var val = item.MoveNext() ? item.Current : string.Empty; - if (key.Length == 0 && val.Length == 0) + if (i == -1) { // encoded is an equals. - pairs.Add(pair.ToString(), new StringValues(string.Empty)); + pairs.Add(section, new StringValues(string.Empty)); continue; } - pairs.Add(key.ToString(), new StringValues(val.ToString())); + pairs.Add(section[0..i], new StringValues(section[(i + 1)..])); } _store = new QueryCollection(pairs); From 62a93d494b1970f67c60984ebbd554c19184fa2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 May 2021 12:19:21 +0000 Subject: [PATCH 168/612] Bump prometheus-net.DotNetRuntime from 4.0.0 to 4.1.0 Bumps [prometheus-net.DotNetRuntime](https://github.com/djluck/prometheus-net.DotNetRuntime) from 4.0.0 to 4.1.0. - [Release notes](https://github.com/djluck/prometheus-net.DotNetRuntime/releases) - [Commits](https://github.com/djluck/prometheus-net.DotNetRuntime/compare/4.0.0...4.1.0) Signed-off-by: dependabot[bot] --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index b8a544b8c..8ea98f454 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,7 +29,7 @@ - + From 7ebe30f0abf674020d6801ab6bd1b4594390b5ed Mon Sep 17 00:00:00 2001 From: bvd13 Date: Tue, 23 Mar 2021 17:23:29 +0000 Subject: [PATCH 169/612] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- Emby.Server.Implementations/Localization/Core/nl.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index ffc329e35..ab1546a29 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -3,9 +3,9 @@ "AppDeviceValues": "App: {0}, Apparaat: {1}", "Application": "Applicatie", "Artists": "Artiesten", - "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", + "AuthenticationSucceededWithUserName": "{0} is succesvol geauthenticeerd", "Books": "Boeken", - "CameraImageUploadedFrom": "Er is een nieuwe camera afbeelding toegevoegd via {0}", + "CameraImageUploadedFrom": "Er is een nieuwe camera afbeelding toegevoegd vanaf {0}", "Channels": "Kanalen", "ChapterNameValue": "Hoofdstuk {0}", "Collections": "Verzamelingen", From c78f584a99d810167fe64d0451bf86b277112448 Mon Sep 17 00:00:00 2001 From: Baptiste Date: Wed, 24 Mar 2021 00:47:31 +0000 Subject: [PATCH 170/612] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 1e195378f..56fd9d452 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -99,7 +99,7 @@ "TaskRefreshChannels": "Rafraîchir les chaines", "TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.", "TaskCleanTranscode": "Nettoyer les dossier des transcodages", - "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour être mises à jour automatiquement.", + "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurées pour être mises à jour automatiquement.", "TaskUpdatePlugins": "Mettre à jour les extensions", "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.", "TaskRefreshPeople": "Rafraîchir les acteurs", From c5783da4f6b68680b111a6781c6f2f25a223b676 Mon Sep 17 00:00:00 2001 From: Csaba Date: Mon, 29 Mar 2021 04:43:25 +0000 Subject: [PATCH 171/612] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/ --- Emby.Server.Implementations/Localization/Core/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index ef8070503..c73cb9c72 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -39,7 +39,7 @@ "MixedContent": "Vegyes tartalom", "Movies": "Filmek", "Music": "Zene", - "MusicVideos": "Zenei videók", + "MusicVideos": "Zenei videóklippek", "NameInstallFailed": "{0} sikertelen telepítés", "NameSeasonNumber": "{0}. évad", "NameSeasonUnknown": "Ismeretlen évad", From 2c66d36f10948285a3abf8dc941a5248aa5f05ad Mon Sep 17 00:00:00 2001 From: WWWesten Date: Mon, 29 Mar 2021 12:45:20 +0000 Subject: [PATCH 172/612] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/ --- Emby.Server.Implementations/Localization/Core/ru.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 46b47cf4a..e58f8c39d 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -39,7 +39,7 @@ "MixedContent": "Смешанное содержимое", "Movies": "Кино", "Music": "Музыка", - "MusicVideos": "Музыкальные клипы", + "MusicVideos": "Муз. видео", "NameInstallFailed": "Установка {0} неудачна", "NameSeasonNumber": "Сезон {0}", "NameSeasonUnknown": "Сезон неопознан", @@ -75,7 +75,7 @@ "StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.", "SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить", "SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}", - "Sync": "Синхронизация", + "Sync": "Синхро", "System": "Система", "TvShows": "ТВ", "User": "Пользователь", From f56898a563875f3a32fd83c9fe0aeaf071eee30b Mon Sep 17 00:00:00 2001 From: Oatavandi Date: Mon, 29 Mar 2021 16:47:06 +0000 Subject: [PATCH 173/612] Translated using Weblate (Tamil) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ta/ --- Emby.Server.Implementations/Localization/Core/ta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index c737ba42b..129986ed0 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -69,7 +69,7 @@ "NameSeasonUnknown": "அறியப்படாத பருவம்", "NameSeasonNumber": "பருவம் {0}", "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது", - "MusicVideos": "இசைப்படங்கள்", + "MusicVideos": "இசை கானொளி", "Music": "இசை", "Movies": "திரைப்படங்கள்", "Latest": "புதியவை", From 55f3dd4f2cd6920f633e2204d7e3783b2bd58d62 Mon Sep 17 00:00:00 2001 From: millallo Date: Thu, 1 Apr 2021 10:08:05 +0000 Subject: [PATCH 174/612] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 110f8043d..90d7d742f 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -87,7 +87,7 @@ "UserOnlineFromDevice": "{0} è online su {1}", "UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}", "UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}", - "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}", + "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di \"{1}\" su {2}", "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}", "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale", "ValueSpecialEpisodeName": "Speciale - {0}", From 95f25467c8092a041c425dc5abbd74e54325edb4 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 6 Apr 2021 06:02:56 +0000 Subject: [PATCH 175/612] Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/ --- Emby.Server.Implementations/Localization/Core/fa.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index e9e4f61b8..8ab657e5b 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -34,7 +34,7 @@ "Latest": "جدیدترین‌ها", "MessageApplicationUpdated": "سرور Jellyfin بروزرسانی شد", "MessageApplicationUpdatedTo": "سرور Jellyfin به نسخه {0} بروزرسانی شد", - "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", + "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", "MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد", "MixedContent": "محتوای مخلوط", "Movies": "فیلم‌ها", From da5ca3f76980ec870e5ad11149a1b841e5473d2e Mon Sep 17 00:00:00 2001 From: Csaba Date: Tue, 6 Apr 2021 05:53:44 +0000 Subject: [PATCH 176/612] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/ --- Emby.Server.Implementations/Localization/Core/hu.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index c73cb9c72..85848fed6 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -74,7 +74,7 @@ "Songs": "Dalok", "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}", + "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}", "Sync": "Szinkronizál", "System": "Rendszer", "TvShows": "TV műsorok", @@ -82,12 +82,12 @@ "UserCreatedWithName": "{0} felhasználó létrehozva", "UserDeletedWithName": "{0} felhasználó törölve", "UserDownloadingItemWithValues": "{0} letölti {1}", - "UserLockedOutWithName": "{0} felhasználó zárolva van", - "UserOfflineFromDevice": "{0} kijelentkezett innen: {1}", + "UserLockedOutWithName": "{0} felhasználó zárolva van", + "UserOfflineFromDevice": "{0} kijelentkezett innen: {1}", "UserOnlineFromDevice": "{0} online innen: {1}", "UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}", "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}", - "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}", + "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}", "UserStoppedPlayingItemWithValues": "{0} befejezte {1} lejátászását itt: {2}", "ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz", "ValueSpecialEpisodeName": "Special - {0}", From 90a8cd83f48a3b5fc2b1661ab5588ed361debef3 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 6 Apr 2021 06:02:27 +0000 Subject: [PATCH 177/612] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Translat?= =?UTF-8?q?e-URL:=20https://translate.jellyfin.org/projects/jellyfin/jelly?= =?UTF-8?q?fin-core/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Emby.Server.Implementations/Localization/Core/nb.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index d5bca9f6c..a4f957a3a 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -88,7 +88,7 @@ "UserPasswordChangedWithName": "Passordet for {0} er oppdatert", "UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}", "UserStartedPlayingItemWithValues": "{0} har startet avspilling {1} på {2}", - "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}", + "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}", "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt", "ValueSpecialEpisodeName": "Spesialepisode - {0}", "VersionNumber": "Versjon {0}", From eaa102fc7400ebebb86d24d42d1311bb8cceec7b Mon Sep 17 00:00:00 2001 From: WWWesten Date: Tue, 6 Apr 2021 06:03:33 +0000 Subject: [PATCH 178/612] Translated using Weblate (Bengali) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/bn/ --- Emby.Server.Implementations/Localization/Core/bn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index a23037af8..556591a22 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -115,7 +115,7 @@ "TaskRefreshLibraryDescription": "নতুন ফাইলের জন্য মিডিয়া লাইব্রেরি স্ক্যান এবং মেটাডাটা রিফ্রেশ করুন।", "Undefined": "অসঙ্গায়িত", "Forced": "জোরকরে", - "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন", + "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন", "TaskCleanActivityLog": "কাজের ফাইল খালি করুন", "Default": "প্রাথমিক" } From b5bde8d46e087fd101311c717b67245b79390552 Mon Sep 17 00:00:00 2001 From: flackseven Date: Sun, 4 Apr 2021 23:53:40 +0000 Subject: [PATCH 179/612] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/uk/ --- Emby.Server.Implementations/Localization/Core/uk.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json index b6073bf6a..5a2069df5 100644 --- a/Emby.Server.Implementations/Localization/Core/uk.json +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -1,5 +1,5 @@ { - "MusicVideos": "Музичні кліпи", + "MusicVideos": "Музичні відеокліпи", "Music": "Музика", "Movies": "Фільми", "MessageApplicationUpdatedTo": "Jellyfin Server оновлено до версії {0}", @@ -113,7 +113,7 @@ "MessageNamedServerConfigurationUpdatedWithValue": "Розділ конфігурації сервера {0} оновлено", "Inherit": "Успадкувати", "HeaderRecordingGroups": "Групи запису", - "Forced": "Примусово", + "Forced": "Форсовані", "TaskCleanActivityLogDescription": "Видаляє старші за встановлений термін записи з журналу активності.", "TaskCleanActivityLog": "Очистити журнал активності", "Undefined": "Не визначено", From 0158f74d3798fcfc84b237726e8d888e528ce65d Mon Sep 17 00:00:00 2001 From: Manjot Singh Date: Mon, 5 Apr 2021 06:24:22 +0000 Subject: [PATCH 180/612] Translated using Weblate (Punjabi) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pa/ --- Emby.Server.Implementations/Localization/Core/pa.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pa.json b/Emby.Server.Implementations/Localization/Core/pa.json index 469fa89b6..d1db09232 100644 --- a/Emby.Server.Implementations/Localization/Core/pa.json +++ b/Emby.Server.Implementations/Localization/Core/pa.json @@ -1,5 +1,5 @@ { - "TaskRefreshChapterImages": "ਐਬਸਟਰੈਕਟ ਅਧਿਆਇ ਅਧਿਆਇ", + "TaskRefreshChapterImages": "ਐਕਸਟਰੈਕਟ ਚੈਪਟਰ ਚਿੱਤਰ", "TaskDownloadMissingSubtitlesDescription": "ਮੈਟਾਡੇਟਾ ਕੌਂਫਿਗਰੇਸ਼ਨ ਦੇ ਅਧਾਰ ਤੇ ਗਾਇਬ ਉਪਸਿਰਲੇਖਾਂ ਲਈ ਇੰਟਰਨੈਟ ਦੀ ਭਾਲ ਕਰਦਾ ਹੈ.", "TaskDownloadMissingSubtitles": "ਗਾਇਬ ਉਪਸਿਰਲੇਖ ਡਾ Download ਨਲੋਡ ਕਰੋ", "TaskRefreshChannelsDescription": "ਇੰਟਰਨੈੱਟ ਚੈਨਲ ਦੀ ਜਾਣਕਾਰੀ ਨੂੰ ਤਾਜ਼ਾ ਕਰਦਾ ਹੈ.", From 061016a2a939c0a47634ef22251467260486c718 Mon Sep 17 00:00:00 2001 From: Robolov Date: Wed, 7 Apr 2021 14:35:57 +0000 Subject: [PATCH 181/612] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 56fd9d452..d12c1ddec 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -39,7 +39,7 @@ "MixedContent": "Contenu mixte", "Movies": "Films", "Music": "Musique", - "MusicVideos": "Vidéos musicales", + "MusicVideos": "Clips musicaux", "NameInstallFailed": "{0} échec de l'installation", "NameSeasonNumber": "Saison {0}", "NameSeasonUnknown": "Saison Inconnue", From dc15cc1370d06a44a4d4f824b3a9d62ae6aa86cd Mon Sep 17 00:00:00 2001 From: WWWesten Date: Thu, 8 Apr 2021 14:15:05 +0000 Subject: [PATCH 182/612] Translated using Weblate (Bengali) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/bn/ --- Emby.Server.Implementations/Localization/Core/bn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index 556591a22..f0e42a052 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -115,7 +115,7 @@ "TaskRefreshLibraryDescription": "নতুন ফাইলের জন্য মিডিয়া লাইব্রেরি স্ক্যান এবং মেটাডাটা রিফ্রেশ করুন।", "Undefined": "অসঙ্গায়িত", "Forced": "জোরকরে", - "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন", + "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন.", "TaskCleanActivityLog": "কাজের ফাইল খালি করুন", "Default": "প্রাথমিক" } From 1237a7b82a3adb92f905fc693fe263ec1aaaab81 Mon Sep 17 00:00:00 2001 From: Koos van Riemsdijk Date: Sun, 11 Apr 2021 08:21:12 +0000 Subject: [PATCH 183/612] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- Emby.Server.Implementations/Localization/Core/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index ab1546a29..2973c8c6e 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -5,7 +5,7 @@ "Artists": "Artiesten", "AuthenticationSucceededWithUserName": "{0} is succesvol geauthenticeerd", "Books": "Boeken", - "CameraImageUploadedFrom": "Er is een nieuwe camera afbeelding toegevoegd vanaf {0}", + "CameraImageUploadedFrom": "Nieuwe camera afbeelding toegevoegd vanaf {0}", "Channels": "Kanalen", "ChapterNameValue": "Hoofdstuk {0}", "Collections": "Verzamelingen", From 2bf27462317979b7e80718b703b501e871813497 Mon Sep 17 00:00:00 2001 From: radiusgreenhill Date: Wed, 14 Apr 2021 21:41:38 +0000 Subject: [PATCH 184/612] Translated using Weblate (Thai) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/th/ --- Emby.Server.Implementations/Localization/Core/th.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json index 5bf58baf8..e26010423 100644 --- a/Emby.Server.Implementations/Localization/Core/th.json +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -113,5 +113,9 @@ "Sync": "ซิงค์", "SubtitleDownloadFailureFromForItem": "ไม่สามารถดาวน์โหลดคำบรรยายจาก {0} สำหรับ {1} ได้", "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่", - "Default": "ค่าเริ่มต้น" + "Default": "ค่าเริ่มต้น", + "TaskCleanActivityLogDescription": "ลบบันทึกกิจกรรมที่เก่ากว่าค่าที่กำหนดไว้", + "TaskCleanActivityLog": "ล้างบันทึกกิจกรรม", + "Undefined": "ไม่ได้กำหนด", + "Forced": "บังคับใช้" } From 7dc721923ee94a2cbc8029e9eeb5557af01704f6 Mon Sep 17 00:00:00 2001 From: Federico Antoniazzi Date: Fri, 16 Apr 2021 22:17:18 +0000 Subject: [PATCH 185/612] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 90d7d742f..bd06f0a25 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -62,7 +62,7 @@ "NotificationOptionVideoPlaybackStopped": "La riproduzione video è stata interrotta", "Photos": "Foto", "Playlists": "Playlist", - "Plugin": "Plug-in", + "Plugin": "Plugin", "PluginInstalledWithName": "{0} è stato Installato", "PluginUninstalledWithName": "{0} è stato disinstallato", "PluginUpdatedWithName": "{0} è stato aggiornato", From 9b2a87f981cdb57af39535e97e0a95477faf1bb4 Mon Sep 17 00:00:00 2001 From: Oskari Lavinto Date: Sun, 18 Apr 2021 05:27:57 +0000 Subject: [PATCH 186/612] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fi/ --- Emby.Server.Implementations/Localization/Core/fi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index fd6148e78..633968d26 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -1,5 +1,5 @@ { - "HeaderLiveTV": "Live TV", + "HeaderLiveTV": "Suora TV", "NewVersionIsAvailable": "Uusi versio Jellyfin-palvelimesta on ladattavissa.", "NameSeasonUnknown": "Tuntematon kausi", "NameSeasonNumber": "Kausi {0}", From 85c771e64b250339679a438907763e1825e25a0b Mon Sep 17 00:00:00 2001 From: a19guillermobf Date: Fri, 23 Apr 2021 17:39:34 +0000 Subject: [PATCH 187/612] Translated using Weblate (Galician) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gl/ --- Emby.Server.Implementations/Localization/Core/gl.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json index 11139d32a..0398e1c9e 100644 --- a/Emby.Server.Implementations/Localization/Core/gl.json +++ b/Emby.Server.Implementations/Localization/Core/gl.json @@ -79,5 +79,14 @@ "PluginUninstalledWithName": "{0} foi desinstalado", "PluginInstalledWithName": "{0} foi instalado", "Playlists": "Listas de reproducción", - "Photos": "Fotos" + "Photos": "Fotos", + "UserLockedOutWithName": "O usuario {0} foi bloqueado", + "UserDownloadingItemWithValues": "{0} está a ser transferido {1}", + "UserDeletedWithName": "O usuario {0} foi borrado", + "UserCreatedWithName": "O usuario {0} foi creado", + "Plugin": "Plugin", + "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo parada", + "NotificationOptionVideoPlayback": "Reproducción de vídeo iniciada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionTaskFailed": "Falla na tarefa axendada" } From 07ea5e2472a623e47b64dd60dd284b1ab458587e Mon Sep 17 00:00:00 2001 From: 0TTA Date: Thu, 22 Apr 2021 04:14:53 +0000 Subject: [PATCH 188/612] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 4b898e6fe..9f84d3a3c 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -113,5 +113,8 @@ "TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.", "TaskRefreshPeople": "إعادة تحميل الأشخاص", "TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.", - "TaskCleanLogs": "حذف دليل السجل" + "TaskCleanLogs": "حذف دليل السجل", + "TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الموضوع.", + "TaskCleanActivityLog": "حذف سجل الأنشطة", + "Default": "الإعدادات الافتراضية" } From 7af45a8f01b033ed9fb5069e6ca0ada073bd24e3 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Thu, 22 Apr 2021 18:39:36 +0000 Subject: [PATCH 189/612] Translated using Weblate (Kazakh) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/ --- .../Localization/Core/kk.json | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 829a29ad4..4eee36989 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -5,23 +5,23 @@ "Artists": "Oryndauşylar", "AuthenticationSucceededWithUserName": "{0} tüpnūsqalyq rastaluy sättı aiaqtaldy", "Books": "Kıtaptar", - "CameraImageUploadedFrom": "{0} kamerasynan jaŋa suret jüktep salyndy", + "CameraImageUploadedFrom": "{0} kamerasynan jaña suret jüktep salyndy", "Channels": "Arnalar", "ChapterNameValue": "{0}-sahna", "Collections": "Jiyntyqtar", "DeviceOfflineWithName": "{0} ajyratylğan", "DeviceOnlineWithName": "{0} qosylğan", "FailedLoginAttemptWithUserName": "{0} tarapynan kıru äreketı sätsız aiaqtaldy", - "Favorites": "Taŋdaulylar", + "Favorites": "Tañdaulylar", "Folders": "Qaltalar", "Genres": "Janrlar", "HeaderAlbumArtists": "Älbom oryndauşylary", "HeaderContinueWatching": "Qaraudy jalğastyru", - "HeaderFavoriteAlbums": "Taŋdauly älbomdar", - "HeaderFavoriteArtists": "Taŋdauly oryndauşylar", - "HeaderFavoriteEpisodes": "Taŋdauly telebölımder", - "HeaderFavoriteShows": "Taŋdauly körsetımder", - "HeaderFavoriteSongs": "Taŋdauly äuender", + "HeaderFavoriteAlbums": "Tañdauly älbomdar", + "HeaderFavoriteArtists": "Tañdauly oryndauşylar", + "HeaderFavoriteEpisodes": "Tañdauly telebölımder", + "HeaderFavoriteShows": "Tañdauly körsetımder", + "HeaderFavoriteSongs": "Tañdauly äuender", "HeaderLiveTV": "Efir", "HeaderNextUp": "Kezektı", "HeaderRecordingGroups": "Jazba toptary", @@ -31,11 +31,11 @@ "ItemRemovedWithName": "{0} tasyğyşhanadan alastaldy", "LabelIpAddressValue": "IP-mekenjaiy: {0}", "LabelRunningTimeValue": "Oinatu uaqyty: {0}", - "Latest": "Eŋ keiıngı", - "MessageApplicationUpdated": "Jellyfin Serverı jaŋartyldy", - "MessageApplicationUpdatedTo": "Jellyfin Serverı {0} nūsqasyna jaŋartyldy", - "MessageNamedServerConfigurationUpdatedWithValue": "Server teŋşelımderınıŋ {0} bölımı jaŋartyldy", - "MessageServerConfigurationUpdated": "Server teŋşelımderı jaŋartyldy", + "Latest": "Eñ keiıngı", + "MessageApplicationUpdated": "Jellyfin Serverı jañartyldy", + "MessageApplicationUpdatedTo": "Jellyfin Serverı {0} nūsqasyna jañartyldy", + "MessageNamedServerConfigurationUpdatedWithValue": "Server teñşelımderınıñ {0} bölımı jañartyldy", + "MessageServerConfigurationUpdated": "Server teñşelımderı jañartyldy", "MixedContent": "Aralas mazmūn", "Movies": "Filmder", "Music": "Muzyka", @@ -43,18 +43,18 @@ "NameInstallFailed": "{0} ornatyluy sätsız", "NameSeasonNumber": "{0}-mausym", "NameSeasonUnknown": "Belgısız mausym", - "NewVersionIsAvailable": "Jaŋa Jellyfin Server nūsqasy jüktep aluğa qoljetımdı.", - "NotificationOptionApplicationUpdateAvailable": "Qoldanba jaŋartuy qoljetımdı", - "NotificationOptionApplicationUpdateInstalled": "Qoldanba jaŋartuy ornatyldy", + "NewVersionIsAvailable": "Jaña Jellyfin Server nūsqasy jüktep aluğa qoljetımdı.", + "NotificationOptionApplicationUpdateAvailable": "Qoldanba jañartuy qoljetımdı", + "NotificationOptionApplicationUpdateInstalled": "Qoldanba jañartuy ornatyldy", "NotificationOptionAudioPlayback": "Dybys oinatuy bastaldy", "NotificationOptionAudioPlaybackStopped": "Dybys oinatuy toqtatyldy", "NotificationOptionCameraImageUploaded": "Kameradan fotosuret jüktep salynğan", "NotificationOptionInstallationFailed": "Ornatu sätsızdıgı", - "NotificationOptionNewLibraryContent": "Jaŋa mazmūn üstelıngen", + "NotificationOptionNewLibraryContent": "Jaña mazmūn üstelıngen", "NotificationOptionPluginError": "Plagin sätsızdıgı", "NotificationOptionPluginInstalled": "Plagin ornatyldy", "NotificationOptionPluginUninstalled": "Plagin ornatuy boldyrylmady", - "NotificationOptionPluginUpdateInstalled": "Plagin jaŋartuy ornatyldy", + "NotificationOptionPluginUpdateInstalled": "Plagin jañartuy ornatyldy", "NotificationOptionServerRestartRequired": "Serverdı qaita ıske qosu qajet", "NotificationOptionTaskFailed": "Josparlağan tapsyrma sätsızdıgı", "NotificationOptionUserLockedOut": "Paidalanuşy qūrsauly", @@ -65,14 +65,14 @@ "Plugin": "Plagin", "PluginInstalledWithName": "{0} ornatyldy", "PluginUninstalledWithName": "{0} joiyldy", - "PluginUpdatedWithName": "{0} jaŋartyldy", + "PluginUpdatedWithName": "{0} jañartyldy", "ProviderValue": "Jetkızuşı: {0}", "ScheduledTaskFailedWithName": "{0} sätsız", "ScheduledTaskStartedWithName": "{0} ıske qosyldy", "ServerNameNeedsToBeRestarted": "{0} qaita ıske qosu qajet", "Shows": "Körsetımder", "Songs": "Äuender", - "StartupEmbyServerIsLoading": "Jellyfin Server jüktelude. Ärekettı köp ūzamai qaitalaŋyz.", + "StartupEmbyServerIsLoading": "Jellyfin Server jüktelude. Ärekettı köp ūzamai qaitalañyz.", "SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз", "SubtitleDownloadFailureFromForItem": "{1} üşın subtitrlerdı {0} közınen jüktep alu sätsız", "Sync": "Ündestıru", @@ -86,7 +86,7 @@ "UserOfflineFromDevice": "{0} — {1} tarapynan ajyratyldy", "UserOnlineFromDevice": "{0} — {1} tarapynan qosyldy", "UserPasswordChangedWithName": "Paidalanuşy {0} üşın paröl özgertıldı", - "UserPolicyUpdatedWithName": "Paidalanuşy {0} üşın saiasattary jaŋartyldy", + "UserPolicyUpdatedWithName": "Paidalanuşy {0} üşın saiasattary jañartyldy", "UserStartedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuda", "UserStoppedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuyn toqtatty", "ValueHasBeenAddedToLibrary": "{0} tasyğyşhanağa üstelındı", @@ -94,10 +94,10 @@ "VersionNumber": "Nūsqasy {0}", "Default": "Ädepkı", "TaskDownloadMissingSubtitles": "Joq subtitrlerdı jüktep alu", - "TaskRefreshChannels": "Arnalardy jaŋğyrtu", + "TaskRefreshChannels": "Arnalardy jañğyrtu", "TaskCleanTranscode": "Qaita kodtau katalogyn tazalau", - "TaskUpdatePlugins": "Plaginderdı jaŋartu", - "TaskRefreshPeople": "Adamdardy jaŋğyrtu", + "TaskUpdatePlugins": "Plaginderdı jañartu", + "TaskRefreshPeople": "Adamdardy jañğyrtu", "TaskCleanLogs": "Jūrnal katalogyn tazalau", "TaskRefreshLibrary": "Tasyğyşhanany skanerleu", "TaskRefreshChapterImages": "Sahna suretterın şyğaryp alu", @@ -109,14 +109,14 @@ "TasksMaintenanceCategory": "Qyzmet körsetu", "Undefined": "Anyqtalmağan", "Forced": "Mäjbürlı", - "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.", - "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.", + "TaskDownloadMissingSubtitlesDescription": "Metaderekter teñşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.", + "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jañğyrtady.", "TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.", - "TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.", - "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.", + "TaskUpdatePluginsDescription": "Avtomatty türde jañartuğa teñşelgen plaginder üşın jañartulardy jüktep alady jäne ornatady.", + "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jañartady.", "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.", - "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.", + "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaña faildardy skanerleidі jäne metaderekterdı jañğyrtady.", "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşın nobailar jasaidy.", "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.", - "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady." + "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teñşelgen jasynan asqan jazbalary joiady." } From d2e8df1a48be515cbf7c1ec4f88d1133100e1f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Thu, 29 Apr 2021 17:58:40 +0000 Subject: [PATCH 190/612] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- Emby.Server.Implementations/Localization/Core/cs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 775267183..ff14c1929 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -39,7 +39,7 @@ "MixedContent": "Smíšený obsah", "Movies": "Filmy", "Music": "Hudba", - "MusicVideos": "Hudební klipy", + "MusicVideos": "Hudební videa", "NameInstallFailed": "Instalace {0} selhala", "NameSeasonNumber": "Sezóna {0}", "NameSeasonUnknown": "Neznámá sezóna", From 566ad0907b9d2331ae004c6702c497b0db8ff433 Mon Sep 17 00:00:00 2001 From: Marcus Hsu Date: Thu, 29 Apr 2021 09:08:31 +0000 Subject: [PATCH 191/612] Translated using Weblate (Chinese (Traditional)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant/ --- Emby.Server.Implementations/Localization/Core/zh-TW.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index affb0e099..c3b223f63 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -37,7 +37,7 @@ "MixedContent": "混合內容", "Movies": "電影", "Music": "音樂", - "MusicVideos": "音樂MV", + "MusicVideos": "音樂錄影帶", "NameInstallFailed": "{0} 安裝失敗", "NameSeasonNumber": "第 {0} 季", "NameSeasonUnknown": "未知季數", From 9c0d1f676df0d559474e58eeacfadc670f7bcc36 Mon Sep 17 00:00:00 2001 From: Levi Date: Sat, 1 May 2021 11:12:09 +0000 Subject: [PATCH 192/612] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/ --- Emby.Server.Implementations/Localization/Core/sv.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index b5a7fa5b8..d992bf79b 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -39,7 +39,7 @@ "MixedContent": "Blandat innehåll", "Movies": "Filmer", "Music": "Musik", - "MusicVideos": "Musikvideos", + "MusicVideos": "Musikvideor", "NameInstallFailed": "{0} installationen misslyckades", "NameSeasonNumber": "Säsong {0}", "NameSeasonUnknown": "Okänd säsong", From 0668ab672182b4ee2e8dded2611d935ef8b7d176 Mon Sep 17 00:00:00 2001 From: Dan Johansen Date: Tue, 4 May 2021 09:42:08 +0000 Subject: [PATCH 193/612] Translated using Weblate (Danish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/ --- Emby.Server.Implementations/Localization/Core/da.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 051d6d009..3453507d9 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -39,7 +39,7 @@ "MixedContent": "Blandet indhold", "Movies": "Film", "Music": "Musik", - "MusicVideos": "Musikvideoer", + "MusicVideos": "Musik videoer", "NameInstallFailed": "{0} installationen mislykkedes", "NameSeasonNumber": "Sæson {0}", "NameSeasonUnknown": "Ukendt Sæson", From ba8ff2a7bcf49e6441840edf3dc93888ed14b48d Mon Sep 17 00:00:00 2001 From: Benjamin Vraspillai Date: Mon, 3 May 2021 15:35:48 +0000 Subject: [PATCH 194/612] Translated using Weblate (Norwegian Nynorsk) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nn/ --- .../Localization/Core/nn.json | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json index 6236515b2..5c0cd5ec5 100644 --- a/Emby.Server.Implementations/Localization/Core/nn.json +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -1,25 +1,25 @@ { - "MessageServerConfigurationUpdated": "Tenar konfigurasjonen har blitt oppdatert", + "MessageServerConfigurationUpdated": "Tenarkonfigurasjonen har blitt oppdatert", "MessageNamedServerConfigurationUpdatedWithValue": "Tenar konfigurasjon seksjon {0} har blitt oppdatert", - "MessageApplicationUpdatedTo": "Jellyfin Tenaren har blitt oppdatert til {0}", - "MessageApplicationUpdated": "Jellyfin Tenaren har blitt oppdatert", + "MessageApplicationUpdatedTo": "Jellyfin-tenaren har blitt oppdatert til {0}", + "MessageApplicationUpdated": "Jellyfin-tenaren har blitt oppdatert", "Latest": "Nyaste", "LabelRunningTimeValue": "Speletid: {0}", - "LabelIpAddressValue": "IP adresse: {0}", + "LabelIpAddressValue": "IP-adresse: {0}", "ItemRemovedWithName": "{0} vart fjerna frå biblioteket", "ItemAddedWithName": "{0} vart lagt til i biblioteket", - "Inherit": "Arv", - "HomeVideos": "Heime Videoar", + "Inherit": "Arve", + "HomeVideos": "Heimevideoar", "HeaderRecordingGroups": "Innspelingsgrupper", "HeaderNextUp": "Neste", "HeaderLiveTV": "Direkte TV", - "HeaderFavoriteSongs": "Favoritt Songar", - "HeaderFavoriteShows": "Favoritt Seriar", - "HeaderFavoriteEpisodes": "Favoritt Episodar", - "HeaderFavoriteArtists": "Favoritt Artistar", - "HeaderFavoriteAlbums": "Favoritt Album", + "HeaderFavoriteSongs": "Favorittsongar", + "HeaderFavoriteShows": "Favorittseriar", + "HeaderFavoriteEpisodes": "Favorittepisodar", + "HeaderFavoriteArtists": "Favorittartistar", + "HeaderFavoriteAlbums": "Favorittalbum", "HeaderContinueWatching": "Fortsett å sjå", - "HeaderAlbumArtists": "Album Artist", + "HeaderAlbumArtists": "Albumartistar", "Genres": "Sjangrar", "Folders": "Mapper", "Favorites": "Favorittar", @@ -37,7 +37,7 @@ "AppDeviceValues": "App: {0}, Eining: {1}", "Albums": "Album", "NotificationOptionServerRestartRequired": "Tenaren krev omstart", - "NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert", + "NotificationOptionPluginUpdateInstalled": "Tilleggsprogramoppdatering vart installert", "NotificationOptionPluginUninstalled": "Tilleggsprogram avinstallert", "NotificationOptionPluginInstalled": "Tilleggsprogram installert", "NotificationOptionPluginError": "Tilleggsprogram feila", @@ -48,33 +48,33 @@ "NotificationOptionAudioPlayback": "Lydavspilling påbyrja", "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering er installert", "NotificationOptionApplicationUpdateAvailable": "Applikasjonsoppdatering er tilgjengeleg", - "NewVersionIsAvailable": "Ein ny versjon av Jellyfin serveren er tilgjengeleg for nedlasting.", + "NewVersionIsAvailable": "Ein ny versjon av Jellyfin-tjenaren er tilgjengeleg for nedlasting.", "NameSeasonUnknown": "Ukjend sesong", "NameSeasonNumber": "Sesong {0}", - "NameInstallFailed": "{0} Installasjonen feila", + "NameInstallFailed": "Installasjonen av {0} feila", "MusicVideos": "Musikkvideoar", "Music": "Musikk", "Movies": "Filmar", "MixedContent": "Blanda innhald", - "Sync": "Synkronisera", + "Sync": "Synkronisere", "TaskDownloadMissingSubtitlesDescription": "Søk Internettet for manglande undertekstar basert på metadatainnstillingar.", "TaskDownloadMissingSubtitles": "Last ned manglande undertekstar", "TaskRefreshChannelsDescription": "Oppdater internettkanalinformasjon.", "TaskRefreshChannels": "Oppdater kanalar", - "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gamal.", - "TaskCleanTranscode": "Reins transkodemappe", + "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gammal.", + "TaskCleanTranscode": "Fjern transkodemappe", "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringar for programtillegg som er sette opp til å oppdaterast automatisk.", "TaskUpdatePlugins": "Oppdaterer programtillegg", "TaskRefreshPeopleDescription": "Oppdaterer metadata for skodespelarar og regissørar i mediebiblioteket ditt.", "TaskRefreshPeople": "Oppdater personar", "TaskCleanLogsDescription": "Slett loggfiler som er meir enn {0} dagar gamle.", - "TaskCleanLogs": "Reins loggmappe", + "TaskCleanLogs": "Slett loggmappe", "TaskRefreshLibraryDescription": "Skannar mediebiblioteket ditt for nye filer og oppdaterer metadata.", "TaskRefreshLibrary": "Skann mediebibliotek", "TaskRefreshChapterImagesDescription": "Lager miniatyrbilete for videoar som har kapittel.", "TaskRefreshChapterImages": "Trekk ut kapittelbilete", - "TaskCleanCacheDescription": "Slettar mellomlagra filer som ikkje lengre trengst av systemet.", - "TaskCleanCache": "Rens mappe for hurtiglager", + "TaskCleanCacheDescription": "Sletter mellomlagra filer som ikkje lengre trengst av systemet.", + "TaskCleanCache": "Reingjer mappe for hurtiglager", "TasksChannelsCategory": "Internettkanalar", "TasksApplicationCategory": "Applikasjon", "TasksLibraryCategory": "Bibliotek", @@ -96,7 +96,7 @@ "TvShows": "TV-seriar", "System": "System", "SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}", - "StartupEmbyServerIsLoading": "Jellyfintenaren laster. Prøv igjen om litt.", + "StartupEmbyServerIsLoading": "Jellyfin-tenaren laster. Prøv igjen om litt.", "Songs": "Songar", "Shows": "Program", "ServerNameNeedsToBeRestarted": "{0} må omstartast", @@ -112,5 +112,10 @@ "NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa", "NotificationOptionVideoPlayback": "Videoavspeling starta", "NotificationOptionUserLockedOut": "Brukar er utestengd", - "NotificationOptionTaskFailed": "Planlagt oppgåve feila" + "NotificationOptionTaskFailed": "Planlagt oppgåve feila", + "TaskCleanActivityLogDescription": "Sletter aktivitetslogginnlegg som er eldre enn den konfigurerte alderen.", + "TaskCleanActivityLog": "Slett aktivitetslogg", + "Undefined": "Udefinert", + "Forced": "Tvungen", + "Default": "Standard" } From 323bc75c13f270c3d21f969e75dbd1dca96c7f0b Mon Sep 17 00:00:00 2001 From: emmanuel billeaud Date: Sat, 8 May 2021 18:08:07 +0000 Subject: [PATCH 195/612] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index d12c1ddec..ce1493be8 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -15,7 +15,7 @@ "Favorites": "Favoris", "Folders": "Dossiers", "Genres": "Genres", - "HeaderAlbumArtists": "Artistes", + "HeaderAlbumArtists": "Artistes de l'album", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes préférés", @@ -107,7 +107,7 @@ "TaskCleanLogs": "Nettoyer le répertoire des journaux", "TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.", "TaskRefreshLibrary": "Scanner toutes les Bibliothèques", - "TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.", + "TaskRefreshChapterImagesDescription": "Crée des vignettes pour les vidéos ayant des chapitres.", "TaskRefreshChapterImages": "Extraire les images de chapitre", "TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.", "TaskCleanCache": "Vider le répertoire cache", From b281488d36e4d4c30c26f7a42df3d32a1aa62166 Mon Sep 17 00:00:00 2001 From: Daniel Wykerd Date: Sun, 9 May 2021 14:50:29 +0000 Subject: [PATCH 196/612] Translated using Weblate (Afrikaans) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/af/ --- Emby.Server.Implementations/Localization/Core/af.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json index b029b7042..4f21c66bc 100644 --- a/Emby.Server.Implementations/Localization/Core/af.json +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -115,5 +115,7 @@ "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde", "Undefined": "Ongedefineerd", "Forced": "Geforseer", - "Default": "Oorspronklik" + "Default": "Oorspronklik", + "TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.", + "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon" } From 0115ca9bc26dbdd13923fc8ac611f4ff50e2fdba Mon Sep 17 00:00:00 2001 From: Benjamin Vraspillai Date: Fri, 7 May 2021 22:09:53 +0000 Subject: [PATCH 197/612] Translated using Weblate (Norwegian Nynorsk) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nn/ --- .../Localization/Core/nn.json | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json index 5c0cd5ec5..16511e686 100644 --- a/Emby.Server.Implementations/Localization/Core/nn.json +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -37,10 +37,10 @@ "AppDeviceValues": "App: {0}, Eining: {1}", "Albums": "Album", "NotificationOptionServerRestartRequired": "Tenaren krev omstart", - "NotificationOptionPluginUpdateInstalled": "Tilleggsprogramoppdatering vart installert", - "NotificationOptionPluginUninstalled": "Tilleggsprogram avinstallert", - "NotificationOptionPluginInstalled": "Tilleggsprogram installert", - "NotificationOptionPluginError": "Tilleggsprogram feila", + "NotificationOptionPluginUpdateInstalled": "Programvaretilleggoppdatering vart installert", + "NotificationOptionPluginUninstalled": "Programvaretillegg avinstallert", + "NotificationOptionPluginInstalled": "Programvaretillegg installert", + "NotificationOptionPluginError": "Programvaretillegg feila", "NotificationOptionNewLibraryContent": "Nytt innhald er lagt til", "NotificationOptionInstallationFailed": "Installasjonsfeil", "NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp", @@ -56,7 +56,7 @@ "Music": "Musikk", "Movies": "Filmar", "MixedContent": "Blanda innhald", - "Sync": "Synkronisere", + "Sync": "Synkroniser", "TaskDownloadMissingSubtitlesDescription": "Søk Internettet for manglande undertekstar basert på metadatainnstillingar.", "TaskDownloadMissingSubtitles": "Last ned manglande undertekstar", "TaskRefreshChannelsDescription": "Oppdater internettkanalinformasjon.", @@ -64,17 +64,17 @@ "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gammal.", "TaskCleanTranscode": "Fjern transkodemappe", "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringar for programtillegg som er sette opp til å oppdaterast automatisk.", - "TaskUpdatePlugins": "Oppdaterer programtillegg", + "TaskUpdatePlugins": "Oppdaterer programvaretillegg", "TaskRefreshPeopleDescription": "Oppdaterer metadata for skodespelarar og regissørar i mediebiblioteket ditt.", "TaskRefreshPeople": "Oppdater personar", "TaskCleanLogsDescription": "Slett loggfiler som er meir enn {0} dagar gamle.", - "TaskCleanLogs": "Slett loggmappe", + "TaskCleanLogs": "Slett loggmappa", "TaskRefreshLibraryDescription": "Skannar mediebiblioteket ditt for nye filer og oppdaterer metadata.", "TaskRefreshLibrary": "Skann mediebibliotek", "TaskRefreshChapterImagesDescription": "Lager miniatyrbilete for videoar som har kapittel.", "TaskRefreshChapterImages": "Trekk ut kapittelbilete", "TaskCleanCacheDescription": "Sletter mellomlagra filer som ikkje lengre trengst av systemet.", - "TaskCleanCache": "Reingjer mappe for hurtiglager", + "TaskCleanCache": "Fjern hurtigbuffer", "TasksChannelsCategory": "Internettkanalar", "TasksApplicationCategory": "Applikasjon", "TasksLibraryCategory": "Bibliotek", @@ -96,7 +96,7 @@ "TvShows": "TV-seriar", "System": "System", "SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}", - "StartupEmbyServerIsLoading": "Jellyfin-tenaren laster. Prøv igjen om litt.", + "StartupEmbyServerIsLoading": "Jellyfin-tenaren laster. Prøv igjen seinare.", "Songs": "Songar", "Shows": "Program", "ServerNameNeedsToBeRestarted": "{0} må omstartast", @@ -106,9 +106,9 @@ "PluginUpdatedWithName": "{0} blei oppdatert", "PluginUninstalledWithName": "{0} blei avinstallert", "PluginInstalledWithName": "{0} blei installert", - "Plugin": "Programtillegg", + "Plugin": "Programvaretillegg", "Playlists": "Speleliste", - "Photos": "Foto", + "Photos": "Bilete", "NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa", "NotificationOptionVideoPlayback": "Videoavspeling starta", "NotificationOptionUserLockedOut": "Brukar er utestengd", From b0a12c0bb0432bfc7c0e903f85d40a7cf64a8d2f Mon Sep 17 00:00:00 2001 From: Benjamin Vraspillai Date: Fri, 7 May 2021 22:02:27 +0000 Subject: [PATCH 198/612] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Translat?= =?UTF-8?q?e-URL:=20https://translate.jellyfin.org/projects/jellyfin/jelly?= =?UTF-8?q?fin-core/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Localization/Core/nb.json | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index a4f957a3a..fbe1f7c4d 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -30,20 +30,20 @@ "ItemAddedWithName": "{0} ble lagt til i biblioteket", "ItemRemovedWithName": "{0} ble fjernet fra biblioteket", "LabelIpAddressValue": "IP-adresse: {0}", - "LabelRunningTimeValue": "Kjøretid {0}", + "LabelRunningTimeValue": "Spilletid {0}", "Latest": "Siste", - "MessageApplicationUpdated": "Jellyfin Server har blitt oppdatert", - "MessageApplicationUpdatedTo": "Jellyfin Server ble oppdatert til {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurasjon seksjon {0} har blitt oppdatert", - "MessageServerConfigurationUpdated": "Serverkonfigurasjon er oppdatert", + "MessageApplicationUpdated": "Jellyfin-tjeneren har blitt oppdatert", + "MessageApplicationUpdatedTo": "Jellyfin-tjeneren ble oppdatert til {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Tjenerkonfigurasjonsseksjon {0} har blitt oppdatert", + "MessageServerConfigurationUpdated": "Tjenerkonfigurasjon er oppdatert", "MixedContent": "Blandet innhold", "Movies": "Filmer", "Music": "Musikk", "MusicVideos": "Musikkvideoer", - "NameInstallFailed": "{0}-installasjonen mislyktes", + "NameInstallFailed": "Installasjonen av {0} mislyktes", "NameSeasonNumber": "Sesong {0}", - "NameSeasonUnknown": "Sesong ukjent", - "NewVersionIsAvailable": "En ny versjon av Jellyfin Server er tilgjengelig for nedlasting.", + "NameSeasonUnknown": "Ukjent sesong", + "NewVersionIsAvailable": "En ny versjon av Jellyfin-tjeneren er tilgjengelig for nedlasting.", "NotificationOptionApplicationUpdateAvailable": "En programvareoppdatering er tilgjengelig", "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering installert", "NotificationOptionAudioPlayback": "Lydavspilling startet", @@ -51,18 +51,18 @@ "NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp", "NotificationOptionInstallationFailed": "Installasjonen feilet", "NotificationOptionNewLibraryContent": "Nytt innhold lagt til", - "NotificationOptionPluginError": "Pluginfeil", - "NotificationOptionPluginInstalled": "Plugin installert", - "NotificationOptionPluginUninstalled": "Plugin avinstallert", - "NotificationOptionPluginUpdateInstalled": "Pluginoppdatering installert", - "NotificationOptionServerRestartRequired": "Serveromstart er nødvendig", + "NotificationOptionPluginError": "Programvareutvidelsesfeil", + "NotificationOptionPluginInstalled": "Programvareutvidelse installert", + "NotificationOptionPluginUninstalled": "Programvareutvidelse avinstallert", + "NotificationOptionPluginUpdateInstalled": "Programvareutvidelsesoppdatering installert", + "NotificationOptionServerRestartRequired": "Tjeneromstart er nødvendig", "NotificationOptionTaskFailed": "Feil under utføring av planlagt oppgave", "NotificationOptionUserLockedOut": "Bruker er utestengt", "NotificationOptionVideoPlayback": "Videoavspilling startet", "NotificationOptionVideoPlaybackStopped": "Videoavspilling stoppet", "Photos": "Bilder", "Playlists": "Spillelister", - "Plugin": "Plugin", + "Plugin": "Programvareutvidelse", "PluginInstalledWithName": "{0} ble installert", "PluginUninstalledWithName": "{0} ble avinstallert", "PluginUpdatedWithName": "{0} ble oppdatert", @@ -72,7 +72,7 @@ "ServerNameNeedsToBeRestarted": "{0} må startes på nytt", "Shows": "Program", "Songs": "Sanger", - "StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.", + "StartupEmbyServerIsLoading": "Jellyfin-tjener laster. Prøv igjen snart.", "SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}", "SubtitleDownloadFailureFromForItem": "Kunne ikke laste ned undertekster fra {0} for {1}", "Sync": "Synkroniser", @@ -86,37 +86,37 @@ "UserOfflineFromDevice": "{0} har koblet fra {1}", "UserOnlineFromDevice": "{0} er tilkoblet fra {1}", "UserPasswordChangedWithName": "Passordet for {0} er oppdatert", - "UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}", + "UserPolicyUpdatedWithName": "Brukerretningslinjene har blitt oppdatert for {0}", "UserStartedPlayingItemWithValues": "{0} har startet avspilling {1} på {2}", "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}", "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt", "ValueSpecialEpisodeName": "Spesialepisode - {0}", "VersionNumber": "Versjon {0}", - "TasksChannelsCategory": "Internett kanaler", + "TasksChannelsCategory": "Internettkanaler", "TasksApplicationCategory": "Applikasjon", "TasksLibraryCategory": "Bibliotek", "TasksMaintenanceCategory": "Vedlikehold", - "TaskCleanCache": "Tøm buffer katalog", + "TaskCleanCache": "Tøm hurtigbuffer", "TaskRefreshLibrary": "Skann mediebibliotek", "TaskRefreshChapterImagesDescription": "Lager forhåndsvisningsbilder for videoer som har kapitler.", - "TaskRefreshChapterImages": "Trekk ut Kapittelbilder", + "TaskRefreshChapterImages": "Trekk ut kapittelbilder", "TaskCleanCacheDescription": "Sletter mellomlagrede filer som ikke lengre trengs av systemet.", - "TaskDownloadMissingSubtitlesDescription": "Søker etter manglende underteksting på nett basert på metadatakonfigurasjon.", - "TaskDownloadMissingSubtitles": "Last ned manglende underteksting", - "TaskRefreshChannelsDescription": "Frisker opp internettkanalinformasjon.", - "TaskRefreshChannels": "Oppfrisk kanaler", + "TaskDownloadMissingSubtitlesDescription": "Søker etter manglende undertekster på nett basert på metadatakonfigurasjon.", + "TaskDownloadMissingSubtitles": "Last ned manglende undertekster", + "TaskRefreshChannelsDescription": "Oppdaterer internettkanalinformasjon.", + "TaskRefreshChannels": "Oppdater kanaler", "TaskCleanTranscodeDescription": "Sletter omkodede filer som er mer enn én dag gamle.", "TaskCleanTranscode": "Tøm transkodingmappe", - "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringer for utvidelser som er stilt inn til å oppdatere automatisk.", - "TaskUpdatePlugins": "Oppdater utvidelser", + "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringer for programvareutvidelser som er stilt inn til å oppdatere automatisk.", + "TaskUpdatePlugins": "Oppdater programvareutvidelse", "TaskRefreshPeopleDescription": "Oppdaterer metadata for skuespillere og regissører i mediebiblioteket ditt.", - "TaskRefreshPeople": "Oppfrisk personer", + "TaskRefreshPeople": "Oppdater personer", "TaskCleanLogsDescription": "Sletter loggfiler som er eldre enn {0} dager gamle.", "TaskCleanLogs": "Tøm loggmappe", "TaskRefreshLibraryDescription": "Skanner mediebibliotekene dine for nye filer og oppdaterer metadata.", "TaskCleanActivityLog": "Tøm aktivitetslogg", "Undefined": "Udefinert", - "Forced": "Tvungen", + "Forced": "Tvunget", "Default": "Standard", "TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen." } From a315ab4fa49f86c288ff4e4c23387c4f085f842e Mon Sep 17 00:00:00 2001 From: thecoldicelander Date: Sat, 8 May 2021 02:04:23 +0000 Subject: [PATCH 199/612] Translated using Weblate (Icelandic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/is/ --- Emby.Server.Implementations/Localization/Core/is.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json index 0f769eaad..b262a8b42 100644 --- a/Emby.Server.Implementations/Localization/Core/is.json +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -25,7 +25,7 @@ "Channels": "Stöðvar", "CameraImageUploadedFrom": "Ný ljósmynd frá myndavél hefur verið hlaðið upp frá {0}", "Books": "Bækur", - "AuthenticationSucceededWithUserName": "{0} náði að auðkennast", + "AuthenticationSucceededWithUserName": "{0} auðkenning tókst", "Artists": "Listamaður", "Application": "Forrit", "AppDeviceValues": "Snjallforrit: {0}, Tæki: {1}", @@ -106,5 +106,6 @@ "TasksChannelsCategory": "Netrásir", "TasksApplicationCategory": "Forrit", "TasksLibraryCategory": "Miðlasafn", - "TasksMaintenanceCategory": "Viðhald" + "TasksMaintenanceCategory": "Viðhald", + "Default": "Sjálfgefið" } From 289c06adca53cd34af3657c3c931f0f2c770fd61 Mon Sep 17 00:00:00 2001 From: Erikas Date: Thu, 6 May 2021 11:40:46 +0000 Subject: [PATCH 200/612] Translated using Weblate (Lithuanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/ --- Emby.Server.Implementations/Localization/Core/lt-LT.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index 9920ef4d5..f3a131d40 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -114,8 +114,9 @@ "TasksApplicationCategory": "Programa", "TasksLibraryCategory": "Mediateka", "TasksMaintenanceCategory": "Priežiūra", - "TaskCleanActivityLog": "Švarus veiklos žurnalas", + "TaskCleanActivityLog": "Išvalyti veiklos žurnalą", "Undefined": "Neapibrėžtas", "Forced": "Priverstas", - "Default": "Numatytas" + "Default": "Numatytas", + "TaskCleanActivityLogDescription": "Ištrina veiklos žuranlo įrašus, kurie yra senesni nei nustatytas amžius." } From b9163ff2aa13f745f61eba6194818707c1de3b8f Mon Sep 17 00:00:00 2001 From: Ishan Badgainya Date: Fri, 7 May 2021 08:21:24 +0000 Subject: [PATCH 201/612] Translated using Weblate (Hindi) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hi/ --- Emby.Server.Implementations/Localization/Core/hi.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json index ef3697b15..82dc601bc 100644 --- a/Emby.Server.Implementations/Localization/Core/hi.json +++ b/Emby.Server.Implementations/Localization/Core/hi.json @@ -51,5 +51,14 @@ "Latest": "सबसे नया", "LabelIpAddressValue": "आई पी एड्रेस: {0}", "ItemRemovedWithName": "{0} लाइब्रेरी में से निकाल दिया है", - "HomeVideos": "होम वीडियोस" + "HomeVideos": "होम वीडियोस", + "NotificationOptionVideoPlayback": "वीडियो प्लेबैक शुरू हुआ", + "NotificationOptionUserLockedOut": "उपयोगकर्ता लॉक हो गया", + "NotificationOptionTaskFailed": "निर्धारित कार्य विफलता", + "NotificationOptionServerRestartRequired": "सर्वर पुनरारंभ आवश्यक है", + "NotificationOptionPluginUpdateInstalled": "प्लगइन अद्यतन स्थापित", + "NotificationOptionNewLibraryContent": "नई सामग्री जोड़ी गई", + "LabelRunningTimeValue": "चलने का समय: {0}", + "ItemAddedWithName": "{0} को लाइब्रेरी में जोड़ा गया", + "Inherit": "इनहेरिट" } From 88bfd1bcf45bdfa6c64e3439d7f406799645163c Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Mon, 10 May 2021 17:58:21 +0200 Subject: [PATCH 202/612] Add tests for LocalizationManager --- .../Localization/LocalizationManagerTests.cs | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs new file mode 100644 index 000000000..acdf74c4f --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -0,0 +1,167 @@ +using System.Linq; +using System.Threading.Tasks; +using Emby.Server.Implementations.Localization; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Localization +{ + public class LocalizationManagerTests + { + private LocalizationManager _localizationManager = null!; + + public LocalizationManagerTests() + { + var config = new ServerConfiguration() { UICulture = "de-DE" }; + Setup(config); + } + + [Fact] + public void GetCountries_All_Success() + { + var countries = _localizationManager.GetCountries(); + var countryInfos = countries.ToList(); + + Assert.Equal(139, countryInfos.Count); + + var germany = countryInfos.FirstOrDefault(x => x.Name == "DE"); + Assert.NotNull(germany); + Assert.Equal("Germany", germany!.DisplayName); + Assert.Equal("DEU", germany!.ThreeLetterISORegionName); + Assert.Equal("DE", germany!.TwoLetterISORegionName); + } + + [Fact] + public async Task GetCultures_All_Success() + { + await _localizationManager.LoadAll(); + var cultures = _localizationManager.GetCultures().ToList(); + + Assert.Equal(189, cultures.Count); + + var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName == "de"); + Assert.NotNull(germany); + Assert.Equal("ger", germany!.ThreeLetterISOLanguageName); + Assert.Equal("German", germany!.DisplayName); + Assert.Equal("German", germany!.Name); + Assert.Contains("deu", germany!.ThreeLetterISOLanguageNames); + Assert.Contains("ger", germany!.ThreeLetterISOLanguageNames); + } + + [Theory] + [InlineData("de")] + [InlineData("ger")] + [InlineData("german")] + public async Task FindLanguage_Valid_Success(string identifier) + { + await _localizationManager.LoadAll(); + + var germany = _localizationManager.FindLanguageInfo(identifier); + Assert.NotNull(germany); + + Assert.Equal("ger", germany!.ThreeLetterISOLanguageName); + Assert.Equal("German", germany!.DisplayName); + Assert.Equal("German", germany!.Name); + Assert.Contains("deu", germany!.ThreeLetterISOLanguageNames); + Assert.Contains("ger", germany!.ThreeLetterISOLanguageNames); + } + + [Fact] + public async Task ParentalRatings_Default_Success() + { + await _localizationManager.LoadAll(); + var ratings = _localizationManager.GetParentalRatings().ToList(); + + Assert.Equal(23, ratings.Count); + + var tvma = ratings.FirstOrDefault(x => x.Name == "TV-MA"); + Assert.NotNull(tvma); + Assert.Equal(9, tvma!.Value); + } + + [Fact] + public async Task ParentalRatings_ConfiguredCountryCode_Success() + { + Setup(new ServerConfiguration() + { + MetadataCountryCode = "DE" + }); + await _localizationManager.LoadAll(); + var ratings = _localizationManager.GetParentalRatings().ToList(); + + Assert.Equal(10, ratings.Count); + + var fsk = ratings.FirstOrDefault(x => x.Name == "FSK-12"); + Assert.NotNull(fsk); + Assert.Equal(7, fsk!.Value); + } + + [Theory] + [InlineData("CA-R", "CA", 10)] + [InlineData("FSK-16", "DE", 8)] + [InlineData("FSK-18", "DE", 9)] + [InlineData("FSK-18", "US", 9)] + [InlineData("TV-MA", "US", 9)] + [InlineData("XXX", "asdf", 100)] + [InlineData("Germany: FSK-18", "DE", 9)] + public async Task GetRatingLevelFromString_Valid_Success(string value, string countryCode, int expectedLevel) + { + Setup(new ServerConfiguration() + { + MetadataCountryCode = countryCode + }); + await _localizationManager.LoadAll(); + var level = _localizationManager.GetRatingLevel(value); + Assert.NotNull(level); + Assert.Equal(expectedLevel, level!); + } + + [Fact] + public async Task GetRatingLevelFromString_Unrated_Success() + { + await _localizationManager.LoadAll(); + Assert.Null(_localizationManager.GetRatingLevel("n/a")); + } + + [Theory] + [InlineData("Default", "Default")] + [InlineData("HeaderLiveTV", "Live TV")] + public void GetLocalizedString_Valid_Success(string key, string expected) + { + Setup(new ServerConfiguration() + { + UICulture = "en-US" + }); + + var translated = _localizationManager.GetLocalizedString(key); + Assert.NotNull(translated); + Assert.Equal(expected, translated); + } + + [Fact] + public void GetLocalizedString_Invalid_Success() + { + Setup(new ServerConfiguration() + { + UICulture = "en-US" + }); + + var key = "SuperInvalidTranslationKeyThatWillNeverBeAdded"; + + var translated = _localizationManager.GetLocalizedString(key); + Assert.NotNull(translated); + Assert.Equal(key, translated); + } + + private void Setup(ServerConfiguration config) + { + var mockConfiguration = new Mock(); + mockConfiguration.SetupGet(x => x.Configuration).Returns(config); + + _localizationManager = new LocalizationManager(mockConfiguration.Object, new NullLogger()); + } + } +} From db2b53a4b52d0c1e9797bfc70030b04421ba46a6 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Mon, 10 May 2021 18:05:35 +0200 Subject: [PATCH 203/612] Refactor LocalizationManager and remove dead method --- .../Localization/LocalizationManager.cs | 426 +++++++++--------- .../Globalization/ILocalizationManager.cs | 8 - 2 files changed, 203 insertions(+), 231 deletions(-) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 220e423bf..efbccaa5b 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -25,18 +25,18 @@ namespace Emby.Server.Implementations.Localization private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; - private readonly IServerConfigurationManager _configurationManager; - private readonly ILogger _logger; - private readonly Dictionary> _allParentalRatings = new Dictionary>(StringComparer.OrdinalIgnoreCase); + private readonly IServerConfigurationManager _configurationManager; + private readonly ConcurrentDictionary> _dictionaries = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); - private List _cultures; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private readonly ILogger _logger; + + private List _cultures; /// /// Initializes a new instance of the class. @@ -51,57 +51,6 @@ namespace Emby.Server.Implementations.Localization _logger = logger; } - /// - /// Loads all resources into memory. - /// - /// . - public async Task LoadAll() - { - const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings."; - - // Extract from the assembly - foreach (var resource in _assembly.GetManifestResourceNames()) - { - if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal)) - { - continue; - } - - string countryCode = resource.Substring(RatingsResource.Length, 2); - var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); - - using (var str = _assembly.GetManifestResourceStream(resource)) - using (var reader = new StreamReader(str)) - { - await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) - { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - string[] parts = line.Split(','); - if (parts.Length == 2 - && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) - { - var name = parts[0]; - dict.Add(name, new ParentalRating(name, value)); - } -#if DEBUG - else - { - _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode); - } -#endif - } - } - - _allParentalRatings[countryCode] = dict; - } - - await LoadCultures().ConfigureAwait(false); - } - /// /// Gets the cultures. /// @@ -109,62 +58,6 @@ namespace Emby.Server.Implementations.Localization public IEnumerable GetCultures() => _cultures; - private async Task LoadCultures() - { - List list = new List(); - - const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt"; - - using (var stream = _assembly.GetManifestResourceStream(ResourcePath)) - using (var reader = new StreamReader(stream)) - { - await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) - { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - var parts = line.Split('|'); - - if (parts.Length == 5) - { - string name = parts[3]; - if (string.IsNullOrWhiteSpace(name)) - { - continue; - } - - string twoCharName = parts[2]; - if (string.IsNullOrWhiteSpace(twoCharName)) - { - continue; - } - - string[] threeletterNames; - if (string.IsNullOrWhiteSpace(parts[1])) - { - threeletterNames = new[] { parts[0] }; - } - else - { - threeletterNames = new[] { parts[0], parts[1] }; - } - - list.Add(new CultureDto - { - DisplayName = name, - Name = name, - ThreeLetterISOLanguageNames = threeletterNames, - TwoLetterISOLanguageName = twoCharName - }); - } - } - } - - _cultures = list; - } - /// public CultureDto FindLanguageInfo(string language) => GetCultures() @@ -186,34 +79,6 @@ namespace Emby.Server.Implementations.Localization public IEnumerable GetParentalRatings() => GetParentalRatingsDictionary().Values; - /// - /// Gets the parental ratings dictionary. - /// - /// . - private Dictionary GetParentalRatingsDictionary() - { - var countryCode = _configurationManager.Configuration.MetadataCountryCode; - - if (string.IsNullOrEmpty(countryCode)) - { - countryCode = "us"; - } - - return GetRatings(countryCode) ?? GetRatings("us"); - } - - /// - /// Gets the ratings. - /// - /// The country code. - /// The ratings. - private Dictionary GetRatings(string countryCode) - { - _allParentalRatings.TryGetValue(countryCode, out var value); - - return value; - } - /// public int? GetRatingLevel(string rating) { @@ -250,7 +115,7 @@ namespace Emby.Server.Implementations.Localization var index = rating.IndexOf(':', StringComparison.Ordinal); if (index != -1) { - rating = rating.Substring(index).TrimStart(':').Trim(); + rating = rating.Substring(index + 1).Trim(); if (!string.IsNullOrWhiteSpace(rating)) { @@ -262,20 +127,6 @@ namespace Emby.Server.Implementations.Localization return null; } - /// - public bool HasUnicodeCategory(string value, UnicodeCategory category) - { - foreach (var chr in value) - { - if (char.GetUnicodeCategory(chr) == category) - { - return true; - } - } - - return false; - } - /// public string GetLocalizedString(string phrase) { @@ -305,74 +156,6 @@ namespace Emby.Server.Implementations.Localization return phrase; } - private Dictionary GetLocalizationDictionary(string culture) - { - if (string.IsNullOrEmpty(culture)) - { - throw new ArgumentNullException(nameof(culture)); - } - - const string Prefix = "Core"; - - return _dictionaries.GetOrAdd( - culture, - f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); - } - - private async Task> GetDictionary(string prefix, string culture, string baseFilename) - { - if (string.IsNullOrEmpty(culture)) - { - throw new ArgumentNullException(nameof(culture)); - } - - var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - - var namespaceName = GetType().Namespace + "." + prefix; - - await CopyInto(dictionary, namespaceName + "." + baseFilename).ConfigureAwait(false); - await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)).ConfigureAwait(false); - - return dictionary; - } - - private async Task CopyInto(IDictionary dictionary, string resourcePath) - { - using (var stream = _assembly.GetManifestResourceStream(resourcePath)) - { - // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain - if (stream != null) - { - var dict = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions).ConfigureAwait(false); - - foreach (var key in dict.Keys) - { - dictionary[key] = dict[key]; - } - } - else - { - _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath); - } - } - } - - private static string GetResourceFilename(string culture) - { - var parts = culture.Split('-'); - - if (parts.Length == 2) - { - culture = parts[0].ToLowerInvariant() + "-" + parts[1].ToUpperInvariant(); - } - else - { - culture = culture.ToLowerInvariant(); - } - - return culture + ".json"; - } - /// public IEnumerable GetLocalizationOptions() { @@ -414,5 +197,202 @@ namespace Emby.Server.Implementations.Localization yield return new LocalizationOption("Turkish", "tr"); yield return new LocalizationOption("Tiếng Việt", "vi"); } + + /// + /// Loads all resources into memory. + /// + /// . + public async Task LoadAll() + { + const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings."; + + // Extract from the assembly + foreach (var resource in _assembly.GetManifestResourceNames()) + { + if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal)) + { + continue; + } + + string countryCode = resource.Substring(RatingsResource.Length, 2); + var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); + + await using var str = _assembly.GetManifestResourceStream(resource); + using var reader = new StreamReader(str); + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + string[] parts = line.Split(','); + if (parts.Length == 2 + && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) + { + var name = parts[0]; + dict.Add(name, new ParentalRating(name, value)); + } +#if DEBUG + else + { + _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode); + } +#endif + } + + _allParentalRatings[countryCode] = dict; + } + + await LoadCultures().ConfigureAwait(false); + } + + private async Task LoadCultures() + { + List list = new List(); + + const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt"; + + await using var stream = _assembly.GetManifestResourceStream(ResourcePath); + using var reader = new StreamReader(stream); + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + var parts = line.Split('|'); + + if (parts.Length == 5) + { + string name = parts[3]; + if (string.IsNullOrWhiteSpace(name)) + { + continue; + } + + string twoCharName = parts[2]; + if (string.IsNullOrWhiteSpace(twoCharName)) + { + continue; + } + + string[] threeletterNames; + if (string.IsNullOrWhiteSpace(parts[1])) + { + threeletterNames = new[] { parts[0] }; + } + else + { + threeletterNames = new[] { parts[0], parts[1] }; + } + + list.Add(new CultureDto + { + DisplayName = name, + Name = name, + ThreeLetterISOLanguageNames = threeletterNames, + TwoLetterISOLanguageName = twoCharName + }); + } + } + + _cultures = list; + } + + /// + /// Gets the parental ratings dictionary. + /// + /// . + private Dictionary GetParentalRatingsDictionary() + { + var countryCode = _configurationManager.Configuration.MetadataCountryCode; + + if (string.IsNullOrEmpty(countryCode)) + { + countryCode = "us"; + } + + return GetRatings(countryCode) ?? GetRatings("us"); + } + + /// + /// Gets the ratings. + /// + /// The country code. + /// The ratings. + private Dictionary GetRatings(string countryCode) + { + _allParentalRatings.TryGetValue(countryCode, out var value); + + return value; + } + + private Dictionary GetLocalizationDictionary(string culture) + { + if (string.IsNullOrEmpty(culture)) + { + throw new ArgumentNullException(nameof(culture)); + } + + const string Prefix = "Core"; + + return _dictionaries.GetOrAdd( + culture, + _ => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); + } + + private async Task> GetDictionary(string prefix, string culture, string baseFilename) + { + if (string.IsNullOrEmpty(culture)) + { + throw new ArgumentNullException(nameof(culture)); + } + + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var namespaceName = GetType().Namespace + "." + prefix; + + await CopyInto(dictionary, namespaceName + "." + baseFilename).ConfigureAwait(false); + await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)).ConfigureAwait(false); + + return dictionary; + } + + private async Task CopyInto(IDictionary dictionary, string resourcePath) + { + await using var stream = _assembly.GetManifestResourceStream(resourcePath); + // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain + if (stream != null) + { + var dict = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions).ConfigureAwait(false); + + foreach (var key in dict.Keys) + { + dictionary[key] = dict[key]; + } + } + else + { + _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath); + } + } + + private static string GetResourceFilename(string culture) + { + var parts = culture.Split('-'); + + if (parts.Length == 2) + { + culture = parts[0].ToLowerInvariant() + "-" + parts[1].ToUpperInvariant(); + } + else + { + culture = culture.ToLowerInvariant(); + } + + return culture + ".json"; + } } } diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index baefeb39c..e0e7317ef 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -56,14 +56,6 @@ namespace MediaBrowser.Model.Globalization /// . IEnumerable GetLocalizationOptions(); - /// - /// Checks if the string contains a character with the specified unicode category. - /// - /// The string. - /// The unicode category. - /// Wether or not the string contains a character with the specified unicode category. - bool HasUnicodeCategory(string value, UnicodeCategory category); - /// /// Returns the correct for the given language. /// From cb01dd8684824ab74e05b8b4d4aaa462cedf0929 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Mon, 10 May 2021 20:51:41 +0200 Subject: [PATCH 204/612] Use TMDb parental rating building from movies for shows --- .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 7 +------ .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 2 +- MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index d22c1b50a..4a0884c07 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -206,12 +206,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (ourRelease != null) { - var ratingPrefix = string.Equals(info.MetadataCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? string.Empty : info.MetadataCountryCode + "-"; - var newRating = ratingPrefix + ourRelease.Certification; - - newRating = newRating.Replace("de-", "FSK-", StringComparison.OrdinalIgnoreCase); - - movie.OfficialRating = newRating; + movie.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Certification); } else if (usRelease != null) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 496e1ae25..da76345b5 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -300,7 +300,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (ourRelease != null) { - series.OfficialRating = ourRelease.Rating; + series.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Rating); } else if (usRelease != null) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 2498ce9c4..b4e165f82 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -173,5 +173,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return imageLanguage; } + + /// + /// Combines the metadata country code and the parental rating from the Api into the value we store in our database. + /// + /// The Iso 3166-1 country code of the rating country. + /// The rating value returned by the Tmdb Api. + /// The combined parental rating of country code+rating value. + public static string BuildParentalRating(string countryCode, string ratingValue) + { + // exclude US because we store us values as TV-14 without the country code. + var ratingPrefix = string.Equals(countryCode, "US", StringComparison.OrdinalIgnoreCase) ? string.Empty : countryCode + "-"; + var newRating = ratingPrefix + ratingValue; + + return newRating.Replace("DE-", "FSK-", StringComparison.OrdinalIgnoreCase); + } } } From 5b08bdda371fbb7221cbe408f4b5f903053df5c6 Mon Sep 17 00:00:00 2001 From: Haziq Rohaizan Date: Mon, 10 May 2021 20:59:48 +0000 Subject: [PATCH 205/612] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- .../Localization/Core/ms.json | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 5e3d095ff..df22f7668 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -39,7 +39,7 @@ "MixedContent": "Kandungan campuran", "Movies": "Filem", "Music": "Muzik", - "MusicVideos": "Video muzik", + "MusicVideos": "Muzik video", "NameInstallFailed": "{0} pemasangan gagal", "NameSeasonNumber": "Musim {0}", "NameSeasonUnknown": "Musim Tidak Diketahui", @@ -60,7 +60,7 @@ "NotificationOptionUserLockedOut": "User locked out", "NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", + "Photos": "Gambar", "Playlists": "Playlists", "Plugin": "Plugin", "PluginInstalledWithName": "{0} was installed", @@ -82,14 +82,22 @@ "UserCreatedWithName": "User {0} has been created", "UserDeletedWithName": "User {0} has been deleted", "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", + "UserLockedOutWithName": "Pengguna {0} telah dikunci", + "UserOfflineFromDevice": "{0} telah terputus dari {1}", + "UserOnlineFromDevice": "{0} dalam talian dari {1}", + "UserPasswordChangedWithName": "Kata laluan telah diubah untuk pengguna {0}", + "UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}", "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueSpecialEpisodeName": "Khas - {0}", - "VersionNumber": "Versi {0}" + "VersionNumber": "Versi {0}", + "TaskCleanActivityLog": "Log Aktiviti Bersih", + "TasksChannelsCategory": "Saluran Internet", + "TasksApplicationCategory": "Aplikasi", + "TasksLibraryCategory": "Perpustakaan", + "TasksMaintenanceCategory": "Penyelenggaraan", + "Undefined": "Tidak ditentukan", + "Forced": "Paksa", + "Default": "Asal" } From af5b09bd97cf91a39dec0b0394f84e46d56a8199 Mon Sep 17 00:00:00 2001 From: archon eleven Date: Mon, 10 May 2021 21:00:57 +0000 Subject: [PATCH 206/612] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index df22f7668..2e0c113a2 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -60,8 +60,8 @@ "NotificationOptionUserLockedOut": "User locked out", "NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Gambar", - "Playlists": "Playlists", + "Photos": "Gambar-gambar", + "Playlists": "Senarai ulangmain", "Plugin": "Plugin", "PluginInstalledWithName": "{0} was installed", "PluginUninstalledWithName": "{0} was uninstalled", From c9db6a78ee9cd8754b7afc7e61950f4d8521e77a Mon Sep 17 00:00:00 2001 From: archon eleven Date: Mon, 10 May 2021 21:15:43 +0000 Subject: [PATCH 207/612] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- .../Localization/Core/ms.json | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 2e0c113a2..104e116ab 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -45,14 +45,14 @@ "NameSeasonUnknown": "Musim Tidak Diketahui", "NewVersionIsAvailable": "Versi terbaru Jellyfin Server bersedia untuk dimuat turunkan.", "NotificationOptionApplicationUpdateAvailable": "Kemas kini aplikasi telah sedia", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionApplicationUpdateInstalled": "Kemas kini aplikasi telah dipasang", + "NotificationOptionAudioPlayback": "Ulangmain audio bermula", + "NotificationOptionAudioPlaybackStopped": "Ulangmain audio dihentikan", + "NotificationOptionCameraImageUploaded": "Imej kamera telah dimuatnaik", "NotificationOptionInstallationFailed": "Pemasangan gagal", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", + "NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah", + "NotificationOptionPluginError": "Kegagalan plugin", + "NotificationOptionPluginInstalled": "Plugin telah dipasang", "NotificationOptionPluginUninstalled": "Plugin uninstalled", "NotificationOptionPluginUpdateInstalled": "Plugin update installed", "NotificationOptionServerRestartRequired": "Server restart required", @@ -71,8 +71,8 @@ "ScheduledTaskStartedWithName": "{0} bermula", "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "Shows": "Series", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "Songs": "Lagu-lagu", + "StartupEmbyServerIsLoading": "Jellyfin Server sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", "Sync": "Sync", @@ -84,8 +84,8 @@ "UserDownloadingItemWithValues": "{0} is downloading {1}", "UserLockedOutWithName": "Pengguna {0} telah dikunci", "UserOfflineFromDevice": "{0} telah terputus dari {1}", - "UserOnlineFromDevice": "{0} dalam talian dari {1}", - "UserPasswordChangedWithName": "Kata laluan telah diubah untuk pengguna {0}", + "UserOnlineFromDevice": "{0} berada dalam talian dari {1}", + "UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}", "UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}", "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", From a84e8c3884291acb721aeb202139372462e6c800 Mon Sep 17 00:00:00 2001 From: Haziq Rohaizan Date: Mon, 10 May 2021 21:12:16 +0000 Subject: [PATCH 208/612] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 104e116ab..0b0458691 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -61,7 +61,7 @@ "NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionVideoPlaybackStopped": "Video playback stopped", "Photos": "Gambar-gambar", - "Playlists": "Senarai ulangmain", + "Playlists": "Senarai main", "Plugin": "Plugin", "PluginInstalledWithName": "{0} was installed", "PluginUninstalledWithName": "{0} was uninstalled", @@ -99,5 +99,7 @@ "TasksMaintenanceCategory": "Penyelenggaraan", "Undefined": "Tidak ditentukan", "Forced": "Paksa", - "Default": "Asal" + "Default": "Asal", + "TaskCleanCache": "Bersihkan Direktori Cache", + "TaskCleanActivityLogDescription": "Padamkan entri log aktiviti yang lebih tua daripada usia yang dikonfigurasi." } From e3f55a0c54a2517361c237543aba717fd2a16e69 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Tue, 11 May 2021 05:55:46 -0600 Subject: [PATCH 209/612] Reduce warnings in MediaBrowser.Controller (#6006) Co-authored-by: Patrick Barron <18354464+barronpm@users.noreply.github.com> --- .../Collections/CollectionManager.cs | 14 ++---- .../Images/BaseDynamicImageProvider.cs | 2 +- Jellyfin.Api/Controllers/MoviesController.cs | 11 ++--- Jellyfin.Api/Controllers/PersonsController.cs | 6 +-- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 14 +++--- .../Channels/ChannelItemInfo.cs | 26 +++++----- .../Channels/ChannelItemResult.cs | 8 ++-- .../Channels/ChannelLatestMediaSearch.cs | 11 +++++ .../Channels/ChannelSearchInfo.cs | 5 -- .../Collections/CollectionCreatedEventArgs.cs | 24 ++++++++++ .../Collections/CollectionCreationOptions.cs | 4 +- ...ents.cs => CollectionModifiedEventArgs.cs} | 23 +++------ .../Drawing/ImageCollageOptions.cs | 4 +- .../Drawing/ImageStream.cs | 10 +++- MediaBrowser.Controller/Dto/DtoOptions.cs | 47 +++++++++---------- .../Entities/AggregateFolder.cs | 1 + .../Entities/Audio/MusicArtist.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 8 ++-- MediaBrowser.Controller/Entities/Book.cs | 10 ++-- MediaBrowser.Controller/Entities/Genre.cs | 2 +- .../Entities/InternalPeopleQuery.cs | 22 +++++---- .../Entities/LinkedChild.cs | 45 +++--------------- .../Entities/LinkedChildComparer.cs | 34 ++++++++++++++ .../Entities/LinkedChildType.cs | 18 +++++++ .../Entities/MusicVideo.cs | 8 ++-- .../Library/DeleteOptions.cs | 8 ++-- .../Library/IIntroProvider.cs | 12 ++--- MediaBrowser.Controller/Library/Profiler.cs | 4 +- .../LiveTv/ActiveRecordingInfo.cs | 19 ++++++++ MediaBrowser.Controller/LiveTv/ChannelInfo.cs | 6 +-- .../LiveTv/ILiveTvManager.cs | 16 ++----- .../LiveTv/LiveTvProgram.cs | 12 ++--- MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 33 +++++++------ .../LiveTv/RecordingInfo.cs | 26 +++++----- .../LiveTv/SeriesTimerInfo.cs | 29 +++++++----- .../MediaBrowser.Controller.csproj | 2 +- .../Net/IWebSocketConnection.cs | 2 +- .../Providers/AlbumInfo.cs | 14 +++--- .../Providers/ArtistInfo.cs | 4 +- .../Providers/EpisodeInfo.cs | 10 ++-- .../Providers/IMetadataService.cs | 12 ++--- .../Providers/IProviderManager.cs | 7 --- .../Providers/ItemLookupInfo.cs | 12 ++--- .../Providers/MetadataRefreshOptions.cs | 31 ++++++------ .../Providers/RefreshPriority.cs | 23 +++++++++ .../Providers/RemoteSearchQuery.cs | 2 +- .../Providers/SeasonInfo.cs | 4 +- .../Resolvers/IResolverIgnoreRule.cs | 2 +- .../Session/ISessionManager.cs | 4 +- .../Session/SessionInfo.cs | 4 +- 50 files changed, 367 insertions(+), 290 deletions(-) create mode 100644 MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs create mode 100644 MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs rename MediaBrowser.Controller/Collections/{CollectionEvents.cs => CollectionModifiedEventArgs.cs} (55%) create mode 100644 MediaBrowser.Controller/Entities/LinkedChildComparer.cs create mode 100644 MediaBrowser.Controller/Entities/LinkedChildType.cs create mode 100644 MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs create mode 100644 MediaBrowser.Controller/Providers/RefreshPriority.cs diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 1b85a9d4b..c56f33448 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -164,7 +164,7 @@ namespace Emby.Server.Implementations.Collections parentFolder.AddChild(collection, CancellationToken.None); - if (options.ItemIdList.Length > 0) + if (options.ItemIdList.Count > 0) { await AddToCollectionAsync( collection.Id, @@ -248,11 +248,7 @@ namespace Emby.Server.Implementations.Collections if (fireEvent) { - ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs - { - Collection = collection, - ItemsChanged = itemList - }); + ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList)); } } } @@ -304,11 +300,7 @@ namespace Emby.Server.Implementations.Collections }, RefreshPriority.High); - ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs - { - Collection = collection, - ItemsChanged = itemList - }); + ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList)); } /// diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 5f7e51858..6fa3c1c61 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.Images InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray() }; - if (options.InputPaths.Length == 0) + if (options.InputPaths.Count == 0) { return null; } diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index d0a2358ae..010a3b19a 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers @@ -300,9 +301,8 @@ namespace Jellyfin.Api.Controllers private IEnumerable GetActors(IEnumerable items) { - var people = _libraryManager.GetPeople(new InternalPeopleQuery + var people = _libraryManager.GetPeople(new InternalPeopleQuery(Array.Empty(), new[] { PersonType.Director }) { - ExcludePersonTypes = new[] { PersonType.Director }, MaxListOrder = 3 }); @@ -316,10 +316,9 @@ namespace Jellyfin.Api.Controllers private IEnumerable GetDirectors(IEnumerable items) { - var people = _libraryManager.GetPeople(new InternalPeopleQuery - { - PersonTypes = new[] { PersonType.Director } - }); + var people = _libraryManager.GetPeople(new InternalPeopleQuery( + new[] { PersonType.Director }, + Array.Empty())); var itemIds = items.Select(i => i.Id).ToList(); diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 70a94e27c..b98307f87 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -94,10 +94,10 @@ namespace Jellyfin.Api.Controllers } var isFavoriteInFilters = filters.Any(f => f == ItemFilter.IsFavorite); - var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery + var peopleItems = _libraryManager.GetPeopleItems(new InternalPeopleQuery( + personTypes, + excludePersonTypes) { - PersonTypes = personTypes, - ExcludePersonTypes = excludePersonTypes, NameContains = searchTerm, User = user, IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite, diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index e9f9aad57..09a370238 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -68,7 +68,7 @@ namespace Jellyfin.Drawing.Skia /// The path at which to place the resulting collage image. /// The desired width of the collage. /// The desired height of the collage. - public void BuildSquareCollage(string[] paths, string outputPath, int width, int height) + public void BuildSquareCollage(IReadOnlyList paths, string outputPath, int width, int height) { using var bitmap = BuildSquareCollageBitmap(paths, width, height); using var outputStream = new SKFileWStream(outputPath); @@ -84,7 +84,7 @@ namespace Jellyfin.Drawing.Skia /// The desired width of the collage. /// The desired height of the collage. /// The name of the library to draw on the collage. - public void BuildThumbCollage(string[] paths, string outputPath, int width, int height, string? libraryName) + public void BuildThumbCollage(IReadOnlyList paths, string outputPath, int width, int height, string? libraryName) { using var bitmap = BuildThumbCollageBitmap(paths, width, height, libraryName); using var outputStream = new SKFileWStream(outputPath); @@ -92,7 +92,7 @@ namespace Jellyfin.Drawing.Skia pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); } - private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height, string? libraryName) + private SKBitmap BuildThumbCollageBitmap(IReadOnlyList paths, int width, int height, string? libraryName) { var bitmap = new SKBitmap(width, height); @@ -152,14 +152,14 @@ namespace Jellyfin.Drawing.Skia return bitmap; } - private SKBitmap? GetNextValidImage(string[] paths, int currentIndex, out int newIndex) + private SKBitmap? GetNextValidImage(IReadOnlyList paths, int currentIndex, out int newIndex) { var imagesTested = new Dictionary(); SKBitmap? bitmap = null; - while (imagesTested.Count < paths.Length) + while (imagesTested.Count < paths.Count) { - if (currentIndex >= paths.Length) + if (currentIndex >= paths.Count) { currentIndex = 0; } @@ -180,7 +180,7 @@ namespace Jellyfin.Drawing.Skia return bitmap; } - private SKBitmap BuildSquareCollageBitmap(string[] paths, int width, int height) + private SKBitmap BuildSquareCollageBitmap(IReadOnlyList paths, int width, int height) { var bitmap = new SKBitmap(width, height); var imageIndex = 0; diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs index fa7aff647..4d1e35f9e 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs @@ -13,6 +13,19 @@ namespace MediaBrowser.Controller.Channels { public class ChannelItemInfo : IHasProviderIds { + public ChannelItemInfo() + { + MediaSources = new List(); + TrailerTypes = new List(); + Genres = new List(); + Studios = new List(); + People = new List(); + Tags = new List(); + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + Artists = new List(); + AlbumArtists = new List(); + } + public string Name { get; set; } public string SeriesName { get; set; } @@ -80,18 +93,5 @@ namespace MediaBrowser.Controller.Channels public bool IsLiveStream { get; set; } public string Etag { get; set; } - - public ChannelItemInfo() - { - MediaSources = new List(); - TrailerTypes = new List(); - Genres = new List(); - Studios = new List(); - People = new List(); - Tags = new List(); - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - Artists = new List(); - AlbumArtists = new List(); - } } } diff --git a/MediaBrowser.Controller/Channels/ChannelItemResult.cs b/MediaBrowser.Controller/Channels/ChannelItemResult.cs index 8e937852f..6b2077662 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemResult.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemResult.cs @@ -8,13 +8,13 @@ namespace MediaBrowser.Controller.Channels { public class ChannelItemResult { - public List Items { get; set; } - - public int? TotalRecordCount { get; set; } - public ChannelItemResult() { Items = new List(); } + + public List Items { get; set; } + + public int? TotalRecordCount { get; set; } } } diff --git a/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs b/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs new file mode 100644 index 000000000..6f0761e64 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs @@ -0,0 +1,11 @@ +#nullable disable + +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public class ChannelLatestMediaSearch + { + public string UserId { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs index 53a73d62a..990b025bc 100644 --- a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs @@ -10,9 +10,4 @@ namespace MediaBrowser.Controller.Channels public string UserId { get; set; } } - - public class ChannelLatestMediaSearch - { - public string UserId { get; set; } - } } diff --git a/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs b/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs new file mode 100644 index 000000000..82b3a4977 --- /dev/null +++ b/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs @@ -0,0 +1,24 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System; +using MediaBrowser.Controller.Entities.Movies; + +namespace MediaBrowser.Controller.Collections +{ + public class CollectionCreatedEventArgs : EventArgs + { + /// + /// Gets or sets the collection. + /// + /// The collection. + public BoxSet Collection { get; set; } + + /// + /// Gets or sets the options. + /// + /// The options. + public CollectionCreationOptions Options { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index 94e7541f8..30f5f4efa 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -25,8 +25,8 @@ namespace MediaBrowser.Controller.Collections public Dictionary ProviderIds { get; set; } - public string[] ItemIdList { get; set; } + public IReadOnlyList ItemIdList { get; set; } - public Guid[] UserIds { get; set; } + public IReadOnlyList UserIds { get; set; } } } diff --git a/MediaBrowser.Controller/Collections/CollectionEvents.cs b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs similarity index 55% rename from MediaBrowser.Controller/Collections/CollectionEvents.cs rename to MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs index 821318ffc..8155cf3db 100644 --- a/MediaBrowser.Controller/Collections/CollectionEvents.cs +++ b/MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs @@ -9,23 +9,14 @@ using MediaBrowser.Controller.Entities.Movies; namespace MediaBrowser.Controller.Collections { - public class CollectionCreatedEventArgs : EventArgs - { - /// - /// Gets or sets the collection. - /// - /// The collection. - public BoxSet Collection { get; set; } - - /// - /// Gets or sets the options. - /// - /// The options. - public CollectionCreationOptions Options { get; set; } - } - public class CollectionModifiedEventArgs : EventArgs { + public CollectionModifiedEventArgs(BoxSet collection, IReadOnlyCollection itemsChanged) + { + Collection = collection; + ItemsChanged = itemsChanged; + } + /// /// Gets or sets the collection. /// @@ -36,6 +27,6 @@ namespace MediaBrowser.Controller.Collections /// Gets or sets the items changed. /// /// The items changed. - public List ItemsChanged { get; set; } + public IReadOnlyCollection ItemsChanged { get; set; } } } diff --git a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs index f06bbe4d0..e9c88ffb5 100644 --- a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs @@ -1,5 +1,7 @@ #nullable disable +using System.Collections.Generic; + #pragma warning disable CS1591 namespace MediaBrowser.Controller.Drawing @@ -10,7 +12,7 @@ namespace MediaBrowser.Controller.Drawing /// Gets or sets the input paths. /// /// The input paths. - public string[] InputPaths { get; set; } + public IReadOnlyList InputPaths { get; set; } /// /// Gets or sets the output path. diff --git a/MediaBrowser.Controller/Drawing/ImageStream.cs b/MediaBrowser.Controller/Drawing/ImageStream.cs index 591cc53d1..5ee781ffa 100644 --- a/MediaBrowser.Controller/Drawing/ImageStream.cs +++ b/MediaBrowser.Controller/Drawing/ImageStream.cs @@ -22,9 +22,15 @@ namespace MediaBrowser.Controller.Drawing public void Dispose() { - if (Stream != null) + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) { - Stream.Dispose(); + Stream?.Dispose(); } } } diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index 758e841a7..ecc833154 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -18,6 +18,28 @@ namespace MediaBrowser.Controller.Dto ItemFields.RefreshState }; + private static readonly ImageType[] AllImageTypes = Enum.GetValues(); + + private static readonly ItemFields[] AllItemFields = Enum.GetValues() + .Except(DefaultExcludedFields) + .ToArray(); + + public DtoOptions() + : this(true) + { + } + + public DtoOptions(bool allFields) + { + ImageTypeLimit = int.MaxValue; + EnableImages = true; + EnableUserData = true; + AddCurrentProgram = true; + + Fields = allFields ? AllItemFields : Array.Empty(); + ImageTypes = AllImageTypes; + } + public IReadOnlyList Fields { get; set; } public IReadOnlyList ImageTypes { get; set; } @@ -32,34 +54,9 @@ namespace MediaBrowser.Controller.Dto public bool AddCurrentProgram { get; set; } - public DtoOptions() - : this(true) - { - } - - private static readonly ImageType[] AllImageTypes = Enum.GetNames(typeof(ImageType)) - .Select(i => (ImageType)Enum.Parse(typeof(ImageType), i, true)) - .ToArray(); - - private static readonly ItemFields[] AllItemFields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .Except(DefaultExcludedFields) - .ToArray(); - public bool ContainsField(ItemFields field) => Fields.Contains(field); - public DtoOptions(bool allFields) - { - ImageTypeLimit = int.MaxValue; - EnableImages = true; - EnableUserData = true; - AddCurrentProgram = true; - - Fields = allFields ? AllItemFields : Array.Empty(); - ImageTypes = AllImageTypes; - } - public int GetImageLimit(ImageType type) { if (EnableImages && ImageTypes.Contains(type)) diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index f1944a7d3..e365bfda1 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -86,6 +86,7 @@ namespace MediaBrowser.Controller.Entities } private bool _requiresRefresh; + public override bool RequiresRefresh() { var changed = base.RequiresRefresh() || _requiresRefresh; diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 6101d3016..b07c3eed1 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -114,7 +114,7 @@ namespace MediaBrowser.Controller.Entities.Audio } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index ca5213273..238c98982 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -339,9 +339,9 @@ namespace MediaBrowser.Controller.Entities get { // if (IsOffline) - //{ + // { // return LocationType.Offline; - //} + // } var path = Path; if (string.IsNullOrEmpty(path)) @@ -2769,11 +2769,11 @@ namespace MediaBrowser.Controller.Entities // var parentId = Id; // if (!video.IsOwnedItem || video.ParentId != parentId) - //{ + // { // video.IsOwnedItem = true; // video.ParentId = parentId; // newOptions.ForceSave = true; - //} + // } if (video == null) { diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 3d0370248..d75beb06d 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -12,6 +12,11 @@ namespace MediaBrowser.Controller.Entities { public class Book : BaseItem, IHasLookupInfo, IHasSeries { + public Book() + { + this.RunTimeTicks = TimeSpan.TicksPerSecond; + } + [JsonIgnore] public override string MediaType => Model.Entities.MediaType.Book; @@ -28,11 +33,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public Guid SeriesId { get; set; } - public Book() - { - this.RunTimeTicks = TimeSpan.TicksPerSecond; - } - public string FindSeriesSortName() { return SeriesName; diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 7987f38a0..310c0c9ec 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. diff --git a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs index b2d6a4609..3e1d89274 100644 --- a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs @@ -3,12 +3,24 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; using Jellyfin.Data.Entities; namespace MediaBrowser.Controller.Entities { public class InternalPeopleQuery { + public InternalPeopleQuery() + : this(Array.Empty(), Array.Empty()) + { + } + + public InternalPeopleQuery(IReadOnlyList personTypes, IReadOnlyList excludePersonTypes) + { + PersonTypes = personTypes; + ExcludePersonTypes = excludePersonTypes; + } + /// /// Gets or sets the maximum number of items the query should return. /// @@ -16,9 +28,9 @@ namespace MediaBrowser.Controller.Entities public Guid ItemId { get; set; } - public string[] PersonTypes { get; set; } + public IReadOnlyList PersonTypes { get; } - public string[] ExcludePersonTypes { get; set; } + public IReadOnlyList ExcludePersonTypes { get; } public int? MaxListOrder { get; set; } @@ -29,11 +41,5 @@ namespace MediaBrowser.Controller.Entities public User User { get; set; } public bool? IsFavorite { get; set; } - - public InternalPeopleQuery() - { - PersonTypes = Array.Empty(); - ExcludePersonTypes = Array.Empty(); - } } } diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index 01c0a9339..fd5fef3dc 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -3,15 +3,18 @@ #pragma warning disable CS1591 using System; -using System.Collections.Generic; using System.Globalization; using System.Text.Json.Serialization; -using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.Entities { public class LinkedChild { + public LinkedChild() + { + Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + } + public string Path { get; set; } public LinkedChildType Type { get; set; } @@ -22,7 +25,7 @@ namespace MediaBrowser.Controller.Entities public string Id { get; set; } /// - /// Serves as a cache. + /// Gets or sets the linked item id. /// public Guid? ItemId { get; set; } @@ -41,41 +44,5 @@ namespace MediaBrowser.Controller.Entities return child; } - - public LinkedChild() - { - Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - } - } - - public enum LinkedChildType - { - Manual = 0, - Shortcut = 1 - } - - public class LinkedChildComparer : IEqualityComparer - { - private readonly IFileSystem _fileSystem; - - public LinkedChildComparer(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - } - - public bool Equals(LinkedChild x, LinkedChild y) - { - if (x.Type == y.Type) - { - return _fileSystem.AreEqual(x.Path, y.Path); - } - - return false; - } - - public int GetHashCode(LinkedChild obj) - { - return ((obj.Path ?? string.Empty) + (obj.LibraryItemId ?? string.Empty) + obj.Type).GetHashCode(); - } } } diff --git a/MediaBrowser.Controller/Entities/LinkedChildComparer.cs b/MediaBrowser.Controller/Entities/LinkedChildComparer.cs new file mode 100644 index 000000000..66fc44b8a --- /dev/null +++ b/MediaBrowser.Controller/Entities/LinkedChildComparer.cs @@ -0,0 +1,34 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using MediaBrowser.Model.IO; + +namespace MediaBrowser.Controller.Entities +{ + public class LinkedChildComparer : IEqualityComparer + { + private readonly IFileSystem _fileSystem; + + public LinkedChildComparer(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + public bool Equals(LinkedChild x, LinkedChild y) + { + if (x.Type == y.Type) + { + return _fileSystem.AreEqual(x.Path, y.Path); + } + + return false; + } + + public int GetHashCode(LinkedChild obj) + { + return ((obj.Path ?? string.Empty) + (obj.LibraryItemId ?? string.Empty) + obj.Type).GetHashCode(); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/LinkedChildType.cs b/MediaBrowser.Controller/Entities/LinkedChildType.cs new file mode 100644 index 000000000..9ddb7b620 --- /dev/null +++ b/MediaBrowser.Controller/Entities/LinkedChildType.cs @@ -0,0 +1,18 @@ +namespace MediaBrowser.Controller.Entities +{ + /// + /// The linked child type. + /// + public enum LinkedChildType + { + /// + /// Manually linked child. + /// + Manual = 0, + + /// + /// Shortcut linked child. + /// + Shortcut = 1 + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index f42e7723c..4e622ba01 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -13,15 +13,15 @@ namespace MediaBrowser.Controller.Entities { public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasLookupInfo { - /// - [JsonIgnore] - public IReadOnlyList Artists { get; set; } - public MusicVideo() { Artists = Array.Empty(); } + /// + [JsonIgnore] + public IReadOnlyList Artists { get; set; } + public override UnratedItem GetBlockUnratedType() { return UnratedItem.Music; diff --git a/MediaBrowser.Controller/Library/DeleteOptions.cs b/MediaBrowser.Controller/Library/DeleteOptions.cs index b7417efcb..408e70284 100644 --- a/MediaBrowser.Controller/Library/DeleteOptions.cs +++ b/MediaBrowser.Controller/Library/DeleteOptions.cs @@ -4,13 +4,13 @@ namespace MediaBrowser.Controller.Library { public class DeleteOptions { - public bool DeleteFileLocation { get; set; } - - public bool DeleteFromExternalProvider { get; set; } - public DeleteOptions() { DeleteFromExternalProvider = true; } + + public bool DeleteFileLocation { get; set; } + + public bool DeleteFromExternalProvider { get; set; } } } diff --git a/MediaBrowser.Controller/Library/IIntroProvider.cs b/MediaBrowser.Controller/Library/IIntroProvider.cs index 3bb1bd9a0..a74d1b9f0 100644 --- a/MediaBrowser.Controller/Library/IIntroProvider.cs +++ b/MediaBrowser.Controller/Library/IIntroProvider.cs @@ -11,6 +11,12 @@ namespace MediaBrowser.Controller.Library /// public interface IIntroProvider { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + /// /// Gets the intros. /// @@ -24,11 +30,5 @@ namespace MediaBrowser.Controller.Library /// /// IEnumerable{System.String}. IEnumerable GetAllIntroFiles(); - - /// - /// Gets the name. - /// - /// The name. - string Name { get; } } } diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs index 8f42d3706..583fd73c3 100644 --- a/MediaBrowser.Controller/Library/Profiler.cs +++ b/MediaBrowser.Controller/Library/Profiler.cs @@ -15,12 +15,12 @@ namespace MediaBrowser.Controller.Library /// /// The name. /// - readonly string _name; + private readonly string _name; /// /// The stopwatch. /// - readonly Stopwatch _stopwatch; + private readonly Stopwatch _stopwatch; /// /// The _logger. diff --git a/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs b/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs new file mode 100644 index 000000000..463061e68 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs @@ -0,0 +1,19 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Threading; + +namespace MediaBrowser.Controller.LiveTv +{ + public class ActiveRecordingInfo + { + public string Id { get; set; } + + public string Path { get; set; } + + public TimerInfo Timer { get; set; } + + public CancellationTokenSource CancellationTokenSource { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index a55fd670d..699c15f93 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.LiveTv public string Number { get; set; } /// - /// Get or sets the Id. + /// Gets or sets the Id. /// /// The id of the channel. public string Id { get; set; } @@ -54,13 +54,13 @@ namespace MediaBrowser.Controller.LiveTv public string ChannelGroup { get; set; } /// - /// Supply the image path if it can be accessed directly from the file system. + /// Gets or sets the the image path if it can be accessed directly from the file system. /// /// The image path. public string ImagePath { get; set; } /// - /// Supply the image url if it can be downloaded. + /// Gets or sets the image url if it can be downloaded. /// /// The image URL. public string ImageUrl { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index c28e0426b..f4dc18e11 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -268,16 +268,21 @@ namespace MediaBrowser.Controller.LiveTv void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user); Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); + Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); IListingsProvider[] ListingProviders { get; } List GetTunerHostTypes(); + Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); event EventHandler> SeriesTimerCancelled; + event EventHandler> TimerCancelled; + event EventHandler> TimerCreated; + event EventHandler> SeriesTimerCreated; string GetEmbyTvActiveRecordingPath(string id); @@ -288,15 +293,4 @@ namespace MediaBrowser.Controller.LiveTv List GetRecordingFolders(User user); } - - public class ActiveRecordingInfo - { - public string Id { get; set; } - - public string Path { get; set; } - - public TimerInfo Timer { get; set; } - - public CancellationTokenSource CancellationTokenSource { get; set; } - } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index d9634a731..a66bec11c 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -101,7 +101,7 @@ namespace MediaBrowser.Controller.LiveTv public bool IsMovie { get; set; } /// - /// Gets or sets a value indicating whether this instance is sports. + /// Gets a value indicating whether this instance is sports. /// /// true if this instance is sports; otherwise, false. [JsonIgnore] @@ -115,35 +115,35 @@ namespace MediaBrowser.Controller.LiveTv public bool IsSeries { get; set; } /// - /// Gets or sets a value indicating whether this instance is live. + /// Gets a value indicating whether this instance is live. /// /// true if this instance is live; otherwise, false. [JsonIgnore] public bool IsLive => Tags.Contains("Live", StringComparer.OrdinalIgnoreCase); /// - /// Gets or sets a value indicating whether this instance is news. + /// Gets a value indicating whether this instance is news. /// /// true if this instance is news; otherwise, false. [JsonIgnore] public bool IsNews => Tags.Contains("News", StringComparer.OrdinalIgnoreCase); /// - /// Gets or sets a value indicating whether this instance is kids. + /// Gets a value indicating whether this instance is kids. /// /// true if this instance is kids; otherwise, false. [JsonIgnore] public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase); /// - /// Gets or sets a value indicating whether this instance is premiere. + /// Gets a value indicating whether this instance is premiere. /// /// true if this instance is premiere; otherwise, false. [JsonIgnore] public bool IsPremiere => Tags.Contains("Premiere", StringComparer.OrdinalIgnoreCase); /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index 4a977c5cc..3c3ac2471 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -10,8 +10,16 @@ namespace MediaBrowser.Controller.LiveTv { public class ProgramInfo { + public ProgramInfo() + { + Genres = new List(); + + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + /// - /// Id of the program. + /// Gets or sets the id of the program. /// public string Id { get; set; } @@ -22,7 +30,7 @@ namespace MediaBrowser.Controller.LiveTv public string ChannelId { get; set; } /// - /// Name of the program. + /// Gets or sets the name of the program. /// public string Name { get; set; } @@ -45,17 +53,17 @@ namespace MediaBrowser.Controller.LiveTv public string ShortOverview { get; set; } /// - /// The start date of the program, in UTC. + /// Gets or sets the start date of the program, in UTC. /// public DateTime StartDate { get; set; } /// - /// The end date of the program, in UTC. + /// Gets or sets the end date of the program, in UTC. /// public DateTime EndDate { get; set; } /// - /// Genre of the program. + /// Gets or sets the genre of the program. /// public List Genres { get; set; } @@ -71,6 +79,9 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is hd; otherwise, false. public bool? IsHD { get; set; } + /// + /// Gets or sets a value indicating whether this instance is 3d. + /// public bool? Is3D { get; set; } /// @@ -100,13 +111,13 @@ namespace MediaBrowser.Controller.LiveTv public string EpisodeTitle { get; set; } /// - /// Supply the image path if it can be accessed directly from the file system. + /// Gets or sets the image path if it can be accessed directly from the file system. /// /// The image path. public string ImagePath { get; set; } /// - /// Supply the image url if it can be downloaded. + /// Gets or sets the image url if it can be downloaded. /// /// The image URL. public string ImageUrl { get; set; } @@ -212,13 +223,5 @@ namespace MediaBrowser.Controller.LiveTv public Dictionary ProviderIds { get; set; } public Dictionary SeriesProviderIds { get; set; } - - public ProgramInfo() - { - Genres = new List(); - - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs index 00135afa8..1dcf7a58f 100644 --- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs +++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs @@ -10,8 +10,13 @@ namespace MediaBrowser.Controller.LiveTv { public class RecordingInfo { + public RecordingInfo() + { + Genres = new List(); + } + /// - /// Id of the recording. + /// Gets or sets the id of the recording. /// public string Id { get; set; } @@ -28,7 +33,7 @@ namespace MediaBrowser.Controller.LiveTv public string TimerId { get; set; } /// - /// ChannelId of the recording. + /// Gets or sets the channelId of the recording. /// public string ChannelId { get; set; } @@ -39,7 +44,7 @@ namespace MediaBrowser.Controller.LiveTv public ChannelType ChannelType { get; set; } /// - /// Name of the recording. + /// Gets or sets the name of the recording. /// public string Name { get; set; } @@ -62,12 +67,12 @@ namespace MediaBrowser.Controller.LiveTv public string Overview { get; set; } /// - /// The start date of the recording, in UTC. + /// Gets or sets the start date of the recording, in UTC. /// public DateTime StartDate { get; set; } /// - /// The end date of the recording, in UTC. + /// Gets or sets the end date of the recording, in UTC. /// public DateTime EndDate { get; set; } @@ -84,7 +89,7 @@ namespace MediaBrowser.Controller.LiveTv public RecordingStatus Status { get; set; } /// - /// Genre of the program. + /// Gets or sets the genre of the program. /// public List Genres { get; set; } @@ -173,13 +178,13 @@ namespace MediaBrowser.Controller.LiveTv public float? CommunityRating { get; set; } /// - /// Supply the image path if it can be accessed directly from the file system. + /// Gets or sets the image path if it can be accessed directly from the file system. /// /// The image path. public string ImagePath { get; set; } /// - /// Supply the image url if it can be downloaded. + /// Gets or sets the image url if it can be downloaded. /// /// The image URL. public string ImageUrl { get; set; } @@ -201,10 +206,5 @@ namespace MediaBrowser.Controller.LiveTv /// /// The date last updated. public DateTime DateLastUpdated { get; set; } - - public RecordingInfo() - { - Genres = new List(); - } } } diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index 1bb649a99..d6811fe14 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -10,13 +10,20 @@ namespace MediaBrowser.Controller.LiveTv { public class SeriesTimerInfo { + public SeriesTimerInfo() + { + Days = new List(); + SkipEpisodesInLibrary = true; + KeepUntil = KeepUntil.UntilDeleted; + } + /// - /// Id of the recording. + /// Gets or sets the id of the recording. /// public string Id { get; set; } /// - /// ChannelId of the recording. + /// Gets or sets the channelId of the recording. /// public string ChannelId { get; set; } @@ -27,24 +34,27 @@ namespace MediaBrowser.Controller.LiveTv public string ProgramId { get; set; } /// - /// Name of the recording. + /// Gets or sets the name of the recording. /// public string Name { get; set; } + /// + /// Gets or sets the service name. + /// public string ServiceName { get; set; } /// - /// Description of the recording. + /// Gets or sets the description of the recording. /// public string Overview { get; set; } /// - /// The start date of the recording, in UTC. + /// Gets or sets the start date of the recording, in UTC. /// public DateTime StartDate { get; set; } /// - /// The end date of the recording, in UTC. + /// Gets or sets the end date of the recording, in UTC. /// public DateTime EndDate { get; set; } @@ -113,12 +123,5 @@ namespace MediaBrowser.Controller.LiveTv /// /// The series identifier. public string SeriesId { get; set; } - - public SeriesTimerInfo() - { - Days = new List(); - SkipEpisodesInLibrary = true; - KeepUntil = KeepUntil.UntilDeleted; - } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 37ce35fc2..3f3a505ea 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -16,7 +16,7 @@ - + diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index 5e9fce550..e50cd9781 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Net DateTime LastKeepAliveDate { get; set; } /// - /// Gets or sets the query string. + /// Gets the query string. /// /// The query string. IQueryCollection QueryString { get; } diff --git a/MediaBrowser.Controller/Providers/AlbumInfo.cs b/MediaBrowser.Controller/Providers/AlbumInfo.cs index 276bcf125..c7fad5974 100644 --- a/MediaBrowser.Controller/Providers/AlbumInfo.cs +++ b/MediaBrowser.Controller/Providers/AlbumInfo.cs @@ -7,6 +7,13 @@ namespace MediaBrowser.Controller.Providers { public class AlbumInfo : ItemLookupInfo { + public AlbumInfo() + { + ArtistProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + SongInfos = new List(); + AlbumArtists = Array.Empty(); + } + /// /// Gets or sets the album artist. /// @@ -20,12 +27,5 @@ namespace MediaBrowser.Controller.Providers public Dictionary ArtistProviderIds { get; set; } public List SongInfos { get; set; } - - public AlbumInfo() - { - ArtistProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - SongInfos = new List(); - AlbumArtists = Array.Empty(); - } } } diff --git a/MediaBrowser.Controller/Providers/ArtistInfo.cs b/MediaBrowser.Controller/Providers/ArtistInfo.cs index adf885baa..e9181f476 100644 --- a/MediaBrowser.Controller/Providers/ArtistInfo.cs +++ b/MediaBrowser.Controller/Providers/ArtistInfo.cs @@ -6,11 +6,11 @@ namespace MediaBrowser.Controller.Providers { public class ArtistInfo : ItemLookupInfo { - public List SongInfos { get; set; } - public ArtistInfo() { SongInfos = new List(); } + + public List SongInfos { get; set; } } } diff --git a/MediaBrowser.Controller/Providers/EpisodeInfo.cs b/MediaBrowser.Controller/Providers/EpisodeInfo.cs index 341bf6936..0c932fa87 100644 --- a/MediaBrowser.Controller/Providers/EpisodeInfo.cs +++ b/MediaBrowser.Controller/Providers/EpisodeInfo.cs @@ -9,6 +9,11 @@ namespace MediaBrowser.Controller.Providers { public class EpisodeInfo : ItemLookupInfo { + public EpisodeInfo() + { + SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + public Dictionary SeriesProviderIds { get; set; } public int? IndexNumberEnd { get; set; } @@ -16,10 +21,5 @@ namespace MediaBrowser.Controller.Providers public bool IsMissingEpisode { get; set; } public string SeriesDisplayOrder { get; set; } - - public EpisodeInfo() - { - SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.Controller/Providers/IMetadataService.cs b/MediaBrowser.Controller/Providers/IMetadataService.cs index 5f3d4274e..05fbb18ee 100644 --- a/MediaBrowser.Controller/Providers/IMetadataService.cs +++ b/MediaBrowser.Controller/Providers/IMetadataService.cs @@ -10,6 +10,12 @@ namespace MediaBrowser.Controller.Providers { public interface IMetadataService { + /// + /// Gets the order. + /// + /// The order. + int Order { get; } + /// /// Determines whether this instance can refresh the specified item. /// @@ -27,11 +33,5 @@ namespace MediaBrowser.Controller.Providers /// The cancellation token. /// Task. Task RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken); - - /// - /// Gets the order. - /// - /// The order. - int Order { get; } } } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index b4d91f396..684bd9e68 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -191,11 +191,4 @@ namespace MediaBrowser.Controller.Providers double? GetRefreshProgress(Guid id); } - - public enum RefreshPriority - { - High = 0, - Normal = 1, - Low = 2 - } } diff --git a/MediaBrowser.Controller/Providers/ItemLookupInfo.cs b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs index f16669a78..e6f49c26a 100644 --- a/MediaBrowser.Controller/Providers/ItemLookupInfo.cs +++ b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs @@ -10,6 +10,12 @@ namespace MediaBrowser.Controller.Providers { public class ItemLookupInfo : IHasProviderIds { + public ItemLookupInfo() + { + IsAutomated = true; + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + /// /// Gets or sets the name. /// @@ -53,11 +59,5 @@ namespace MediaBrowser.Controller.Providers public DateTime? PremiereDate { get; set; } public bool IsAutomated { get; set; } - - public ItemLookupInfo() - { - IsAutomated = true; - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 5afc358ba..115250466 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -11,21 +11,6 @@ namespace MediaBrowser.Controller.Providers { public class MetadataRefreshOptions : ImageRefreshOptions { - /// - /// When paired with MetadataRefreshMode=FullRefresh, all existing data will be overwritten with new data from the providers. - /// - public bool ReplaceAllMetadata { get; set; } - - public MetadataRefreshMode MetadataRefreshMode { get; set; } - - public RemoteSearchResult SearchResult { get; set; } - - public string[] RefreshPaths { get; set; } - - public bool ForceSave { get; set; } - - public bool EnableRemoteContentProbe { get; set; } - public MetadataRefreshOptions(IDirectoryService directoryService) : base(directoryService) { @@ -53,6 +38,22 @@ namespace MediaBrowser.Controller.Providers } } + /// + /// Gets or sets a value indicating whether all existing data should be overwritten with new data from providers + /// when paired with MetadataRefreshMode=FullRefresh + /// + public bool ReplaceAllMetadata { get; set; } + + public MetadataRefreshMode MetadataRefreshMode { get; set; } + + public RemoteSearchResult SearchResult { get; set; } + + public string[] RefreshPaths { get; set; } + + public bool ForceSave { get; set; } + + public bool EnableRemoteContentProbe { get; set; } + public bool RefreshItem(BaseItem item) { if (RefreshPaths != null && RefreshPaths.Length > 0) diff --git a/MediaBrowser.Controller/Providers/RefreshPriority.cs b/MediaBrowser.Controller/Providers/RefreshPriority.cs new file mode 100644 index 000000000..3619f679d --- /dev/null +++ b/MediaBrowser.Controller/Providers/RefreshPriority.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Controller.Providers +{ + /// + /// Provider refresh priority. + /// + public enum RefreshPriority + { + /// + /// High priority. + /// + High = 0, + + /// + /// Normal priority. + /// + Normal = 1, + + /// + /// Low priority. + /// + Low = 2 + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs b/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs index d830231cf..d4df5fa0d 100644 --- a/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs +++ b/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Providers public Guid ItemId { get; set; } /// - /// Will only search within the given provider when set. + /// Gets or sets the provider name to search within if set. /// public string SearchProviderName { get; set; } diff --git a/MediaBrowser.Controller/Providers/SeasonInfo.cs b/MediaBrowser.Controller/Providers/SeasonInfo.cs index 2a4c1f03c..7e39bc37a 100644 --- a/MediaBrowser.Controller/Providers/SeasonInfo.cs +++ b/MediaBrowser.Controller/Providers/SeasonInfo.cs @@ -7,11 +7,11 @@ namespace MediaBrowser.Controller.Providers { public class SeasonInfo : ItemLookupInfo { - public Dictionary SeriesProviderIds { get; set; } - public SeasonInfo() { SeriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); } + + public Dictionary SeriesProviderIds { get; set; } } } diff --git a/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs b/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs index bb80e6025..a07b3e898 100644 --- a/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs +++ b/MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.Resolvers { /// - /// Provides a base "rule" that anyone can use to have paths ignored by the resolver + /// Provides a base "rule" that anyone can use to have paths ignored by the resolver. /// public interface IResolverIgnoreRule { diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 7eda49c60..4c3cf5ffe 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -346,21 +346,19 @@ namespace MediaBrowser.Controller.Session /// Logouts the specified access token. /// /// The access token. - /// Task. void Logout(string accessToken); + void Logout(AuthenticationInfo accessToken); /// /// Revokes the user tokens. /// - /// Task. void RevokeUserTokens(Guid userId, string currentAccessToken); /// /// Revokes the token. /// /// The identifier. - /// Task. void RevokeToken(string id); void CloseIfNeeded(SessionInfo session); diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 5da3783bf..6134c0cf3 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -54,7 +54,7 @@ namespace MediaBrowser.Controller.Session public string RemoteEndPoint { get; set; } /// - /// Gets or sets the playable media types. + /// Gets the playable media types. /// /// The playable media types. public IReadOnlyList PlayableMediaTypes @@ -230,7 +230,7 @@ namespace MediaBrowser.Controller.Session public string UserPrimaryImageTag { get; set; } /// - /// Gets or sets the supported commands. + /// Gets the supported commands. /// /// The supported commands. public IReadOnlyList SupportedCommands From 85ecea77221ff6dfda73a7b75efb5d29f2e3cc96 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 11 May 2021 21:45:15 +0100 Subject: [PATCH 210/612] corrected tests --- .../Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs index a89d6e86f..0d1e408c8 100644 --- a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs +++ b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs @@ -19,7 +19,6 @@ namespace Jellyfin.Server.Integration.Tests [Theory] [InlineData("a=1&b=2&c=3", "a=1&b=2&c=3")] // won't be processed as there is more than 1. [InlineData("a=1", "a=1")] // won't be processed as it has a value - [InlineData("%3D", "==")] // will decode with an empty string value '=' = ''. [InlineData("a%3D1%26b%3D2%26c%3D3", "a=1&b=2&c=3")] // will be processed. public async Task Ensure_Decoding_Of_Urls_Is_Working(string sourceUrl, string unencodedUrl) { From c584d36fd4a643cfd252eddabbda8a91c2d8e0da Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Wed, 12 May 2021 14:48:45 +0200 Subject: [PATCH 211/612] Fix Tmdb person language --- .../Plugins/Tmdb/People/TmdbPersonImageProvider.cs | 5 ++--- .../Plugins/Tmdb/People/TmdbPersonProvider.cs | 5 ++--- MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs | 4 +++- MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs | 6 ++++++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index bf42ceade..d523d0315 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 -using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -48,6 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { + var language = item.GetPreferredMetadataLanguage(); var person = (Person)item; if (!person.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId)) @@ -55,14 +55,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People return Enumerable.Empty(); } - var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false); + var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), language, cancellationToken).ConfigureAwait(false); if (personResult?.Images?.Profiles == null) { return Enumerable.Empty(); } var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count]; - var language = item.GetPreferredMetadataLanguage(); for (var i = 0; i < personResult.Images.Profiles.Count; i++) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 1757c8267..6db550b1d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -32,7 +31,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People { if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId)) { - var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false); + var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); if (personResult != null) { @@ -96,7 +95,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People if (personTmdbId > 0) { - var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false); + var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false); result.HasMetadata = true; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 05e5d3ced..125b1b604 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -276,9 +276,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// Gets a person eg. cast or crew member from the TMDb API based on its TMDb id. /// /// The person's TMDb id. + /// The episode's language. /// The cancellation token. /// The TMDb person information or null if not found. - public async Task GetPersonAsync(int personTmdbId, CancellationToken cancellationToken) + public async Task GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken) { var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}"; if (_memoryCache.TryGetValue(key, out Person person)) @@ -290,6 +291,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb person = await _tmDbClient.GetPersonAsync( personTmdbId, + TmdbUtils.NormalizeLanguage(language), PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index b4e165f82..7c64035fe 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -148,6 +148,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb if (parts.Length == 2) { + // TMDB doesn't support Switzerland (de-CH, it-CH or fr-CH) so use the language (de, it or fr) without country code + if (string.Equals(parts[1], "ch", StringComparison.Ordinal)) + { + return parts[0]; + } + language = parts[0] + "-" + parts[1].ToUpperInvariant(); } From d0bfb56d2ef0607b10d83706f7ec76fc16bb4cf9 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Wed, 12 May 2021 16:19:08 +0100 Subject: [PATCH 212/612] changed to slice. --- Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs index dd05f7bc5..7d2c30b9d 100644 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Web; @@ -66,17 +67,16 @@ namespace Jellyfin.Server.Middleware foreach (var pair in queryString) { - var section = pair.ToString(); - var i = section.IndexOf('=', System.StringComparison.Ordinal); + var i = pair.IndexOf('='); if (i == -1) { // encoded is an equals. - pairs.Add(section, new StringValues(string.Empty)); + pairs.Add(pair[0..i].ToString(), new StringValues(string.Empty)); continue; } - pairs.Add(section[0..i], new StringValues(section[(i + 1)..])); + pairs.Add(pair[0..i].ToString(), new StringValues(pair[(i + 1)..].ToString())); } _store = new QueryCollection(pairs); From f53aa55bdbcdf83c527648a9fc3c89191039ecd1 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 12 May 2021 13:32:54 -0600 Subject: [PATCH 213/612] Don't logout if deviceId is null. --- .../Session/SessionManager.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6844152ea..a1c8fae6f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1540,23 +1540,26 @@ namespace Emby.Server.Implementations.Session Limit = 1 }).Items.FirstOrDefault(); - var allExistingForDevice = _authRepo.Get( - new AuthenticationInfoQuery - { - DeviceId = deviceId - }).Items; - - foreach (var auth in allExistingForDevice) + if (!string.IsNullOrEmpty(deviceId)) { - if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal)) + var allExistingForDevice = _authRepo.Get( + new AuthenticationInfoQuery + { + DeviceId = deviceId + }).Items; + + foreach (var auth in allExistingForDevice) { - try + if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal)) { - Logout(auth); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error while logging out."); + try + { + Logout(auth); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while logging out."); + } } } } From 59c4170966f68a511c4c143eb1b8e27ac3f5cbc0 Mon Sep 17 00:00:00 2001 From: Benjamin Vraspillai Date: Wed, 12 May 2021 16:34:33 +0000 Subject: [PATCH 214/612] Translated using Weblate (Norwegian Nynorsk) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nn/ --- Emby.Server.Implementations/Localization/Core/nn.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json index 16511e686..f5683f35b 100644 --- a/Emby.Server.Implementations/Localization/Core/nn.json +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -19,7 +19,7 @@ "HeaderFavoriteArtists": "Favorittartistar", "HeaderFavoriteAlbums": "Favorittalbum", "HeaderContinueWatching": "Fortsett å sjå", - "HeaderAlbumArtists": "Albumartistar", + "HeaderAlbumArtists": "Albumartist", "Genres": "Sjangrar", "Folders": "Mapper", "Favorites": "Favorittar", @@ -97,8 +97,8 @@ "System": "System", "SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}", "StartupEmbyServerIsLoading": "Jellyfin-tenaren laster. Prøv igjen seinare.", - "Songs": "Songar", - "Shows": "Program", + "Songs": "Sangar", + "Shows": "Seriar", "ServerNameNeedsToBeRestarted": "{0} må omstartast", "ScheduledTaskStartedWithName": "{0} starta", "ScheduledTaskFailedWithName": "{0} feila", @@ -107,7 +107,7 @@ "PluginUninstalledWithName": "{0} blei avinstallert", "PluginInstalledWithName": "{0} blei installert", "Plugin": "Programvaretillegg", - "Playlists": "Speleliste", + "Playlists": "Spelelister", "Photos": "Bilete", "NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa", "NotificationOptionVideoPlayback": "Videoavspeling starta", From abbdaa997ed9fb1261f868c10acb11c406f3fbb7 Mon Sep 17 00:00:00 2001 From: archon eleven Date: Mon, 10 May 2021 21:17:33 +0000 Subject: [PATCH 215/612] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 0b0458691..676e59ab4 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -59,7 +59,7 @@ "NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionUserLockedOut": "User locked out", "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", + "NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan", "Photos": "Gambar-gambar", "Playlists": "Senarai main", "Plugin": "Plugin", @@ -74,7 +74,7 @@ "Songs": "Lagu-lagu", "StartupEmbyServerIsLoading": "Jellyfin Server sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", "Sync": "Sync", "System": "Sistem", "TvShows": "TV Shows", From 88a7875a2739bef91f1d7216c5ebd89b4c267911 Mon Sep 17 00:00:00 2001 From: Haziq Rohaizan Date: Mon, 10 May 2021 21:16:08 +0000 Subject: [PATCH 216/612] Translated using Weblate (Malay) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/ --- Emby.Server.Implementations/Localization/Core/ms.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 676e59ab4..5b4c8ae10 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -72,7 +72,7 @@ "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "Shows": "Series", "Songs": "Lagu-lagu", - "StartupEmbyServerIsLoading": "Jellyfin Server sedang dimuatkan. Sila cuba sebentar lagi.", + "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", "Sync": "Sync", From 6bcbc2b88ae84b1d7cfc50f0872580bed437a60f Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 13 May 2021 07:32:02 -0600 Subject: [PATCH 217/612] Reduce warnings in MediaBrowser.Controller --- MediaBrowser.Controller/Channels/Channel.cs | 2 +- .../Channels/IChannelManager.cs | 26 ++- .../Channels/IDisableMediaSourceDisplay.cs | 8 + .../Channels/IHasFolderAttributes.cs | 9 + .../Channels/IRequiresMediaInfoCallback.cs | 8 +- .../Channels/ISearchableChannel.cs | 32 ---- .../Channels/ISupportsDelete.cs | 17 ++ .../Channels/ISupportsLatestMedia.cs | 21 +++ .../Channels/ISupportsMediaProbe.cs | 8 + .../Channels/InternalChannelFeatures.cs | 4 +- .../Devices/IDeviceManager.cs | 4 +- MediaBrowser.Controller/Dto/IDtoService.cs | 6 + .../Entities/AggregateFolder.cs | 8 +- .../Entities/Audio/MusicArtist.cs | 4 +- .../Entities/Audio/MusicGenre.cs | 6 +- MediaBrowser.Controller/Entities/BaseItem.cs | 39 ++-- .../Entities/CollectionFolder.cs | 66 ++++--- MediaBrowser.Controller/Entities/Folder.cs | 170 +++++++++--------- MediaBrowser.Controller/Entities/Genre.cs | 42 +++-- .../Entities/IHasSeries.cs | 2 +- .../Entities/IHasShares.cs | 11 ++ .../Entities/Movies/Movie.cs | 4 +- .../Entities/MusicVideo.cs | 4 +- MediaBrowser.Controller/Entities/Person.cs | 9 +- MediaBrowser.Controller/Entities/Share.cs | 5 - MediaBrowser.Controller/Entities/Studio.cs | 6 +- .../Entities/TV/Episode.cs | 13 +- MediaBrowser.Controller/Entities/TV/Season.cs | 6 +- MediaBrowser.Controller/Entities/TV/Series.cs | 5 +- MediaBrowser.Controller/Entities/Trailer.cs | 4 +- .../Entities/UserItemData.cs | 2 +- .../Entities/UserRootFolder.cs | 5 +- MediaBrowser.Controller/Entities/UserView.cs | 20 ++- MediaBrowser.Controller/Entities/Year.cs | 10 +- .../Extensions/StringExtensions.cs | 2 +- .../Library/ILibraryManager.cs | 6 + .../LiveTv/LiveTvChannel.cs | 2 +- .../LiveTv/LiveTvProgram.cs | 2 +- MediaBrowser.Controller/LiveTv/TimerInfo.cs | 15 +- .../MediaEncoding/EncodingHelper.cs | 1 + .../MediaEncoding/EncodingJobInfo.cs | 31 +--- .../MediaEncoding/IMediaEncoder.cs | 2 +- .../MediaEncoding/TranscodingJobType.cs | 23 +++ MediaBrowser.Controller/Playlists/Playlist.cs | 3 +- .../Plugins/IRunBeforeStartup.cs | 9 + .../Plugins/IServerEntryPoint.cs | 8 +- .../Providers/MetadataRefreshOptions.cs | 2 +- .../Sync/IHasDynamicAccess.cs | 22 --- .../Sync/IRemoteSyncProvider.cs | 9 - .../Sync/IServerSyncProvider.cs | 32 ---- MediaBrowser.Controller/Sync/ISyncProvider.cs | 31 ---- .../Sync/SyncedFileInfo.cs | 43 ----- .../TV/ITVSeriesManager.cs | 10 ++ 53 files changed, 412 insertions(+), 427 deletions(-) create mode 100644 MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs create mode 100644 MediaBrowser.Controller/Channels/IHasFolderAttributes.cs create mode 100644 MediaBrowser.Controller/Channels/ISupportsDelete.cs create mode 100644 MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs create mode 100644 MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs create mode 100644 MediaBrowser.Controller/Entities/IHasShares.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs create mode 100644 MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs delete mode 100644 MediaBrowser.Controller/Sync/IHasDynamicAccess.cs delete mode 100644 MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs delete mode 100644 MediaBrowser.Controller/Sync/IServerSyncProvider.cs delete mode 100644 MediaBrowser.Controller/Sync/ISyncProvider.cs delete mode 100644 MediaBrowser.Controller/Sync/SyncedFileInfo.cs diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index 26c64e0da..26a936be0 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Channels internal static bool IsChannelVisible(BaseItem channelItem, User user) { - var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString("")); + var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString(string.Empty)); return channel.IsVisible(user); } diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 4c5626338..95d95465e 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -51,32 +51,47 @@ namespace MediaBrowser.Controller.Channels /// Gets the channels internal. /// /// The query. + /// The of . QueryResult GetChannelsInternal(ChannelQuery query); /// /// Gets the channels. /// /// The query. + /// The of . QueryResult GetChannels(ChannelQuery query); /// - /// Gets the latest media. + /// Gets the latest channel items. /// + /// The item query. + /// The cancellation token. + /// A containing the of . Task> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); /// - /// Gets the latest media. + /// Gets the latest channel items. /// + /// The item query. + /// The cancellation token. + /// A containing the of . Task> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken); /// /// Gets the channel items. /// + /// The query. + /// The cancellation token. + /// A containing the of . Task> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); /// - /// Gets the channel items internal. + /// Gets the channel items. /// + /// The query. + /// The progress to report to. + /// The cancellation token. + /// A containing the of . Task> GetChannelItemsInternal(InternalItemsQuery query, IProgress progress, CancellationToken cancellationToken); /// @@ -87,6 +102,11 @@ namespace MediaBrowser.Controller.Channels /// Task{IEnumerable{MediaSourceInfo}}. IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken); + /// + /// Whether the item supports media probe. + /// + /// The item. + /// Whether media probe should be enabled. bool EnableMediaProbe(BaseItem item); } } diff --git a/MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs b/MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs new file mode 100644 index 000000000..a2dc5682d --- /dev/null +++ b/MediaBrowser.Controller/Channels/IDisableMediaSourceDisplay.cs @@ -0,0 +1,8 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public interface IDisableMediaSourceDisplay + { + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs b/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs new file mode 100644 index 000000000..47277a8cc --- /dev/null +++ b/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs @@ -0,0 +1,9 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public interface IHasFolderAttributes + { + string[] Attributes { get; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs b/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs index 589295543..eeaa6b622 100644 --- a/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs +++ b/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -7,11 +5,17 @@ using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Channels { + /// + /// The channel requires a media info callback. + /// public interface IRequiresMediaInfoCallback { /// /// Gets the channel item media information. /// + /// The channel item id. + /// The cancellation token. + /// The enumerable of media source info. Task> GetChannelItemMediaInfo(string id, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Channels/ISearchableChannel.cs b/MediaBrowser.Controller/Channels/ISearchableChannel.cs index b58446fc4..b87943a6e 100644 --- a/MediaBrowser.Controller/Channels/ISearchableChannel.cs +++ b/MediaBrowser.Controller/Channels/ISearchableChannel.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Channels { @@ -19,35 +18,4 @@ namespace MediaBrowser.Controller.Channels /// Task{IEnumerable{ChannelItemInfo}}. Task> Search(ChannelSearchInfo searchInfo, CancellationToken cancellationToken); } - - public interface ISupportsLatestMedia - { - /// - /// Gets the latest media. - /// - /// The request. - /// The cancellation token. - /// Task{IEnumerable{ChannelItemInfo}}. - Task> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken); - } - - public interface ISupportsDelete - { - bool CanDelete(BaseItem item); - - Task DeleteItem(string id, CancellationToken cancellationToken); - } - - public interface IDisableMediaSourceDisplay - { - } - - public interface ISupportsMediaProbe - { - } - - public interface IHasFolderAttributes - { - string[] Attributes { get; } - } } diff --git a/MediaBrowser.Controller/Channels/ISupportsDelete.cs b/MediaBrowser.Controller/Channels/ISupportsDelete.cs new file mode 100644 index 000000000..d7234fa38 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ISupportsDelete.cs @@ -0,0 +1,17 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Channels +{ + public interface ISupportsDelete + { + bool CanDelete(BaseItem item); + + Task DeleteItem(string id, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs new file mode 100644 index 000000000..6820d9222 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs @@ -0,0 +1,21 @@ +#nullable disable + +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Channels +{ + public interface ISupportsLatestMedia + { + /// + /// Gets the latest media. + /// + /// The request. + /// The cancellation token. + /// Task{IEnumerable{ChannelItemInfo}}. + Task> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs b/MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs new file mode 100644 index 000000000..2682de51c --- /dev/null +++ b/MediaBrowser.Controller/Channels/ISupportsMediaProbe.cs @@ -0,0 +1,8 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Channels +{ + public interface ISupportsMediaProbe + { + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs index 152c653dc..45cd08173 100644 --- a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs +++ b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Channels public List ContentTypes { get; set; } /// - /// Represents the maximum number of records the channel allows retrieving at a time. + /// Gets or sets the maximum number of records the channel allows retrieving at a time. /// public int? MaxPageSize { get; set; } @@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.Channels public List DefaultSortFields { get; set; } /// - /// Indicates if a sort ascending/descending toggle is supported or not. + /// Gets or sets a value indicating whether a sort ascending/descending toggle is supported or not. /// public bool SupportsSortOrderToggle { get; set; } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index ef17c8fb3..8096be1bd 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -20,7 +20,6 @@ namespace MediaBrowser.Controller.Devices /// /// The reported identifier. /// The capabilities. - /// Task. void SaveCapabilities(string reportedId, ClientCapabilities capabilities); /// @@ -47,6 +46,9 @@ namespace MediaBrowser.Controller.Devices /// /// Determines whether this instance [can access device] the specified user identifier. /// + /// The user to test. + /// The device id to test. + /// Whether the user can access the device. bool CanAccessDevice(User user, string deviceId); void UpdateDeviceOptions(string deviceId, DeviceOptions options); diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 7f4bbead0..e0950b1f6 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -36,11 +36,17 @@ namespace MediaBrowser.Controller.Dto /// The options. /// The user. /// The owner. + /// The of . IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null); /// /// Gets the item by name dto. /// + /// The item. + /// The dto options. + /// The list of tagged items. + /// The user. + /// The . BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null); } } diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index e365bfda1..533130fc8 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -22,6 +22,8 @@ namespace MediaBrowser.Controller.Entities /// public class AggregateFolder : Folder { + private bool _requiresRefresh; + public AggregateFolder() { PhysicalLocationsList = Array.Empty(); @@ -85,8 +87,6 @@ namespace MediaBrowser.Controller.Entities } } - private bool _requiresRefresh; - public override bool RequiresRefresh() { var changed = base.RequiresRefresh() || _requiresRefresh; @@ -106,11 +106,11 @@ namespace MediaBrowser.Controller.Entities return changed; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { ClearCache(); - var changed = base.BeforeMetadataRefresh(replaceAllMetdata) || _requiresRefresh; + var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh; _requiresRefresh = false; return changed; } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index b07c3eed1..0928a8073 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -208,9 +208,9 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (IsAccessedByName) { diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index b07d47ffd..a682a2e58 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Entities.Audio public override bool IsDisplayedAsFolder => true; /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. @@ -106,9 +106,9 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 238c98982..6e46b4cec 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -92,7 +92,8 @@ namespace MediaBrowser.Controller.Entities public const string ShortsFolderName = "shorts"; public const string FeaturettesFolderName = "featurettes"; - public static readonly string[] AllExtrasTypesFolderNames = { + public static readonly string[] AllExtrasTypesFolderNames = + { ExtrasFolderName, BehindTheScenesFolderName, DeletedScenesFolderName, @@ -177,7 +178,7 @@ namespace MediaBrowser.Controller.Entities public virtual bool AlwaysScanInternalMetadataPath => false; /// - /// Gets a value indicating whether this instance is in mixed folder. + /// Gets or sets a value indicating whether this instance is in mixed folder. /// /// true if this instance is in mixed folder; otherwise, false. [JsonIgnore] @@ -244,7 +245,7 @@ namespace MediaBrowser.Controller.Entities public ProgramAudio? Audio { get; set; } /// - /// Return the id that should be used to key display prefs for this item. + /// Gets the id that should be used to key display prefs for this item. /// Default is based on the type for everything except actual generic folders. /// /// The display prefs id. @@ -280,7 +281,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// [JsonIgnore] @@ -305,8 +306,11 @@ namespace MediaBrowser.Controller.Entities public string ServiceName { get; set; } /// - /// If this content came from an external service, the id of the content on that service. + /// Gets or sets the external id. /// + /// + /// If this content came from an external service, the id of the content on that service. + /// [JsonIgnore] public string ExternalId { get; set; } @@ -330,7 +334,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Gets or sets the type of the location. + /// Gets the type of the location. /// /// The type of the location. [JsonIgnore] @@ -449,8 +453,11 @@ namespace MediaBrowser.Controller.Entities } /// - /// This is just a helper for convenience. + /// Gets the primary image path. /// + /// + /// This is just a helper for convenience. + /// /// The primary image path. [JsonIgnore] public string PrimaryImagePath => this.GetImagePath(ImageType.Primary); @@ -541,7 +548,7 @@ namespace MediaBrowser.Controller.Entities public DateTime DateLastRefreshed { get; set; } /// - /// The logger. + /// Gets or sets the logger. /// public static ILogger Logger { get; set; } @@ -621,7 +628,7 @@ namespace MediaBrowser.Controller.Entities private Guid[] _themeVideoIds; /// - /// Gets the name of the sort. + /// Gets or sets the name of the sort. /// /// The name of the sort. [JsonIgnore] @@ -848,7 +855,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// When the item first debuted. For movies this could be premiere date, episodes would be first aired + /// Gets or sets the date that the item first debuted. For movies this could be premiere date, episodes would be first aired. /// /// The premiere date. [JsonIgnore] @@ -945,7 +952,7 @@ namespace MediaBrowser.Controller.Entities public int? ProductionYear { get; set; } /// - /// If the item is part of a series, this is it's number in the series. + /// Gets or sets the index number. If the item is part of a series, this is it's number in the series. /// This could be episode number, album track number, etc. /// /// The index number. @@ -953,7 +960,7 @@ namespace MediaBrowser.Controller.Entities public int? IndexNumber { get; set; } /// - /// For an episode this could be the season number, or for a song this could be the disc number. + /// Gets or sets the parent index number. For an episode this could be the season number, or for a song this could be the disc number. /// /// The parent index number. [JsonIgnore] @@ -1017,9 +1024,9 @@ namespace MediaBrowser.Controller.Entities } // if (!user.IsParentalScheduleAllowed()) - //{ + // { // return PlayAccess.None; - //} + // } return PlayAccess.Full; } @@ -2645,7 +2652,9 @@ namespace MediaBrowser.Controller.Entities /// /// This is called before any metadata refresh and returns true if changes were made. /// - public virtual bool BeforeMetadataRefresh(bool replaceAllMetdata) + /// Whether to replace all metadata. + /// true if the item has change, else false. + public virtual bool BeforeMetadataRefresh(bool replaceAllMetadata) { _sortName = null; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index a86da29ce..d0fb3997d 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -29,30 +29,45 @@ namespace MediaBrowser.Controller.Entities public class CollectionFolder : Folder, ICollectionFolder { private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - public static IXmlSerializer XmlSerializer { get; set; } - - public static IServerApplicationHost ApplicationHost { get; set; } + private static readonly Dictionary _libraryOptions = new Dictionary(); + private bool _requiresRefresh; + /// + /// Initializes a new instance of the class. + /// public CollectionFolder() { PhysicalLocationsList = Array.Empty(); PhysicalFolderIds = Array.Empty(); } + public static IXmlSerializer XmlSerializer { get; set; } + + public static IServerApplicationHost ApplicationHost { get; set; } + [JsonIgnore] public override bool SupportsPlayedStatus => false; [JsonIgnore] public override bool SupportsInheritedParentImages => false; + public string CollectionType { get; set; } + + /// + /// Gets the item's children. + /// + /// + /// Our children are actually just references to the ones in the physical root... + /// + /// The actual children. + [JsonIgnore] + public override IEnumerable Children => GetActualChildren(); + public override bool CanDelete() { return false; } - public string CollectionType { get; set; } - - private static readonly Dictionary LibraryOptions = new Dictionary(); public LibraryOptions GetLibraryOptions() { return GetLibraryOptions(Path); @@ -106,12 +121,12 @@ namespace MediaBrowser.Controller.Entities public static LibraryOptions GetLibraryOptions(string path) { - lock (LibraryOptions) + lock (_libraryOptions) { - if (!LibraryOptions.TryGetValue(path, out var options)) + if (!_libraryOptions.TryGetValue(path, out var options)) { options = LoadLibraryOptions(path); - LibraryOptions[path] = options; + _libraryOptions[path] = options; } return options; @@ -120,9 +135,9 @@ namespace MediaBrowser.Controller.Entities public static void SaveLibraryOptions(string path, LibraryOptions options) { - lock (LibraryOptions) + lock (_libraryOptions) { - LibraryOptions[path] = options; + _libraryOptions[path] = options; var clone = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions); foreach (var mediaPath in clone.PathInfos) @@ -139,15 +154,18 @@ namespace MediaBrowser.Controller.Entities public static void OnCollectionFolderChange() { - lock (LibraryOptions) + lock (_libraryOptions) { - LibraryOptions.Clear(); + _libraryOptions.Clear(); } } /// - /// Allow different display preferences for each collection folder. + /// Gets the display preferences id. /// + /// + /// Allow different display preferences for each collection folder. + /// /// The display prefs id. [JsonIgnore] public override Guid DisplayPreferencesId => Id; @@ -155,21 +173,20 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override string[] PhysicalLocations => PhysicalLocationsList; + public string[] PhysicalLocationsList { get; set; } + + public Guid[] PhysicalFolderIds { get; set; } + public override bool IsSaveLocalMetadataEnabled() { return true; } - public string[] PhysicalLocationsList { get; set; } - - public Guid[] PhysicalFolderIds { get; set; } - protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) { return CreateResolveArgs(directoryService, true).FileSystemChildren; } - private bool _requiresRefresh; public override bool RequiresRefresh() { var changed = base.RequiresRefresh() || _requiresRefresh; @@ -201,9 +218,9 @@ namespace MediaBrowser.Controller.Entities return changed; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var changed = base.BeforeMetadataRefresh(replaceAllMetdata) || _requiresRefresh; + var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh; _requiresRefresh = false; return changed; } @@ -312,13 +329,6 @@ namespace MediaBrowser.Controller.Entities return Task.CompletedTask; } - /// - /// Our children are actually just references to the ones in the physical root... - /// - /// The actual children. - [JsonIgnore] - public override IEnumerable Children => GetActualChildren(); - public IEnumerable GetActualChildren() { return GetPhysicalFolders(true).SelectMany(c => c.Children); diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index a59f5c6e4..29d837c14 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -37,6 +37,11 @@ namespace MediaBrowser.Controller.Entities /// public class Folder : BaseItem { + public Folder() + { + LinkedChildren = Array.Empty(); + } + public static IUserViewManager UserViewManager { get; set; } /// @@ -50,11 +55,6 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public DateTime? DateLastMediaAdded { get; set; } - public Folder() - { - LinkedChildren = Array.Empty(); - } - [JsonIgnore] public override bool SupportsThemeMedia => true; @@ -86,6 +86,85 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool SupportsDateLastMediaAdded => false; + [JsonIgnore] + public override string FileNameWithoutExtension + { + get + { + if (IsFileProtocol) + { + return System.IO.Path.GetFileName(Path); + } + + return null; + } + } + + /// + /// Gets the actual children. + /// + /// The actual children. + [JsonIgnore] + public virtual IEnumerable Children => LoadChildren(); + + /// + /// Gets thread-safe access to all recursive children of this folder - without regard to user. + /// + /// The recursive children. + [JsonIgnore] + public IEnumerable RecursiveChildren => GetRecursiveChildren(); + + [JsonIgnore] + protected virtual bool SupportsShortcutChildren => false; + + protected virtual bool FilterLinkedChildrenPerUser => false; + + [JsonIgnore] + protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren; + + [JsonIgnore] + public virtual bool SupportsUserDataFromChildren + { + get + { + // These are just far too slow. + if (this is ICollectionFolder) + { + return false; + } + + if (this is UserView) + { + return false; + } + + if (this is UserRootFolder) + { + return false; + } + + if (this is Channel) + { + return false; + } + + if (SourceType != SourceType.Library) + { + return false; + } + + if (this is IItemByName) + { + if (this is not IHasDualAccess hasDualAccess || hasDualAccess.IsAccessedByName) + { + return false; + } + } + + return true; + } + } + public override bool CanDelete() { if (IsRoot) @@ -108,20 +187,6 @@ namespace MediaBrowser.Controller.Entities return baseResult; } - [JsonIgnore] - public override string FileNameWithoutExtension - { - get - { - if (IsFileProtocol) - { - return System.IO.Path.GetFileName(Path); - } - - return null; - } - } - protected override bool IsAllowTagFilterEnforced() { if (this is ICollectionFolder) @@ -137,9 +202,6 @@ namespace MediaBrowser.Controller.Entities return true; } - [JsonIgnore] - protected virtual bool SupportsShortcutChildren => false; - /// /// Adds the child. /// @@ -169,20 +231,6 @@ namespace MediaBrowser.Controller.Entities LibraryManager.CreateItem(item, this); } - /// - /// Gets the actual children. - /// - /// The actual children. - [JsonIgnore] - public virtual IEnumerable Children => LoadChildren(); - - /// - /// thread-safe access to all recursive children of this folder - without regard to user. - /// - /// The recursive children. - [JsonIgnore] - public IEnumerable RecursiveChildren => GetRecursiveChildren(); - public override bool IsVisible(User user) { if (this is ICollectionFolder && !(this is BasePluginFolder)) @@ -1428,8 +1476,6 @@ namespace MediaBrowser.Controller.Entities return list; } - protected virtual bool FilterLinkedChildrenPerUser => false; - public bool ContainsLinkedChildByItemId(Guid itemId) { var linkedChildren = LinkedChildren; @@ -1530,9 +1576,6 @@ namespace MediaBrowser.Controller.Entities .Where(i => i.Item2 != null); } - [JsonIgnore] - protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren; - protected override async Task RefreshedOwnedItems(MetadataRefreshOptions options, List fileSystemChildren, CancellationToken cancellationToken) { var changesFound = false; @@ -1696,51 +1739,6 @@ namespace MediaBrowser.Controller.Entities return !IsPlayed(user); } - [JsonIgnore] - public virtual bool SupportsUserDataFromChildren - { - get - { - // These are just far too slow. - if (this is ICollectionFolder) - { - return false; - } - - if (this is UserView) - { - return false; - } - - if (this is UserRootFolder) - { - return false; - } - - if (this is Channel) - { - return false; - } - - if (SourceType != SourceType.Library) - { - return false; - } - - var iItemByName = this as IItemByName; - if (iItemByName != null) - { - var hasDualAccess = this as IHasDualAccess; - if (hasDualAccess == null || hasDualAccess.IsAccessedByName) - { - return false; - } - } - - return true; - } - } - public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields) { if (!SupportsUserDataFromChildren) diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 310c0c9ec..698643b44 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -16,6 +16,23 @@ namespace MediaBrowser.Controller.Entities /// public class Genre : BaseItem, IItemByName { + /// + /// Gets the folder containing the item. + /// If the item is a folder, it returns the folder itself. + /// + /// The containing folder path. + [JsonIgnore] + public override string ContainingFolderPath => Path; + + [JsonIgnore] + public override bool IsDisplayedAsFolder => true; + + [JsonIgnore] + public override bool SupportsAncestors => false; + + [JsonIgnore] + public override bool SupportsPeople => false; + public override List GetUserDataKeys() { var list = base.GetUserDataKeys(); @@ -34,20 +51,6 @@ namespace MediaBrowser.Controller.Entities return 1; } - /// - /// Gets the folder containing the item. - /// If the item is a folder, it returns the folder itself. - /// - /// The containing folder path. - [JsonIgnore] - public override string ContainingFolderPath => Path; - - [JsonIgnore] - public override bool IsDisplayedAsFolder => true; - - [JsonIgnore] - public override bool SupportsAncestors => false; - public override bool IsSaveLocalMetadataEnabled() { return true; @@ -72,9 +75,6 @@ namespace MediaBrowser.Controller.Entities return LibraryManager.GetItemList(query); } - [JsonIgnore] - public override bool SupportsPeople => false; - public static string GetPath(string name) { return GetPath(name, true); @@ -107,12 +107,10 @@ namespace MediaBrowser.Controller.Entities return base.RequiresRefresh(); } - /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made. - /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + /// + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index 64d769d5b..5f774bbde 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Entities public interface IHasSeries { /// - /// Gets the name of the series. + /// Gets or sets the name of the series. /// /// The name of the series. string SeriesName { get; set; } diff --git a/MediaBrowser.Controller/Entities/IHasShares.cs b/MediaBrowser.Controller/Entities/IHasShares.cs new file mode 100644 index 000000000..bdde744a3 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasShares.cs @@ -0,0 +1,11 @@ +#nullable disable + +#pragma warning disable CS1591 + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasShares + { + Share[] Shares { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 64d60c2e9..b54bbf5eb 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -144,9 +144,9 @@ namespace MediaBrowser.Controller.Entities.Movies } /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!ProductionYear.HasValue) { diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 4e622ba01..237ad5198 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -36,9 +36,9 @@ namespace MediaBrowser.Controller.Entities return info; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!ProductionYear.HasValue) { diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index d9ff55362..913f76d3b 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. @@ -67,6 +67,9 @@ namespace MediaBrowser.Controller.Entities return true; } + /// + /// Gets a value indicating whether to enable alpha numeric sorting. + /// [JsonIgnore] public override bool EnableAlphaNumericSorting => false; @@ -126,9 +129,9 @@ namespace MediaBrowser.Controller.Entities /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Entities/Share.cs b/MediaBrowser.Controller/Entities/Share.cs index 7e4ec1830..64f446eef 100644 --- a/MediaBrowser.Controller/Entities/Share.cs +++ b/MediaBrowser.Controller/Entities/Share.cs @@ -4,11 +4,6 @@ namespace MediaBrowser.Controller.Entities { - public interface IHasShares - { - Share[] Shares { get; set; } - } - public class Share { public string UserId { get; set; } diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index ae1d10447..6fd0a6c6c 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. @@ -105,9 +105,9 @@ namespace MediaBrowser.Controller.Entities /// /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 2724bd9b3..1b4cc7a78 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Entities.TV public IReadOnlyList RemoteTrailerIds { get; set; } /// - /// Gets the season in which it aired. + /// Gets or sets the season in which it aired. /// /// The aired season. public int? AirsBeforeSeasonNumber { get; set; } @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Entities.TV public int? AirsBeforeEpisodeNumber { get; set; } /// - /// This is the ending episode number for double episodes. + /// Gets or sets the ending episode number for double episodes. /// /// The index number. public int? IndexNumberEnd { get; set; } @@ -116,7 +116,7 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// This Episode's Series Instance. + /// Gets the Episode's Series Instance. /// /// The series. [JsonIgnore] @@ -261,6 +261,7 @@ namespace MediaBrowser.Controller.Entities.TV [JsonIgnore] public Guid SeasonId { get; set; } + [JsonIgnore] public Guid SeriesId { get; set; } @@ -318,9 +319,9 @@ namespace MediaBrowser.Controller.Entities.TV return id; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!IsLocked) { @@ -328,7 +329,7 @@ namespace MediaBrowser.Controller.Entities.TV { try { - if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetdata)) + if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetadata)) { hasChanges = true; } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index ad3e0fe8d..5e2053dcc 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// This Episode's Series Instance. + /// Gets this Episode's Series Instance. /// /// The series. [JsonIgnore] @@ -242,9 +242,9 @@ namespace MediaBrowser.Controller.Entities.TV /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// /// true if XXXX, false otherwise. - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path)) { diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index ded825abc..44d07b4a4 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -59,8 +59,11 @@ namespace MediaBrowser.Controller.Entities.TV public IReadOnlyList RemoteTrailerIds { get; set; } /// - /// airdate, dvd or absolute. + /// Gets or sets the display order. /// + /// + /// Valid options are airdate, dvd or absolute. + /// public string DisplayOrder { get; set; } /// diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index b086b5906..732b45521 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -45,9 +45,9 @@ namespace MediaBrowser.Controller.Entities return info; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (!ProductionYear.HasValue) { diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index f60359c01..6ab2116d7 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Entities public const double MinLikeValue = 6.5; /// - /// This is an interpreted property to indicate likes or dislikes + /// Gets or sets a value indicating whether the item is liked or not. /// This should never be serialized. /// /// null if [likes] contains no value, true if [likes]; otherwise, false. diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index e492740ed..2dea2e50b 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.Controller.Entities { private List _childrenIds = null; private readonly object _childIdsLock = new object(); + protected override List LoadChildren() { lock (_childIdsLock) @@ -87,10 +88,10 @@ namespace MediaBrowser.Controller.Entities return list; } - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { ClearCache(); - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 0dfde2766..1e6c01bf8 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -15,13 +15,19 @@ namespace MediaBrowser.Controller.Entities { public class UserView : Folder, IHasCollectionType { - /// + /// + /// Gets or sets the view type. + /// public string ViewType { get; set; } - /// + /// + /// Gets or sets the display parent id. + /// public new Guid DisplayParentId { get; set; } - /// + /// + /// Gets or sets the user id. + /// public Guid? UserId { get; set; } public static ITVSeriesManager TVSeriesManager; @@ -110,10 +116,10 @@ namespace MediaBrowser.Controller.Entities return GetChildren(user, false); } - private static string[] UserSpecificViewTypes = new string[] - { - Model.Entities.CollectionType.Playlists - }; + private static readonly string[] UserSpecificViewTypes = new string[] + { + Model.Entities.CollectionType.Playlists + }; public static bool IsUserSpecific(Folder folder) { diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index 4d84a151a..f268bc939 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Returns the folder containing the item. + /// Gets the folder containing the item. /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. @@ -112,11 +112,13 @@ namespace MediaBrowser.Controller.Entities } /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made. + /// This is called before any metadata refresh and returns true if changes were made. /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) + /// Whether to replace all metadata. + /// true if the item has change, else false. + public override bool BeforeMetadataRefresh(bool replaceAllMetadata) { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); + var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata); var newPath = GetRebasedPath(); if (!string.Equals(Path, newPath, StringComparison.Ordinal)) diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs index 8441a3171..48bd9522a 100644 --- a/MediaBrowser.Controller/Extensions/StringExtensions.cs +++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.Extensions { // will throw if input contains invalid unicode chars // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ - text = Regex.Replace(text, "([\ud800-\udbff](?![\udc00-\udfff]))|((? /// Resolves a set of files into a list of BaseItem. /// + /// The list of tiles. + /// Instance of the interface. + /// The parent folder. + /// The library options. + /// The collection type. + /// The list of . IEnumerable ResolvePaths( IEnumerable files, IDirectoryService directoryService, diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 51e56f4b5..1a893fc2d 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Controller.LiveTv public bool IsNews { get; set; } /// - /// Gets or sets a value indicating whether this instance is kids. + /// Gets a value indicating whether this instance is kids. /// /// true if this instance is kids; otherwise, false. [JsonIgnore] diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index a66bec11c..e2adec000 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -71,7 +71,7 @@ namespace MediaBrowser.Controller.LiveTv public override SourceType SourceType => SourceType.LiveTV; /// - /// The start date of the program, in UTC. + /// Gets or sets start date of the program, in UTC. /// [JsonIgnore] public DateTime StartDate { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index e54dc967c..1a2e8acb3 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -28,18 +28,17 @@ namespace MediaBrowser.Controller.LiveTv public string[] Tags { get; set; } /// - /// Id of the recording. + /// Gets or sets the id of the recording. /// public string Id { get; set; } /// /// Gets or sets the series timer identifier. /// - /// The series timer identifier. public string SeriesTimerId { get; set; } /// - /// ChannelId of the recording. + /// Gets or sets the channelId of the recording. /// public string ChannelId { get; set; } @@ -52,24 +51,24 @@ namespace MediaBrowser.Controller.LiveTv public string ShowId { get; set; } /// - /// Name of the recording. + /// Gets or sets the name of the recording. /// public string Name { get; set; } /// - /// Description of the recording. + /// Gets or sets the description of the recording. /// public string Overview { get; set; } public string SeriesId { get; set; } /// - /// The start date of the recording, in UTC. + /// Gets or sets the start date of the recording, in UTC. /// public DateTime StartDate { get; set; } /// - /// The end date of the recording, in UTC. + /// Gets or sets the end date of the recording, in UTC. /// public DateTime EndDate { get; set; } @@ -133,7 +132,7 @@ namespace MediaBrowser.Controller.LiveTv public bool IsSeries { get; set; } /// - /// Gets or sets a value indicating whether this instance is live. + /// Gets a value indicating whether this instance is live. /// /// true if this instance is live; otherwise, false. [JsonIgnore] diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 97cb8d63b..9300fd49a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2933,6 +2933,7 @@ namespace MediaBrowser.Controller.MediaEncoding return threads; } + #nullable disable public void TryStreamCopy(EncodingJobInfo state) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 1e13382b7..bc34785ee 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -430,7 +430,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target video level. /// public double? TargetVideoLevel { @@ -453,7 +453,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target video bit depth. /// public int? TargetVideoBitDepth { @@ -488,7 +488,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target framerate. /// public float? TargetFramerate { @@ -520,7 +520,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target packet length. /// public int? TargetPacketLength { @@ -536,7 +536,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the target video profile. /// public string TargetVideoProfile { @@ -700,25 +700,4 @@ namespace MediaBrowser.Controller.MediaEncoding Progress.Report(percentComplete.Value); } } - - /// - /// Enum TranscodingJobType. - /// - public enum TranscodingJobType - { - /// - /// The progressive. - /// - Progressive, - - /// - /// The HLS. - /// - Hls, - - /// - /// The dash. - /// - Dash - } } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index d3260280a..76a9fd7c7 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.MediaEncoding public interface IMediaEncoder : ITranscoderSupport { /// - /// The location of the discovered FFmpeg tool. + /// Gets location of the discovered FFmpeg tool. /// FFmpegLocation EncoderLocation { get; } diff --git a/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs b/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs new file mode 100644 index 000000000..66b628371 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Controller.MediaEncoding +{ + /// + /// Enum TranscodingJobType. + /// + public enum TranscodingJobType + { + /// + /// The progressive. + /// + Progressive, + + /// + /// The HLS. + /// + Hls, + + /// + /// The dash. + /// + Dash + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index a80c11643..f767c2c2b 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -22,7 +22,8 @@ namespace MediaBrowser.Controller.Playlists { public class Playlist : Folder, IHasShares { - public static string[] SupportedExtensions = + public static readonly IReadOnlyList SupportedExtensions = + new[] { ".m3u", ".m3u8", diff --git a/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs b/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs new file mode 100644 index 000000000..ea966c282 --- /dev/null +++ b/MediaBrowser.Controller/Plugins/IRunBeforeStartup.cs @@ -0,0 +1,9 @@ +namespace MediaBrowser.Controller.Plugins +{ + /// + /// Indicates that a should be invoked as a pre-startup task. + /// + public interface IRunBeforeStartup + { + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs index b44e2531e..6024661e1 100644 --- a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs +++ b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs @@ -14,13 +14,7 @@ namespace MediaBrowser.Controller.Plugins /// /// Run the initialization for this module. This method is invoked at application start. /// + /// A representing the asynchronous operation. Task RunAsync(); } - - /// - /// Indicates that a should be invoked as a pre-startup task. - /// - public interface IRunBeforeStartup - { - } } diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 115250466..2cf536779 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers /// /// Gets or sets a value indicating whether all existing data should be overwritten with new data from providers - /// when paired with MetadataRefreshMode=FullRefresh + /// when paired with MetadataRefreshMode=FullRefresh. /// public bool ReplaceAllMetadata { get; set; } diff --git a/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs b/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs deleted file mode 100644 index 3d3e44da0..000000000 --- a/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs +++ /dev/null @@ -1,22 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Sync; - -namespace MediaBrowser.Controller.Sync -{ - public interface IHasDynamicAccess - { - /// - /// Gets the synced file information. - /// - /// The identifier. - /// The target. - /// The cancellation token. - /// Task<SyncedFileInfo>. - Task GetSyncedFileInfo(string id, SyncTarget target, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs b/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs deleted file mode 100644 index b2c53365c..000000000 --- a/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Controller.Sync -{ - /// - /// A marker interface. - /// - public interface IRemoteSyncProvider - { - } -} diff --git a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs deleted file mode 100644 index 3891ac0a6..000000000 --- a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs +++ /dev/null @@ -1,32 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Sync; - -namespace MediaBrowser.Controller.Sync -{ - public interface IServerSyncProvider : ISyncProvider - { - /// - /// Transfers the file. - /// - Task SendFile(SyncJob syncJob, string originalMediaPath, Stream inputStream, bool isMedia, string[] outputPathParts, SyncTarget target, IProgress progress, CancellationToken cancellationToken); - - Task> GetFiles(string[] directoryPathParts, SyncTarget target, CancellationToken cancellationToken); - } - - public interface ISupportsDirectCopy - { - /// - /// Sends the file. - /// - Task SendFile(SyncJob syncJob, string originalMediaPath, string inputPath, bool isMedia, string[] outputPathParts, SyncTarget target, IProgress progress, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Sync/ISyncProvider.cs b/MediaBrowser.Controller/Sync/ISyncProvider.cs deleted file mode 100644 index ea20014c7..000000000 --- a/MediaBrowser.Controller/Sync/ISyncProvider.cs +++ /dev/null @@ -1,31 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Model.Sync; - -namespace MediaBrowser.Controller.Sync -{ - public interface ISyncProvider - { - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - - /// - /// Gets the synchronize targets. - /// - /// The user identifier. - /// IEnumerable<SyncTarget>. - List GetSyncTargets(string userId); - - /// - /// Gets all synchronize targets. - /// - /// IEnumerable<SyncTarget>. - List GetAllSyncTargets(); - } -} diff --git a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs deleted file mode 100644 index 7eac52299..000000000 --- a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs +++ /dev/null @@ -1,43 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.Controller.Sync -{ - public class SyncedFileInfo - { - public SyncedFileInfo() - { - RequiredHttpHeaders = new Dictionary(); - } - - /// - /// Gets or sets the path. - /// - /// The path. - public string Path { get; set; } - - public string[] PathParts { get; set; } - - /// - /// Gets or sets the protocol. - /// - /// The protocol. - public MediaProtocol Protocol { get; set; } - - /// - /// Gets or sets the required HTTP headers. - /// - /// The required HTTP headers. - public Dictionary RequiredHttpHeaders { get; set; } - - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - } -} diff --git a/MediaBrowser.Controller/TV/ITVSeriesManager.cs b/MediaBrowser.Controller/TV/ITVSeriesManager.cs index 291dea04e..328cf18f6 100644 --- a/MediaBrowser.Controller/TV/ITVSeriesManager.cs +++ b/MediaBrowser.Controller/TV/ITVSeriesManager.cs @@ -6,16 +6,26 @@ using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.TV { + /// + /// The TV Series manager. + /// public interface ITVSeriesManager { /// /// Gets the next up. /// + /// The next up query. + /// The dto options. + /// The query result of . QueryResult GetNextUp(NextUpQuery query, DtoOptions options); /// /// Gets the next up. /// + /// The next up request. + /// The list of parent folders. + /// The dto options. + /// The query result of . QueryResult GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options); } } From 3c981cf41f2a8e498e674dc707f15b4ebb2679ae Mon Sep 17 00:00:00 2001 From: peterspenler Date: Thu, 13 May 2021 09:49:20 -0400 Subject: [PATCH 218/612] Reorder requested audio channels checks --- CONTRIBUTORS.md | 1 + .../MediaEncoding/EncodingJobInfo.cs | 20 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7a763a46c..10ea6e883 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -146,6 +146,7 @@ - [nielsvanvelzen](https://github.com/nielsvanvelzen) - [skyfrk](https://github.com/skyfrk) - [ianjazz246](https://github.com/ianjazz246) + - [peterspenler](https://github.com/peterspenler) # Emby Contributors diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 1e13382b7..0bb46df3f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -274,6 +274,16 @@ namespace MediaBrowser.Controller.MediaEncoding public int? GetRequestedAudioChannels(string codec) { + if (!string.IsNullOrEmpty(codec)) + { + var value = BaseRequest.GetOption(codec, "audiochannels"); + if (!string.IsNullOrEmpty(value) + && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + { + return result; + } + } + if (BaseRequest.MaxAudioChannels.HasValue) { return BaseRequest.MaxAudioChannels; @@ -289,16 +299,6 @@ namespace MediaBrowser.Controller.MediaEncoding return BaseRequest.TranscodingMaxAudioChannels; } - if (!string.IsNullOrEmpty(codec)) - { - var value = BaseRequest.GetOption(codec, "audiochannels"); - if (!string.IsNullOrEmpty(value) - && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) - { - return result; - } - } - return null; } From 81992ef205c373a854d4da338c6be157e12668dc Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 13 May 2021 10:47:00 -0400 Subject: [PATCH 219/612] Fix build --- MediaBrowser.Providers/Studios/StudiosImageProvider.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index cd24c7e66..f6153dd53 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -172,8 +172,8 @@ namespace MediaBrowser.Providers.Studios public IEnumerable GetAvailableImages(string file) { - using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)); - using (var reader = new StreamReader(fileStream)); + using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); + using var reader = new StreamReader(fileStream); var lines = new List(); foreach (var line in reader.ReadAllLines()) @@ -184,11 +184,6 @@ namespace MediaBrowser.Providers.Studios } } - if (!string.IsNullOrWhiteSpace(text)) - { - lines.Add(text); - } - return lines; } } From 73654481e2faf97a089706d9f449de8b9d85238c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Wed, 12 May 2021 21:06:53 +0200 Subject: [PATCH 220/612] chore: replace GH_TOKEN with JF_BOT_TOKEN --- .github/workflows/automation.yml | 10 +++++----- .github/workflows/merge-conflicts.yml | 2 +- .github/workflows/rebase.yml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index 2529d8099..de1590c74 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -20,7 +20,7 @@ jobs: with: project: Current Release action: delete - repo-token: ${{ secrets.GH_TOKEN }} + repo-token: ${{ secrets.JF_BOT_TOKEN }} - name: Add to 'Release Next' project uses: alex-page/github-project-automation-plus@v0.7.1 @@ -29,7 +29,7 @@ jobs: with: project: Release Next column: In progress - repo-token: ${{ secrets.GH_TOKEN }} + repo-token: ${{ secrets.JF_BOT_TOKEN }} - name: Add to 'Current Release' project uses: alex-page/github-project-automation-plus@v0.7.1 @@ -38,7 +38,7 @@ jobs: with: project: Current Release column: In progress - repo-token: ${{ secrets.GH_TOKEN }} + repo-token: ${{ secrets.JF_BOT_TOKEN }} - name: Check number of comments from the team member if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' @@ -52,7 +52,7 @@ jobs: with: project: Issue Triage for Main Repo column: Needs triage - repo-token: ${{ secrets.GH_TOKEN }} + repo-token: ${{ secrets.JF_BOT_TOKEN }} - name: Add issue to triage project uses: alex-page/github-project-automation-plus@v0.7.1 @@ -61,4 +61,4 @@ jobs: with: project: Issue Triage for Main Repo column: Pending response - repo-token: ${{ secrets.GH_TOKEN }} + repo-token: ${{ secrets.JF_BOT_TOKEN }} diff --git a/.github/workflows/merge-conflicts.yml b/.github/workflows/merge-conflicts.yml index ce808617a..1b04eab46 100644 --- a/.github/workflows/merge-conflicts.yml +++ b/.github/workflows/merge-conflicts.yml @@ -14,4 +14,4 @@ jobs: - uses: eps1lon/actions-label-merge-conflict@v2.0.1 with: dirtyLabel: 'merge conflict' - repoToken: ${{ secrets.GH_TOKEN }} + repoToken: ${{ secrets.JF_BOT_TOKEN }} diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 3172ec0d9..3eec9fa03 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -11,17 +11,17 @@ jobs: - name: Notify as seen uses: peter-evans/create-or-update-comment@v1.4.5 with: - token: ${{ secrets.GH_TOKEN }} + token: ${{ secrets.JF_BOT_TOKEN }} comment-id: ${{ github.event.comment.id }} reactions: '+1' - name: Checkout the latest code uses: actions/checkout@v2 with: - token: ${{ secrets.GH_TOKEN }} + token: ${{ secrets.JF_BOT_TOKEN }} fetch-depth: 0 - name: Automatic Rebase uses: cirrus-actions/rebase@1.4 env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }} From 66b185898f6c6545989d4c4adcf093f590c7668a Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 14 May 2021 17:28:36 -0600 Subject: [PATCH 221/612] Update to dotnet5.0.6 --- .../Emby.Server.Implementations.csproj | 2 +- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- .../Jellyfin.Server.Implementations.csproj | 8 ++++---- Jellyfin.Server/Jellyfin.Server.csproj | 4 ++-- deployment/Dockerfile.debian.amd64 | 3 ++- deployment/Dockerfile.debian.arm64 | 3 ++- deployment/Dockerfile.debian.armhf | 3 ++- deployment/Dockerfile.linux.amd64 | 3 ++- deployment/Dockerfile.linux.amd64-musl | 3 ++- deployment/Dockerfile.linux.arm64 | 3 ++- deployment/Dockerfile.linux.armhf | 3 ++- deployment/Dockerfile.macos | 3 ++- deployment/Dockerfile.portable | 3 ++- deployment/Dockerfile.ubuntu.amd64 | 3 ++- deployment/Dockerfile.ubuntu.arm64 | 3 ++- deployment/Dockerfile.ubuntu.armhf | 3 ++- deployment/Dockerfile.windows.amd64 | 3 ++- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- .../Jellyfin.Server.Integration.Tests.csproj | 2 +- tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj | 2 +- 20 files changed, 37 insertions(+), 24 deletions(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 8ea98f454..14f6f565c 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -27,7 +27,7 @@ - + diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index c10c34b59..eb9fc4f14 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -15,7 +15,7 @@ - + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 2c6a176b6..d24c73526 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -27,13 +27,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 3496cabe8..f83de7ac8 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -38,8 +38,8 @@ - - + + diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64 index ec0321f47..99adf7e1c 100644 --- a/deployment/Dockerfile.debian.amd64 +++ b/deployment/Dockerfile.debian.amd64 @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64 index 8fd5ddb93..4e10a12f2 100644 --- a/deployment/Dockerfile.debian.arm64 +++ b/deployment/Dockerfile.debian.arm64 @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf index 14615d19f..17cd01d3e 100644 --- a/deployment/Dockerfile.debian.armhf +++ b/deployment/Dockerfile.debian.armhf @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64 index 1f6ca1558..4e5d6486f 100644 --- a/deployment/Dockerfile.linux.amd64 +++ b/deployment/Dockerfile.linux.amd64 @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl index 6af5d8baf..3dbe00a58 100644 --- a/deployment/Dockerfile.linux.amd64-musl +++ b/deployment/Dockerfile.linux.amd64-musl @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64 index 15b59e29d..0e17c0c4a 100644 --- a/deployment/Dockerfile.linux.arm64 +++ b/deployment/Dockerfile.linux.arm64 @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf index 71a0fda21..7df4e51b5 100644 --- a/deployment/Dockerfile.linux.armhf +++ b/deployment/Dockerfile.linux.armhf @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos index 9291bcbb9..e3479ae9c 100644 --- a/deployment/Dockerfile.macos +++ b/deployment/Dockerfile.macos @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index e98ba74f8..f1774839a 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -15,7 +15,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index d1fd8818e..f723c4cdd 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index 8e79d417c..a810844c0 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index 627caa95a..3838bab82 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,7 +16,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64 index 5723abcae..007d2648b 100644 --- a/deployment/Dockerfile.windows.amd64 +++ b/deployment/Dockerfile.windows.amd64 @@ -15,7 +15,8 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz + -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 397b863b7..839cfb280 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 938385a2a..4bf6faef7 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 72e40ebcb..260b99df9 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -13,7 +13,7 @@ - + From 42be818a09034103301cfc26de0941da10b34fd0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 15 May 2021 20:28:15 +0200 Subject: [PATCH 222/612] Fully disable stupid rules --- jellyfin.ruleset | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 19c0a08b2..b450a3aaf 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -1,13 +1,6 @@ - - - - - - - @@ -22,6 +15,10 @@ + + + + From 53bfe0e77de6b3d9e8bb4b9740c8fbe009b145c5 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 15 May 2021 20:24:41 +0100 Subject: [PATCH 223/612] Changes as requested --- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 3 +-- .../Controllers/EncoderController.cs | 6 +++--- .../EncodedQueryStringTest.cs | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) rename Jellyfin.Api/Controllers/TestsController.cs => tests/Jellyfin.Server.Integration.Tests/Controllers/EncoderController.cs (87%) diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index e3577caee..806e4ea1d 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -36,8 +36,7 @@ - - + diff --git a/Jellyfin.Api/Controllers/TestsController.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/EncoderController.cs similarity index 87% rename from Jellyfin.Api/Controllers/TestsController.cs rename to tests/Jellyfin.Server.Integration.Tests/Controllers/EncoderController.cs index 1d1e1899f..98ea00de6 100644 --- a/Jellyfin.Api/Controllers/TestsController.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/EncoderController.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Jellyfin.Api.Constants; using Microsoft.AspNetCore.Authorization; @@ -8,9 +8,9 @@ using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers { /// - /// Controller for testing. + /// Controller for testing the encoded url. /// - public class TestsController : BaseJellyfinApiController + public class EncoderController : BaseJellyfinApiController { /// /// Tests the url decoding. diff --git a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs index 0d1e408c8..29d3fe33d 100644 --- a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs +++ b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Server.Integration.Tests { var client = _factory.CreateClient(); - var response = await client.GetAsync("Tests/UrlDecode?" + sourceUrl).ConfigureAwait(false); + var response = await client.GetAsync("Encoder/UrlDecode?" + sourceUrl).ConfigureAwait(false); Assert.Equal(HttpStatusCode.OK, response.StatusCode); string reply = await response.Content.ReadAsStringAsync().ConfigureAwait(false); Assert.Equal(unencodedUrl, reply); From b6dda30a33e7ea3465629e041be74b3e9022d810 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 15 May 2021 15:33:50 -0600 Subject: [PATCH 224/612] Fix suggestions from review --- .../Channels/IChannelManager.cs | 14 +++++++------- .../Channels/ISupportsDelete.cs | 4 +--- .../Channels/ISupportsLatestMedia.cs | 2 +- MediaBrowser.Controller/Dto/IDtoService.cs | 2 +- .../Library/ILibraryManager.cs | 2 +- MediaBrowser.Controller/Playlists/Playlist.cs | 17 ++++++++--------- MediaBrowser.Controller/TV/ITVSeriesManager.cs | 4 ++-- 7 files changed, 21 insertions(+), 24 deletions(-) diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 95d95465e..49be897ef 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -51,14 +51,14 @@ namespace MediaBrowser.Controller.Channels /// Gets the channels internal. /// /// The query. - /// The of . + /// The channels. QueryResult GetChannelsInternal(ChannelQuery query); /// /// Gets the channels. /// /// The query. - /// The of . + /// The channels. QueryResult GetChannels(ChannelQuery query); /// @@ -66,7 +66,7 @@ namespace MediaBrowser.Controller.Channels /// /// The item query. /// The cancellation token. - /// A containing the of . + /// The latest channels. Task> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); /// @@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.Channels /// /// The item query. /// The cancellation token. - /// A containing the of . + /// The latest channels. Task> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken); /// @@ -82,7 +82,7 @@ namespace MediaBrowser.Controller.Channels /// /// The query. /// The cancellation token. - /// A containing the of . + /// The channel items. Task> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); /// @@ -91,7 +91,7 @@ namespace MediaBrowser.Controller.Channels /// The query. /// The progress to report to. /// The cancellation token. - /// A containing the of . + /// The channel items. Task> GetChannelItemsInternal(InternalItemsQuery query, IProgress progress, CancellationToken cancellationToken); /// @@ -99,7 +99,7 @@ namespace MediaBrowser.Controller.Channels /// /// The item. /// The cancellation token. - /// Task{IEnumerable{MediaSourceInfo}}. + /// The item media sources. IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken); /// diff --git a/MediaBrowser.Controller/Channels/ISupportsDelete.cs b/MediaBrowser.Controller/Channels/ISupportsDelete.cs index d7234fa38..204054374 100644 --- a/MediaBrowser.Controller/Channels/ISupportsDelete.cs +++ b/MediaBrowser.Controller/Channels/ISupportsDelete.cs @@ -1,6 +1,4 @@ -#nullable disable - -#pragma warning disable CS1591 +#pragma warning disable CS1591 using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs index 6820d9222..dbba7cba2 100644 --- a/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs +++ b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Channels /// /// The request. /// The cancellation token. - /// Task{IEnumerable{ChannelItemInfo}}. + /// The latest media. Task> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index e0950b1f6..61d796235 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.Dto /// The dto options. /// The list of tagged items. /// The user. - /// The . + /// The item dto. BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null); } } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index ca21569a7..3fd4ff899 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Controller.Library /// The parent folder. /// The library options. /// The collection type. - /// The list of . + /// The items resolved from the paths. IEnumerable ResolvePaths( IEnumerable files, IDirectoryService directoryService, diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index f767c2c2b..bb9e5da1e 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -22,15 +22,14 @@ namespace MediaBrowser.Controller.Playlists { public class Playlist : Folder, IHasShares { - public static readonly IReadOnlyList SupportedExtensions = - new[] - { - ".m3u", - ".m3u8", - ".pls", - ".wpl", - ".zpl" - }; + public static readonly IReadOnlyList SupportedExtensions = new[] + { + ".m3u", + ".m3u8", + ".pls", + ".wpl", + ".zpl" + }; public Guid OwnerUserId { get; set; } diff --git a/MediaBrowser.Controller/TV/ITVSeriesManager.cs b/MediaBrowser.Controller/TV/ITVSeriesManager.cs index 328cf18f6..e066c03fd 100644 --- a/MediaBrowser.Controller/TV/ITVSeriesManager.cs +++ b/MediaBrowser.Controller/TV/ITVSeriesManager.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.TV /// /// The next up query. /// The dto options. - /// The query result of . + /// The next up items. QueryResult GetNextUp(NextUpQuery query, DtoOptions options); /// @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.TV /// The next up request. /// The list of parent folders. /// The dto options. - /// The query result of . + /// The next up items. QueryResult GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options); } } From a9f44c21eb273f395bd4f1ae11885700bcde178b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 15 May 2021 21:49:04 +0200 Subject: [PATCH 225/612] Add tests for Recordinghelper --- .../LiveTv/EmbyTV/RecordingHelper.cs | 14 +-- jellyfin.ruleset | 2 + .../LiveTv/RecordingHelperTests.cs | 115 ++++++++++++++++++ 3 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 142c59542..32245f899 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -6,7 +6,7 @@ using MediaBrowser.Controller.LiveTv; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - internal class RecordingHelper + internal static class RecordingHelper { public static DateTime GetStartTime(TimerInfo timer) { @@ -70,17 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private static string GetDateString(DateTime date) { - date = date.ToLocalTime(); - - return string.Format( - CultureInfo.InvariantCulture, - "{0}_{1}_{2}_{3}_{4}_{5}", - date.Year.ToString("0000", CultureInfo.InvariantCulture), - date.Month.ToString("00", CultureInfo.InvariantCulture), - date.Day.ToString("00", CultureInfo.InvariantCulture), - date.Hour.ToString("00", CultureInfo.InvariantCulture), - date.Minute.ToString("00", CultureInfo.InvariantCulture), - date.Second.ToString("00", CultureInfo.InvariantCulture)); + return date.ToLocalTime().ToString("yyyy_MM_dd_HH_mm_ss", CultureInfo.InvariantCulture); } } } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 19c0a08b2..6ba36d35c 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -43,6 +43,8 @@ or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token --> + + diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs new file mode 100644 index 000000000..e8b93b437 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using Emby.Server.Implementations.LiveTv.EmbyTV; +using MediaBrowser.Controller.LiveTv; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.LiveTv +{ + public static class RecordingHelperTests + { + public static IEnumerable GetRecordingName_Success_TestData() + { + yield return new object[] + { + "The Incredibles 2020_04_20_21_06_00", + new TimerInfo + { + Name = "The Incredibles", + StartDate = new DateTime(2020, 4, 20, 21, 6, 0, DateTimeKind.Local), + IsMovie = true + } + }; + + yield return new object[] + { + "The Incredibles (2004)", + new TimerInfo + { + Name = "The Incredibles", + IsMovie = true, + ProductionYear = 2004 + } + }; + + yield return new object[] + { + "The Big Bang Theory 2020_04_20_21_06_00", + new TimerInfo + { + Name = "The Big Bang Theory", + StartDate = new DateTime(2020, 4, 20, 21, 6, 0, DateTimeKind.Local), + IsProgramSeries = true, + } + }; + + yield return new object[] + { + "The Big Bang Theory S12E10", + new TimerInfo + { + Name = "The Big Bang Theory", + IsProgramSeries = true, + SeasonNumber = 12, + EpisodeNumber = 10 + } + }; + + yield return new object[] + { + "The Big Bang Theory S12E10 The VCR Illumination", + new TimerInfo + { + Name = "The Big Bang Theory", + IsProgramSeries = true, + SeasonNumber = 12, + EpisodeNumber = 10, + EpisodeTitle = "The VCR Illumination" + } + }; + + yield return new object[] + { + "The Big Bang Theory 2018-12-06", + new TimerInfo + { + Name = "The Big Bang Theory", + IsProgramSeries = true, + OriginalAirDate = new DateTime(2018, 12, 6) + } + }; + + yield return new object[] + { + "The Big Bang Theory 2018-12-06 - The VCR Illumination", + new TimerInfo + { + Name = "The Big Bang Theory", + IsProgramSeries = true, + OriginalAirDate = new DateTime(2018, 12, 6), + EpisodeTitle = "The VCR Illumination" + } + }; + + yield return new object[] + { + "The Big Bang Theory 2018_12_06_21_06_00 - The VCR Illumination", + new TimerInfo + { + Name = "The Big Bang Theory", + StartDate = new DateTime(2018, 12, 6, 21, 6, 0, DateTimeKind.Local), + IsProgramSeries = true, + OriginalAirDate = new DateTime(2018, 12, 6), + EpisodeTitle = "The VCR Illumination" + } + }; + } + + [Theory] + [MemberData(nameof(GetRecordingName_Success_TestData))] + public static void GetRecordingName_Success(string expected, TimerInfo timerInfo) + { + Assert.Equal(expected, RecordingHelper.GetRecordingName(timerInfo)); + } + } +} From ffc5aba023ee8ba77331447929079f0b0f4068a2 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 16 May 2021 18:40:28 +0800 Subject: [PATCH 226/612] Fix the 'No decoder surfaces left' error on Cuda --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 97cb8d63b..61d583d94 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -596,7 +596,8 @@ namespace MediaBrowser.Controller.MediaEncoding && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecDecoder) { - arg.Append("-hwaccel_output_format cuda -autorotate 0 "); + // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562 + arg.Append("-hwaccel_output_format cuda -extra_hw_frames 3 -autorotate 0 "); } if (state.IsVideoRequest @@ -1070,7 +1071,6 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc) { - // following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead. switch (encodingOptions.EncoderPreset) { case "veryslow": @@ -1251,7 +1251,7 @@ namespace MediaBrowser.Controller.MediaEncoding } if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase)) + && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase)) { profile = "constrained_baseline"; } From 1b49435a0eb18595c84236fb8cf7671263f3c3cf Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 16 May 2021 14:49:11 +0200 Subject: [PATCH 227/612] Reduce some allocations --- Emby.Naming/Audio/AudioFileParser.cs | 6 +- Emby.Naming/Emby.Naming.csproj | 1 + Emby.Naming/Video/ExtraResolver.cs | 119 ++-- Emby.Naming/Video/VideoResolver.cs | 22 +- .../Data/BaseSqliteRepository.cs | 6 +- .../Data/SqliteExtensions.cs | 110 +++- .../Data/SqliteItemRepository.cs | 569 +++++++----------- .../Data/SqliteUserDataRepository.cs | 16 +- .../Security/AuthorizationContext.cs | 36 +- .../Library/Resolvers/BaseVideoResolver.cs | 16 +- .../Security/AuthenticationRepository.cs | 41 +- Jellyfin.sln | 11 + .../Extensions/EnumerableExtensions.cs | 51 ++ .../Images/LocalImageProvider.cs | 17 +- .../MediaInfo/SubtitleResolver.cs | 219 ++++--- .../Jellyfin.Providers.Tests.csproj | 37 ++ .../MediaInfo/SubtitleResolverTests.cs | 96 +++ 17 files changed, 778 insertions(+), 595 deletions(-) create mode 100644 MediaBrowser.Common/Extensions/EnumerableExtensions.cs create mode 100644 tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs index 8b47dd12e..af4aa0059 100644 --- a/Emby.Naming/Audio/AudioFileParser.cs +++ b/Emby.Naming/Audio/AudioFileParser.cs @@ -1,7 +1,7 @@ using System; using System.IO; -using System.Linq; using Emby.Naming.Common; +using MediaBrowser.Common.Extensions; namespace Emby.Naming.Audio { @@ -18,8 +18,8 @@ namespace Emby.Naming.Audio /// True if file at path is audio file. public static bool IsAudioFile(string path, NamingOptions options) { - var extension = Path.GetExtension(path); - return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + var extension = Path.GetExtension(path.AsSpan()); + return options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); } } } diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 63116f368..a93ddbcb8 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -27,6 +27,7 @@ + diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index 1d3b36a1a..f9d06c09b 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -29,72 +29,75 @@ namespace Emby.Naming.Video /// Path to file. /// Returns object. public ExtraResult GetExtraInfo(string path) - { - return _options.VideoExtraRules - .Select(i => GetExtraInfo(path, i)) - .FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult(); - } - - private ExtraResult GetExtraInfo(string path, ExtraRule rule) { var result = new ExtraResult(); - if (rule.MediaType == MediaType.Audio) + for (var i = 0; i < _options.VideoExtraRules.Length; i++) { - if (!AudioFileParser.IsAudioFile(path, _options)) + var rule = _options.VideoExtraRules[i]; + if (rule.MediaType == MediaType.Audio) + { + if (!AudioFileParser.IsAudioFile(path, _options)) + { + continue; + } + } + else if (rule.MediaType == MediaType.Video) + { + if (!new VideoResolver(_options).IsVideoFile(path)) + { + continue; + } + } + + var pathSpan = path.AsSpan(); + if (rule.RuleType == ExtraRuleType.Filename) + { + var filename = Path.GetFileNameWithoutExtension(pathSpan); + + if (filename.Equals(rule.Token, StringComparison.OrdinalIgnoreCase)) + { + result.ExtraType = rule.ExtraType; + result.Rule = rule; + } + } + else if (rule.RuleType == ExtraRuleType.Suffix) + { + var filename = Path.GetFileNameWithoutExtension(pathSpan); + + if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase)) + { + result.ExtraType = rule.ExtraType; + result.Rule = rule; + } + } + else if (rule.RuleType == ExtraRuleType.Regex) + { + var filename = Path.GetFileName(path); + + var regex = new Regex(rule.Token, RegexOptions.IgnoreCase); + + if (regex.IsMatch(filename)) + { + result.ExtraType = rule.ExtraType; + result.Rule = rule; + } + } + else if (rule.RuleType == ExtraRuleType.DirectoryName) + { + var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan)); + if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase)) + { + result.ExtraType = rule.ExtraType; + result.Rule = rule; + } + } + + if (result.ExtraType != null) { return result; } } - else if (rule.MediaType == MediaType.Video) - { - if (!new VideoResolver(_options).IsVideoFile(path)) - { - return result; - } - } - - if (rule.RuleType == ExtraRuleType.Filename) - { - var filename = Path.GetFileNameWithoutExtension(path); - - if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase)) - { - result.ExtraType = rule.ExtraType; - result.Rule = rule; - } - } - else if (rule.RuleType == ExtraRuleType.Suffix) - { - var filename = Path.GetFileNameWithoutExtension(path); - - if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0) - { - result.ExtraType = rule.ExtraType; - result.Rule = rule; - } - } - else if (rule.RuleType == ExtraRuleType.Regex) - { - var filename = Path.GetFileName(path); - - var regex = new Regex(rule.Token, RegexOptions.IgnoreCase); - - if (regex.IsMatch(filename)) - { - result.ExtraType = rule.ExtraType; - result.Rule = rule; - } - } - else if (rule.RuleType == ExtraRuleType.DirectoryName) - { - var directoryName = Path.GetFileName(Path.GetDirectoryName(path)); - if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase)) - { - result.ExtraType = rule.ExtraType; - result.Rule = rule; - } - } return result; } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 79a6da8f7..d1c294f4f 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using Emby.Naming.Common; +using MediaBrowser.Common.Extensions; namespace Emby.Naming.Video { @@ -59,15 +59,15 @@ namespace Emby.Naming.Video } bool isStub = false; - string? container = null; + ReadOnlySpan container = null; string? stubType = null; if (!isDirectory) { - var extension = Path.GetExtension(path); + var extension = Path.GetExtension(path.AsSpan()); // Check supported extensions - if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { // It's not supported. Check stub extensions if (!StubResolver.TryResolveFile(path, _options, out stubType)) @@ -86,9 +86,7 @@ namespace Emby.Naming.Video var extraResult = new ExtraResolver(_options).GetExtraInfo(path); - var name = isDirectory - ? Path.GetFileName(path) - : Path.GetFileNameWithoutExtension(path); + var name = Path.GetFileNameWithoutExtension(path); int? year = null; @@ -107,7 +105,7 @@ namespace Emby.Naming.Video return new VideoFileInfo( path: path, - container: container, + container: container.ToString(), isStub: isStub, name: name, year: year, @@ -126,8 +124,8 @@ namespace Emby.Naming.Video /// True if is video file. public bool IsVideoFile(string path) { - var extension = Path.GetExtension(path); - return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + var extension = Path.GetExtension(path.AsSpan()); + return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); } /// @@ -137,8 +135,8 @@ namespace Emby.Naming.Video /// True if is video file stub. public bool IsStubFile(string path) { - var extension = Path.GetExtension(path); - return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + var extension = Path.GetExtension(path.AsSpan()); + return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); } /// diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 8c756a7f4..c331a6112 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -181,11 +181,9 @@ namespace Emby.Server.Implementations.Data foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) { - if (row[1].SQLiteType != SQLiteType.Null) + if (row.TryGetString(1, out var columnName)) { - var name = row[1].ToString(); - - columnNames.Add(name); + columnNames.Add(columnName); } } diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index a04d63088..db3010207 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using SQLitePCL.pretty; @@ -96,21 +97,42 @@ namespace Emby.Server.Implementations.Data DateTimeStyles.None).ToUniversalTime(); } - public static DateTime? TryReadDateTime(this IResultSetValue result) + public static bool TryReadDateTime(this IReadOnlyList reader, int index, [NotNullWhen(true)] out DateTime? result) { - var dateText = result.ToString(); + result = null; + var item = reader[index]; + if (item.IsDbNull()) + { + return false; + } + + var dateText = item.ToString(); if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult)) { - return dateTimeResult.ToUniversalTime(); + result = dateTimeResult.ToUniversalTime(); + return true; } - return null; + return false; } - public static bool IsDBNull(this IReadOnlyList result, int index) + public static bool TryGetGuid(this IReadOnlyList reader, int index, [NotNullWhen(true)] out Guid? result) { - return result[index].SQLiteType == SQLiteType.Null; + result = null; + var item = reader[index]; + if (item.IsDbNull()) + { + return false; + } + + result = item.ReadGuidFromBlob(); + return true; + } + + private static bool IsDbNull(this IResultSetValue result) + { + return result.SQLiteType == SQLiteType.Null; } public static string GetString(this IReadOnlyList result, int index) @@ -118,14 +140,48 @@ namespace Emby.Server.Implementations.Data return result[index].ToString(); } + public static bool TryGetString(this IReadOnlyList reader, int index, out string result) + { + result = null; + var item = reader[index]; + if (item.IsDbNull()) + { + return false; + } + + result = item.ToString(); + return true; + } + public static bool GetBoolean(this IReadOnlyList result, int index) { return result[index].ToBool(); } - public static int GetInt32(this IReadOnlyList result, int index) + public static bool TryGetBoolean(this IReadOnlyList reader, int index, [NotNullWhen(true)] out bool? result) { - return result[index].ToInt(); + result = null; + var item = reader[index]; + if (item.IsDbNull()) + { + return false; + } + + result = item.ToBool(); + return true; + } + + public static bool TryGetInt(this IReadOnlyList reader, int index, out int? result) + { + result = null; + var item = reader[index]; + if (item.IsDbNull()) + { + return false; + } + + result = item.ToInt(); + return true; } public static long GetInt64(this IReadOnlyList result, int index) @@ -133,9 +189,43 @@ namespace Emby.Server.Implementations.Data return result[index].ToInt64(); } - public static float GetFloat(this IReadOnlyList result, int index) + public static bool TryGetLong(this IReadOnlyList reader, int index, out long? result) { - return result[index].ToFloat(); + result = null; + var item = reader[index]; + if (item.IsDbNull()) + { + return false; + } + + result = item.ToInt64(); + return true; + } + + public static bool TryGetFloat(this IReadOnlyList reader, int index, out float? result) + { + result = null; + var item = reader[index]; + if (item.IsDbNull()) + { + return false; + } + + result = item.ToFloat(); + return true; + } + + public static bool TryGetDouble(this IReadOnlyList reader, int index, out double? result) + { + result = null; + var item = reader[index]; + if (item.IsDbNull()) + { + return false; + } + + result = item.ToDouble(); + return true; } public static Guid GetGuid(this IReadOnlyList result, int index) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 835eb0692..53c9a4cad 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1332,27 +1332,23 @@ namespace Emby.Server.Implementations.Data if (queryHasStartDate) { - if (!reader.IsDBNull(index)) + if (item is IHasStartDate hasStartDate && reader.TryReadDateTime(index, out var startDate)) { - if (item is IHasStartDate hasStartDate) - { - hasStartDate.StartDate = reader[index].ReadDateTime(); - } + hasStartDate.StartDate = startDate.Value; } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var endDate)) { - item.EndDate = reader[index].TryReadDateTime(); + item.EndDate = endDate; } - index++; - - if (!reader.IsDBNull(index)) + var channelId = reader[index]; + if (channelId.SQLiteType != SQLiteType.Null) { - if (!Utf8Parser.TryParse(reader[index].ToBlob(), out Guid value, out _, standardFormat: 'N')) + if (!Utf8Parser.TryParse(channelId.ToBlob(), out Guid value, out _, standardFormat: 'N')) { var str = reader.GetString(index); Logger.LogWarning("{ChannelId} isn't in the expected format", str); @@ -1368,33 +1364,25 @@ namespace Emby.Server.Implementations.Data { if (item is IHasProgramAttributes hasProgramAttributes) { - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isMovie)) { - hasProgramAttributes.IsMovie = reader.GetBoolean(index); + hasProgramAttributes.IsMovie = isMovie.Value; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isSeries)) { - hasProgramAttributes.IsSeries = reader.GetBoolean(index); + hasProgramAttributes.IsSeries = isSeries.Value; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var episodeTitle)) { - hasProgramAttributes.EpisodeTitle = reader.GetString(index); + hasProgramAttributes.EpisodeTitle = episodeTitle; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isRepeat)) { - hasProgramAttributes.IsRepeat = reader.GetBoolean(index); + hasProgramAttributes.IsRepeat = isRepeat.Value; } - - index++; } else { @@ -1402,242 +1390,189 @@ namespace Emby.Server.Implementations.Data } } - if (!reader.IsDBNull(index)) + if (reader.TryGetFloat(index++, out var communityRating)) { - item.CommunityRating = reader.GetFloat(index); + item.CommunityRating = communityRating; } - index++; - if (HasField(query, ItemFields.CustomRating)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var customRating)) { - item.CustomRating = reader.GetString(index); + item.CustomRating = customRating; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetInt(index++, out var indexNumber)) { - item.IndexNumber = reader.GetInt32(index); + item.IndexNumber = indexNumber; } - index++; - if (HasField(query, ItemFields.Settings)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isLocked)) { - item.IsLocked = reader.GetBoolean(index); + item.IsLocked = isLocked.Value; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var preferredMetadataLanguage)) { - item.PreferredMetadataLanguage = reader.GetString(index); + item.PreferredMetadataLanguage = preferredMetadataLanguage; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var preferredMetadataCountryCode)) { - item.PreferredMetadataCountryCode = reader.GetString(index); + item.PreferredMetadataCountryCode = preferredMetadataCountryCode; } - - index++; } if (HasField(query, ItemFields.Width)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetInt(index++, out var width)) { - item.Width = reader.GetInt32(index); + item.Width = width.Value; } - - index++; } if (HasField(query, ItemFields.Height)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetInt(index++, out var height)) { - item.Height = reader.GetInt32(index); + item.Height = height.Value; } - - index++; } if (HasField(query, ItemFields.DateLastRefreshed)) { - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var dateLastRefreshed)) { - item.DateLastRefreshed = reader[index].ReadDateTime(); + item.DateLastRefreshed = dateLastRefreshed.Value; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var name)) { - item.Name = reader.GetString(index); + item.Name = name; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var restorePath)) { - item.Path = RestorePath(reader.GetString(index)); + item.Path = RestorePath(restorePath); } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var premiereDate)) { - item.PremiereDate = reader[index].TryReadDateTime(); + item.PremiereDate = premiereDate; } - index++; - if (HasField(query, ItemFields.Overview)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var overview)) { - item.Overview = reader.GetString(index); + item.Overview = overview; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetInt(index++, out var parentIndexNumber)) { - item.ParentIndexNumber = reader.GetInt32(index); + item.ParentIndexNumber = parentIndexNumber; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetInt(index++, out var productionYear)) { - item.ProductionYear = reader.GetInt32(index); + item.ProductionYear = productionYear; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var officialRating)) { - item.OfficialRating = reader.GetString(index); + item.OfficialRating = officialRating; } - index++; - if (HasField(query, ItemFields.SortName)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var forcedSortName)) { - item.ForcedSortName = reader.GetString(index); + item.ForcedSortName = forcedSortName; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetLong(index++, out var runTimeTicks)) { - item.RunTimeTicks = reader.GetInt64(index); + item.RunTimeTicks = runTimeTicks; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetLong(index++, out var size)) { - item.Size = reader.GetInt64(index); + item.Size = size; } - index++; - if (HasField(query, ItemFields.DateCreated)) { - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var dateCreated)) { - item.DateCreated = reader[index].ReadDateTime(); + item.DateCreated = dateCreated.Value; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var dateModified)) { - item.DateModified = reader[index].ReadDateTime(); + item.DateModified = dateModified.Value; } - index++; - - item.Id = reader.GetGuid(index); - index++; + item.Id = reader.GetGuid(index++); if (HasField(query, ItemFields.Genres)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var genres)) { - item.Genres = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + item.Genres = genres.Split('|', StringSplitOptions.RemoveEmptyEntries); } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetGuid(index++, out var parentId)) { - item.ParentId = reader.GetGuid(index); + item.ParentId = parentId.Value; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var audioString)) { - if (Enum.TryParse(reader.GetString(index), true, out ProgramAudio audio)) + if (Enum.TryParse(audioString, true, out ProgramAudio audio)) { item.Audio = audio; } } - index++; - // TODO: Even if not needed by apps, the server needs it internally // But get this excluded from contexts where it is not needed if (hasServiceName) { if (item is LiveTvChannel liveTvChannel) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var serviceName)) { - liveTvChannel.ServiceName = reader.GetString(index); + liveTvChannel.ServiceName = serviceName; } } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isInMixedFolder)) { - item.IsInMixedFolder = reader.GetBoolean(index); + item.IsInMixedFolder = isInMixedFolder.Value; } - index++; - if (HasField(query, ItemFields.DateLastSaved)) { - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var dateLastSaved)) { - item.DateLastSaved = reader[index].ReadDateTime(); + item.DateLastSaved = dateLastSaved.Value; } - - index++; } if (HasField(query, ItemFields.Settings)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var lockedFields)) { IEnumerable GetLockedFields(string s) { @@ -1650,37 +1585,31 @@ namespace Emby.Server.Implementations.Data } } - item.LockedFields = GetLockedFields(reader.GetString(index)).ToArray(); + item.LockedFields = GetLockedFields(lockedFields).ToArray(); } - - index++; } if (HasField(query, ItemFields.Studios)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var studios)) { - item.Studios = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + item.Studios = studios.Split('|', StringSplitOptions.RemoveEmptyEntries); } - - index++; } if (HasField(query, ItemFields.Tags)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var tags)) { - item.Tags = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + item.Tags = tags.Split('|', StringSplitOptions.RemoveEmptyEntries); } - - index++; } if (hasTrailerTypes) { if (item is Trailer trailer) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var trailerTypes)) { IEnumerable GetTrailerTypes(string s) { @@ -1693,7 +1622,7 @@ namespace Emby.Server.Implementations.Data } } - trailer.TrailerTypes = GetTrailerTypes(reader.GetString(index)).ToArray(); + trailer.TrailerTypes = GetTrailerTypes(trailerTypes).ToArray(); } } @@ -1702,19 +1631,17 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.OriginalTitle)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var originalTitle)) { - item.OriginalTitle = reader.GetString(index); + item.OriginalTitle = originalTitle; } - - index++; } if (item is Video video) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var primaryVersionId)) { - video.PrimaryVersionId = reader.GetString(index); + video.PrimaryVersionId = primaryVersionId; } } @@ -1722,40 +1649,34 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.DateLastMediaAdded)) { - if (item is Folder folder && !reader.IsDBNull(index)) + if (item is Folder folder && reader.TryReadDateTime(index, out var dateLastMediaAdded)) { - folder.DateLastMediaAdded = reader[index].TryReadDateTime(); + folder.DateLastMediaAdded = dateLastMediaAdded; } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var album)) { - item.Album = reader.GetString(index); + item.Album = album; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetFloat(index++, out var criticRating)) { - item.CriticRating = reader.GetFloat(index); + item.CriticRating = criticRating; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isVirtualItem)) { - item.IsVirtualItem = reader.GetBoolean(index); + item.IsVirtualItem = isVirtualItem.Value; } - index++; - if (item is IHasSeries hasSeriesName) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var seriesName)) { - hasSeriesName.SeriesName = reader.GetString(index); + hasSeriesName.SeriesName = seriesName; } } @@ -1765,15 +1686,15 @@ namespace Emby.Server.Implementations.Data { if (item is Episode episode) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var seasonName)) { - episode.SeasonName = reader.GetString(index); + episode.SeasonName = seasonName; } index++; - if (!reader.IsDBNull(index)) + if (reader.TryGetGuid(index, out var seasonId)) { - episode.SeasonId = reader.GetGuid(index); + episode.SeasonId = seasonId.Value; } } else @@ -1789,9 +1710,9 @@ namespace Emby.Server.Implementations.Data { if (hasSeries != null) { - if (!reader.IsDBNull(index)) + if (reader.TryGetGuid(index, out var seriesId)) { - hasSeries.SeriesId = reader.GetGuid(index); + hasSeries.SeriesId = seriesId.Value; } } @@ -1800,56 +1721,48 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.PresentationUniqueKey)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var presentationUniqueKey)) { - item.PresentationUniqueKey = reader.GetString(index); + item.PresentationUniqueKey = presentationUniqueKey; } - - index++; } if (HasField(query, ItemFields.InheritedParentalRatingValue)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetInt(index++, out var parentalRating)) { - item.InheritedParentalRatingValue = reader.GetInt32(index); + item.InheritedParentalRatingValue = parentalRating.Value; } - - index++; } if (HasField(query, ItemFields.ExternalSeriesId)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var externalSeriesId)) { - item.ExternalSeriesId = reader.GetString(index); + item.ExternalSeriesId = externalSeriesId; } - - index++; } if (HasField(query, ItemFields.Taglines)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var tagLine)) { - item.Tagline = reader.GetString(index); + item.Tagline = tagLine; } - - index++; } - if (item.ProviderIds.Count == 0 && !reader.IsDBNull(index)) + if (item.ProviderIds.Count == 0 && reader.TryGetString(index, out var providerIds)) { - DeserializeProviderIds(reader.GetString(index), item); + DeserializeProviderIds(providerIds, item); } index++; if (query.DtoOptions.EnableImages) { - if (item.ImageInfos.Length == 0 && !reader.IsDBNull(index)) + if (item.ImageInfos.Length == 0 && reader.TryGetString(index, out var imageInfos)) { - item.ImageInfos = DeserializeImages(reader.GetString(index)); + item.ImageInfos = DeserializeImages(imageInfos); } index++; @@ -1857,72 +1770,62 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.ProductionLocations)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var productionLocations)) { - item.ProductionLocations = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries).ToArray(); + item.ProductionLocations = productionLocations.Split('|', StringSplitOptions.RemoveEmptyEntries); } - - index++; } if (HasField(query, ItemFields.ExtraIds)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var extraIds)) { - item.ExtraIds = SplitToGuids(reader.GetString(index)); + item.ExtraIds = SplitToGuids(extraIds); } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetInt(index++, out var totalBitrate)) { - item.TotalBitrate = reader.GetInt32(index); + item.TotalBitrate = totalBitrate; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var extraTypeString)) { - if (Enum.TryParse(reader.GetString(index), true, out ExtraType extraType)) + if (Enum.TryParse(extraTypeString, true, out ExtraType extraType)) { item.ExtraType = extraType; } } - index++; - if (hasArtistFields) { - if (item is IHasArtist hasArtists && !reader.IsDBNull(index)) + if (item is IHasArtist hasArtists && reader.TryGetString(index, out var artists)) { - hasArtists.Artists = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + hasArtists.Artists = artists.Split('|', StringSplitOptions.RemoveEmptyEntries); } index++; - if (item is IHasAlbumArtist hasAlbumArtists && !reader.IsDBNull(index)) + if (item is IHasAlbumArtist hasAlbumArtists && reader.TryGetString(index, out var albumArtists)) { - hasAlbumArtists.AlbumArtists = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + hasAlbumArtists.AlbumArtists = albumArtists.Split('|', StringSplitOptions.RemoveEmptyEntries); } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var externalId)) { - item.ExternalId = reader.GetString(index); + item.ExternalId = externalId; } - index++; - if (HasField(query, ItemFields.SeriesPresentationUniqueKey)) { if (hasSeries != null) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var seriesPresentationUniqueKey)) { - hasSeries.SeriesPresentationUniqueKey = reader.GetString(index); + hasSeries.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; } } @@ -1931,21 +1834,19 @@ namespace Emby.Server.Implementations.Data if (enableProgramAttributes) { - if (item is LiveTvProgram program && !reader.IsDBNull(index)) + if (item is LiveTvProgram program && reader.TryGetString(index, out var showId)) { - program.ShowId = reader.GetString(index); + program.ShowId = showId; } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetGuid(index, out var ownerId)) { - item.OwnerId = reader.GetGuid(index); + item.OwnerId = ownerId.Value; } - index++; - return item; } @@ -2032,14 +1933,14 @@ namespace Emby.Server.Implementations.Data StartPositionTicks = reader.GetInt64(0) }; - if (!reader.IsDBNull(1)) + if (reader.TryGetString(1, out var chapterName)) { - chapter.Name = reader.GetString(1); + chapter.Name = chapterName; } - if (!reader.IsDBNull(2)) + if (reader.TryGetString(2, out var imagePath)) { - chapter.ImagePath = reader.GetString(2); + chapter.ImagePath = imagePath; if (!string.IsNullOrEmpty(chapter.ImagePath)) { @@ -2054,9 +1955,9 @@ namespace Emby.Server.Implementations.Data } } - if (!reader.IsDBNull(3)) + if (reader.TryReadDateTime(3, out var imageDateModified)) { - chapter.ImageDateModified = reader[3].ReadDateTime(); + chapter.ImageDateModified = imageDateModified.Value; } return chapter; @@ -3228,12 +3129,8 @@ namespace Emby.Server.Implementations.Data foreach (var row in statement.ExecuteQuery()) { var id = row.GetGuid(0); - string path = null; - if (!row.IsDBNull(1)) - { - path = row.GetString(1); - } + row.TryGetString(1, out var path); list.Add(new Tuple(id, path)); } @@ -5327,9 +5224,9 @@ AND Type = @InternalPersonType)"); { foreach (var row in statement.ExecuteQuery()) { - if (!row.IsDBNull(0)) + if (row.TryGetString(0, out var result)) { - list.Add(row.GetString(0)); + list.Add(result); } } } @@ -5603,7 +5500,7 @@ AND Type = @InternalPersonType)"); return result; } - private ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, string[] typesToCount) + private static ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, string[] typesToCount) { var counts = new ItemCounts(); @@ -5612,51 +5509,43 @@ AND Type = @InternalPersonType)"); return counts; } - var typeString = reader.IsDBNull(countStartColumn) ? null : reader.GetString(countStartColumn); - - if (string.IsNullOrWhiteSpace(typeString)) + if (!reader.TryGetString(countStartColumn, out var typeString)) { return counts; } - var allTypes = typeString.Split('|', StringSplitOptions.RemoveEmptyEntries) - .ToLookup(x => x); - - foreach (var type in allTypes) + foreach (var typeName in typeString.AsSpan().Split('|')) { - var value = type.Count(); - var typeName = type.Key; - - if (string.Equals(typeName, typeof(Series).FullName, StringComparison.OrdinalIgnoreCase)) + if (typeName.Equals(typeof(Series).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.SeriesCount = value; + counts.SeriesCount++; } - else if (string.Equals(typeName, typeof(Episode).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(Episode).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.EpisodeCount = value; + counts.EpisodeCount++; } - else if (string.Equals(typeName, typeof(Movie).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(Movie).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.MovieCount = value; + counts.MovieCount++; } - else if (string.Equals(typeName, typeof(MusicAlbum).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(MusicAlbum).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.AlbumCount = value; + counts.AlbumCount++; } - else if (string.Equals(typeName, typeof(MusicArtist).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(MusicArtist).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.ArtistCount = value; + counts.ArtistCount++; } - else if (string.Equals(typeName, typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.SongCount = value; + counts.SongCount++; } - else if (string.Equals(typeName, typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.TrailerCount = value; + counts.TrailerCount++; } - counts.ItemCount += value; + counts.ItemCount++; } return counts; @@ -5850,19 +5739,19 @@ AND Type = @InternalPersonType)"); Name = reader.GetString(1) }; - if (!reader.IsDBNull(2)) + if (reader.TryGetString(2, out var role)) { - item.Role = reader.GetString(2); + item.Role = role; } - if (!reader.IsDBNull(3)) + if (reader.TryGetString(3, out var type)) { - item.Type = reader.GetString(3); + item.Type = type; } - if (!reader.IsDBNull(4)) + if (reader.TryGetInt(4, out var sortOrder)) { - item.SortOrder = reader.GetInt32(4); + item.SortOrder = sortOrder; } return item; @@ -6058,150 +5947,150 @@ AND Type = @InternalPersonType)"); item.Type = Enum.Parse(reader[2].ToString(), true); - if (reader[3].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(3, out var codec)) { - item.Codec = reader[3].ToString(); + item.Codec = codec; } - if (reader[4].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(4, out var language)) { - item.Language = reader[4].ToString(); + item.Language = language; } - if (reader[5].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(5, out var channelLayout)) { - item.ChannelLayout = reader[5].ToString(); + item.ChannelLayout = channelLayout; } - if (reader[6].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(6, out var profile)) { - item.Profile = reader[6].ToString(); + item.Profile = profile; } - if (reader[7].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(7, out var aspectRatio)) { - item.AspectRatio = reader[7].ToString(); + item.AspectRatio = aspectRatio; } - if (reader[8].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(8, out var path)) { - item.Path = RestorePath(reader[8].ToString()); + item.Path = RestorePath(path); } item.IsInterlaced = reader.GetBoolean(9); - if (reader[10].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt(10, out var bitrate)) { - item.BitRate = reader.GetInt32(10); + item.BitRate = bitrate; } - if (reader[11].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt(11, out var channels)) { - item.Channels = reader.GetInt32(11); + item.Channels = channels; } - if (reader[12].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt(12, out var sampleRate)) { - item.SampleRate = reader.GetInt32(12); + item.SampleRate = sampleRate; } item.IsDefault = reader.GetBoolean(13); item.IsForced = reader.GetBoolean(14); item.IsExternal = reader.GetBoolean(15); - if (reader[16].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt(16, out var width)) { - item.Width = reader.GetInt32(16); + item.Width = width; } - if (reader[17].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt(17, out var height)) { - item.Height = reader.GetInt32(17); + item.Height = height; } - if (reader[18].SQLiteType != SQLiteType.Null) + if (reader.TryGetFloat(18, out var averageFrameRate)) { - item.AverageFrameRate = reader.GetFloat(18); + item.AverageFrameRate = averageFrameRate; } - if (reader[19].SQLiteType != SQLiteType.Null) + if (reader.TryGetFloat(19, out var realFrameRate)) { - item.RealFrameRate = reader.GetFloat(19); + item.RealFrameRate = realFrameRate; } - if (reader[20].SQLiteType != SQLiteType.Null) + if (reader.TryGetFloat(20, out var level)) { - item.Level = reader.GetFloat(20); + item.Level = level; } - if (reader[21].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(21, out var pixelFormat)) { - item.PixelFormat = reader[21].ToString(); + item.PixelFormat = pixelFormat; } - if (reader[22].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt(22, out var bitDepth)) { - item.BitDepth = reader.GetInt32(22); + item.BitDepth = bitDepth; } - if (reader[23].SQLiteType != SQLiteType.Null) + if (reader.TryGetBoolean(23, out var isAnamorphic)) { - item.IsAnamorphic = reader.GetBoolean(23); + item.IsAnamorphic = isAnamorphic; } - if (reader[24].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt(24, out var refFrames)) { - item.RefFrames = reader.GetInt32(24); + item.RefFrames = refFrames; } - if (reader[25].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(25, out var codecTag)) { - item.CodecTag = reader.GetString(25); + item.CodecTag = codecTag; } - if (reader[26].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(26, out var comment)) { - item.Comment = reader.GetString(26); + item.Comment = comment; } - if (reader[27].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(27, out var nalLengthSize)) { - item.NalLengthSize = reader.GetString(27); + item.NalLengthSize = nalLengthSize; } - if (reader[28].SQLiteType != SQLiteType.Null) + if (reader.TryGetBoolean(28, out var isAVC)) { - item.IsAVC = reader[28].ToBool(); + item.IsAVC = isAVC; } - if (reader[29].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(29, out var title)) { - item.Title = reader[29].ToString(); + item.Title = title; } - if (reader[30].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(30, out var timeBase)) { - item.TimeBase = reader[30].ToString(); + item.TimeBase = timeBase; } - if (reader[31].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(31, out var codecTimeBase)) { - item.CodecTimeBase = reader[31].ToString(); + item.CodecTimeBase = codecTimeBase; } - if (reader[32].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(32, out var colorPrimaries)) { - item.ColorPrimaries = reader[32].ToString(); + item.ColorPrimaries = colorPrimaries; } - if (reader[33].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(33, out var colorSpace)) { - item.ColorSpace = reader[33].ToString(); + item.ColorSpace = colorSpace; } - if (reader[34].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(34, out var colorTransfer)) { - item.ColorTransfer = reader[34].ToString(); + item.ColorTransfer = colorTransfer; } if (item.Type == MediaStreamType.Subtitle) @@ -6357,29 +6246,29 @@ AND Type = @InternalPersonType)"); Index = reader[1].ToInt() }; - if (reader[2].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(2, out var codec)) { - item.Codec = reader[2].ToString(); + item.Codec = codec; } - if (reader[2].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(3, out var codecTag)) { - item.CodecTag = reader[3].ToString(); + item.CodecTag = codecTag; } - if (reader[4].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(4, out var comment)) { - item.Comment = reader[4].ToString(); + item.Comment = comment; } - if (reader[6].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(5, out var fileName)) { - item.FileName = reader[5].ToString(); + item.FileName = fileName; } - if (reader[6].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(6, out var mimeType)) { - item.MimeType = reader[6].ToString(); + item.MimeType = mimeType; } return item; diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 6574db607..479e5e846 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -355,9 +355,9 @@ namespace Emby.Server.Implementations.Data userData.Key = reader[0].ToString(); // userData.UserId = reader[1].ReadGuidFromBlob(); - if (reader[2].SQLiteType != SQLiteType.Null) + if (reader.TryGetDouble(2, out var rating)) { - userData.Rating = reader[2].ToDouble(); + userData.Rating = rating; } userData.Played = reader[3].ToBool(); @@ -365,19 +365,19 @@ namespace Emby.Server.Implementations.Data userData.IsFavorite = reader[5].ToBool(); userData.PlaybackPositionTicks = reader[6].ToInt64(); - if (reader[7].SQLiteType != SQLiteType.Null) + if (reader.TryReadDateTime(7, out var lastPlayedDate)) { - userData.LastPlayedDate = reader[7].TryReadDateTime(); + userData.LastPlayedDate = lastPlayedDate; } - if (reader[8].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt(8, out var audioStreamIndex)) { - userData.AudioStreamIndex = reader[8].ToInt(); + userData.AudioStreamIndex = audioStreamIndex; } - if (reader[9].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt(9, out var subtitleStreamIndex)) { - userData.SubtitleStreamIndex = reader[9].ToInt(); + userData.SubtitleStreamIndex = subtitleStreamIndex; } return userData; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 024404ceb..035c5df64 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -2,8 +2,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security auth = httpReq.Request.Headers[HeaderNames.Authorization]; } - return GetAuthorization(auth); + return GetAuthorization(auth.Count > 0 ? auth[0] : null); } /// @@ -232,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security auth = httpReq.Headers[HeaderNames.Authorization]; } - return GetAuthorization(auth); + return GetAuthorization(auth.Count > 0 ? auth[0] : null); } /// @@ -240,43 +240,43 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The authorization header. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorization(string authorizationHeader) + private Dictionary GetAuthorization(ReadOnlySpan authorizationHeader) { if (authorizationHeader == null) { return null; } - var parts = authorizationHeader.Split(' ', 2); + var firstSpace = authorizationHeader.IndexOf(' '); - // There should be at least to parts - if (parts.Length != 2) + // There should be at least two parts + if (firstSpace == -1) { return null; } - var acceptedNames = new[] { "MediaBrowser", "Emby" }; + var name = authorizationHeader[..firstSpace]; - // It has to be a digest request - if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase)) + if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase) + && name.Equals("Emby", StringComparison.OrdinalIgnoreCase)) { return null; } - // Remove uptil the first space - authorizationHeader = parts[1]; - parts = authorizationHeader.Split(','); + authorizationHeader = authorizationHeader[(firstSpace + 1)..]; var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var item in parts) + foreach (var item in authorizationHeader.Split(',')) { - var param = item.Trim().Split('=', 2); + var trimmedItem = item.Trim(); + var firstEqualsSign = trimmedItem.IndexOf('='); - if (param.Length == 2) + if (firstEqualsSign > 0) { - var value = NormalizeValue(param[1].Trim('"')); - result[param[0]] = value; + var key = trimmedItem[..firstEqualsSign].ToString(); + var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString()); + result[key] = value; } } diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 6e688693b..16050185f 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -165,13 +165,13 @@ namespace Emby.Server.Implementations.Library.Resolvers protected void SetVideoType(Video video, VideoFileInfo videoInfo) { - var extension = Path.GetExtension(video.Path); - video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) || - string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ? - VideoType.Iso : - VideoType.VideoFile; + var extension = Path.GetExtension(video.Path.AsSpan()); + video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".img", StringComparison.OrdinalIgnoreCase) + ? VideoType.Iso + : VideoType.VideoFile; - video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase); + video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase); video.IsPlaceHolder = videoInfo.IsStub; if (videoInfo.IsStub) @@ -193,11 +193,11 @@ namespace Emby.Server.Implementations.Library.Resolvers { if (video.VideoType == VideoType.Iso) { - if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1) + if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase)) { video.IsoType = IsoType.Dvd; } - else if (video.Path.IndexOf("bluray", StringComparison.OrdinalIgnoreCase) != -1) + else if (video.Path.Contains("bluray", StringComparison.OrdinalIgnoreCase)) { video.IsoType = IsoType.BluRay; } diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 4bc12f44a..ca7e74858 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -297,50 +297,49 @@ namespace Emby.Server.Implementations.Security AccessToken = reader[1].ToString() }; - if (reader[2].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(2, out var deviceId)) { - info.DeviceId = reader[2].ToString(); + info.DeviceId = deviceId; } - if (reader[3].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(3, out var appName)) { - info.AppName = reader[3].ToString(); + info.AppName = appName; } - if (reader[4].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(4, out var appVersion)) { - info.AppVersion = reader[4].ToString(); + info.AppVersion = appVersion; } - if (reader[5].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(6, out var userId)) { - info.DeviceName = reader[5].ToString(); + info.UserId = new Guid(userId); } - if (reader[6].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(7, out var userName)) { - info.UserId = new Guid(reader[6].ToString()); - } - - if (reader[7].SQLiteType != SQLiteType.Null) - { - info.UserName = reader[7].ToString(); + info.UserName = userName; } info.DateCreated = reader[8].ReadDateTime(); - if (reader[9].SQLiteType != SQLiteType.Null) + if (reader.TryReadDateTime(9, out var dateLastActivity)) { - info.DateLastActivity = reader[9].ReadDateTime(); + info.DateLastActivity = dateLastActivity.Value; } else { info.DateLastActivity = info.DateCreated; } - if (reader[10].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(10, out var customName)) { - info.DeviceName = reader[10].ToString(); + info.DeviceName = customName; + } + else if (reader.TryGetString(5, out var deviceName)) + { + info.DeviceName = deviceName; } return info; @@ -361,9 +360,9 @@ namespace Emby.Server.Implementations.Security foreach (var row in statement.ExecuteQuery()) { - if (row[0].SQLiteType != SQLiteType.Null) + if (row.TryGetString(0, out var customName)) { - result.CustomName = row[0].ToString(); + result.CustomName = customName; } } diff --git a/Jellyfin.sln b/Jellyfin.sln index 8626a4b1b..ffcec0439 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -81,6 +81,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -223,6 +225,14 @@ Global {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU + {50928738-D268-43A3-BACA-3513D9AA6B8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50928738-D268-43A3-BACA-3513D9AA6B8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50928738-D268-43A3-BACA-3513D9AA6B8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50928738-D268-43A3-BACA-3513D9AA6B8E}.Release|Any CPU.Build.0 = Release|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -240,6 +250,7 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/MediaBrowser.Common/Extensions/EnumerableExtensions.cs b/MediaBrowser.Common/Extensions/EnumerableExtensions.cs new file mode 100644 index 000000000..2b8a6c395 --- /dev/null +++ b/MediaBrowser.Common/Extensions/EnumerableExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Common.Extensions +{ + /// + /// Static extensions for the interface. + /// + public static class EnumerableExtensions + { + /// + /// Determines whether the value is contained in the source collection. + /// + /// An instance of the interface. + /// The value to look for in the collection. + /// The string comparison. + /// A value indicating whether the value is contained in the collection. + /// The source is null. + public static bool Contains(this IEnumerable source, ReadOnlySpan value, StringComparison stringComparison) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source is IList list) + { + int len = list.Count; + for (int i = 0; i < len; i++) + { + if (value.Equals(list[i], stringComparison)) + { + return true; + } + } + + return false; + } + + foreach (string element in source) + { + if (value.Equals(element, stringComparison)) + { + return true; + } + } + + return false; + } + } +} diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 7ad8c24e8..31475e22f 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -466,7 +466,7 @@ namespace MediaBrowser.LocalMetadata.Images return added; } - private bool AddImage(IEnumerable files, List images, string name, ImageType type) + private bool AddImage(List files, List images, string name, ImageType type) { var image = GetImage(files, name); @@ -484,9 +484,20 @@ namespace MediaBrowser.LocalMetadata.Images return false; } - private FileSystemMetadata? GetImage(IEnumerable files, string name) + private static FileSystemMetadata? GetImage(IReadOnlyList files, string name) { - return files.FirstOrDefault(i => !i.IsDirectory && string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase) && i.Length > 0); + for (var i = 0; i < files.Count; i++) + { + var file = files[i]; + if (!file.IsDirectory + && file.Length > 0 + && Path.GetFileNameWithoutExtension(file.FullName.AsSpan()).Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return file; + } + } + + return null; } } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index e9f999c6d..186c77747 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -15,17 +15,6 @@ namespace MediaBrowser.Providers.MediaInfo { private readonly ILocalizationManager _localization; - private static readonly HashSet SubtitleExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) - { - ".srt", - ".ssa", - ".ass", - ".sub", - ".smi", - ".sami", - ".vtt" - }; - public SubtitleResolver(ILocalizationManager localization) { _localization = localization; @@ -88,6 +77,115 @@ namespace MediaBrowser.Providers.MediaInfo return list; } + public void AddExternalSubtitleStreams( + List streams, + string videoPath, + int startIndex, + string[] files) + { + var videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoPath); + + foreach (var fullName in files) + { + var extension = Path.GetExtension(fullName.AsSpan()); + if (!IsSubtitleExtension(extension)) + { + continue; + } + + var fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fullName); + + MediaStream mediaStream; + + // The subtitle filename must either be equal to the video filename or start with the video filename followed by a dot + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + mediaStream = new MediaStream + { + Index = startIndex++, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = fullName + }; + } + else if (fileNameWithoutExtension.Length >= videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + var isForced = fullName.Contains(".forced.", StringComparison.OrdinalIgnoreCase) + || fullName.Contains(".foreign.", StringComparison.OrdinalIgnoreCase); + + var isDefault = fullName.Contains(".default.", StringComparison.OrdinalIgnoreCase); + + // Support xbmc naming conventions - 300.spanish.srt + var languageSpan = fileNameWithoutExtension; + while (languageSpan.Length > 0) + { + var lastDot = languageSpan.LastIndexOf('.'); + var currentSlice = languageSpan[lastDot..]; + if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase) + || currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase) + || currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase)) + { + languageSpan = languageSpan[..lastDot]; + continue; + } + + languageSpan = languageSpan[(lastDot + 1)..]; + break; + } + + var language = languageSpan.ToString(); + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var culture = _localization.FindLanguageInfo(language); + + if (culture != null) + { + language = culture.ThreeLetterISOLanguageName; + } + + mediaStream = new MediaStream + { + Index = startIndex++, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = fullName, + Language = language, + IsForced = isForced, + IsDefault = isDefault + }; + } + else + { + continue; + } + + mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); + + streams.Add(mediaStream); + } + } + + private static bool IsSubtitleExtension(ReadOnlySpan extension) + { + return extension.Equals(".srt", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".ssa", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".ass", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".sub", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".vtt", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".smi", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".sami", StringComparison.OrdinalIgnoreCase); + } + + private static ReadOnlySpan NormalizeFilenameForSubtitleComparison(string filename) + { + // Try to account for sloppy file naming + filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); + filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); + return Path.GetFileNameWithoutExtension(filename.AsSpan()); + } + private void AddExternalSubtitleStreams( List streams, string folder, @@ -100,104 +198,5 @@ namespace MediaBrowser.Providers.MediaInfo AddExternalSubtitleStreams(streams, videoPath, startIndex, files); } - - public void AddExternalSubtitleStreams( - List streams, - string videoPath, - int startIndex, - string[] files) - { - var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoPath); - videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoFileNameWithoutExtension); - - foreach (var fullName in files) - { - var extension = Path.GetExtension(fullName); - - if (!SubtitleExtensions.Contains(extension)) - { - continue; - } - - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); - fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fileNameWithoutExtension); - - if (!string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) && - !fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var codec = Path.GetExtension(fullName).ToLowerInvariant().TrimStart('.'); - - if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase)) - { - codec = "srt"; - } - - // If the subtitle file matches the video file name - if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) - { - streams.Add(new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName, - Codec = codec - }); - } - else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) - { - var isForced = fullName.IndexOf(".forced.", StringComparison.OrdinalIgnoreCase) != -1 || - fullName.IndexOf(".foreign.", StringComparison.OrdinalIgnoreCase) != -1; - - var isDefault = fullName.IndexOf(".default.", StringComparison.OrdinalIgnoreCase) != -1; - - // Support xbmc naming conventions - 300.spanish.srt - var language = fileNameWithoutExtension - .Replace(".forced", string.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(".foreign", string.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(".default", string.Empty, StringComparison.OrdinalIgnoreCase) - .Split('.') - .LastOrDefault(); - - // Try to translate to three character code - // Be flexible and check against both the full and three character versions - var culture = _localization.FindLanguageInfo(language); - - if (culture != null) - { - language = culture.ThreeLetterISOLanguageName; - } - - streams.Add(new MediaStream - { - Index = startIndex++, - Type = MediaStreamType.Subtitle, - IsExternal = true, - Path = fullName, - Codec = codec, - Language = language, - IsForced = isForced, - IsDefault = isDefault - }); - } - } - } - - private string NormalizeFilenameForSubtitleComparison(string filename) - { - // Try to account for sloppy file naming - filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); - filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); - - // can't normalize this due to languages such as pt-br - // filename = filename.Replace("-", string.Empty); - - // filename = filename.Replace(".", string.Empty); - - return filename; - } } } diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj new file mode 100644 index 000000000..670289f37 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -0,0 +1,37 @@ + + + + net5.0 + false + true + enable + AllEnabledByDefault + ../jellyfin-tests.ruleset + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs new file mode 100644 index 000000000..80b0bc370 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs @@ -0,0 +1,96 @@ +#pragma warning disable CA1002 // Do not expose generic lists + +using System.Collections.Generic; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Providers.MediaInfo; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo +{ + public class SubtitleResolverTests + { + public static IEnumerable AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData() + { + var index = 0; + yield return new object[] + { + new List(), + "/video/My.Video.mkv", + index, + new[] + { + "/video/My.Video.mp3", + "/video/My.Video.png", + "/video/My.Video.srt", + "/video/My.Video.txt", + "/video/My.Video.vtt", + "/video/My.Video.ass", + "/video/My.Video.sub", + "/video/My.Video.ssa", + "/video/My.Video.smi", + "/video/My.Video.sami", + "/video/My.Video.en.srt", + "/video/My.Video.default.en.srt", + "/video/My.Video.default.forced.en.srt", + "/video/My.Video.en.default.forced.srt", + "/video/My.Video.With.Additional.Garbage.en.srt", + "/video/My.Video With Additional Garbage.srt" + }, + new List + { + CreateMediaStream("/video/My.Video.srt", "srt", null, index++), + CreateMediaStream("/video/My.Video.vtt", "vtt", null, index++), + CreateMediaStream("/video/My.Video.ass", "ass", null, index++), + CreateMediaStream("/video/My.Video.sub", "sub", null, index++), + CreateMediaStream("/video/My.Video.ssa", "ssa", null, index++), + CreateMediaStream("/video/My.Video.smi", "smi", null, index++), + CreateMediaStream("/video/My.Video.sami", "sami", null, index++), + CreateMediaStream("/video/My.Video.en.srt", "srt", "en", index++), + CreateMediaStream("/video/My.Video.default.en.srt", "srt", "en", index++, isDefault: true), + CreateMediaStream("/video/My.Video.default.forced.en.srt", "srt", "en", index++, isForced: true, isDefault: true), + CreateMediaStream("/video/My.Video.en.default.forced.srt", "srt", "en", index++, isForced: true, isDefault: true), + CreateMediaStream("/video/My.Video.With.Additional.Garbage.en.srt", "srt", "en", index), + } + }; + } + + [Theory] + [MemberData(nameof(AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData))] + public void AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles(List streams, string videoPath, int startIndex, string[] files, List expectedResult) + { + new SubtitleResolver(Mock.Of()).AddExternalSubtitleStreams(streams, videoPath, startIndex, files); + + Assert.Equal(expectedResult.Count, streams.Count); + for (var i = 0; i < expectedResult.Count; i++) + { + var expected = expectedResult[i]; + var actual = streams[i]; + + Assert.Equal(expected.Index, actual.Index); + Assert.Equal(expected.Type, actual.Type); + Assert.Equal(expected.IsExternal, actual.IsExternal); + Assert.Equal(expected.Path, actual.Path); + Assert.Equal(expected.IsDefault, actual.IsDefault); + Assert.Equal(expected.IsForced, actual.IsForced); + Assert.Equal(expected.Language, actual.Language); + } + } + + private static MediaStream CreateMediaStream(string path, string codec, string? language, int index, bool isForced = false, bool isDefault = false) + { + return new () + { + Index = index, + Codec = codec, + Type = MediaStreamType.Subtitle, + IsExternal = true, + Path = path, + IsDefault = isDefault, + IsForced = isForced, + Language = language + }; + } + } +} From eaafbdd623feed3125c2ce7e3c44ec7b26b42a81 Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 16 May 2021 14:52:56 +0200 Subject: [PATCH 228/612] Remove some leftover junk from a project that isn't in the sln --- Jellyfin.sln | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Jellyfin.sln b/Jellyfin.sln index ffcec0439..9fbd9d266 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -225,10 +225,6 @@ Global {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU - {50928738-D268-43A3-BACA-3513D9AA6B8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {50928738-D268-43A3-BACA-3513D9AA6B8E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {50928738-D268-43A3-BACA-3513D9AA6B8E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {50928738-D268-43A3-BACA-3513D9AA6B8E}.Release|Any CPU.Build.0 = Release|Any CPU {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU From 415e8fc0f3b7f214fbcbdc8fa39a863b06690243 Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 16 May 2021 14:54:36 +0200 Subject: [PATCH 229/612] Forward --- Emby.Naming/Emby.Naming.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index a93ddbcb8..3224ff412 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -23,12 +23,12 @@ - + - - + + From 652909e8a57075866e56c270852e34714bf06c06 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 16 May 2021 15:27:31 +0200 Subject: [PATCH 230/612] Update Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs Co-authored-by: Cody Robibero --- .../HttpServer/Security/AuthorizationContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 035c5df64..fbf9254d1 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.HttpServer.Security var name = authorizationHeader[..firstSpace]; if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase) - && name.Equals("Emby", StringComparison.OrdinalIgnoreCase)) + && !name.Equals("Emby", StringComparison.OrdinalIgnoreCase)) { return null; } From be4aeb5c2cb7fa6e05810aa1d0c935b5166fb64a Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 16 May 2021 19:05:56 +0200 Subject: [PATCH 231/612] Rename SQL extension methods --- .../Data/SqliteExtensions.cs | 8 ++-- .../Data/SqliteItemRepository.cs | 44 +++++++++---------- .../Data/SqliteUserDataRepository.cs | 4 +- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index db3010207..51a53c6cb 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -171,7 +171,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetInt(this IReadOnlyList reader, int index, out int? result) + public static bool TryGetInt32(this IReadOnlyList reader, int index, [NotNullWhen(true)] out int? result) { result = null; var item = reader[index]; @@ -189,7 +189,7 @@ namespace Emby.Server.Implementations.Data return result[index].ToInt64(); } - public static bool TryGetLong(this IReadOnlyList reader, int index, out long? result) + public static bool TryGetInt64(this IReadOnlyList reader, int index, [NotNullWhen(true)] out long? result) { result = null; var item = reader[index]; @@ -202,7 +202,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetFloat(this IReadOnlyList reader, int index, out float? result) + public static bool TryGetSingle(this IReadOnlyList reader, int index, [NotNullWhen(true)] out float? result) { result = null; var item = reader[index]; @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetDouble(this IReadOnlyList reader, int index, out double? result) + public static bool TryGetDouble(this IReadOnlyList reader, int index, [NotNullWhen(true)] out double? result) { result = null; var item = reader[index]; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 53c9a4cad..996cdf133 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1390,7 +1390,7 @@ namespace Emby.Server.Implementations.Data } } - if (reader.TryGetFloat(index++, out var communityRating)) + if (reader.TryGetSingle(index++, out var communityRating)) { item.CommunityRating = communityRating; } @@ -1403,7 +1403,7 @@ namespace Emby.Server.Implementations.Data } } - if (reader.TryGetInt(index++, out var indexNumber)) + if (reader.TryGetInt32(index++, out var indexNumber)) { item.IndexNumber = indexNumber; } @@ -1428,7 +1428,7 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.Width)) { - if (reader.TryGetInt(index++, out var width)) + if (reader.TryGetInt32(index++, out var width)) { item.Width = width.Value; } @@ -1436,7 +1436,7 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.Height)) { - if (reader.TryGetInt(index++, out var height)) + if (reader.TryGetInt32(index++, out var height)) { item.Height = height.Value; } @@ -1473,12 +1473,12 @@ namespace Emby.Server.Implementations.Data } } - if (reader.TryGetInt(index++, out var parentIndexNumber)) + if (reader.TryGetInt32(index++, out var parentIndexNumber)) { item.ParentIndexNumber = parentIndexNumber; } - if (reader.TryGetInt(index++, out var productionYear)) + if (reader.TryGetInt32(index++, out var productionYear)) { item.ProductionYear = productionYear; } @@ -1496,12 +1496,12 @@ namespace Emby.Server.Implementations.Data } } - if (reader.TryGetLong(index++, out var runTimeTicks)) + if (reader.TryGetInt64(index++, out var runTimeTicks)) { item.RunTimeTicks = runTimeTicks; } - if (reader.TryGetLong(index++, out var size)) + if (reader.TryGetInt64(index++, out var size)) { item.Size = size; } @@ -1662,7 +1662,7 @@ namespace Emby.Server.Implementations.Data item.Album = album; } - if (reader.TryGetFloat(index++, out var criticRating)) + if (reader.TryGetSingle(index++, out var criticRating)) { item.CriticRating = criticRating; } @@ -1729,7 +1729,7 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.InheritedParentalRatingValue)) { - if (reader.TryGetInt(index++, out var parentalRating)) + if (reader.TryGetInt32(index++, out var parentalRating)) { item.InheritedParentalRatingValue = parentalRating.Value; } @@ -1784,7 +1784,7 @@ namespace Emby.Server.Implementations.Data } } - if (reader.TryGetInt(index++, out var totalBitrate)) + if (reader.TryGetInt32(index++, out var totalBitrate)) { item.TotalBitrate = totalBitrate; } @@ -5749,7 +5749,7 @@ AND Type = @InternalPersonType)"); item.Type = type; } - if (reader.TryGetInt(4, out var sortOrder)) + if (reader.TryGetInt32(4, out var sortOrder)) { item.SortOrder = sortOrder; } @@ -5979,17 +5979,17 @@ AND Type = @InternalPersonType)"); item.IsInterlaced = reader.GetBoolean(9); - if (reader.TryGetInt(10, out var bitrate)) + if (reader.TryGetInt32(10, out var bitrate)) { item.BitRate = bitrate; } - if (reader.TryGetInt(11, out var channels)) + if (reader.TryGetInt32(11, out var channels)) { item.Channels = channels; } - if (reader.TryGetInt(12, out var sampleRate)) + if (reader.TryGetInt32(12, out var sampleRate)) { item.SampleRate = sampleRate; } @@ -5998,27 +5998,27 @@ AND Type = @InternalPersonType)"); item.IsForced = reader.GetBoolean(14); item.IsExternal = reader.GetBoolean(15); - if (reader.TryGetInt(16, out var width)) + if (reader.TryGetInt32(16, out var width)) { item.Width = width; } - if (reader.TryGetInt(17, out var height)) + if (reader.TryGetInt32(17, out var height)) { item.Height = height; } - if (reader.TryGetFloat(18, out var averageFrameRate)) + if (reader.TryGetSingle(18, out var averageFrameRate)) { item.AverageFrameRate = averageFrameRate; } - if (reader.TryGetFloat(19, out var realFrameRate)) + if (reader.TryGetSingle(19, out var realFrameRate)) { item.RealFrameRate = realFrameRate; } - if (reader.TryGetFloat(20, out var level)) + if (reader.TryGetSingle(20, out var level)) { item.Level = level; } @@ -6028,7 +6028,7 @@ AND Type = @InternalPersonType)"); item.PixelFormat = pixelFormat; } - if (reader.TryGetInt(22, out var bitDepth)) + if (reader.TryGetInt32(22, out var bitDepth)) { item.BitDepth = bitDepth; } @@ -6038,7 +6038,7 @@ AND Type = @InternalPersonType)"); item.IsAnamorphic = isAnamorphic; } - if (reader.TryGetInt(24, out var refFrames)) + if (reader.TryGetInt32(24, out var refFrames)) { item.RefFrames = refFrames; } diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 479e5e846..e0ebd0a6c 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -370,12 +370,12 @@ namespace Emby.Server.Implementations.Data userData.LastPlayedDate = lastPlayedDate; } - if (reader.TryGetInt(8, out var audioStreamIndex)) + if (reader.TryGetInt32(8, out var audioStreamIndex)) { userData.AudioStreamIndex = audioStreamIndex; } - if (reader.TryGetInt(9, out var subtitleStreamIndex)) + if (reader.TryGetInt32(9, out var subtitleStreamIndex)) { userData.SubtitleStreamIndex = subtitleStreamIndex; } From 81ac11828bbf9310a507af034b0d749323db3688 Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 16 May 2021 20:16:47 +0200 Subject: [PATCH 232/612] Fix ArrayIndexOutOfBounds --- MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index 186c77747..b086ef07b 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -108,7 +108,7 @@ namespace MediaBrowser.Providers.MediaInfo Path = fullName }; } - else if (fileNameWithoutExtension.Length >= videoFileNameWithoutExtension.Length + else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { From 96e05e5b669e0d275b98bebfd9dd422dcc8f7103 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Mon, 17 May 2021 12:55:27 +0200 Subject: [PATCH 233/612] Add tests for NoralizeLanguage --- Jellyfin.sln | 7 +++++ .../Tmdb/People/TmdbPersonImageProvider.cs | 2 +- .../Plugins/Tmdb/TmdbUtils.cs | 2 +- .../Jellyfin.Providers.Tests.csproj | 30 +++++++++++++++++++ .../Tmdb/TmdbUtilsTests.cs | 27 +++++++++++++++++ 5 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj create mode 100644 tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs diff --git a/Jellyfin.sln b/Jellyfin.sln index 8626a4b1b..bfb43a23d 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -81,6 +81,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{CE21845F-74AD-42F6-B7BA-90506E6CD09E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -223,6 +225,10 @@ Global {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU + {CE21845F-74AD-42F6-B7BA-90506E6CD09E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE21845F-74AD-42F6-B7BA-90506E6CD09E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE21845F-74AD-42F6-B7BA-90506E6CD09E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE21845F-74AD-42F6-B7BA-90506E6CD09E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -240,6 +246,7 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {CE21845F-74AD-42F6-B7BA-90506E6CD09E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index d523d0315..99a43966f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -47,8 +47,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { - var language = item.GetPreferredMetadataLanguage(); var person = (Person)item; + var language = item.GetPreferredMetadataLanguage(); if (!person.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId)) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 7c64035fe..b713736a0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb if (parts.Length == 2) { // TMDB doesn't support Switzerland (de-CH, it-CH or fr-CH) so use the language (de, it or fr) without country code - if (string.Equals(parts[1], "ch", StringComparison.Ordinal)) + if (string.Equals(parts[1], "CH", StringComparison.OrdinalIgnoreCase)) { return parts[0]; } diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj new file mode 100644 index 000000000..2d8174b88 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -0,0 +1,30 @@ + + + + net5.0 + false + true + enable + AllEnabledByDefault + ../jellyfin-tests.ruleset + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs new file mode 100644 index 000000000..f6a7c676f --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs @@ -0,0 +1,27 @@ +using MediaBrowser.Providers.Plugins.Tmdb; +using Xunit; + +namespace Jellyfin.Providers.Tests.Tmdb +{ + public static class TmdbUtilsTests + { + [Theory] + [InlineData("de", "de")] + [InlineData("En", "En")] + [InlineData("de-de", "de-DE")] + [InlineData("en-US", "en-US")] + [InlineData("de-CH", "de")] + public static void NormalizeLanguage_Valid_Success(string input, string expected) + { + Assert.Equal(expected, TmdbUtils.NormalizeLanguage(input)); + } + + [Theory] + [InlineData(null, null)] + [InlineData("", "")] + public static void NormalizeLanguage_Invalid_Equal(string? input, string? expected) + { + Assert.Equal(expected, TmdbUtils.NormalizeLanguage(input!)); + } + } +} From 0cb69e28b906947c2daf9dfd7b5e213bea9e05b9 Mon Sep 17 00:00:00 2001 From: siankatabg Date: Sun, 16 May 2021 19:23:07 +0000 Subject: [PATCH 234/612] Translated using Weblate (Bulgarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/bg/ --- Emby.Server.Implementations/Localization/Core/bg-BG.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index 9db3b50d9..bc25531d3 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -39,7 +39,7 @@ "MixedContent": "Смесено съдържание", "Movies": "Филми", "Music": "Музика", - "MusicVideos": "Музикални клипове", + "MusicVideos": "Музикални видеа", "NameInstallFailed": "{0} не можа да се инсталира", "NameSeasonNumber": "Сезон {0}", "NameSeasonUnknown": "Неразпознат сезон", @@ -62,7 +62,7 @@ "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно", "Photos": "Снимки", "Playlists": "Списъци", - "Plugin": "Приставка", + "Plugin": "Добавка", "PluginInstalledWithName": "{0} е инсталиранa", "PluginUninstalledWithName": "{0} е деинсталиранa", "PluginUpdatedWithName": "{0} е обновенa", @@ -116,5 +116,7 @@ "TasksMaintenanceCategory": "Поддръжка", "Undefined": "Неопределено", "Forced": "Принудително", - "Default": "По подразбиране" + "Default": "По подразбиране", + "TaskCleanActivityLogDescription": "Изтрива записите в дневника с активност по стари от конфигурираната възраст.", + "TaskCleanActivityLog": "Изчисти дневника с активност" } From 48bb3383521f8cfea968981d3241ed6d355b89cc Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Mon, 17 May 2021 23:34:50 +0100 Subject: [PATCH 235/612] Enable child items to be returned if a musicAlbum --- MediaBrowser.Controller/Entities/Folder.cs | 25 ++++++++++++++++--- .../Entities/InternalItemsQuery.cs | 5 ++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index cac5026f7..c907e09eb 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -939,7 +939,13 @@ namespace MediaBrowser.Controller.Entities } else { - items = GetChildren(user, true).Where(filter); + // need to pass this param to the children. + var childQuery = new InternalItemsQuery + { + DisplayAlbumFolders = query.DisplayAlbumFolders + }; + + items = GetChildren(user, true, childQuery).Where(filter); } return PostFilterAndSort(items, query, true); @@ -1275,10 +1281,23 @@ namespace MediaBrowser.Controller.Entities /// /// Adds the children to list. /// - /// true if XXXX, false otherwise private void AddChildren(User user, bool includeLinkedChildren, Dictionary result, bool recursive, InternalItemsQuery query) { - foreach (var child in GetEligibleChildrenForRecursiveChildren(user)) + // If Query.AlbumFolders is set, then enforce the format as per the db in that it permits sub-folders in music albums. + IEnumerable children = null; + if ((query?.DisplayAlbumFolders ?? false) && (this is MusicAlbum)) + { + children = Children; + query = null; + } + + // If there are not sub-folders, proceed as normal. + if (children == null) + { + children = GetEligibleChildrenForRecursiveChildren(user); + } + + foreach (var child in children) { bool? isVisibleToUser = null; diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 270217356..d2716117b 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -265,6 +265,11 @@ namespace MediaBrowser.Controller.Entities public bool? IsDeadPerson { get; set; } + /// + /// Gets or sets a value indicating whether album sub-folders should be returned if they exist. + /// + public bool? DisplayAlbumFolders { get; set; } + public InternalItemsQuery() { AlbumArtistIds = Array.Empty(); From be17028e22c6fbb1ce71a8aa9ece9b8ebc9aac3d Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 17 May 2021 18:34:44 -0600 Subject: [PATCH 236/612] Remove erroneous newline --- deployment/Dockerfile.debian.amd64 | 3 +-- deployment/Dockerfile.debian.arm64 | 3 +-- deployment/Dockerfile.debian.armhf | 3 +-- deployment/Dockerfile.linux.amd64 | 3 +-- deployment/Dockerfile.linux.amd64-musl | 3 +-- deployment/Dockerfile.linux.arm64 | 3 +-- deployment/Dockerfile.linux.armhf | 3 +-- deployment/Dockerfile.macos | 3 +-- deployment/Dockerfile.portable | 3 +-- deployment/Dockerfile.ubuntu.amd64 | 3 +-- deployment/Dockerfile.ubuntu.arm64 | 3 +-- deployment/Dockerfile.ubuntu.armhf | 3 +-- deployment/Dockerfile.windows.amd64 | 3 +-- 13 files changed, 13 insertions(+), 26 deletions(-) diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64 index 99adf7e1c..092364500 100644 --- a/deployment/Dockerfile.debian.amd64 +++ b/deployment/Dockerfile.debian.amd64 @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64 index 4e10a12f2..eef272d5b 100644 --- a/deployment/Dockerfile.debian.arm64 +++ b/deployment/Dockerfile.debian.arm64 @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf index 17cd01d3e..154481d64 100644 --- a/deployment/Dockerfile.debian.armhf +++ b/deployment/Dockerfile.debian.armhf @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64 index 4e5d6486f..171ebe372 100644 --- a/deployment/Dockerfile.linux.amd64 +++ b/deployment/Dockerfile.linux.amd64 @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl index 3dbe00a58..a0a5f6923 100644 --- a/deployment/Dockerfile.linux.amd64-musl +++ b/deployment/Dockerfile.linux.amd64-musl @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64 index 0e17c0c4a..ae59802cd 100644 --- a/deployment/Dockerfile.linux.arm64 +++ b/deployment/Dockerfile.linux.arm64 @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf index 7df4e51b5..236b35ce5 100644 --- a/deployment/Dockerfile.linux.armhf +++ b/deployment/Dockerfile.linux.armhf @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos index e3479ae9c..2e80d4a6e 100644 --- a/deployment/Dockerfile.macos +++ b/deployment/Dockerfile.macos @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index f1774839a..852a6b553 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -15,8 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index f723c4cdd..bffeb7307 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index a810844c0..e90da4636 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index 3838bab82..aae262ee6 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,8 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64 index 007d2648b..c847b1621 100644 --- a/deployment/Dockerfile.windows.amd64 +++ b/deployment/Dockerfile.windows.amd64 @@ -15,8 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz - -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet From 7a17de84d9eac292547db5e78516f692f48f97fd Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 17 May 2021 21:35:58 -0400 Subject: [PATCH 237/612] Add optional to nextUpDateCutoff help text --- Jellyfin.Api/Controllers/TvShowsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 5e90e37f3..dc2f7d191 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Optional. Include user data. - /// Starting date of shows to show in Next Up section. + /// Optional. Starting date of shows to show in Next Up section. /// Whether to enable the total records count. Defaults to true. /// Whether to disable sending the first episode in a series as next up. /// A with the next up episodes. From 2b321d8b89bda1da80f6630a05f7d87fc6c747dd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 18 May 2021 12:23:26 +0200 Subject: [PATCH 238/612] Enable nullable for InternalItemsQuery --- .../Data/SqliteItemRepository.cs | 4 +- .../Entities/InternalItemsQuery.cs | 87 +++++++++---------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 835eb0692..4ae7a842c 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -4427,7 +4427,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(string.Join(" AND ", excludeIds)); } - if (query.ExcludeProviderIds.Count > 0) + if (query.ExcludeProviderIds != null && query.ExcludeProviderIds.Count > 0) { var excludeIds = new List(); @@ -4457,7 +4457,7 @@ namespace Emby.Server.Implementations.Data } } - if (query.HasAnyProviderId.Count > 0) + if (query.HasAnyProviderId != null && query.HasAnyProviderId.Count > 0) { var hasProviderIds = new List(); diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index c06021029..75fea365b 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -20,9 +18,9 @@ namespace MediaBrowser.Controller.Entities public int? Limit { get; set; } - public User User { get; set; } + public User? User { get; set; } - public BaseItem SimilarTo { get; set; } + public BaseItem? SimilarTo { get; set; } public bool? IsFolder { get; set; } @@ -58,23 +56,23 @@ namespace MediaBrowser.Controller.Entities public bool? CollapseBoxSetItems { get; set; } - public string NameStartsWithOrGreater { get; set; } + public string? NameStartsWithOrGreater { get; set; } - public string NameStartsWith { get; set; } + public string? NameStartsWith { get; set; } - public string NameLessThan { get; set; } + public string? NameLessThan { get; set; } - public string NameContains { get; set; } + public string? NameContains { get; set; } - public string MinSortName { get; set; } + public string? MinSortName { get; set; } - public string PresentationUniqueKey { get; set; } + public string? PresentationUniqueKey { get; set; } - public string Path { get; set; } + public string? Path { get; set; } - public string Name { get; set; } + public string? Name { get; set; } - public string Person { get; set; } + public string? Person { get; set; } public Guid[] PersonIds { get; set; } @@ -82,7 +80,7 @@ namespace MediaBrowser.Controller.Entities public Guid[] ExcludeItemIds { get; set; } - public string AdjacentTo { get; set; } + public string? AdjacentTo { get; set; } public string[] PersonTypes { get; set; } @@ -182,13 +180,13 @@ namespace MediaBrowser.Controller.Entities public Guid ParentId { get; set; } - public string ParentType { get; set; } + public string? ParentType { get; set; } public Guid[] AncestorIds { get; set; } public Guid[] TopParentIds { get; set; } - public BaseItem Parent + public BaseItem? Parent { set { @@ -213,9 +211,9 @@ namespace MediaBrowser.Controller.Entities public SeriesStatus[] SeriesStatuses { get; set; } - public string ExternalSeriesId { get; set; } + public string? ExternalSeriesId { get; set; } - public string ExternalId { get; set; } + public string? ExternalId { get; set; } public Guid[] AlbumIds { get; set; } @@ -223,9 +221,9 @@ namespace MediaBrowser.Controller.Entities public Guid[] ExcludeArtistIds { get; set; } - public string AncestorWithPresentationUniqueKey { get; set; } + public string? AncestorWithPresentationUniqueKey { get; set; } - public string SeriesPresentationUniqueKey { get; set; } + public string? SeriesPresentationUniqueKey { get; set; } public bool GroupByPresentationUniqueKey { get; set; } @@ -235,7 +233,7 @@ namespace MediaBrowser.Controller.Entities public bool ForceDirect { get; set; } - public Dictionary ExcludeProviderIds { get; set; } + public Dictionary? ExcludeProviderIds { get; set; } public bool EnableGroupByMetadataKey { get; set; } @@ -253,13 +251,13 @@ namespace MediaBrowser.Controller.Entities public int MinSimilarityScore { get; set; } - public string HasNoAudioTrackWithLanguage { get; set; } + public string? HasNoAudioTrackWithLanguage { get; set; } - public string HasNoInternalSubtitleTrackWithLanguage { get; set; } + public string? HasNoInternalSubtitleTrackWithLanguage { get; set; } - public string HasNoExternalSubtitleTrackWithLanguage { get; set; } + public string? HasNoExternalSubtitleTrackWithLanguage { get; set; } - public string HasNoSubtitleTrackWithLanguage { get; set; } + public string? HasNoSubtitleTrackWithLanguage { get; set; } public bool? IsDeadArtist { get; set; } @@ -283,12 +281,10 @@ namespace MediaBrowser.Controller.Entities ExcludeInheritedTags = Array.Empty(); ExcludeItemIds = Array.Empty(); ExcludeItemTypes = Array.Empty(); - ExcludeProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); ExcludeTags = Array.Empty(); GenreIds = Array.Empty(); Genres = Array.Empty(); GroupByPresentationUniqueKey = true; - HasAnyProviderId = new Dictionary(StringComparer.OrdinalIgnoreCase); ImageTypes = Array.Empty(); IncludeItemTypes = Array.Empty(); ItemIds = Array.Empty(); @@ -309,32 +305,33 @@ namespace MediaBrowser.Controller.Entities Years = Array.Empty(); } - public InternalItemsQuery(User user) + public InternalItemsQuery(User? user) : this() { - SetUser(user); + if (user != null) + { + SetUser(user); + } } public void SetUser(User user) { - if (user != null) + MaxParentalRating = user.MaxParentalAgeRating; + + if (MaxParentalRating.HasValue) { - MaxParentalRating = user.MaxParentalAgeRating; - - if (MaxParentalRating.HasValue) - { - BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) - .Where(i => i != UnratedItem.Other.ToString()) - .Select(e => Enum.Parse(e, true)).ToArray(); - } - - ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags); - - User = user; + string other = UnratedItem.Other.ToString(); + BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) + .Where(i => i != other) + .Select(e => Enum.Parse(e, true)).ToArray(); } + + ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags); + + User = user; } - public Dictionary HasAnyProviderId { get; set; } + public Dictionary? HasAnyProviderId { get; set; } public Guid[] AlbumArtistIds { get; set; } @@ -356,8 +353,8 @@ namespace MediaBrowser.Controller.Entities public int? MinWidth { get; set; } - public string SearchTerm { get; set; } + public string? SearchTerm { get; set; } - public string SeriesTimerId { get; set; } + public string? SeriesTimerId { get; set; } } } From 8407c3d558b33ea56c17d8a6bce81757eba805d9 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Tue, 18 May 2021 12:37:00 +0200 Subject: [PATCH 239/612] Properly detect Dolby Vision files derived from AV1, AVC and HEVC --- MediaBrowser.Model/Entities/MediaStream.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index e644c9ba7..c67f30d04 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -104,6 +104,19 @@ namespace MediaBrowser.Model.Entities return "HDR"; } + // For some Dolby Vision files, no color transfer is provided, so check the codec + + var codecTag = CodecTag; + + if (string.Equals(codecTag, "dva1", StringComparison.OrdinalIgnoreCase) + || string.Equals(codecTag, "dvav", StringComparison.OrdinalIgnoreCase) + || string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase) + || string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase) + || string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase)) + { + return "HDR"; + } + return "SDR"; } } From 6353966abdc31a7806c6d4d62154cf3da7c86adf Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Tue, 18 May 2021 13:17:34 +0200 Subject: [PATCH 240/612] Fix cache key --- MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 125b1b604..79ec6139d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -281,7 +281,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// The TMDb person information or null if not found. public async Task GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken) { - var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}"; + var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; if (_memoryCache.TryGetValue(key, out Person person)) { return person; From ee7a95e088520f835a4d179c7c5faec5eaa8f9ff Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Tue, 18 May 2021 13:44:38 +0200 Subject: [PATCH 241/612] Move GetMetadataLanguage --- .../Plugins/Tmdb/People/TmdbPersonImageProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index 99a43966f..e4c908a62 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -48,13 +48,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { var person = (Person)item; - var language = item.GetPreferredMetadataLanguage(); if (!person.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId)) { return Enumerable.Empty(); } + var language = item.GetPreferredMetadataLanguage(); var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), language, cancellationToken).ConfigureAwait(false); if (personResult?.Images?.Profiles == null) { From 1027792b16996e3fe7e4835805204e8c91beda01 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 19 May 2021 08:51:46 +0200 Subject: [PATCH 242/612] Review changes --- Emby.Naming/Video/VideoResolver.cs | 4 ++-- .../Data/SqliteExtensions.cs | 22 +++++++++---------- .../Data/SqliteItemRepository.cs | 21 +++++++++--------- .../Video/VideoResolverTests.cs | 6 ++++- .../MediaInfo/SubtitleResolverTests.cs | 8 +++---- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index d1c294f4f..27e73208c 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -59,7 +59,7 @@ namespace Emby.Naming.Video } bool isStub = false; - ReadOnlySpan container = null; + ReadOnlySpan container = ReadOnlySpan.Empty; string? stubType = null; if (!isDirectory) @@ -105,7 +105,7 @@ namespace Emby.Naming.Video return new VideoFileInfo( path: path, - container: container.ToString(), + container: container.IsEmpty ? null : container.ToString(), isStub: isStub, name: name, year: year, diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index 51a53c6cb..3499713e1 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.Data return true; } - private static bool IsDbNull(this IResultSetValue result) + public static bool IsDbNull(this IResultSetValue result) { return result.SQLiteType == SQLiteType.Null; } @@ -158,12 +158,12 @@ namespace Emby.Server.Implementations.Data return result[index].ToBool(); } - public static bool TryGetBoolean(this IReadOnlyList reader, int index, [NotNullWhen(true)] out bool? result) + public static bool TryGetBoolean(this IReadOnlyList reader, int index, [NotNullWhen(true)] out bool result) { - result = null; var item = reader[index]; if (item.IsDbNull()) { + result = default; return false; } @@ -171,12 +171,12 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetInt32(this IReadOnlyList reader, int index, [NotNullWhen(true)] out int? result) + public static bool TryGetInt32(this IReadOnlyList reader, int index, [NotNullWhen(true)] out int result) { - result = null; var item = reader[index]; if (item.IsDbNull()) { + result = default; return false; } @@ -189,12 +189,12 @@ namespace Emby.Server.Implementations.Data return result[index].ToInt64(); } - public static bool TryGetInt64(this IReadOnlyList reader, int index, [NotNullWhen(true)] out long? result) + public static bool TryGetInt64(this IReadOnlyList reader, int index, [NotNullWhen(true)] out long result) { - result = null; var item = reader[index]; if (item.IsDbNull()) { + result = default; return false; } @@ -202,12 +202,12 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetSingle(this IReadOnlyList reader, int index, [NotNullWhen(true)] out float? result) + public static bool TryGetSingle(this IReadOnlyList reader, int index, [NotNullWhen(true)] out float result) { - result = null; var item = reader[index]; if (item.IsDbNull()) { + result = default; return false; } @@ -215,12 +215,12 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetDouble(this IReadOnlyList reader, int index, [NotNullWhen(true)] out double? result) + public static bool TryGetDouble(this IReadOnlyList reader, int index, [NotNullWhen(true)] out double result) { - result = null; var item = reader[index]; if (item.IsDbNull()) { + result = default; return false; } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 996cdf133..69e67d133 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1346,7 +1346,7 @@ namespace Emby.Server.Implementations.Data } var channelId = reader[index]; - if (channelId.SQLiteType != SQLiteType.Null) + if (!channelId.IsDbNull()) { if (!Utf8Parser.TryParse(channelId.ToBlob(), out Guid value, out _, standardFormat: 'N')) { @@ -1366,12 +1366,12 @@ namespace Emby.Server.Implementations.Data { if (reader.TryGetBoolean(index++, out var isMovie)) { - hasProgramAttributes.IsMovie = isMovie.Value; + hasProgramAttributes.IsMovie = isMovie; } if (reader.TryGetBoolean(index++, out var isSeries)) { - hasProgramAttributes.IsSeries = isSeries.Value; + hasProgramAttributes.IsSeries = isSeries; } if (reader.TryGetString(index++, out var episodeTitle)) @@ -1381,7 +1381,7 @@ namespace Emby.Server.Implementations.Data if (reader.TryGetBoolean(index++, out var isRepeat)) { - hasProgramAttributes.IsRepeat = isRepeat.Value; + hasProgramAttributes.IsRepeat = isRepeat; } } else @@ -1412,7 +1412,7 @@ namespace Emby.Server.Implementations.Data { if (reader.TryGetBoolean(index++, out var isLocked)) { - item.IsLocked = isLocked.Value; + item.IsLocked = isLocked; } if (reader.TryGetString(index++, out var preferredMetadataLanguage)) @@ -1430,7 +1430,7 @@ namespace Emby.Server.Implementations.Data { if (reader.TryGetInt32(index++, out var width)) { - item.Width = width.Value; + item.Width = width; } } @@ -1438,7 +1438,7 @@ namespace Emby.Server.Implementations.Data { if (reader.TryGetInt32(index++, out var height)) { - item.Height = height.Value; + item.Height = height; } } @@ -1536,6 +1536,7 @@ namespace Emby.Server.Implementations.Data if (reader.TryGetString(index++, out var audioString)) { + // TODO Span overload coming in the future https://github.com/dotnet/runtime/issues/1916 if (Enum.TryParse(audioString, true, out ProgramAudio audio)) { item.Audio = audio; @@ -1559,7 +1560,7 @@ namespace Emby.Server.Implementations.Data if (reader.TryGetBoolean(index++, out var isInMixedFolder)) { - item.IsInMixedFolder = isInMixedFolder.Value; + item.IsInMixedFolder = isInMixedFolder; } if (HasField(query, ItemFields.DateLastSaved)) @@ -1669,7 +1670,7 @@ namespace Emby.Server.Implementations.Data if (reader.TryGetBoolean(index++, out var isVirtualItem)) { - item.IsVirtualItem = isVirtualItem.Value; + item.IsVirtualItem = isVirtualItem; } if (item is IHasSeries hasSeriesName) @@ -1731,7 +1732,7 @@ namespace Emby.Server.Implementations.Data { if (reader.TryGetInt32(index++, out var parentalRating)) { - item.InheritedParentalRatingValue = parentalRating.Value; + item.InheritedParentalRatingValue = parentalRating; } } diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs index 9bbbe2970..c56046f03 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -148,7 +148,7 @@ namespace Jellyfin.Naming.Tests.Video yield return new object[] { new VideoFileInfo( - path: @"/server/Movies/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - Ozlem/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - Ozlem.mp4", + path: @"/server/Movies/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - JEFF/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - JEFF.mp4", container: "mp4", name: "Rain Man", year: 1988) @@ -200,6 +200,10 @@ namespace Jellyfin.Naming.Tests.Video Assert.NotNull(results[0]); Assert.NotNull(results[1]); Assert.Null(results[2]); + foreach (var result in results) + { + Assert.Null(result?.Container); + } } } } diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs index 80b0bc370..b160e676e 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs @@ -38,7 +38,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo "/video/My.Video.With.Additional.Garbage.en.srt", "/video/My.Video With Additional Garbage.srt" }, - new List + new[] { CreateMediaStream("/video/My.Video.srt", "srt", null, index++), CreateMediaStream("/video/My.Video.vtt", "vtt", null, index++), @@ -58,12 +58,12 @@ namespace Jellyfin.Providers.Tests.MediaInfo [Theory] [MemberData(nameof(AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData))] - public void AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles(List streams, string videoPath, int startIndex, string[] files, List expectedResult) + public void AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles(List streams, string videoPath, int startIndex, string[] files, MediaStream[] expectedResult) { new SubtitleResolver(Mock.Of()).AddExternalSubtitleStreams(streams, videoPath, startIndex, files); - Assert.Equal(expectedResult.Count, streams.Count); - for (var i = 0; i < expectedResult.Count; i++) + Assert.Equal(expectedResult.Length, streams.Count); + for (var i = 0; i < expectedResult.Length; i++) { var expected = expectedResult[i]; var actual = streams[i]; From 7e6a45c402988cba27c36cc6b1b339a4a52b732a Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 19 May 2021 19:33:24 +0200 Subject: [PATCH 243/612] Review changes --- .../Data/SqliteExtensions.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index 3499713e1..a8f3feb58 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -97,12 +97,12 @@ namespace Emby.Server.Implementations.Data DateTimeStyles.None).ToUniversalTime(); } - public static bool TryReadDateTime(this IReadOnlyList reader, int index, [NotNullWhen(true)] out DateTime? result) + public static bool TryReadDateTime(this IReadOnlyList reader, int index, out DateTime result) { - result = null; var item = reader[index]; if (item.IsDbNull()) { + result = default; return false; } @@ -114,15 +114,16 @@ namespace Emby.Server.Implementations.Data return true; } + result = default; return false; } - public static bool TryGetGuid(this IReadOnlyList reader, int index, [NotNullWhen(true)] out Guid? result) + public static bool TryGetGuid(this IReadOnlyList reader, int index, out Guid result) { - result = null; var item = reader[index]; if (item.IsDbNull()) { + result = default; return false; } @@ -158,7 +159,7 @@ namespace Emby.Server.Implementations.Data return result[index].ToBool(); } - public static bool TryGetBoolean(this IReadOnlyList reader, int index, [NotNullWhen(true)] out bool result) + public static bool TryGetBoolean(this IReadOnlyList reader, int index, out bool result) { var item = reader[index]; if (item.IsDbNull()) @@ -171,7 +172,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetInt32(this IReadOnlyList reader, int index, [NotNullWhen(true)] out int result) + public static bool TryGetInt32(this IReadOnlyList reader, int index, out int result) { var item = reader[index]; if (item.IsDbNull()) @@ -189,7 +190,7 @@ namespace Emby.Server.Implementations.Data return result[index].ToInt64(); } - public static bool TryGetInt64(this IReadOnlyList reader, int index, [NotNullWhen(true)] out long result) + public static bool TryGetInt64(this IReadOnlyList reader, int index, out long result) { var item = reader[index]; if (item.IsDbNull()) @@ -202,7 +203,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetSingle(this IReadOnlyList reader, int index, [NotNullWhen(true)] out float result) + public static bool TryGetSingle(this IReadOnlyList reader, int index, out float result) { var item = reader[index]; if (item.IsDbNull()) @@ -215,7 +216,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetDouble(this IReadOnlyList reader, int index, [NotNullWhen(true)] out double result) + public static bool TryGetDouble(this IReadOnlyList reader, int index, out double result) { var item = reader[index]; if (item.IsDbNull()) From e0f793799afccd4e2d640dbf5554f72d8a8f8b02 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 19 May 2021 19:52:45 +0200 Subject: [PATCH 244/612] Fix build --- .../Data/SqliteItemRepository.cs | 20 +++++++++---------- .../Security/AuthenticationRepository.cs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 69e67d133..99275a749 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1334,7 +1334,7 @@ namespace Emby.Server.Implementations.Data { if (item is IHasStartDate hasStartDate && reader.TryReadDateTime(index, out var startDate)) { - hasStartDate.StartDate = startDate.Value; + hasStartDate.StartDate = startDate; } index++; @@ -1446,7 +1446,7 @@ namespace Emby.Server.Implementations.Data { if (reader.TryReadDateTime(index++, out var dateLastRefreshed)) { - item.DateLastRefreshed = dateLastRefreshed.Value; + item.DateLastRefreshed = dateLastRefreshed; } } @@ -1510,13 +1510,13 @@ namespace Emby.Server.Implementations.Data { if (reader.TryReadDateTime(index++, out var dateCreated)) { - item.DateCreated = dateCreated.Value; + item.DateCreated = dateCreated; } } if (reader.TryReadDateTime(index++, out var dateModified)) { - item.DateModified = dateModified.Value; + item.DateModified = dateModified; } item.Id = reader.GetGuid(index++); @@ -1531,7 +1531,7 @@ namespace Emby.Server.Implementations.Data if (reader.TryGetGuid(index++, out var parentId)) { - item.ParentId = parentId.Value; + item.ParentId = parentId; } if (reader.TryGetString(index++, out var audioString)) @@ -1567,7 +1567,7 @@ namespace Emby.Server.Implementations.Data { if (reader.TryReadDateTime(index++, out var dateLastSaved)) { - item.DateLastSaved = dateLastSaved.Value; + item.DateLastSaved = dateLastSaved; } } @@ -1695,7 +1695,7 @@ namespace Emby.Server.Implementations.Data index++; if (reader.TryGetGuid(index, out var seasonId)) { - episode.SeasonId = seasonId.Value; + episode.SeasonId = seasonId; } } else @@ -1713,7 +1713,7 @@ namespace Emby.Server.Implementations.Data { if (reader.TryGetGuid(index, out var seriesId)) { - hasSeries.SeriesId = seriesId.Value; + hasSeries.SeriesId = seriesId; } } @@ -1845,7 +1845,7 @@ namespace Emby.Server.Implementations.Data if (reader.TryGetGuid(index, out var ownerId)) { - item.OwnerId = ownerId.Value; + item.OwnerId = ownerId; } return item; @@ -1958,7 +1958,7 @@ namespace Emby.Server.Implementations.Data if (reader.TryReadDateTime(3, out var imageDateModified)) { - chapter.ImageDateModified = imageDateModified.Value; + chapter.ImageDateModified = imageDateModified; } return chapter; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index ca7e74858..76f863c95 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -326,7 +326,7 @@ namespace Emby.Server.Implementations.Security if (reader.TryReadDateTime(9, out var dateLastActivity)) { - info.DateLastActivity = dateLastActivity.Value; + info.DateLastActivity = dateLastActivity; } else { From c6ca722e57c7b83c5105bbf9b0de63b616d556de Mon Sep 17 00:00:00 2001 From: Sanaf Raw Date: Tue, 18 May 2021 20:00:59 +0000 Subject: [PATCH 245/612] Translated using Weblate (Bengali) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/bn/ --- Emby.Server.Implementations/Localization/Core/bn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index f0e42a052..c3fbe2408 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -1,7 +1,7 @@ { "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে", "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে", - "Collections": "কলেক্শন", + "Collections": "সংগ্রহ", "ChapterNameValue": "অধ্যায় {0}", "Channels": "চ্যানেল", "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে", From 26d7fc828075dbaa3068ac9c323ebef3370fd023 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 20 May 2021 22:10:19 +0200 Subject: [PATCH 246/612] Enable nullable reference types for MediaBrowser.MediaEncoding.Subtitles --- .../Attachments/AttachmentExtractor.cs | 1 + .../BdInfo/BdInfoDirectoryInfo.cs | 6 +- .../BdInfo/BdInfoFileInfo.cs | 2 +- .../Encoder/EncoderValidator.cs | 15 ++-- .../Encoder/MediaEncoder.cs | 1 + .../MediaBrowser.MediaEncoding.csproj | 1 + .../Probing/FFProbeHelpers.cs | 2 + .../Probing/InternalMediaInfoResult.cs | 2 + .../Probing/MediaChapter.cs | 1 + .../Probing/MediaFormatInfo.cs | 2 + .../Probing/MediaStreamInfo.cs | 2 + .../Probing/ProbeResultNormalizer.cs | 1 + .../Subtitles/AssParser.cs | 2 - .../Subtitles/ParserValues.cs | 1 - .../Subtitles/SrtParser.cs | 2 - .../Subtitles/SsaParser.cs | 2 - .../Subtitles/SubtitleEditParser.cs | 2 - .../Subtitles/SubtitleEncoder.cs | 72 ++++++++++--------- 18 files changed, 65 insertions(+), 52 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index e8aeabf9d..a0ec3bd90 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs index ef9943722..e86e518be 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs @@ -9,9 +9,9 @@ namespace MediaBrowser.MediaEncoding.BdInfo { public class BdInfoDirectoryInfo : IDirectoryInfo { - private readonly IFileSystem _fileSystem = null; + private readonly IFileSystem _fileSystem; - private readonly FileSystemMetadata _impl = null; + private readonly FileSystemMetadata _impl; public BdInfoDirectoryInfo(IFileSystem fileSystem, string path) { @@ -29,7 +29,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo public string FullName => _impl.FullName; - public IDirectoryInfo Parent + public IDirectoryInfo? Parent { get { diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs index 0a8af8e9c..41143c259 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs @@ -7,7 +7,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo { public class BdInfoFileInfo : BDInfo.IO.IFileInfo { - private FileSystemMetadata _impl = null; + private FileSystemMetadata _impl; public BdInfoFileInfo(FileSystemMetadata impl) { diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 9e2417603..f782e65bd 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -121,11 +121,11 @@ namespace MediaBrowser.MediaEncoding.Encoder // When changing this, also change the minimum library versions in _ffmpegMinimumLibraryVersions public static Version MinVersion { get; } = new Version(4, 0); - public static Version MaxVersion { get; } = null; + public static Version? MaxVersion { get; } = null; public bool ValidateVersion() { - string output = null; + string output; try { output = GetProcessOutput(_encoderPath, "-version"); @@ -133,6 +133,7 @@ namespace MediaBrowser.MediaEncoding.Encoder catch (Exception ex) { _logger.LogError(ex, "Error validating encoder"); + return false; } if (string.IsNullOrWhiteSpace(output)) @@ -207,7 +208,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// The output from "ffmpeg -version". /// The FFmpeg version. - internal Version GetFFmpegVersion(string output) + internal Version? GetFFmpegVersion(string output) { // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)"); @@ -275,7 +276,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private IEnumerable GetHwaccelTypes() { - string output = null; + string? output = null; try { output = GetProcessOutput(_encoderPath, "-hwaccels"); @@ -303,7 +304,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } - string output = null; + string output; try { output = GetProcessOutput(_encoderPath, "-h filter=" + filter); @@ -311,6 +312,7 @@ namespace MediaBrowser.MediaEncoding.Encoder catch (Exception ex) { _logger.LogError(ex, "Error detecting the given filter"); + return false; } if (output.Contains("Filter " + filter, StringComparison.Ordinal)) @@ -331,7 +333,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private IEnumerable GetCodecs(Codec codec) { string codecstr = codec == Codec.Encoder ? "encoders" : "decoders"; - string output = null; + string output; try { output = GetProcessOutput(_encoderPath, "-" + codecstr); @@ -339,6 +341,7 @@ namespace MediaBrowser.MediaEncoding.Encoder catch (Exception ex) { _logger.LogError(ex, "Error detecting available {Codec}", codecstr); + return Enumerable.Empty(); } if (string.IsNullOrWhiteSpace(output)) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 62c0c0bb1..cdb778bf2 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 39fb0b47c..7733e715f 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -10,6 +10,7 @@ false true true + enable AllEnabledByDefault ../jellyfin.ruleset diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index da37687e8..1fa90bb21 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Globalization; diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs index 0e319c1a8..d4d153b08 100644 --- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs +++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs index de062d06b..a1cef7a9f 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs index 8af122ef9..d50da37b8 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs index 7b7744163..c9c8c34c2 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 884ec0a29..2ec9dc346 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs index 8219aa7b4..08ee5c72e 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.Extensions.Logging; using Nikse.SubtitleEdit.Core.SubtitleFormats; diff --git a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs index dca5c1e8a..cec1aaf08 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 namespace MediaBrowser.MediaEncoding.Subtitles diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs index 19fb951dc..78d54ca51 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.Extensions.Logging; using Nikse.SubtitleEdit.Core.SubtitleFormats; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs index 36dc2e01f..17c2ae40e 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.Extensions.Logging; using Nikse.SubtitleEdit.Core.SubtitleFormats; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs index 82ec6ca21..639a34d99 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Globalization; using System.IO; using System.Linq; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 39bec8da1..608ebf443 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -71,8 +72,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles try { - var reader = GetReader(inputFormat, true); - + var reader = GetReader(inputFormat); var trackInfo = reader.Parse(stream, cancellationToken); FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps); @@ -139,10 +139,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles .ConfigureAwait(false); var inputFormat = subtitle.format; - var writer = TryGetWriter(outputFormat); // Return the original if we don't have any way of converting it - if (writer == null) + if (!TryGetWriter(outputFormat, out var writer)) { return subtitle.stream; } @@ -239,7 +238,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec) .TrimStart('.'); - if (GetReader(currentFormat, false) == null) + if (TryGetReader(currentFormat, out _)) { // Convert var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt"); @@ -257,37 +256,41 @@ namespace MediaBrowser.MediaEncoding.Subtitles return new SubtitleInfo(subtitleStream.Path, mediaSource.Protocol, currentFormat, true); } - private ISubtitleParser GetReader(string format, bool throwIfMissing) + private bool TryGetReader(string format, [NotNullWhen(true)] out ISubtitleParser? value) { - if (string.IsNullOrEmpty(format)) - { - throw new ArgumentNullException(nameof(format)); - } - if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) { - return new SrtParser(_logger); + value = new SrtParser(_logger); + return true; } if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase)) { - return new SsaParser(_logger); + value = new SsaParser(_logger); + return true; } if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)) { - return new AssParser(_logger); + value = new AssParser(_logger); + return true; } - if (throwIfMissing) - { - throw new ArgumentException("Unsupported format: " + format); - } - - return null; + value = null; + return false; } - private ISubtitleWriter TryGetWriter(string format) + private ISubtitleParser GetReader(string format) + { + if (TryGetReader(format, out var reader)) + { + return reader; + } + + throw new ArgumentException("Unsupported format: " + format); + } + + private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value) { if (string.IsNullOrEmpty(format)) { @@ -296,32 +299,35 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase)) { - return new JsonWriter(); + value = new JsonWriter(); + return true; } if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) { - return new SrtWriter(); + value = new SrtWriter(); + return true; } if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase)) { - return new VttWriter(); + value = new VttWriter(); + return true; } if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase)) { - return new TtmlWriter(); + value = new TtmlWriter(); + return true; } - return null; + value = null; + return false; } private ISubtitleWriter GetWriter(string format) { - var writer = TryGetWriter(format); - - if (writer != null) + if (TryGetWriter(format, out var writer)) { return writer; } @@ -391,7 +397,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException(nameof(outputPath)); } - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath))); var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, mediaSource.Protocol, cancellationToken).ConfigureAwait(false); @@ -549,7 +555,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException(nameof(outputPath)); } - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath))); var processArgs = string.Format( CultureInfo.InvariantCulture, @@ -715,7 +721,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false)) { - var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName; + var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName ?? string.Empty; // UTF16 is automatically converted to UTF8 by FFmpeg, do not specify a character encoding if ((path.EndsWith(".ass", StringComparison.Ordinal) || path.EndsWith(".ssa", StringComparison.Ordinal) || path.EndsWith(".srt", StringComparison.Ordinal)) @@ -725,7 +731,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles charset = string.Empty; } - _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path); + _logger.LogDebug("charset {0} detected for {Path}", charset, path); return charset; } From 7e8428e588b3f0a0574da44081098c64fe1a47d7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 20 May 2021 21:28:18 +0200 Subject: [PATCH 247/612] Enable nullable reference types for Emby.Server.Implementations --- .../AppBase/BaseApplicationPaths.cs | 8 ++------ .../AppBase/BaseConfigurationManager.cs | 2 ++ .../AppBase/ConfigurationHelper.cs | 2 -- .../ApplicationHost.cs | 2 ++ .../Channels/ChannelManager.cs | 2 ++ .../Collections/CollectionImageProvider.cs | 4 ++-- .../Collections/CollectionManager.cs | 2 ++ .../ServerConfigurationManager.cs | 2 ++ .../Cryptography/CryptographyProvider.cs | 2 -- .../Data/BaseSqliteRepository.cs | 2 ++ .../Data/ManagedConnection.cs | 2 +- .../Data/SqliteExtensions.cs | 1 + .../Data/SqliteItemRepository.cs | 2 ++ .../Data/SqliteUserDataRepository.cs | 2 ++ .../Data/TypeMapper.cs | 6 +++--- .../Devices/DeviceId.cs | 2 ++ .../Devices/DeviceManager.cs | 2 ++ Emby.Server.Implementations/Dto/DtoService.cs | 2 ++ .../Emby.Server.Implementations.csproj | 1 + .../EntryPoints/ExternalPortForwarding.cs | 2 ++ .../EntryPoints/LibraryChangedNotifier.cs | 2 ++ .../EntryPoints/RecordingNotifier.cs | 2 ++ .../EntryPoints/UdpServerEntryPoint.cs | 2 -- .../EntryPoints/UserDataChangeNotifier.cs | 2 ++ .../Security/AuthorizationContext.cs | 20 +++++++++---------- .../HttpServer/Security/SessionContext.cs | 4 ++-- .../HttpServer/WebSocketConnection.cs | 2 -- .../HttpServer/WebSocketManager.cs | 2 ++ .../IO/FileRefresher.cs | 2 ++ .../IO/LibraryMonitor.cs | 2 ++ .../IO/ManagedFileSystem.cs | 6 +++--- .../IO/MbLinkShortcutHandler.cs | 2 +- .../IO/StreamHelper.cs | 2 +- .../IStartupOptions.cs | 1 - .../Images/BaseDynamicImageProvider.cs | 2 ++ .../Images/CollectionFolderImageProvider.cs | 2 ++ .../Images/DynamicImageProvider.cs | 2 ++ .../Images/FolderImageProvider.cs | 2 ++ .../Images/GenreImageProvider.cs | 2 ++ .../Images/PlaylistImageProvider.cs | 2 ++ .../Library/ExclusiveLiveStream.cs | 2 ++ .../Library/IgnorePatterns.cs | 2 -- .../Library/LibraryManager.cs | 2 ++ .../Library/LiveStreamHelper.cs | 2 ++ .../Library/MediaSourceManager.cs | 2 ++ .../Library/MediaStreamSelector.cs | 2 ++ .../Library/MusicManager.cs | 2 ++ .../Library/PathExtensions.cs | 2 -- .../Library/ResolverHelper.cs | 2 -- .../Library/Resolvers/Audio/AudioResolver.cs | 2 ++ .../Resolvers/Audio/MusicAlbumResolver.cs | 2 ++ .../Resolvers/Audio/MusicArtistResolver.cs | 2 ++ .../Library/Resolvers/BaseVideoResolver.cs | 2 ++ .../Library/Resolvers/Books/BookResolver.cs | 2 ++ .../Library/Resolvers/FolderResolver.cs | 2 ++ .../Library/Resolvers/ItemResolver.cs | 2 ++ .../Resolvers/Movies/BoxSetResolver.cs | 2 ++ .../Library/Resolvers/Movies/MovieResolver.cs | 2 ++ .../Library/Resolvers/PhotoAlbumResolver.cs | 2 ++ .../Library/Resolvers/PhotoResolver.cs | 2 ++ .../Library/Resolvers/PlaylistResolver.cs | 2 ++ .../Resolvers/SpecialFolderResolver.cs | 2 ++ .../Library/Resolvers/TV/EpisodeResolver.cs | 2 ++ .../Library/Resolvers/TV/SeasonResolver.cs | 2 ++ .../Library/Resolvers/TV/SeriesResolver.cs | 2 ++ .../Library/Resolvers/VideoResolver.cs | 2 ++ .../Library/SearchEngine.cs | 2 ++ .../Library/UserDataManager.cs | 2 ++ .../Library/UserViewManager.cs | 2 ++ .../LiveTv/EmbyTV/DirectRecorder.cs | 2 ++ .../LiveTv/EmbyTV/EmbyTV.cs | 2 ++ .../LiveTv/EmbyTV/EncodedRecorder.cs | 2 ++ .../LiveTv/EmbyTV/EpgChannelData.cs | 7 +++---- .../LiveTv/EmbyTV/ItemDataProvider.cs | 2 ++ .../LiveTv/EmbyTV/RecordingHelper.cs | 2 ++ .../LiveTv/EmbyTV/TimerManager.cs | 2 ++ .../LiveTv/Listings/SchedulesDirect.cs | 2 ++ .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 ++ .../LiveTv/LiveTvDtoService.cs | 2 ++ .../LiveTv/LiveTvManager.cs | 2 ++ .../LiveTv/LiveTvMediaSourceProvider.cs | 2 ++ .../LiveTv/TunerHosts/BaseTunerHost.cs | 2 ++ .../LiveTv/TunerHosts/HdHomerun/Channels.cs | 2 ++ .../TunerHosts/HdHomerun/DiscoverResponse.cs | 2 ++ .../TunerHosts/HdHomerun/HdHomerunHost.cs | 2 ++ .../TunerHosts/HdHomerun/HdHomerunManager.cs | 2 ++ .../HdHomerun/HdHomerunUdpStream.cs | 2 ++ .../LiveTv/TunerHosts/LiveStream.cs | 2 ++ .../LiveTv/TunerHosts/M3UTunerHost.cs | 2 ++ .../LiveTv/TunerHosts/M3uParser.cs | 2 ++ .../LiveTv/TunerHosts/SharedHttpStream.cs | 2 ++ .../Localization/LocalizationManager.cs | 2 ++ .../MediaEncoder/EncodingManager.cs | 2 ++ .../Net/SocketFactory.cs | 2 ++ Emby.Server.Implementations/Net/UdpSocket.cs | 2 ++ .../Playlists/PlaylistManager.cs | 2 ++ .../Plugins/PluginManager.cs | 2 -- .../QuickConnect/QuickConnectManager.cs | 2 ++ .../ScheduledTasks/ScheduledTaskWorker.cs | 2 ++ .../ScheduledTasks/TaskManager.cs | 2 ++ .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 6 ++++-- .../Tasks/CleanActivityLogTask.cs | 4 ++-- .../ScheduledTasks/Triggers/DailyTrigger.cs | 2 ++ .../Triggers/IntervalTrigger.cs | 2 ++ .../ScheduledTasks/Triggers/StartupTrigger.cs | 2 ++ .../ScheduledTasks/Triggers/WeeklyTrigger.cs | 2 ++ .../Security/AuthenticationRepository.cs | 2 ++ .../Serialization/MyXmlSerializer.cs | 10 ++++++---- .../Session/SessionManager.cs | 2 ++ .../Session/SessionWebSocketListener.cs | 2 ++ .../Session/WebSocketController.cs | 1 - .../Sorting/AiredEpisodeOrderComparer.cs | 2 +- .../Sorting/AlbumArtistComparer.cs | 4 ++-- .../Sorting/AlbumComparer.cs | 4 ++-- .../Sorting/ArtistComparer.cs | 4 ++-- .../Sorting/CommunityRatingComparer.cs | 2 +- .../Sorting/CriticRatingComparer.cs | 6 +++--- .../Sorting/DateCreatedComparer.cs | 2 +- .../Sorting/DateLastMediaAddedComparer.cs | 1 + .../Sorting/DatePlayedComparer.cs | 2 ++ .../Sorting/IsFavoriteOrLikeComparer.cs | 1 + .../Sorting/IsFolderComparer.cs | 6 +++--- .../Sorting/IsPlayedComparer.cs | 2 ++ .../Sorting/IsUnplayedComparer.cs | 2 ++ .../Sorting/NameComparer.cs | 2 +- .../Sorting/OfficialRatingComparer.cs | 2 +- .../Sorting/PlayCountComparer.cs | 2 ++ .../Sorting/PremiereDateComparer.cs | 9 +++++++-- .../Sorting/ProductionYearComparer.cs | 9 +++++++-- .../Sorting/RandomComparer.cs | 2 +- .../Sorting/RuntimeComparer.cs | 2 ++ .../Sorting/SeriesSortNameComparer.cs | 2 ++ .../Sorting/SortNameComparer.cs | 2 ++ .../Sorting/StartDateComparer.cs | 2 ++ .../Sorting/StudioComparer.cs | 2 ++ Emby.Server.Implementations/SyncPlay/Group.cs | 2 ++ .../SyncPlay/SyncPlayManager.cs | 2 ++ .../TV/TVSeriesManager.cs | 2 ++ Emby.Server.Implementations/Udp/UdpServer.cs | 2 ++ .../Updates/InstallationManager.cs | 2 ++ .../Controllers/DynamicHlsController.cs | 4 ++-- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 4 +++- .../PlaybackDtos/TranscodingThrottler.cs | 3 ++- .../Migrations/Routines/MigrateUserDb.cs | 11 ++++++---- .../Extensions/StringExtensions.cs | 1 + .../Net/ISessionContext.cs | 4 ++-- .../Sorting/IBaseItemComparer.cs | 2 +- MediaBrowser.Model/IO/IFileSystem.cs | 7 +++---- MediaBrowser.Model/IO/IShortcutHandler.cs | 2 +- MediaBrowser.Model/IO/IStreamHelper.cs | 2 +- MediaBrowser.Model/Tasks/ITaskTrigger.cs | 2 +- 151 files changed, 300 insertions(+), 99 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 660bbb2de..6edfad575 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.AppBase CachePath = cacheDirectoryPath; WebPath = webDirectoryPath; - DataPath = Path.Combine(ProgramDataPath, "data"); + _dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName; } /// @@ -55,11 +55,7 @@ namespace Emby.Server.Implementations.AppBase /// Gets the folder path to the data directory. /// /// The data directory. - public string DataPath - { - get => _dataPath; - private set => _dataPath = Directory.CreateDirectory(value).FullName; - } + public string DataPath => _dataPath; /// public string VirtualDataPath => "%AppDataPath%"; diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 4f72c8ce1..8c919db43 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 29bac6634..de770f59e 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.IO; using System.Linq; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 75d8fc113..82995deb3 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 7324b0ee9..448f12403 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index c69a07e83..ca8409402 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -82,9 +82,9 @@ namespace Emby.Server.Implementations.Collections return null; }) .Where(i => i != null) - .GroupBy(x => x.Id) + .GroupBy(x => x!.Id) // We removed the null values .Select(x => x.First()) - .ToList(); + .ToList()!; // Again... the list doesn't contain any null values } /// diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index c56f33448..82d80fc83 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index 7a8ed8c29..ff5602f24 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Globalization; using System.IO; diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 12a9e44e7..4a9b28085 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Security.Cryptography; diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index c331a6112..6f23a0888 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 5c094ddd2..10c6f837e 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Data { public class ManagedConnection : IDisposable { - private SQLiteDatabaseConnection _db; + private SQLiteDatabaseConnection? _db; private readonly SemaphoreSlim _writeLock; private bool _disposed = false; diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index a8f3feb58..e532825af 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index b3d8860a9..5b4bbb339 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index e0ebd0a6c..1756bcae0 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/TypeMapper.cs b/Emby.Server.Implementations/Data/TypeMapper.cs index 7044b1d19..7f1306d15 100644 --- a/Emby.Server.Implementations/Data/TypeMapper.cs +++ b/Emby.Server.Implementations/Data/TypeMapper.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Data /// This holds all the types in the running assemblies /// so that we can de-serialize properly when we don't have strong types. /// - private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary(); /// /// Gets the type. @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Data /// Name of the type. /// Type. /// typeName is null. - public Type GetType(string typeName) + public Type? GetType(string typeName) { if (string.IsNullOrEmpty(typeName)) { @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Data /// /// Name of the type. /// Type. - private Type LookupType(string typeName) + private Type? LookupType(string typeName) { return AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType(typeName)) diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index fa6ac95fd..3d15b3e76 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index da5047d24..2637addce 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 4ae35039a..7411239a1 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 14f6f565c..113863519 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -44,6 +44,7 @@ false true true + enable AD0001 AllEnabledByDefault diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 14201ead2..cc3e4a2c2 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index ae1b51b4c..5bb4100ba 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 824bb85f4..e0ca02d98 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 3624e079f..211941f44 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Net.Sockets; using System.Threading; diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 1989e9ed2..332fb3385 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index fbf9254d1..c87f7dbbd 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached)) { - return (AuthorizationInfo)cached; + return (AuthorizationInfo)cached!; // Cache should never contain null } return GetAuthorization(requestContext); @@ -55,15 +55,15 @@ namespace Emby.Server.Implementations.HttpServer.Security } private AuthorizationInfo GetAuthorizationInfoFromDictionary( - in Dictionary auth, + in Dictionary? auth, in IHeaderDictionary headers, in IQueryCollection queryString) { - string deviceId = null; - string device = null; - string client = null; - string version = null; - string token = null; + string? deviceId = null; + string? device = null; + string? client = null; + string? version = null; + string? token = null; if (auth != null) { @@ -206,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorizationDictionary(HttpContext httpReq) + private Dictionary? GetAuthorizationDictionary(HttpContext httpReq) { var auth = httpReq.Request.Headers["X-Emby-Authorization"]; @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorizationDictionary(HttpRequest httpReq) + private Dictionary? GetAuthorizationDictionary(HttpRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -240,7 +240,7 @@ namespace Emby.Server.Implementations.HttpServer.Security ///
/// The authorization header. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorization(ReadOnlySpan authorizationHeader) + private Dictionary? GetAuthorization(ReadOnlySpan authorizationHeader) { if (authorizationHeader == null) { diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index dd77b45d8..c375f36ce 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -36,14 +36,14 @@ namespace Emby.Server.Implementations.HttpServer.Security return GetSession((HttpContext)requestContext); } - public User GetUser(HttpContext requestContext) + public User? GetUser(HttpContext requestContext) { var session = GetSession(requestContext); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public User GetUser(object requestContext) + public User? GetUser(object requestContext) { return GetUser(((HttpRequest)requestContext).HttpContext); } diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 06acb5606..8f7d60669 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Buffers; using System.IO.Pipelines; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 1bee1ac31..861c0a95e 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 7435e9d0b..47a83d77c 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 3353fae9d..aa80bccd7 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 27096ed33..6a554e68a 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.IO /// The filename. /// System.String. /// filename - public virtual string ResolveShortcut(string filename) + public virtual string? ResolveShortcut(string filename) { if (string.IsNullOrEmpty(filename)) { @@ -601,7 +601,7 @@ namespace Emby.Server.Implementations.IO return GetFiles(path, null, false, recursive); } - public virtual IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable GetFiles(string path, IReadOnlyList? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) { var enumerationOptions = GetEnumerationOptions(recursive); @@ -655,7 +655,7 @@ namespace Emby.Server.Implementations.IO return GetFilePaths(path, null, false, recursive); } - public virtual IEnumerable GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) { var enumerationOptions = GetEnumerationOptions(recursive); diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs index e6696b8c4..76c58d5dc 100644 --- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs +++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO public string Extension => ".mblink"; - public string Resolve(string shortcutPath) + public string? Resolve(string shortcutPath) { if (string.IsNullOrEmpty(shortcutPath)) { diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index c16ebd61b..e4f5f4cf0 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.IO { public class StreamHelper : IStreamHelper { - public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) + public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken) { byte[] buffer = ArrayPool.Shared.Rent(bufferSize); try diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index f719dc5f8..a430b9e72 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#nullable enable namespace Emby.Server.Implementations { diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 6fa3c1c61..833fb0b7a 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 161b4c452..ff5f26ce0 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 50c531482..900b3fd9c 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/FolderImageProvider.cs b/Emby.Server.Implementations/Images/FolderImageProvider.cs index 0224ab32a..859017f86 100644 --- a/Emby.Server.Implementations/Images/FolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/FolderImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index 381788231..6da431c68 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs index a4c106e87..b8f0f0d65 100644 --- a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 236453e80..6c65b5899 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index e30a67593..5384c04b3 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Linq; using DotNet.Globbing; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 4d207471a..f8d8197d4 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index c2951dd15..4ef7923db 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 85d6d3043..38e81d14c 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 28fa06239..b833122ea 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index f8bae4fd1..06300adeb 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 0de4edb7e..86b8039fa 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Diagnostics.CodeAnalysis; using MediaBrowser.Common.Providers; diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 1d9b44874..ac75e5d3a 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.IO; using System.Linq; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 4ad84579d..e893d6335 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index bf32381eb..8e1eccb10 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 60f82806f..3d2ae95d2 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 16050185f..a3dcdc944 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 0525c7e30..68076730b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs index 7dbce7a6e..7aaee017d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs index 92fb2a753..fa45ccf84 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index 295e9e120..69d71d0d9 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 16bf4dc4a..02c528764 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 204c8a62e..534bc80dd 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 3cb6542cf..57bf40e9e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index 5f051321f..ecd44be47 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 99f304190..7b4e14334 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 6f29bc649..d6ae91056 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 768e2e4f5..7d707df18 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Globalization; using Emby.Naming.TV; using MediaBrowser.Controller.Entities.TV; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 8fc3e3e75..a1562abd3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs index 62268fce9..9599faea4 100644 --- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index bcdf854ca..26e615fa0 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 827e3c64b..667e46613 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index ac041bcf6..e2da672a3 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 7a6b1d8b6..3fcadf5b1 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 28a2095e1..797063120 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 9372b0f6c..26e4ef1ed 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs index 8c27ca76e..0ec52a959 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller.LiveTv; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - internal class EpgChannelData { @@ -39,13 +38,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - public ChannelInfo GetChannelById(string id) + public ChannelInfo? GetChannelById(string id) => _channelsById.GetValueOrDefault(id); - public ChannelInfo GetChannelByNumber(string number) + public ChannelInfo? GetChannelByNumber(string number) => _channelsByNumber.GetValueOrDefault(number); - public ChannelInfo GetChannelByName(string name) + public ChannelInfo? GetChannelByName(string name) => _channelsByName.GetValueOrDefault(name); public static string NormalizeName(string value) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 1cac9cb96..bdab8c3e4 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 32245f899..108863869 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 1efa90e25..6c52a9a73 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 9af65cabb..00d02873c 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 6824aa442..ebad4eddf 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 6af49dd45..21e1409ac 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1145d8aa1..1f1628900 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 3a738fd5d..ecd28097d 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index fbcd4ef37..00a37bb02 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs index 740cbb66e..0f0453189 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { internal class Channels diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs index 09d77f838..42068cd34 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index bbac6e055..c5700db71 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index a7fda1d72..3016eeda2 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index b16ccc561..50a2d9abb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index f8baf55da..96a678c1d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 4b170b2e4..69035dac9 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 84d416149..48a0c3cd3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index eeb2426f4..137ed27e2 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 220e423bf..dd5dee1d1 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 031b5d2e7..8aaa1f7bb 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 0781a0e33..137728616 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 4e25768cf..a8b18d292 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 2d1a559f1..9a1ca9946 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 14df20936..48281b75f 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 0259dc436..7cfd1fced 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Globalization; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 101d9b537..ccbd4289e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index af316e108..4f0df75bf 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 2312c85d9..baeb86a22 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -140,8 +140,10 @@ namespace Emby.Server.Implementations.ScheduledTasks previouslyFailedImages.Add(key); var parentPath = Path.GetDirectoryName(failHistoryPath); - - Directory.CreateDirectory(parentPath); + if (parentPath != null) + { + Directory.CreateDirectory(parentPath); + } string text = string.Join('|', previouslyFailedImages); File.WriteAllText(failHistoryPath, text); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs index 4abbf784b..50ba9bc89 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -75,4 +75,4 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Enumerable.Empty(); } } -} \ No newline at end of file +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 3b40320ab..3b63536a4 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index b04fd7c7e..e13782fe0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index 7cd5493da..ced14195b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 0c0ebec08..a67f940b7 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 76f863c95..30823ab8f 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs index 27024e4e1..8d8b82f0a 100644 --- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -19,7 +19,9 @@ namespace Emby.Server.Implementations.Serialization new ConcurrentDictionary(); private static XmlSerializer GetSerializer(Type type) - => _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type)); + => _serializers.GetOrAdd( + type.FullName ?? throw new ArgumentException($"Invalid type {type}."), + _ => new XmlSerializer(type)); /// /// Serializes to writer. @@ -38,7 +40,7 @@ namespace Emby.Server.Implementations.Serialization /// The type. /// The stream. /// System.Object. - public object DeserializeFromStream(Type type, Stream stream) + public object? DeserializeFromStream(Type type, Stream stream) { using (var reader = XmlReader.Create(stream)) { @@ -81,7 +83,7 @@ namespace Emby.Server.Implementations.Serialization /// The type. /// The file. /// System.Object. - public object DeserializeFromFile(Type type, string file) + public object? DeserializeFromFile(Type type, string file) { using (var stream = File.OpenRead(file)) { @@ -95,7 +97,7 @@ namespace Emby.Server.Implementations.Serialization /// The type. /// The buffer. /// System.Object. - public object DeserializeFromBytes(Type type, byte[] buffer) + public object? DeserializeFromBytes(Type type, byte[] buffer) { using (var stream = new MemoryStream(buffer, 0, buffer.Length, false, true)) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6844152ea..ef467da7e 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 39c369a01..e9e3ca7f4 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index a653b58c2..ed1dfca59 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 #pragma warning disable SA1600 -#nullable enable using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 60698e803..2b0ab536f 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 7657cc74e..42e644970 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as IHasAlbumArtist; diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs index 7dfdd9ecf..1db3f5e9c 100644 --- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Sorting ///
/// The x. /// System.String. - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as Audio; diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs index 756d3c5b6..98bee3fd9 100644 --- a/Emby.Server.Implementations/Sorting/ArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting public string Name => ItemSortBy.Artist; /// - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Sorting ///
/// The x. /// System.String. - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { if (!(x is Audio audio)) { diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs index 980954ba0..5f142fa4b 100644 --- a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs index fa136c36d..d20dedc2d 100644 --- a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -15,14 +15,14 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } - private static float GetValue(BaseItem x) + private static float GetValue(BaseItem? x) { - return x.CriticRating ?? 0; + return x?.CriticRating ?? 0; } /// diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs index cbca300d2..d3f10f78c 100644 --- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 03ff19d21..b1cb123ce 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 16bd2aff8..08a44319f 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index 0c4e82d01..73e628cf7 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs index a35192eff..3c5ddeefa 100644 --- a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -30,9 +30,9 @@ namespace Emby.Server.Implementations.Sorting /// /// The x. /// System.String. - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { - return x.IsFolder ? 0 : 1; + return x?.IsFolder ?? true ? 0 : 1; } } } diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index d95948406..7d77a8bc5 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index 1632c5a7a..926835f90 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index da020d8d8..4de81a69e 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index 76bb798b5..a81f78ebf 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index 5c2830322..04e4865cb 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs index 92ac04dc6..c98f97bf1 100644 --- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetDate(x).CompareTo(GetDate(y)); } @@ -26,8 +26,13 @@ namespace Emby.Server.Implementations.Sorting ///
/// The x. /// DateTime. - private static DateTime GetDate(BaseItem x) + private static DateTime GetDate(BaseItem? x) { + if (x == null) + { + return DateTime.MinValue; + } + if (x.PremiereDate.HasValue) { return x.PremiereDate.Value; diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs index e2857df0b..df9f9957d 100644 --- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs +++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -25,8 +25,13 @@ namespace Emby.Server.Implementations.Sorting ///
/// The x. /// DateTime. - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { + if (x == null) + { + return 0; + } + if (x.ProductionYear.HasValue) { return x.ProductionYear.Value; diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs index 7739d0418..af3bc2750 100644 --- a/Emby.Server.Implementations/Sorting/RandomComparer.cs +++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// The x. /// The y. /// System.Int32. - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return Guid.NewGuid().CompareTo(Guid.NewGuid()); } diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs index dde44333d..129315303 100644 --- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs +++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index b9205ee07..4123a59f8 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs index f745e193b..8d30716d3 100644 --- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/StartDateComparer.cs b/Emby.Server.Implementations/Sorting/StartDateComparer.cs index 558a3d351..c3df7c47e 100644 --- a/Emby.Server.Implementations/Sorting/StartDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/StartDateComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index 5766dc542..01445c525 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index 7c2ad2477..12efff261 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 72c0a838e..993456196 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 829df64bf..a837f09ca 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index db5265e79..750f00168 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 653b1381b..2351b7d8c 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #nullable enable using System; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index b4154b361..45559fce9 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1762,9 +1762,9 @@ namespace Jellyfin.Api.Controllers private static FileSystemMetadata? GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem) { - var folder = Path.GetDirectoryName(playlist); + var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException("Path can't be a root directory.", nameof(playlist)); - var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty; + var filePrefix = Path.GetFileNameWithoutExtension(playlist); try { diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 0879cbd18..7cb015993 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -380,7 +380,9 @@ namespace Jellyfin.Api.Helpers /// The output file path. private void DeleteHlsPartialStreamFiles(string outputFilePath) { - var directory = Path.GetDirectoryName(outputFilePath); + var directory = Path.GetDirectoryName(outputFilePath) + ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath)); + var name = Path.GetFileNameWithoutExtension(outputFilePath); var filesToDelete = _fileSystem.GetFilePaths(directory) diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs index e33e552ed..7b32d76ba 100644 --- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs +++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs @@ -145,7 +145,8 @@ namespace Jellyfin.Api.Models.PlaybackDtos var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var downloadPositionTicks = job.DownloadPositionTicks ?? 0; - var path = job.Path; + var path = job.Path ?? throw new ArgumentException("Path can't be null."); + var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index a15a38177..96bd2ccc4 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -82,11 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name); - var config = File.Exists(Path.Combine(userDataDir, "config.xml")) - ? (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml")) + var configPath = Path.Combine(userDataDir, "config.xml"); + var config = File.Exists(configPath) + ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration() : new UserConfiguration(); - var policy = File.Exists(Path.Combine(userDataDir, "policy.xml")) - ? (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml")) + + var policyPath = Path.Combine(userDataDir, "policy.xml"); + var policy = File.Exists(policyPath) + ? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy() : new UserPolicy(); policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace( "Emby.Server.Implementations.Library", diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs index 48bd9522a..1853896ee 100644 --- a/MediaBrowser.Controller/Extensions/StringExtensions.cs +++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs index a60dc2ea1..6b896b41f 100644 --- a/MediaBrowser.Controller/Net/ISessionContext.cs +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -10,10 +10,10 @@ namespace MediaBrowser.Controller.Net { SessionInfo GetSession(object requestContext); - User GetUser(object requestContext); + User? GetUser(object requestContext); SessionInfo GetSession(HttpContext requestContext); - User GetUser(HttpContext requestContext); + User? GetUser(HttpContext requestContext); } } diff --git a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs index 727cbe639..07fe1ea8a 100644 --- a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs @@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Sorting /// /// Interface IBaseItemComparer. /// - public interface IBaseItemComparer : IComparer + public interface IBaseItemComparer : IComparer { /// /// Gets the name. diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index e5c26430a..be4f1e16b 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -25,7 +24,7 @@ namespace MediaBrowser.Model.IO /// /// The filename. /// System.String. - string ResolveShortcut(string filename); + string? ResolveShortcut(string filename); /// /// Creates the shortcut. @@ -160,7 +159,7 @@ namespace MediaBrowser.Model.IO /// All found files. IEnumerable GetFiles(string path, bool recursive = false); - IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable GetFiles(string path, IReadOnlyList? extensions, bool enableCaseSensitiveExtensions, bool recursive); /// /// Gets the file system entries. @@ -186,7 +185,7 @@ namespace MediaBrowser.Model.IO /// IEnumerable<System.String>. IEnumerable GetFilePaths(string path, bool recursive = false); - IEnumerable GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive); /// /// Gets the file system entry paths. diff --git a/MediaBrowser.Model/IO/IShortcutHandler.cs b/MediaBrowser.Model/IO/IShortcutHandler.cs index 14d5c4b62..2c364a962 100644 --- a/MediaBrowser.Model/IO/IShortcutHandler.cs +++ b/MediaBrowser.Model/IO/IShortcutHandler.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.IO /// /// The shortcut path. /// System.String. - string Resolve(string shortcutPath); + string? Resolve(string shortcutPath); /// /// Creates the specified shortcut path. diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs index 0e09db16e..f900da556 100644 --- a/MediaBrowser.Model/IO/IStreamHelper.cs +++ b/MediaBrowser.Model/IO/IStreamHelper.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Model.IO { public interface IStreamHelper { - Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken); + Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken); Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs index cbd60cca1..db9fba696 100644 --- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs +++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Tasks /// /// Fires when the trigger condition is satisfied and the task should run. /// - event EventHandler Triggered; + event EventHandler? Triggered; /// /// Gets or sets the options of this task. From e1f70860778687703fcc0e950fb1496afa22775e Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 20 May 2021 20:39:22 -0400 Subject: [PATCH 248/612] Remove unnecessary query class --- Jellyfin.Api/Controllers/DevicesController.cs | 3 +-- .../Devices/DeviceManager.cs | 13 +++++------- .../Devices/IDeviceManager.cs | 5 +++-- MediaBrowser.Model/Devices/DeviceQuery.cs | 21 ------------------- 4 files changed, 9 insertions(+), 33 deletions(-) delete mode 100644 MediaBrowser.Model/Devices/DeviceQuery.cs diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 99f8ede3a..4cfae568a 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -51,8 +51,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) { - var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; - return await _deviceManager.GetDevices(deviceQuery); + return await _deviceManager.GetDevicesForUser(userId, supportsSync); } /// diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index 4758f24f3..bde8eb86e 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -91,7 +91,7 @@ namespace Jellyfin.Server.Implementations.Devices } /// - public async Task> GetDevices(DeviceQuery query) + public async Task> GetDevicesForUser(Guid? userId, bool? supportsSync) { await using var dbContext = _dbProvider.CreateContext(); var sessions = dbContext.Devices @@ -100,17 +100,14 @@ namespace Jellyfin.Server.Implementations.Devices .ThenByDescending(d => d.DateLastActivity) .AsAsyncEnumerable(); - // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger. - if (query.SupportsSync.HasValue) + if (supportsSync.HasValue) { - var val = query.SupportsSync.Value; - - sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val); + sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == supportsSync.Value); } - if (!query.UserId.Equals(Guid.Empty)) + if (userId.HasValue) { - var user = _userManager.GetUserById(query.UserId); + var user = _userManager.GetUserById(userId.Value); sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 26afd9394..28612cea3 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -41,9 +41,10 @@ namespace MediaBrowser.Controller.Devices /// /// Gets the devices. /// - /// The query. + /// The user's id, or null. + /// A value indicating whether the device supports sync, or null. /// IEnumerable<DeviceInfo>. - Task> GetDevices(DeviceQuery query); + Task> GetDevicesForUser(Guid? userId, bool? supportsSync); /// /// Determines whether this instance [can access device] the specified user identifier. diff --git a/MediaBrowser.Model/Devices/DeviceQuery.cs b/MediaBrowser.Model/Devices/DeviceQuery.cs deleted file mode 100644 index 64e366a56..000000000 --- a/MediaBrowser.Model/Devices/DeviceQuery.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Devices -{ - public class DeviceQuery - { - /// - /// Gets or sets a value indicating whether [supports synchronize]. - /// - /// null if [supports synchronize] contains no value, true if [supports synchronize]; otherwise, false. - public bool? SupportsSync { get; set; } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public Guid UserId { get; set; } - } -} From ab63a7745ce43ddab8ab48dd04076773bebe69cd Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 20 May 2021 20:48:41 -0400 Subject: [PATCH 249/612] Add PaginatedQuery abstract class, change startIndex to skip --- .../Controllers/ActivityLogController.cs | 2 +- Jellyfin.Data/Queries/ActivityLogQuery.cs | 12 +----------- Jellyfin.Data/Queries/PaginatedQuery.cs | 18 ++++++++++++++++++ .../Activity/ActivityManager.cs | 2 +- 4 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 Jellyfin.Data/Queries/PaginatedQuery.cs diff --git a/Jellyfin.Api/Controllers/ActivityLogController.cs b/Jellyfin.Api/Controllers/ActivityLogController.cs index b429cebec..ae45f647f 100644 --- a/Jellyfin.Api/Controllers/ActivityLogController.cs +++ b/Jellyfin.Api/Controllers/ActivityLogController.cs @@ -47,7 +47,7 @@ namespace Jellyfin.Api.Controllers { return await _activityManager.GetPagedResultAsync(new ActivityLogQuery { - StartIndex = startIndex, + Skip = startIndex, Limit = limit, MinDate = minDate, HasUserId = hasUserId diff --git a/Jellyfin.Data/Queries/ActivityLogQuery.cs b/Jellyfin.Data/Queries/ActivityLogQuery.cs index 92919d3a5..f1af099d3 100644 --- a/Jellyfin.Data/Queries/ActivityLogQuery.cs +++ b/Jellyfin.Data/Queries/ActivityLogQuery.cs @@ -5,18 +5,8 @@ namespace Jellyfin.Data.Queries /// /// A class representing a query to the activity logs. /// - public class ActivityLogQuery + public class ActivityLogQuery : PaginatedQuery { - /// - /// Gets or sets the index to start at. - /// - public int? StartIndex { get; set; } - - /// - /// Gets or sets the maximum number of items to include. - /// - public int? Limit { get; set; } - /// /// Gets or sets a value indicating whether to take entries with a user id. /// diff --git a/Jellyfin.Data/Queries/PaginatedQuery.cs b/Jellyfin.Data/Queries/PaginatedQuery.cs new file mode 100644 index 000000000..58267ebe7 --- /dev/null +++ b/Jellyfin.Data/Queries/PaginatedQuery.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Data.Queries +{ + /// + /// An abstract class for paginated queries. + /// + public abstract class PaginatedQuery + { + /// + /// Gets or sets the index to start at. + /// + public int? Skip { get; set; } + + /// + /// Gets or sets the maximum number of items to include. + /// + public int? Limit { get; set; } + } +} diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 27360afb0..976bb4570 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -62,7 +62,7 @@ namespace Jellyfin.Server.Implementations.Activity return new QueryResult { Items = await entries - .Skip(query.StartIndex ?? 0) + .Skip(query.Skip ?? 0) .Take(query.Limit ?? 100) .AsAsyncEnumerable() .Select(ConvertToOldModel) From f4d1c3ef7ab46cb37f400ef7688dd328d5d1626e Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 20 May 2021 20:48:53 -0400 Subject: [PATCH 250/612] Add device query class --- Jellyfin.Data/Queries/DeviceQuery.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Jellyfin.Data/Queries/DeviceQuery.cs diff --git a/Jellyfin.Data/Queries/DeviceQuery.cs b/Jellyfin.Data/Queries/DeviceQuery.cs new file mode 100644 index 000000000..083e00548 --- /dev/null +++ b/Jellyfin.Data/Queries/DeviceQuery.cs @@ -0,0 +1,25 @@ +using System; + +namespace Jellyfin.Data.Queries +{ + /// + /// A query to retrieve devices. + /// + public class DeviceQuery : PaginatedQuery + { + /// + /// Gets or sets the user id of the device. + /// + public Guid? UserId { get; set; } + + /// + /// Gets or sets the device id. + /// + public string? DeviceId { get; set; } + + /// + /// Gets or sets the access token. + /// + public string? AccessToken { get; set; } + } +} From b03f2353d8bb023e659e735e12bd6230f07ca19c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 20 May 2021 20:57:10 -0400 Subject: [PATCH 251/612] Generate Access token in constructor --- Jellyfin.Data/Entities/Security/Device.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Data/Entities/Security/Device.cs b/Jellyfin.Data/Entities/Security/Device.cs index 9bff7f0cf..bb05cf5a4 100644 --- a/Jellyfin.Data/Entities/Security/Device.cs +++ b/Jellyfin.Data/Entities/Security/Device.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Globalization; namespace Jellyfin.Data.Entities.Security { @@ -13,20 +14,19 @@ namespace Jellyfin.Data.Entities.Security /// Initializes a new instance of the class. /// /// The user id. - /// The access token. /// The app name. /// The app version. /// The device name. /// The device id. - public Device(Guid userId, string accessToken, string appName, string appVersion, string deviceName, string deviceId) + public Device(Guid userId, string appName, string appVersion, string deviceName, string deviceId) { UserId = userId; - AccessToken = accessToken; AppName = appName; AppVersion = appVersion; DeviceName = deviceName; DeviceId = deviceId; + AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); DateCreated = DateTime.UtcNow; DateLastActivity = DateCreated; From a0c6f7276211ac0429877fafa400368aba1430a9 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 20 May 2021 23:56:59 -0400 Subject: [PATCH 252/612] Migrate authentication db to EF Core --- .../ApplicationHost.cs | 10 +- .../HttpServer/Security/AuthService.cs | 5 +- .../HttpServer/Security/SessionContext.cs | 12 +- .../HttpServer/WebSocketManager.cs | 2 +- .../Security/AuthenticationRepository.cs | 407 ----------- .../Session/SessionManager.cs | 80 +-- .../Auth/CustomAuthenticationHandler.cs | 10 +- .../Controllers/CollectionController.cs | 2 +- Jellyfin.Api/Controllers/DevicesController.cs | 18 +- Jellyfin.Api/Controllers/ImageController.cs | 8 +- Jellyfin.Api/Controllers/LibraryController.cs | 10 +- .../Controllers/MediaInfoController.cs | 2 +- .../Controllers/PlaystateController.cs | 6 +- Jellyfin.Api/Controllers/SessionController.cs | 6 +- .../Controllers/SubtitleController.cs | 2 +- .../Controllers/UniversalAudioController.cs | 4 +- Jellyfin.Api/Controllers/UserController.cs | 42 +- .../Controllers/UserViewsController.cs | 5 +- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 2 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 6 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 4 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../Devices/DeviceManager.cs | 73 ++ Jellyfin.Server.Implementations/JellyfinDb.cs | 9 + .../20210521032224_AddDevices.Designer.cs | 645 ++++++++++++++++++ .../Migrations/20210521032224_AddDevices.cs | 126 ++++ .../Migrations/JellyfinDbModelSnapshot.cs | 114 +++- .../Security/AuthorizationContext.cs | 102 ++- .../Users/DeviceAccessEntryPoint.cs | 22 +- Jellyfin.Server/CoreAppHost.cs | 3 + .../Devices/IDeviceManager.cs | 19 + MediaBrowser.Controller/Net/IAuthService.cs | 3 +- .../Net/IAuthorizationContext.cs | 9 +- .../Security/AuthenticationInfoQuery.cs | 53 -- .../Security/IAuthenticationRepository.cs | 39 -- .../Session/ISessionManager.cs | 19 +- MediaBrowser.Model/Devices/DeviceInfo.cs | 5 + .../Auth/CustomAuthenticationHandlerTests.cs | 2 +- 38 files changed, 1172 insertions(+), 716 deletions(-) delete mode 100644 Emby.Server.Implementations/Security/AuthenticationRepository.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.cs rename {Emby.Server.Implementations/HttpServer => Jellyfin.Server.Implementations}/Security/AuthorizationContext.cs (64%) delete mode 100644 MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs delete mode 100644 MediaBrowser.Controller/Security/IAuthenticationRepository.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 213890c67..a69c4d035 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -36,7 +36,6 @@ using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Plugins; using Emby.Server.Implementations.QuickConnect; using Emby.Server.Implementations.ScheduledTasks; -using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SyncPlay; @@ -57,7 +56,6 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -73,7 +71,6 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.Resolvers; -using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; @@ -599,8 +596,6 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); @@ -657,8 +652,7 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); - ServiceCollection.AddSingleton(); + ServiceCollection.AddScoped(); ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); @@ -687,8 +681,6 @@ namespace Emby.Server.Implementations _mediaEncoder = Resolve(); _sessionManager = Resolve(); - ((AuthenticationRepository)Resolve()).Initialize(); - SetStaticProperties(); var userDataRepo = (SqliteUserDataRepository)Resolve(); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 9afabf527..e2ad07177 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System.Threading.Tasks; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; @@ -17,9 +18,9 @@ namespace Emby.Server.Implementations.HttpServer.Security _authorizationContext = authorizationContext; } - public AuthorizationInfo Authenticate(HttpRequest request) + public async Task Authenticate(HttpRequest request) { - var auth = _authorizationContext.GetAuthorizationInfo(request); + var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false); if (!auth.HasToken) { diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 414ba7ca0..cd1b9cba0 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -24,12 +24,18 @@ namespace Emby.Server.Implementations.HttpServer.Security _sessionManager = sessionManager; } - public Task GetSession(HttpContext requestContext) + public async Task GetSession(HttpContext requestContext) { - var authorization = _authContext.GetAuthorizationInfo(requestContext); + var authorization = await _authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false); var user = authorization.User; - return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user); + return await _sessionManager.LogSessionActivity( + authorization.Client, + authorization.Version, + authorization.DeviceId, + authorization.Device, + requestContext.GetNormalizedRemoteIp().ToString(), + user).ConfigureAwait(false); } public Task GetSession(object requestContext) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 1bee1ac31..b71ffdaee 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.HttpServer /// public async Task WebSocketRequestHandler(HttpContext context) { - _ = _authService.Authenticate(context.Request); + _ = await _authService.Authenticate(context.Request).ConfigureAwait(false); try { _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs deleted file mode 100644 index 0d0a2b1df..000000000 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ /dev/null @@ -1,407 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using Emby.Server.Implementations.Data; -using Jellyfin.Data.Entities.Security; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Querying; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Security -{ - public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository - { - public AuthenticationRepository(ILogger logger, IServerConfigurationManager config) - : base(logger) - { - DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db"); - } - - public void Initialize() - { - string[] queries = - { - "create table if not exists Tokens (Id INTEGER PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT NOT NULL, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, DeviceName TEXT NOT NULL, UserId TEXT, UserName TEXT, IsActive BIT NOT NULL, DateCreated DATETIME NOT NULL, DateLastActivity DATETIME NOT NULL)", - "create table if not exists Devices (Id TEXT NOT NULL PRIMARY KEY, CustomName TEXT, Capabilities TEXT)", - "drop index if exists idx_AccessTokens", - "drop index if exists Tokens1", - "drop index if exists Tokens2", - - "create index if not exists Tokens3 on Tokens (AccessToken, DateLastActivity)", - "create index if not exists Tokens4 on Tokens (Id, DateLastActivity)", - "create index if not exists Devices1 on Devices (Id)" - }; - - using (var connection = GetConnection()) - { - var tableNewlyCreated = !TableExists(connection, "Tokens"); - - connection.RunQueries(queries); - - TryMigrate(connection, tableNewlyCreated); - } - } - - private void TryMigrate(ManagedConnection connection, bool tableNewlyCreated) - { - try - { - if (tableNewlyCreated && TableExists(connection, "AccessTokens")) - { - connection.RunInTransaction( - db => - { - var existingColumnNames = GetColumnNames(db, "AccessTokens"); - - AddColumn(db, "AccessTokens", "UserName", "TEXT", existingColumnNames); - AddColumn(db, "AccessTokens", "DateLastActivity", "DATETIME", existingColumnNames); - AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames); - }, TransactionMode); - - connection.RunQueries(new[] - { - "update accesstokens set DateLastActivity=DateCreated where DateLastActivity is null", - "update accesstokens set DeviceName='Unknown' where DeviceName is null", - "update accesstokens set AppName='Unknown' where AppName is null", - "update accesstokens set AppVersion='1' where AppVersion is null", - "INSERT INTO Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) SELECT AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity FROM AccessTokens where deviceid not null and devicename not null and appname not null and isactive=1" - }); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating authentication database"); - } - } - - public void Create(AuthenticationInfo info) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)")) - { - statement.TryBind("@AccessToken", info.AccessToken); - - statement.TryBind("@DeviceId", info.DeviceId); - statement.TryBind("@AppName", info.AppName); - statement.TryBind("@AppVersion", info.AppVersion); - statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture)); - statement.TryBind("@UserName", info.UserName); - statement.TryBind("@IsActive", true); - statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); - statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - - public void Update(AuthenticationInfo info) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id")) - { - statement.TryBind("@Id", info.Id); - - statement.TryBind("@AccessToken", info.AccessToken); - - statement.TryBind("@DeviceId", info.DeviceId); - statement.TryBind("@AppName", info.AppName); - statement.TryBind("@AppVersion", info.AppVersion); - statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture)); - statement.TryBind("@UserName", info.UserName); - statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); - statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - - public void Delete(AuthenticationInfo info) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id")) - { - statement.TryBind("@Id", info.Id); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - - private const string BaseSelectText = "select Tokens.Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, DateCreated, DateLastActivity, Devices.CustomName from Tokens left join Devices on Tokens.DeviceId=Devices.Id"; - - private static void BindAuthenticationQueryParams(AuthenticationInfoQuery query, IStatement statement) - { - if (!string.IsNullOrEmpty(query.AccessToken)) - { - statement.TryBind("@AccessToken", query.AccessToken); - } - - if (!query.UserId.Equals(Guid.Empty)) - { - statement.TryBind("@UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture)); - } - - if (!string.IsNullOrEmpty(query.DeviceId)) - { - statement.TryBind("@DeviceId", query.DeviceId); - } - } - - public QueryResult Get(AuthenticationInfoQuery query) - { - if (query == null) - { - throw new ArgumentNullException(nameof(query)); - } - - var commandText = BaseSelectText; - - var whereClauses = new List(); - - if (!string.IsNullOrEmpty(query.AccessToken)) - { - whereClauses.Add("AccessToken=@AccessToken"); - } - - if (!string.IsNullOrEmpty(query.DeviceId)) - { - whereClauses.Add("DeviceId=@DeviceId"); - } - - if (!query.UserId.Equals(Guid.Empty)) - { - whereClauses.Add("UserId=@UserId"); - } - - if (query.HasUser.HasValue) - { - if (query.HasUser.Value) - { - whereClauses.Add("UserId not null"); - } - else - { - whereClauses.Add("UserId is null"); - } - } - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - commandText += whereTextWithoutPaging; - - commandText += " ORDER BY DateLastActivity desc"; - - if (query.Limit.HasValue || query.StartIndex.HasValue) - { - var offset = query.StartIndex ?? 0; - - if (query.Limit.HasValue || offset > 0) - { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); - } - - if (offset > 0) - { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); - } - } - - var statementTexts = new[] - { - commandText, - "select count (Id) from Tokens" + whereTextWithoutPaging - }; - - var list = new List(); - var result = new QueryResult(); - using (var connection = GetConnection(true)) - { - connection.RunInTransaction( - db => - { - var statements = PrepareAll(db, statementTexts); - - using (var statement = statements[0]) - { - BindAuthenticationQueryParams(query, statement); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(Get(row)); - } - - using (var totalCountStatement = statements[1]) - { - BindAuthenticationQueryParams(query, totalCountStatement); - - result.TotalRecordCount = totalCountStatement.ExecuteQuery() - .SelectScalarInt() - .First(); - } - } - }, - ReadTransactionMode); - } - - result.Items = list; - return result; - } - - private static AuthenticationInfo Get(IReadOnlyList reader) - { - var info = new AuthenticationInfo - { - Id = reader[0].ToInt64(), - AccessToken = reader[1].ToString() - }; - - if (reader[2].SQLiteType != SQLiteType.Null) - { - info.DeviceId = reader[2].ToString(); - } - - if (reader[3].SQLiteType != SQLiteType.Null) - { - info.AppName = reader[3].ToString(); - } - - if (reader[4].SQLiteType != SQLiteType.Null) - { - info.AppVersion = reader[4].ToString(); - } - - if (reader[5].SQLiteType != SQLiteType.Null) - { - info.DeviceName = reader[5].ToString(); - } - - if (reader[6].SQLiteType != SQLiteType.Null) - { - info.UserId = new Guid(reader[6].ToString()); - } - - if (reader[7].SQLiteType != SQLiteType.Null) - { - info.UserName = reader[7].ToString(); - } - - info.DateCreated = reader[8].ReadDateTime(); - - if (reader[9].SQLiteType != SQLiteType.Null) - { - info.DateLastActivity = reader[9].ReadDateTime(); - } - else - { - info.DateLastActivity = info.DateCreated; - } - - if (reader[10].SQLiteType != SQLiteType.Null) - { - info.DeviceName = reader[10].ToString(); - } - - return info; - } - - public DeviceOptions GetDeviceOptions(string deviceId) - { - using (var connection = GetConnection(true)) - { - return connection.RunInTransaction( - db => - { - using (var statement = base.PrepareStatement(db, "select CustomName from Devices where Id=@DeviceId")) - { - statement.TryBind("@DeviceId", deviceId); - - var result = new DeviceOptions(deviceId); - - foreach (var row in statement.ExecuteQuery()) - { - if (row[0].SQLiteType != SQLiteType.Null) - { - result.CustomName = row[0].ToString(); - } - } - - return result; - } - }, ReadTransactionMode); - } - } - - public void UpdateDeviceOptions(string deviceId, DeviceOptions options) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))")) - { - statement.TryBind("@Id", deviceId); - - if (string.IsNullOrWhiteSpace(options.CustomName)) - { - statement.TryBindNull("@CustomName"); - } - else - { - statement.TryBind("@CustomName", options.CustomName); - } - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } -} diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 2fb5040f9..92ef65bf1 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -11,6 +11,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; @@ -23,7 +24,6 @@ using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Session; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -52,7 +52,6 @@ namespace Emby.Server.Implementations.Session private readonly IImageProcessor _imageProcessor; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerApplicationHost _appHost; - private readonly IAuthenticationRepository _authRepo; private readonly IDeviceManager _deviceManager; /// @@ -75,7 +74,6 @@ namespace Emby.Server.Implementations.Session IDtoService dtoService, IImageProcessor imageProcessor, IServerApplicationHost appHost, - IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) { @@ -88,7 +86,6 @@ namespace Emby.Server.Implementations.Session _dtoService = dtoService; _imageProcessor = imageProcessor; _appHost = appHost; - _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; @@ -1486,7 +1483,7 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("User is at their maximum number of sessions."); } - var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); + var token = await GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false); var session = await LogSessionActivity( request.App, @@ -1509,21 +1506,21 @@ namespace Emby.Server.Implementations.Session return returnResult; } - private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) + private async Task GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) { - var existing = _authRepo.Get( - new AuthenticationInfoQuery + var existing = (await _deviceManager.GetDevices( + new DeviceQuery { DeviceId = deviceId, UserId = user.Id, Limit = 1 - }).Items.FirstOrDefault(); + }).ConfigureAwait(false)).Items.FirstOrDefault(); - var allExistingForDevice = _authRepo.Get( - new AuthenticationInfoQuery + var allExistingForDevice = (await _deviceManager.GetDevices( + new DeviceQuery { DeviceId = deviceId - }).Items; + }).ConfigureAwait(false)).Items; foreach (var auth in allExistingForDevice) { @@ -1531,7 +1528,7 @@ namespace Emby.Server.Implementations.Session { try { - Logout(auth); + await Logout(auth).ConfigureAwait(false); } catch (Exception ex) { @@ -1546,29 +1543,14 @@ namespace Emby.Server.Implementations.Session return existing.AccessToken; } - var now = DateTime.UtcNow; - - var newToken = new AuthenticationInfo - { - AppName = app, - AppVersion = appVersion, - DateCreated = now, - DateLastActivity = now, - DeviceId = deviceId, - DeviceName = deviceName, - UserId = user.Id, - AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), - UserName = user.Username - }; - _logger.LogInformation("Creating new access token for user {0}", user.Id); - _authRepo.Create(newToken); + var device = await _deviceManager.CreateDevice(new Device(user.Id, app, appVersion, deviceName, deviceId)).ConfigureAwait(false); - return newToken.AccessToken; + return device.AccessToken; } /// - public void Logout(string accessToken) + public async Task Logout(string accessToken) { CheckDisposed(); @@ -1577,27 +1559,27 @@ namespace Emby.Server.Implementations.Session throw new ArgumentNullException(nameof(accessToken)); } - var existing = _authRepo.Get( - new AuthenticationInfoQuery + var existing = (await _deviceManager.GetDevices( + new DeviceQuery { Limit = 1, AccessToken = accessToken - }).Items; + }).ConfigureAwait(false)).Items; if (existing.Count > 0) { - Logout(existing[0]); + await Logout(existing[0]).ConfigureAwait(false); } } /// - public void Logout(AuthenticationInfo existing) + public async Task Logout(Device existing) { CheckDisposed(); _logger.LogInformation("Logging out access token {0}", existing.AccessToken); - _authRepo.Delete(existing); + await _deviceManager.DeleteDevice(existing).ConfigureAwait(false); var sessions = Sessions .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) @@ -1617,30 +1599,24 @@ namespace Emby.Server.Implementations.Session } /// - public void RevokeUserTokens(Guid userId, string currentAccessToken) + public async Task RevokeUserTokens(Guid userId, string currentAccessToken) { CheckDisposed(); - var existing = _authRepo.Get(new AuthenticationInfoQuery + var existing = await _deviceManager.GetDevices(new DeviceQuery { UserId = userId - }); + }).ConfigureAwait(false); foreach (var info in existing.Items) { if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase)) { - Logout(info); + await Logout(info).ConfigureAwait(false); } } } - /// - public void RevokeToken(string token) - { - Logout(token); - } - /// /// Reports the capabilities. /// @@ -1792,7 +1768,7 @@ namespace Emby.Server.Implementations.Session } /// - public Task GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) + public Task GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion) { if (info == null) { @@ -1825,20 +1801,20 @@ namespace Emby.Server.Implementations.Session } /// - public Task GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) + public async Task GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) { - var items = _authRepo.Get(new AuthenticationInfoQuery + var items = (await _deviceManager.GetDevices(new DeviceQuery { AccessToken = token, Limit = 1 - }).Items; + }).ConfigureAwait(false)).Items; if (items.Count == 0) { return null; } - return GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null); + return await GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null).ConfigureAwait(false); } /// diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index c56233794..369e846ae 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -40,11 +40,11 @@ namespace Jellyfin.Api.Auth } /// - protected override Task HandleAuthenticateAsync() + protected override async Task HandleAuthenticateAsync() { try { - var authorizationInfo = _authService.Authenticate(Request); + var authorizationInfo = await _authService.Authenticate(Request).ConfigureAwait(false); var role = UserRoles.User; if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)) { @@ -68,16 +68,16 @@ namespace Jellyfin.Api.Auth var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, Scheme.Name); - return Task.FromResult(AuthenticateResult.Success(ticket)); + return AuthenticateResult.Success(ticket); } catch (AuthenticationException ex) { _logger.LogDebug(ex, "Error authenticating with {Handler}", nameof(CustomAuthenticationHandler)); - return Task.FromResult(AuthenticateResult.NoResult()); + return AuthenticateResult.NoResult(); } catch (SecurityException ex) { - return Task.FromResult(AuthenticateResult.Fail(ex)); + return AuthenticateResult.Fail(ex); } } } diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index 852d1e9cb..8a98d856c 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -58,7 +58,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? parentId, [FromQuery] bool isLocked = false) { - var userId = _authContext.GetAuthorizationInfo(Request).UserId; + var userId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).UserId; var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions { diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 4cfae568a..8af7b8f73 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -3,8 +3,8 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Data.Entities.Security; +using Jellyfin.Data.Queries; using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; @@ -21,22 +21,18 @@ namespace Jellyfin.Api.Controllers public class DevicesController : BaseJellyfinApiController { private readonly IDeviceManager _deviceManager; - private readonly IAuthenticationRepository _authenticationRepository; private readonly ISessionManager _sessionManager; /// /// Initializes a new instance of the class. /// /// Instance of interface. - /// Instance of interface. /// Instance of interface. public DevicesController( IDeviceManager deviceManager, - IAuthenticationRepository authenticationRepository, ISessionManager sessionManager) { _deviceManager = deviceManager; - _authenticationRepository = authenticationRepository; _sessionManager = sessionManager; } @@ -111,7 +107,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, Required] string id, [FromBody, Required] DeviceOptions deviceOptions) { - var existingDeviceOptions = _deviceManager.GetDeviceOptions(id); + var existingDeviceOptions = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false); if (existingDeviceOptions == null) { return NotFound(); @@ -131,19 +127,19 @@ namespace Jellyfin.Api.Controllers [HttpDelete] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DeleteDevice([FromQuery, Required] string id) + public async Task DeleteDevice([FromQuery, Required] string id) { - var existingDevice = _deviceManager.GetDevice(id); + var existingDevice = await _deviceManager.GetDevice(id).ConfigureAwait(false); if (existingDevice == null) { return NotFound(); } - var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items; + var sessions = await _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }).ConfigureAwait(false); - foreach (var session in sessions) + foreach (var session in sessions.Items) { - _sessionManager.Logout(session); + await _sessionManager.Logout(session).ConfigureAwait(false); } return NoContent(); diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 8f7500ac6..9dc280e13 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] ImageType imageType, [FromQuery] int? index = null) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); } @@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] ImageType imageType, [FromRoute] int index) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); } @@ -190,7 +190,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] ImageType imageType, [FromQuery] int? index = null) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image."); } @@ -234,7 +234,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] ImageType imageType, [FromRoute] int index) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image."); } diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 4ed15e1d5..504f58790 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -331,10 +331,10 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public ActionResult DeleteItem(Guid itemId) + public async Task DeleteItem(Guid itemId) { var item = _libraryManager.GetItemById(itemId); - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var user = auth.User; if (!item.CanDelete(user)) @@ -361,7 +361,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) + public async Task DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) { if (ids.Length == 0) { @@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers foreach (var i in ids) { var item = _libraryManager.GetItemById(i); - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var user = auth.User; if (!item.CanDelete(user)) @@ -627,7 +627,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var user = auth.User; diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index e330f02b6..6c29e6bb8 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -123,7 +123,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, ParameterObsolete] bool? allowAudioStreamCopy, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto) { - var authInfo = _authContext.GetAuthorizationInfo(Request); + var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var profile = playbackInfoDto?.DeviceProfile; _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile); diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index cc8c630b3..6dee1c219 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -171,7 +171,8 @@ namespace Jellyfin.Api.Controllers _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) { - await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); + var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); + await _transcodingJobHelper.KillTranscodingJobs(authInfo.DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); @@ -320,7 +321,8 @@ namespace Jellyfin.Api.Controllers _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) { - await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); + var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); + await _transcodingJobHelper.KillTranscodingJobs(authInfo.DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 116d669e4..3a04cb3a4 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -471,11 +471,11 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/Logout")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult ReportSessionEnded() + public async Task ReportSessionEnded() { - AuthorizationInfo auth = _authContext.GetAuthorizationInfo(Request); + AuthorizationInfo auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); - _sessionManager.Logout(auth.Token); + await _sessionManager.Logout(auth.Token).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 1669a659d..2d5339b16 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -361,7 +361,7 @@ namespace Jellyfin.Api.Controllers long positionTicks = 0; - var accessToken = _authContext.GetAuthorizationInfo(Request).Token; + var accessToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token; while (positionTicks < runtime) { diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 679f055bc..20a02bf4a 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -116,9 +116,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool enableRedirection = true) { var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels); - _authorizationContext.GetAuthorizationInfo(Request).DeviceId = deviceId; + (await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId = deviceId; - var authInfo = _authorizationContext.GetAuthorizationInfo(Request); + var authInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false); _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile); diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index b13db4baa..8e2298bb7 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -77,11 +77,11 @@ namespace Jellyfin.Api.Controllers [HttpGet] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetUsers( + public async Task>> GetUsers( [FromQuery] bool? isHidden, [FromQuery] bool? isDisabled) { - var users = Get(isHidden, isDisabled, false, false); + var users = await Get(isHidden, isDisabled, false, false).ConfigureAwait(false); return Ok(users); } @@ -92,15 +92,15 @@ namespace Jellyfin.Api.Controllers /// An containing the public users. [HttpGet("Public")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetPublicUsers() + public async Task>> GetPublicUsers() { // If the startup wizard hasn't been completed then just return all users if (!_config.Configuration.IsStartupWizardCompleted) { - return Ok(Get(false, false, false, false)); + return Ok(await Get(false, false, false, false).ConfigureAwait(false)); } - return Ok(Get(false, false, true, true)); + return Ok(await Get(false, false, true, true).ConfigureAwait(false)); } /// @@ -141,7 +141,7 @@ namespace Jellyfin.Api.Controllers public async Task DeleteUser([FromRoute, Required] Guid userId) { var user = _userManager.GetUserById(userId); - _sessionManager.RevokeUserTokens(user.Id, null); + await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false); await _userManager.DeleteUserAsync(userId).ConfigureAwait(false); return NoContent(); } @@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public async Task> AuthenticateUserByName([FromBody, Required] AuthenticateUserByName request) { - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); try { @@ -230,7 +230,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public async Task> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request) { - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); try { @@ -271,7 +271,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromBody, Required] UpdateUserPassword request) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password."); } @@ -303,9 +303,9 @@ namespace Jellyfin.Api.Controllers await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false); - var currentToken = _authContext.GetAuthorizationInfo(Request).Token; + var currentToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token; - _sessionManager.RevokeUserTokens(user.Id, currentToken); + await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false); } return NoContent(); @@ -325,11 +325,11 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateUserEasyPassword( + public async Task UpdateUserEasyPassword( [FromRoute, Required] Guid userId, [FromBody, Required] UpdateUserEasyPassword request) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the easy password."); } @@ -343,11 +343,11 @@ namespace Jellyfin.Api.Controllers if (request.ResetPassword) { - _userManager.ResetEasyPassword(user); + await _userManager.ResetEasyPassword(user).ConfigureAwait(false); } else { - _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword); + await _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword).ConfigureAwait(false); } return NoContent(); @@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromBody, Required] UserDto updateUser) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed."); } @@ -431,8 +431,8 @@ namespace Jellyfin.Api.Controllers return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one enabled user in the system."); } - var currentToken = _authContext.GetAuthorizationInfo(Request).Token; - _sessionManager.RevokeUserTokens(user.Id, currentToken); + var currentToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token; + await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false); } await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false); @@ -456,7 +456,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromBody, Required] UserConfiguration userConfig) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User configuration update not allowed"); } @@ -555,7 +555,7 @@ namespace Jellyfin.Api.Controllers return _userManager.GetUserDto(user); } - private IEnumerable Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork) + private async Task> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork) { var users = _userManager.Users; @@ -571,7 +571,7 @@ namespace Jellyfin.Api.Controllers if (filterByDevice) { - var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; + var deviceId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId; if (!string.IsNullOrWhiteSpace(deviceId)) { diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs index 7bc5ecdf1..3d27371f6 100644 --- a/Jellyfin.Api/Controllers/UserViewsController.cs +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.UserViewDtos; @@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers /// An containing the user views. [HttpGet("Users/{userId}/Views")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetUserViews( + public async Task>> GetUserViews( [FromRoute, Required] Guid userId, [FromQuery] bool? includeExternalContent, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] presetViews, @@ -86,7 +87,7 @@ namespace Jellyfin.Api.Controllers query.PresetViews = presetViews; } - var app = _authContext.GetAuthorizationInfo(Request).Client ?? string.Empty; + var app = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Client ?? string.Empty; if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1) { query.PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows }; diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 295cfaf08..3b8dc7e31 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -468,7 +468,7 @@ namespace Jellyfin.Api.Helpers /// A containing the . public async Task OpenMediaSource(HttpRequest httpRequest, LiveStreamRequest request) { - var authInfo = _authContext.GetAuthorizationInfo(httpRequest); + var authInfo = await _authContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false); var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false); diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 3810f7477..0efd3443b 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -60,9 +60,9 @@ namespace Jellyfin.Api.Helpers /// The user id. /// Whether to restrict the user preferences. /// A whether the user can update the entry. - internal static bool AssertCanUpdateUser(IAuthorizationContext authContext, HttpRequest requestContext, Guid userId, bool restrictUserPreferences) + internal static async Task AssertCanUpdateUser(IAuthorizationContext authContext, HttpRequest requestContext, Guid userId, bool restrictUserPreferences) { - var auth = authContext.GetAuthorizationInfo(requestContext); + var auth = await authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false); var authenticatedUser = auth.User; @@ -78,7 +78,7 @@ namespace Jellyfin.Api.Helpers internal static async Task GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request) { - var authorization = authContext.GetAuthorizationInfo(request); + var authorization = await authContext.GetAuthorizationInfo(request).ConfigureAwait(false); var user = authorization.User; var session = await sessionManager.LogSessionActivity( authorization.Client, diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 8cffe9c4c..cecbd36c1 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -17,9 +17,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -101,7 +99,7 @@ namespace Jellyfin.Api.Helpers EnableDlnaHeaders = enableDlnaHeaders }; - var auth = authorizationContext.GetAuthorizationInfo(httpRequest); + var auth = await authorizationContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false); if (!auth.UserId.Equals(Guid.Empty)) { state.User = userManager.GetUserById(auth.UserId); diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 0879cbd18..242077963 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -489,7 +489,7 @@ namespace Jellyfin.Api.Helpers if (state.VideoRequest != null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { - var auth = _authorizationContext.GetAuthorizationInfo(request); + var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false); if (auth.User != null && !auth.User.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) { this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index bde8eb86e..9fd2e5ad4 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -6,6 +6,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Devices; @@ -55,6 +56,17 @@ namespace Jellyfin.Server.Implementations.Devices DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs>(new Tuple(deviceId, options))); } + /// + public async Task CreateDevice(Device device) + { + await using var dbContext = _dbProvider.CreateContext(); + + dbContext.Devices.Add(device); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + return device; + } + /// public async Task GetDeviceOptions(string deviceId) { @@ -90,6 +102,61 @@ namespace Jellyfin.Server.Implementations.Devices return deviceInfo; } + /// + public async Task> GetDevices(DeviceQuery query) + { + await using var dbContext = _dbProvider.CreateContext(); + + var devices = dbContext.Devices.AsQueryable(); + + if (query.UserId.HasValue) + { + devices = devices.Where(device => device.UserId == query.UserId.Value); + } + + if (query.DeviceId != null) + { + devices = devices.Where(device => device.DeviceId == query.DeviceId); + } + + if (query.AccessToken != null) + { + devices = devices.Where(device => device.AccessToken == query.AccessToken); + } + + if (query.Skip.HasValue) + { + devices = devices.Skip(query.Skip.Value); + } + + var count = await devices.CountAsync().ConfigureAwait(false); + + if (query.Limit.HasValue) + { + devices = devices.Take(query.Limit.Value); + } + + return new QueryResult + { + Items = await devices.ToListAsync().ConfigureAwait(false), + StartIndex = query.Skip ?? 0, + TotalRecordCount = count + }; + } + + /// + public async Task> GetDeviceInfos(DeviceQuery query) + { + var devices = await GetDevices(query).ConfigureAwait(false); + + return new QueryResult + { + Items = devices.Items.Select(ToDeviceInfo).ToList(), + StartIndex = devices.StartIndex, + TotalRecordCount = devices.TotalRecordCount + }; + } + /// public async Task> GetDevicesForUser(Guid? userId, bool? supportsSync) { @@ -117,6 +184,12 @@ namespace Jellyfin.Server.Implementations.Devices return new QueryResult(array); } + /// + public async Task DeleteDevice(Device device) + { + await using var dbContext = _dbProvider.CreateContext(); + } + /// public bool CanAccessDevice(User user, string deviceId) { diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 059e884e5..6f35a2c1c 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -214,6 +214,15 @@ namespace Jellyfin.Server.Implementations modelBuilder.Entity() .HasIndex(entity => new { entity.DeviceId, entity.DateLastActivity }); + modelBuilder.Entity() + .HasIndex(entity => new { entity.AccessToken, entity.DateLastActivity }); + + modelBuilder.Entity() + .HasIndex(entity => new { entity.UserId, entity.DeviceId }); + + modelBuilder.Entity() + .HasIndex(entity => entity.DeviceId); + modelBuilder.Entity() .HasIndex(entity => entity.DeviceId) .IsUnique(); diff --git a/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs new file mode 100644 index 000000000..e1faef7a2 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs @@ -0,0 +1,645 @@ +#pragma warning disable CS1591 +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20210521032224_AddDevices")] + partial class AddDevices + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client", "Key") + .IsUnique(); + + b.ToTable("CustomItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChromecastVersion") + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("DashboardTheme") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("EnableNextVideoInfoOverlay") + .HasColumnType("INTEGER"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("ScrollDirection") + .HasColumnType("INTEGER"); + + b.Property("ShowBackdrop") + .HasColumnType("INTEGER"); + + b.Property("ShowSidebar") + .HasColumnType("INTEGER"); + + b.Property("SkipBackwardLength") + .HasColumnType("INTEGER"); + + b.Property("SkipForwardLength") + .HasColumnType("INTEGER"); + + b.Property("TvHome") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client") + .IsUnique(); + + b.ToTable("DisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DisplayPreferencesId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisplayPreferencesId"); + + b.ToTable("HomeSection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccessToken") + .IsUnique(); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("AppName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("AppVersion") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("DateLastActivity") + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("AccessToken", "DateLastActivity"); + + b.HasIndex("DeviceId", "DateLastActivity"); + + b.HasIndex("UserId", "DeviceId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CustomName") + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .IsUnique(); + + b.ToTable("DeviceOptions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxActiveSessions") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null) + .WithMany("HomeSections") + .HasForeignKey("DisplayPreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ItemDisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.HasOne("Jellyfin.Data.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Navigation("HomeSections"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Navigation("AccessSchedules"); + + b.Navigation("DisplayPreferences"); + + b.Navigation("ItemDisplayPreferences"); + + b.Navigation("Permissions"); + + b.Navigation("Preferences"); + + b.Navigation("ProfileImage"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.cs b/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.cs new file mode 100644 index 000000000..2da8d3788 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.cs @@ -0,0 +1,126 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class AddDevices : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApiKeys", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateCreated = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false), + AccessToken = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiKeys", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DeviceOptions", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DeviceId = table.Column(type: "TEXT", nullable: false), + CustomName = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceOptions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Devices", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + AccessToken = table.Column(type: "TEXT", nullable: false), + AppName = table.Column(type: "TEXT", maxLength: 64, nullable: false), + AppVersion = table.Column(type: "TEXT", maxLength: 32, nullable: false), + DeviceName = table.Column(type: "TEXT", maxLength: 64, nullable: false), + DeviceId = table.Column(type: "TEXT", maxLength: 256, nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + DateCreated = table.Column(type: "TEXT", nullable: false), + DateLastActivity = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Devices", x => x.Id); + table.ForeignKey( + name: "FK_Devices_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiKeys_AccessToken", + schema: "jellyfin", + table: "ApiKeys", + column: "AccessToken", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceOptions_DeviceId", + schema: "jellyfin", + table: "DeviceOptions", + column: "DeviceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Devices_AccessToken_DateLastActivity", + schema: "jellyfin", + table: "Devices", + columns: new[] { "AccessToken", "DateLastActivity" }); + + migrationBuilder.CreateIndex( + name: "IX_Devices_DeviceId", + schema: "jellyfin", + table: "Devices", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_Devices_DeviceId_DateLastActivity", + schema: "jellyfin", + table: "Devices", + columns: new[] { "DeviceId", "DateLastActivity" }); + + migrationBuilder.CreateIndex( + name: "IX_Devices_UserId_DeviceId", + schema: "jellyfin", + table: "Devices", + columns: new[] { "UserId", "DeviceId" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiKeys", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "DeviceOptions", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Devices", + schema: "jellyfin"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 286eb7468..8a1ae16f8 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "5.0.3"); + .HasAnnotation("ProductVersion", "5.0.5"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -332,6 +332,107 @@ namespace Jellyfin.Server.Implementations.Migrations b.ToTable("Preferences"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccessToken") + .IsUnique(); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("AppName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("AppVersion") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("DateLastActivity") + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("AccessToken", "DateLastActivity"); + + b.HasIndex("DeviceId", "DateLastActivity"); + + b.HasIndex("UserId", "DeviceId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CustomName") + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .IsUnique(); + + b.ToTable("DeviceOptions"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => { b.Property("Id") @@ -505,6 +606,17 @@ namespace Jellyfin.Server.Implementations.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.HasOne("Jellyfin.Data.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => { b.Navigation("HomeSections"); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs similarity index 64% rename from Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs rename to Jellyfin.Server.Implementations/Security/AuthorizationContext.cs index 024404ceb..775edafc2 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs @@ -4,39 +4,39 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; -namespace Emby.Server.Implementations.HttpServer.Security +namespace Jellyfin.Server.Implementations.Security { public class AuthorizationContext : IAuthorizationContext { - private readonly IAuthenticationRepository _authRepo; + private readonly JellyfinDb _jellyfinDb; private readonly IUserManager _userManager; - public AuthorizationContext(IAuthenticationRepository authRepo, IUserManager userManager) + public AuthorizationContext(JellyfinDb jellyfinDb, IUserManager userManager) { - _authRepo = authRepo; + _jellyfinDb = jellyfinDb; _userManager = userManager; } - public AuthorizationInfo GetAuthorizationInfo(HttpContext requestContext) + public Task GetAuthorizationInfo(HttpContext requestContext) { - if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached)) + if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached) && cached != null) { - return (AuthorizationInfo)cached; + return Task.FromResult((AuthorizationInfo)cached); } return GetAuthorization(requestContext); } - public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext) + public async Task GetAuthorizationInfo(HttpRequest requestContext) { var auth = GetAuthorizationDictionary(requestContext); - var authInfo = GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query); + var authInfo = await GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query).ConfigureAwait(false); return authInfo; } @@ -45,35 +45,37 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The HTTP req. /// Dictionary{System.StringSystem.String}. - private AuthorizationInfo GetAuthorization(HttpContext httpReq) + private async Task GetAuthorization(HttpContext httpReq) { var auth = GetAuthorizationDictionary(httpReq); - var authInfo = GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query); + var authInfo = await GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query).ConfigureAwait(false); httpReq.Request.HttpContext.Items["AuthorizationInfo"] = authInfo; return authInfo; } - private AuthorizationInfo GetAuthorizationInfoFromDictionary( - in Dictionary auth, - in IHeaderDictionary headers, - in IQueryCollection queryString) + private async Task GetAuthorizationInfoFromDictionary( + IReadOnlyDictionary? auth, + IHeaderDictionary headers, + IQueryCollection queryString) { - string deviceId = null; - string device = null; - string client = null; - string version = null; - string token = null; + string? deviceId = null; + string? deviceName = null; + string? client = null; + string? version = null; + string? token = null; if (auth != null) { auth.TryGetValue("DeviceId", out deviceId); - auth.TryGetValue("Device", out device); + auth.TryGetValue("Device", out deviceName); auth.TryGetValue("Client", out client); auth.TryGetValue("Version", out version); auth.TryGetValue("Token", out token); } +#pragma warning disable CA1508 + // headers can return StringValues.Empty if (string.IsNullOrEmpty(token)) { token = headers["X-Emby-Token"]; @@ -98,7 +100,7 @@ namespace Emby.Server.Implementations.HttpServer.Security var authInfo = new AuthorizationInfo { Client = client, - Device = device, + Device = deviceName, DeviceId = deviceId, Version = version, Token = token, @@ -111,80 +113,69 @@ namespace Emby.Server.Implementations.HttpServer.Security // Request doesn't contain a token. return authInfo; } +#pragma warning restore CA1508 authInfo.HasToken = true; - var result = _authRepo.Get(new AuthenticationInfoQuery - { - AccessToken = token - }); + var device = await _jellyfinDb.Devices.FirstOrDefaultAsync(d => d.AccessToken == token).ConfigureAwait(false); - if (result.Items.Count > 0) + if (device != null) { authInfo.IsAuthenticated = true; } - var originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null; - - if (originalAuthenticationInfo != null) + if (device != null) { var updateToken = false; // TODO: Remove these checks for IsNullOrWhiteSpace if (string.IsNullOrWhiteSpace(authInfo.Client)) { - authInfo.Client = originalAuthenticationInfo.AppName; + authInfo.Client = device.AppName; } if (string.IsNullOrWhiteSpace(authInfo.DeviceId)) { - authInfo.DeviceId = originalAuthenticationInfo.DeviceId; + authInfo.DeviceId = device.DeviceId; } // Temporary. TODO - allow clients to specify that the token has been shared with a casting device - var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1; + var allowTokenInfoUpdate = !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(authInfo.Device)) { - authInfo.Device = originalAuthenticationInfo.DeviceName; + authInfo.Device = device.DeviceName; } - else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) + else if (!string.Equals(authInfo.Device, device.DeviceName, StringComparison.OrdinalIgnoreCase)) { if (allowTokenInfoUpdate) { updateToken = true; - originalAuthenticationInfo.DeviceName = authInfo.Device; + device.DeviceName = authInfo.Device; } } if (string.IsNullOrWhiteSpace(authInfo.Version)) { - authInfo.Version = originalAuthenticationInfo.AppVersion; + authInfo.Version = device.AppVersion; } - else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase)) + else if (!string.Equals(authInfo.Version, device.AppVersion, StringComparison.OrdinalIgnoreCase)) { if (allowTokenInfoUpdate) { updateToken = true; - originalAuthenticationInfo.AppVersion = authInfo.Version; + device.AppVersion = authInfo.Version; } } - if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3) + if ((DateTime.UtcNow - device.DateLastActivity).TotalMinutes > 3) { - originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow; + device.DateLastActivity = DateTime.UtcNow; updateToken = true; } - if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty)) + if (!device.UserId.Equals(Guid.Empty)) { - authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId); - - if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase)) - { - originalAuthenticationInfo.UserName = authInfo.User.Username; - updateToken = true; - } - + authInfo.User = _userManager.GetUserById(device.UserId); authInfo.IsApiKey = false; } else @@ -194,7 +185,8 @@ namespace Emby.Server.Implementations.HttpServer.Security if (updateToken) { - _authRepo.Update(originalAuthenticationInfo); + _jellyfinDb.Devices.Update(device); + await _jellyfinDb.SaveChangesAsync().ConfigureAwait(false); } } @@ -206,7 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorizationDictionary(HttpContext httpReq) + private Dictionary? GetAuthorizationDictionary(HttpContext httpReq) { var auth = httpReq.Request.Headers["X-Emby-Authorization"]; @@ -223,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorizationDictionary(HttpRequest httpReq) + private Dictionary? GetAuthorizationDictionary(HttpRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -240,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// /// The authorization header. /// Dictionary{System.StringSystem.String}. - private Dictionary GetAuthorization(string authorizationHeader) + private Dictionary? GetAuthorization(string? authorizationHeader) { if (authorizationHeader == null) { diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs index dbba80c21..a471ea1d5 100644 --- a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs @@ -4,10 +4,10 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; namespace Jellyfin.Server.Implementations.Users @@ -15,14 +15,12 @@ namespace Jellyfin.Server.Implementations.Users public sealed class DeviceAccessEntryPoint : IServerEntryPoint { private readonly IUserManager _userManager; - private readonly IAuthenticationRepository _authRepo; private readonly IDeviceManager _deviceManager; private readonly ISessionManager _sessionManager; - public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + public DeviceAccessEntryPoint(IUserManager userManager, IDeviceManager deviceManager, ISessionManager sessionManager) { _userManager = userManager; - _authRepo = authRepo; _deviceManager = deviceManager; _sessionManager = sessionManager; } @@ -38,27 +36,27 @@ namespace Jellyfin.Server.Implementations.Users { } - private void OnUserUpdated(object? sender, GenericEventArgs e) + private async void OnUserUpdated(object? sender, GenericEventArgs e) { var user = e.Argument; if (!user.HasPermission(PermissionKind.EnableAllDevices)) { - UpdateDeviceAccess(user); + await UpdateDeviceAccess(user).ConfigureAwait(false); } } - private void UpdateDeviceAccess(User user) + private async Task UpdateDeviceAccess(User user) { - var existing = _authRepo.Get(new AuthenticationInfoQuery + var existing = (await _deviceManager.GetDevices(new DeviceQuery { UserId = user.Id - }).Items; + }).ConfigureAwait(false)).Items; - foreach (var authInfo in existing) + foreach (var device in existing) { - if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + if (!string.IsNullOrEmpty(device.DeviceId) && !_deviceManager.CanAccessDevice(user, device.DeviceId)) { - _sessionManager.Logout(authInfo); + await _sessionManager.Logout(device).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index b20acae32..362be8531 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -11,6 +11,7 @@ using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Devices; using Jellyfin.Server.Implementations.Events; +using Jellyfin.Server.Implementations.Security; using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; using MediaBrowser.Controller.BaseItemManager; @@ -94,6 +95,8 @@ namespace Jellyfin.Server ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(); + ServiceCollection.AddScoped(); + base.RegisterServices(); } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 28612cea3..6ff4422d2 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; @@ -17,6 +18,13 @@ namespace MediaBrowser.Controller.Devices { event EventHandler>> DeviceOptionsUpdated; + /// + /// Creates a new device. + /// + /// The device to create. + /// A representing the creation of the device. + Task CreateDevice(Device device); + /// /// Saves the capabilities. /// @@ -38,6 +46,15 @@ namespace MediaBrowser.Controller.Devices /// DeviceInfo. Task GetDevice(string id); + /// + /// Gets devices based on the provided query. + /// + /// The device query. + /// A representing the retrieval of the devices. + Task> GetDevices(DeviceQuery query); + + Task> GetDeviceInfos(DeviceQuery query); + /// /// Gets the devices. /// @@ -46,6 +63,8 @@ namespace MediaBrowser.Controller.Devices /// IEnumerable<DeviceInfo>. Task> GetDevicesForUser(Guid? userId, bool? supportsSync); + Task DeleteDevice(Device device); + /// /// Determines whether this instance [can access device] the specified user identifier. /// diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index d15c6d318..a7da740e0 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net @@ -12,6 +13,6 @@ namespace MediaBrowser.Controller.Net /// /// The request. /// Authorization information. Null if unauthenticated. - AuthorizationInfo Authenticate(HttpRequest request); + Task Authenticate(HttpRequest request); } } diff --git a/MediaBrowser.Controller/Net/IAuthorizationContext.cs b/MediaBrowser.Controller/Net/IAuthorizationContext.cs index 0d310548d..5c6ca43d1 100644 --- a/MediaBrowser.Controller/Net/IAuthorizationContext.cs +++ b/MediaBrowser.Controller/Net/IAuthorizationContext.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net @@ -11,14 +12,14 @@ namespace MediaBrowser.Controller.Net /// Gets the authorization information. /// /// The request context. - /// AuthorizationInfo. - AuthorizationInfo GetAuthorizationInfo(HttpContext requestContext); + /// A task containing the authorization info. + Task GetAuthorizationInfo(HttpContext requestContext); /// /// Gets the authorization information. /// /// The request context. - /// AuthorizationInfo. - AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext); + /// A containing the authorization info. + Task GetAuthorizationInfo(HttpRequest requestContext); } } diff --git a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs deleted file mode 100644 index 3af6a525c..000000000 --- a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs +++ /dev/null @@ -1,53 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Controller.Security -{ - public class AuthenticationInfoQuery - { - /// - /// Gets or sets the device identifier. - /// - /// The device identifier. - public string DeviceId { get; set; } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public Guid UserId { get; set; } - - /// - /// Gets or sets the access token. - /// - /// The access token. - public string AccessToken { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is active. - /// - /// null if [is active] contains no value, true if [is active]; otherwise, false. - public bool? IsActive { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has user. - /// - /// null if [has user] contains no value, true if [has user]; otherwise, false. - public bool? HasUser { get; set; } - - /// - /// Gets or sets the start index. - /// - /// The start index. - public int? StartIndex { get; set; } - - /// - /// Gets or sets the limit. - /// - /// The limit. - public int? Limit { get; set; } - } -} diff --git a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs deleted file mode 100644 index 9685005ba..000000000 --- a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs +++ /dev/null @@ -1,39 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using Jellyfin.Data.Entities.Security; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Controller.Security -{ - public interface IAuthenticationRepository - { - /// - /// Creates the specified information. - /// - /// The information. - /// Task. - void Create(AuthenticationInfo info); - - /// - /// Updates the specified information. - /// - /// The information. - /// Task. - void Update(AuthenticationInfo info); - - /// - /// Gets the specified query. - /// - /// The query. - /// QueryResult{AuthenticationInfo}. - QueryResult Get(AuthenticationInfoQuery query); - - void Delete(AuthenticationInfo info); - - DeviceOptions GetDeviceOptions(string deviceId); - - void UpdateDeviceOptions(string deviceId, DeviceOptions options); - } -} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 30a83d6e7..1000da247 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -6,10 +6,12 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Devices; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; @@ -325,26 +327,23 @@ namespace MediaBrowser.Controller.Session /// The remote endpoint. /// The application version. /// Task<SessionInfo>. - Task GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion); + Task GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion); /// /// Logouts the specified access token. /// /// The access token. - void Logout(string accessToken); + /// A representing the log out process. + Task Logout(string accessToken); - void Logout(AuthenticationInfo accessToken); + Task Logout(Device accessToken); /// /// Revokes the user tokens. /// - void RevokeUserTokens(Guid userId, string currentAccessToken); - - /// - /// Revokes the token. - /// - /// The identifier. - void RevokeToken(string id); + /// The user's id. + /// The current access token. + Task RevokeUserTokens(Guid userId, string currentAccessToken); void CloseIfNeeded(SessionInfo session); } diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index 0cccf931c..7a1c7a738 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -15,6 +15,11 @@ namespace MediaBrowser.Model.Devices public string Name { get; set; } + /// + /// Gets or sets the access token. + /// + public string AccessToken { get; set; } + /// /// Gets or sets the identifier. /// diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs index de03aa5f5..cd03958b6 100644 --- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs @@ -136,7 +136,7 @@ namespace Jellyfin.Api.Tests.Auth _jellyfinAuthServiceMock.Setup( a => a.Authenticate( It.IsAny())) - .Returns(authorizationInfo); + .Returns(Task.FromResult(authorizationInfo)); return authorizationInfo; } From 37a8a82ac54a795402006b116a9ef5ece4a3eb6c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 21 May 2021 00:19:54 -0400 Subject: [PATCH 253/612] Make methods static in AuthenticationContext --- .../Security/AuthorizationContext.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs index 688ff7168..b10b7a998 100644 --- a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs +++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs @@ -199,7 +199,7 @@ namespace Jellyfin.Server.Implementations.Security /// /// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary? GetAuthorizationDictionary(HttpContext httpReq) + private static Dictionary? GetAuthorizationDictionary(HttpContext httpReq) { var auth = httpReq.Request.Headers["X-Emby-Authorization"]; @@ -216,7 +216,7 @@ namespace Jellyfin.Server.Implementations.Security ///
/// The HTTP req. /// Dictionary{System.StringSystem.String}. - private Dictionary? GetAuthorizationDictionary(HttpRequest httpReq) + private static Dictionary? GetAuthorizationDictionary(HttpRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -233,7 +233,7 @@ namespace Jellyfin.Server.Implementations.Security ///
/// The authorization header. /// Dictionary{System.StringSystem.String}. - private Dictionary? GetAuthorization(ReadOnlySpan authorizationHeader) + private static Dictionary? GetAuthorization(ReadOnlySpan authorizationHeader) { var firstSpace = authorizationHeader.IndexOf(' '); From 9b8eb1ba53b8fc18b32217d4e1208b7b9604b644 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 21 May 2021 13:43:49 +0200 Subject: [PATCH 254/612] Remove dead code --- MediaBrowser.Controller/Library/Profiler.cs | 85 ----------- MediaBrowser.Model/Querying/EpisodeQuery.cs | 75 ---------- .../Querying/MovieRecommendationQuery.cs | 47 ------ .../Querying/UpcomingEpisodesQuery.cs | 64 --------- MediaBrowser.Model/Sync/SyncCategory.cs | 22 --- MediaBrowser.Model/Sync/SyncJob.cs | 135 ------------------ MediaBrowser.Model/Sync/SyncJobStatus.cs | 15 -- MediaBrowser.Model/Sync/SyncTarget.cs | 20 --- MediaBrowser.Model/Users/UserAction.cs | 24 ---- 9 files changed, 487 deletions(-) delete mode 100644 MediaBrowser.Controller/Library/Profiler.cs delete mode 100644 MediaBrowser.Model/Querying/EpisodeQuery.cs delete mode 100644 MediaBrowser.Model/Querying/MovieRecommendationQuery.cs delete mode 100644 MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs delete mode 100644 MediaBrowser.Model/Sync/SyncCategory.cs delete mode 100644 MediaBrowser.Model/Sync/SyncJob.cs delete mode 100644 MediaBrowser.Model/Sync/SyncJobStatus.cs delete mode 100644 MediaBrowser.Model/Sync/SyncTarget.cs delete mode 100644 MediaBrowser.Model/Users/UserAction.cs diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs deleted file mode 100644 index 583fd73c3..000000000 --- a/MediaBrowser.Controller/Library/Profiler.cs +++ /dev/null @@ -1,85 +0,0 @@ -#nullable disable - -using System; -using System.Diagnostics; -using System.Globalization; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Controller.Library -{ - /// - /// Class Profiler. - /// - public class Profiler : IDisposable - { - /// - /// The name. - /// - private readonly string _name; - - /// - /// The stopwatch. - /// - private readonly Stopwatch _stopwatch; - - /// - /// The _logger. - /// - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The logger. - public Profiler(string name, ILogger logger) - { - this._name = name; - - _logger = logger; - - _stopwatch = new Stopwatch(); - _stopwatch.Start(); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - _stopwatch.Stop(); - string message; - if (_stopwatch.ElapsedMilliseconds > 300000) - { - message = string.Format( - CultureInfo.InvariantCulture, - "{0} took {1} minutes.", - _name, - ((float)_stopwatch.ElapsedMilliseconds / 60000).ToString("F", CultureInfo.InvariantCulture)); - } - else - { - message = string.Format( - CultureInfo.InvariantCulture, - "{0} took {1} seconds.", - _name, - ((float)_stopwatch.ElapsedMilliseconds / 1000).ToString("#0.000", CultureInfo.InvariantCulture)); - } - - _logger.LogInformation(message); - } - } - } -} diff --git a/MediaBrowser.Model/Querying/EpisodeQuery.cs b/MediaBrowser.Model/Querying/EpisodeQuery.cs deleted file mode 100644 index 56a7f3320..000000000 --- a/MediaBrowser.Model/Querying/EpisodeQuery.cs +++ /dev/null @@ -1,75 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Querying -{ - public class EpisodeQuery - { - public EpisodeQuery() - { - Fields = Array.Empty(); - } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public string UserId { get; set; } - - /// - /// Gets or sets the season identifier. - /// - /// The season identifier. - public string SeasonId { get; set; } - - /// - /// Gets or sets the series identifier. - /// - /// The series identifier. - public string SeriesId { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is missing. - /// - /// null if [is missing] contains no value, true if [is missing]; otherwise, false. - public bool? IsMissing { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is virtual unaired. - /// - /// null if [is virtual unaired] contains no value, true if [is virtual unaired]; otherwise, false. - public bool? IsVirtualUnaired { get; set; } - - /// - /// Gets or sets the season number. - /// - /// The season number. - public int? SeasonNumber { get; set; } - - /// - /// Gets or sets the fields. - /// - /// The fields. - public ItemFields[] Fields { get; set; } - - /// - /// Gets or sets the start index. - /// - /// The start index. - public int? StartIndex { get; set; } - - /// - /// Gets or sets the limit. - /// - /// The limit. - public int? Limit { get; set; } - - /// - /// Gets or sets the start item identifier. - /// - /// The start item identifier. - public string StartItemId { get; set; } - } -} diff --git a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs deleted file mode 100644 index b800f5de5..000000000 --- a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs +++ /dev/null @@ -1,47 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Querying -{ - public class MovieRecommendationQuery - { - public MovieRecommendationQuery() - { - ItemLimit = 10; - CategoryLimit = 6; - Fields = Array.Empty(); - } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public string UserId { get; set; } - - /// - /// Gets or sets the parent identifier. - /// - /// The parent identifier. - public string ParentId { get; set; } - - /// - /// Gets or sets the item limit. - /// - /// The item limit. - public int ItemLimit { get; set; } - - /// - /// Gets or sets the category limit. - /// - /// The category limit. - public int CategoryLimit { get; set; } - - /// - /// Gets or sets the fields. - /// - /// The fields. - public ItemFields[] Fields { get; set; } - } -} diff --git a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs deleted file mode 100644 index 2cf0f0d5f..000000000 --- a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs +++ /dev/null @@ -1,64 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Model.Querying -{ - public class UpcomingEpisodesQuery - { - public UpcomingEpisodesQuery() - { - EnableImageTypes = Array.Empty(); - } - - /// - /// Gets or sets the user id. - /// - /// The user id. - public string UserId { get; set; } - - /// - /// Gets or sets the parent identifier. - /// - /// The parent identifier. - public string ParentId { get; set; } - - /// - /// Gets or sets the start index. Use for paging. - /// - /// The start index. - public int? StartIndex { get; set; } - - /// - /// Gets or sets the maximum number of items to return. - /// - /// The limit. - public int? Limit { get; set; } - - /// - /// Gets or sets the fields to return within the items, in addition to basic information. - /// - /// The fields. - public ItemFields[] Fields { get; set; } - - /// - /// Gets or sets a value indicating whether [enable images]. - /// - /// null if [enable images] contains no value, true if [enable images]; otherwise, false. - public bool? EnableImages { get; set; } - - /// - /// Gets or sets the image type limit. - /// - /// The image type limit. - public int? ImageTypeLimit { get; set; } - - /// - /// Gets or sets the enable image types. - /// - /// The enable image types. - public ImageType[] EnableImageTypes { get; set; } - } -} diff --git a/MediaBrowser.Model/Sync/SyncCategory.cs b/MediaBrowser.Model/Sync/SyncCategory.cs deleted file mode 100644 index 1248c2f73..000000000 --- a/MediaBrowser.Model/Sync/SyncCategory.cs +++ /dev/null @@ -1,22 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Sync -{ - public enum SyncCategory - { - /// - /// The latest. - /// - Latest = 0, - - /// - /// The next up. - /// - NextUp = 1, - - /// - /// The resume. - /// - Resume = 2 - } -} diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs deleted file mode 100644 index 3e396e5d1..000000000 --- a/MediaBrowser.Model/Sync/SyncJob.cs +++ /dev/null @@ -1,135 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Sync -{ - public class SyncJob - { - public SyncJob() - { - RequestedItemIds = Array.Empty(); - } - - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - - /// - /// Gets or sets the device identifier. - /// - /// The device identifier. - public string TargetId { get; set; } - - /// - /// Gets or sets the name of the target. - /// - /// The name of the target. - public string TargetName { get; set; } - - /// - /// Gets or sets the quality. - /// - /// The quality. - public string Quality { get; set; } - - /// - /// Gets or sets the bitrate. - /// - /// The bitrate. - public int? Bitrate { get; set; } - - /// - /// Gets or sets the profile. - /// - /// The profile. - public string Profile { get; set; } - - /// - /// Gets or sets the category. - /// - /// The category. - public SyncCategory? Category { get; set; } - - /// - /// Gets or sets the parent identifier. - /// - /// The parent identifier. - public string ParentId { get; set; } - - /// - /// Gets or sets the current progress. - /// - /// The current progress. - public double? Progress { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the status. - /// - /// The status. - public SyncJobStatus Status { get; set; } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public string UserId { get; set; } - - /// - /// Gets or sets a value indicating whether [unwatched only]. - /// - /// true if [unwatched only]; otherwise, false. - public bool UnwatchedOnly { get; set; } - - /// - /// Gets or sets a value indicating whether [synchronize new content]. - /// - /// true if [synchronize new content]; otherwise, false. - public bool SyncNewContent { get; set; } - - /// - /// Gets or sets the item limit. - /// - /// The item limit. - public int? ItemLimit { get; set; } - - /// - /// Gets or sets the requested item ids. - /// - /// The requested item ids. - public Guid[] RequestedItemIds { get; set; } - - /// - /// Gets or sets the date created. - /// - /// The date created. - public DateTime DateCreated { get; set; } - - /// - /// Gets or sets the date last modified. - /// - /// The date last modified. - public DateTime DateLastModified { get; set; } - - /// - /// Gets or sets the item count. - /// - /// The item count. - public int ItemCount { get; set; } - - public string ParentName { get; set; } - - public string PrimaryImageItemId { get; set; } - - public string PrimaryImageTag { get; set; } - } -} diff --git a/MediaBrowser.Model/Sync/SyncJobStatus.cs b/MediaBrowser.Model/Sync/SyncJobStatus.cs deleted file mode 100644 index 226a47d4c..000000000 --- a/MediaBrowser.Model/Sync/SyncJobStatus.cs +++ /dev/null @@ -1,15 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Sync -{ - public enum SyncJobStatus - { - Queued = 0, - Converting = 1, - ReadyToTransfer = 2, - Transferring = 3, - Completed = 4, - CompletedWithError = 5, - Failed = 6 - } -} diff --git a/MediaBrowser.Model/Sync/SyncTarget.cs b/MediaBrowser.Model/Sync/SyncTarget.cs deleted file mode 100644 index 9e6bbbc00..000000000 --- a/MediaBrowser.Model/Sync/SyncTarget.cs +++ /dev/null @@ -1,20 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Sync -{ - public class SyncTarget - { - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - } -} diff --git a/MediaBrowser.Model/Users/UserAction.cs b/MediaBrowser.Model/Users/UserAction.cs deleted file mode 100644 index 7646db4a8..000000000 --- a/MediaBrowser.Model/Users/UserAction.cs +++ /dev/null @@ -1,24 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Users -{ - public class UserAction - { - public string Id { get; set; } - - public string ServerId { get; set; } - - public Guid UserId { get; set; } - - public Guid ItemId { get; set; } - - public UserActionType Type { get; set; } - - public DateTime Date { get; set; } - - public long? PositionTicks { get; set; } - } -} From d6ab9ab328a661be079035e977b6df3e38774bac Mon Sep 17 00:00:00 2001 From: TokieSan Date: Thu, 20 May 2021 12:30:56 +0000 Subject: [PATCH 255/612] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 9f84d3a3c..3d6e159b1 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -116,5 +116,7 @@ "TaskCleanLogs": "حذف دليل السجل", "TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الموضوع.", "TaskCleanActivityLog": "حذف سجل الأنشطة", - "Default": "الإعدادات الافتراضية" + "Default": "الإعدادات الافتراضية", + "Undefined": "غير معرف", + "Forced": "ملحقة" } From 19e2e0b7b81c2c5734e947e4d6fa7b57bd4163e3 Mon Sep 17 00:00:00 2001 From: Marius Lindvall Date: Thu, 20 May 2021 11:58:30 +0000 Subject: [PATCH 256/612] Translated using Weblate (Norwegian Nynorsk) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nn/ --- Emby.Server.Implementations/Localization/Core/nn.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json index f5683f35b..32d4f3a8b 100644 --- a/Emby.Server.Implementations/Localization/Core/nn.json +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -29,9 +29,9 @@ "Collections": "Samlingar", "ChapterNameValue": "Kapittel {0}", "Channels": "Kanalar", - "CameraImageUploadedFrom": "Eit nytt kamera bilete har blitt lasta opp frå {0}", + "CameraImageUploadedFrom": "Eit nytt kamerabilete har blitt lasta opp frå {0}", "Books": "Bøker", - "AuthenticationSucceededWithUserName": "{0} Har logga inn", + "AuthenticationSucceededWithUserName": "{0} har logga inn", "Artists": "Artistar", "Application": "Program", "AppDeviceValues": "App: {0}, Eining: {1}", From 3b59064f972d3435f1e4fe1859b85866f400d7df Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 21 May 2021 19:35:00 +0200 Subject: [PATCH 257/612] Bump SQLitePCL.pretty.netstandard to 3.0.1 --- .../Data/ManagedConnection.cs | 4 +-- .../Data/SqliteExtensions.cs | 33 +++++++++---------- .../Data/SqliteItemRepository.cs | 14 ++++---- .../Data/SqliteUserDataRepository.cs | 2 +- .../Emby.Server.Implementations.csproj | 2 +- .../Security/AuthenticationRepository.cs | 2 +- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 10c6f837e..afc8966f9 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -54,12 +54,12 @@ namespace Emby.Server.Implementations.Data return _db.RunInTransaction(action, mode); } - public IEnumerable> Query(string sql) + public IEnumerable> Query(string sql) { return _db.Query(sql); } - public IEnumerable> Query(string sql, params object[] values) + public IEnumerable> Query(string sql, params object[] values) { return _db.Query(sql, values); } diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index e532825af..3289e7609 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using SQLitePCL.pretty; @@ -66,7 +65,7 @@ namespace Emby.Server.Implementations.Data }); } - public static Guid ReadGuidFromBlob(this IResultSetValue result) + public static Guid ReadGuidFromBlob(this ResultSetValue result) { return new Guid(result.ToBlob()); } @@ -87,7 +86,7 @@ namespace Emby.Server.Implementations.Data private static string GetDateTimeKindFormat(DateTimeKind kind) => (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal; - public static DateTime ReadDateTime(this IResultSetValue result) + public static DateTime ReadDateTime(this ResultSetValue result) { var dateText = result.ToString(); @@ -98,7 +97,7 @@ namespace Emby.Server.Implementations.Data DateTimeStyles.None).ToUniversalTime(); } - public static bool TryReadDateTime(this IReadOnlyList reader, int index, out DateTime result) + public static bool TryReadDateTime(this IReadOnlyList reader, int index, out DateTime result) { var item = reader[index]; if (item.IsDbNull()) @@ -119,7 +118,7 @@ namespace Emby.Server.Implementations.Data return false; } - public static bool TryGetGuid(this IReadOnlyList reader, int index, out Guid result) + public static bool TryGetGuid(this IReadOnlyList reader, int index, out Guid result) { var item = reader[index]; if (item.IsDbNull()) @@ -132,17 +131,17 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool IsDbNull(this IResultSetValue result) + public static bool IsDbNull(this ResultSetValue result) { return result.SQLiteType == SQLiteType.Null; } - public static string GetString(this IReadOnlyList result, int index) + public static string GetString(this IReadOnlyList result, int index) { return result[index].ToString(); } - public static bool TryGetString(this IReadOnlyList reader, int index, out string result) + public static bool TryGetString(this IReadOnlyList reader, int index, out string result) { result = null; var item = reader[index]; @@ -155,12 +154,12 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool GetBoolean(this IReadOnlyList result, int index) + public static bool GetBoolean(this IReadOnlyList result, int index) { return result[index].ToBool(); } - public static bool TryGetBoolean(this IReadOnlyList reader, int index, out bool result) + public static bool TryGetBoolean(this IReadOnlyList reader, int index, out bool result) { var item = reader[index]; if (item.IsDbNull()) @@ -173,7 +172,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetInt32(this IReadOnlyList reader, int index, out int result) + public static bool TryGetInt32(this IReadOnlyList reader, int index, out int result) { var item = reader[index]; if (item.IsDbNull()) @@ -186,12 +185,12 @@ namespace Emby.Server.Implementations.Data return true; } - public static long GetInt64(this IReadOnlyList result, int index) + public static long GetInt64(this IReadOnlyList result, int index) { return result[index].ToInt64(); } - public static bool TryGetInt64(this IReadOnlyList reader, int index, out long result) + public static bool TryGetInt64(this IReadOnlyList reader, int index, out long result) { var item = reader[index]; if (item.IsDbNull()) @@ -204,7 +203,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetSingle(this IReadOnlyList reader, int index, out float result) + public static bool TryGetSingle(this IReadOnlyList reader, int index, out float result) { var item = reader[index]; if (item.IsDbNull()) @@ -217,7 +216,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static bool TryGetDouble(this IReadOnlyList reader, int index, out double result) + public static bool TryGetDouble(this IReadOnlyList reader, int index, out double result) { var item = reader[index]; if (item.IsDbNull()) @@ -230,7 +229,7 @@ namespace Emby.Server.Implementations.Data return true; } - public static Guid GetGuid(this IReadOnlyList result, int index) + public static Guid GetGuid(this IReadOnlyList result, int index) { return result[index].ReadGuidFromBlob(); } @@ -442,7 +441,7 @@ namespace Emby.Server.Implementations.Data } } - public static IEnumerable> ExecuteQuery(this IStatement statement) + public static IEnumerable> ExecuteQuery(this IStatement statement) { while (statement.MoveNext()) { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 5b4bbb339..2d060dd65 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1284,12 +1284,12 @@ namespace Emby.Server.Implementations.Data return true; } - private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query) + private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query) { return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query)); } - private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields) + private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields) { var typeString = reader.GetString(0); @@ -1929,7 +1929,7 @@ namespace Emby.Server.Implementations.Data /// The reader. /// The item. /// ChapterInfo. - private ChapterInfo GetChapter(IReadOnlyList reader, BaseItem item) + private ChapterInfo GetChapter(IReadOnlyList reader, BaseItem item) { var chapter = new ChapterInfo { @@ -5503,7 +5503,7 @@ AND Type = @InternalPersonType)"); return result; } - private static ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, string[] typesToCount) + private static ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, string[] typesToCount) { var counts = new ItemCounts(); @@ -5734,7 +5734,7 @@ AND Type = @InternalPersonType)"); } } - private PersonInfo GetPerson(IReadOnlyList reader) + private PersonInfo GetPerson(IReadOnlyList reader) { var item = new PersonInfo { @@ -5941,7 +5941,7 @@ AND Type = @InternalPersonType)"); ///
/// The reader. /// ChapterInfo. - private MediaStream GetMediaStream(IReadOnlyList reader) + private MediaStream GetMediaStream(IReadOnlyList reader) { var item = new MediaStream { @@ -6242,7 +6242,7 @@ AND Type = @InternalPersonType)"); ///
/// The reader. /// MediaAttachment. - private MediaAttachment GetMediaAttachment(IReadOnlyList reader) + private MediaAttachment GetMediaAttachment(IReadOnlyList reader) { var item = new MediaAttachment { diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 1756bcae0..ef9af1dcd 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -350,7 +350,7 @@ namespace Emby.Server.Implementations.Data /// Read a row from the specified reader into the provided userData object. ///
/// - private UserItemData ReadRow(IReadOnlyList reader) + private UserItemData ReadRow(IReadOnlyList reader) { var userData = new UserItemData(); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 113863519..a72a87462 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -31,7 +31,7 @@ - + diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 30823ab8f..e8eac315b 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -291,7 +291,7 @@ namespace Emby.Server.Implementations.Security return result; } - private static AuthenticationInfo Get(IReadOnlyList reader) + private static AuthenticationInfo Get(IReadOnlyList reader) { var info = new AuthenticationInfo { From e01ce826e096e44473a7d86fd6f55b6f59137fc7 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 23 May 2021 08:38:05 -0600 Subject: [PATCH 258/612] Allow sorting for AlbumArtist --- Jellyfin.Api/Controllers/ArtistsController.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 85d7c50d3..154a56702 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -281,6 +281,8 @@ namespace Jellyfin.Api.Controllers /// Optional filter by items whose name is sorted equally or greater than a given input string. /// Optional filter by items whose name is sorted equally than a given input string. /// Optional filter by items whose name is equally or lesser than a given input string. + /// Optional. Specify one or more sort orders, comma delimited. + /// Sort Order - Ascending,Descending. /// Optional, include image information in output. /// Total record count. /// Album artists returned. @@ -316,6 +318,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { @@ -354,7 +358,8 @@ namespace Jellyfin.Api.Controllers MinCommunityRating = minCommunityRating, DtoOptions = dtoOptions, SearchTerm = searchTerm, - EnableTotalRecordCount = enableTotalRecordCount + EnableTotalRecordCount = enableTotalRecordCount, + OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder) }; if (parentId.HasValue) From 42a2cc1747c7859c63334a7a45792e0af1410e1a Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 24 May 2021 00:30:41 +0200 Subject: [PATCH 259/612] Remove some unnecessary allocations --- Emby.Naming/TV/EpisodeResolver.cs | 5 +- Emby.Naming/Video/ExtraResolver.cs | 2 +- Emby.Naming/Video/FlagParser.cs | 53 -- Emby.Naming/Video/Format3DParser.cs | 94 ++-- Emby.Naming/Video/Format3DResult.cs | 23 +- Emby.Naming/Video/StackResolver.cs | 4 +- Emby.Naming/Video/VideoFileInfo.cs | 7 +- Emby.Naming/Video/VideoListResolver.cs | 260 ++++++---- Emby.Naming/Video/VideoResolver.cs | 60 +-- .../AppBase/BaseConfigurationManager.cs | 36 +- .../Data/SqliteItemRepository.cs | 489 ++++++++++-------- .../Data/TypeMapper.cs | 16 +- .../IO/ManagedFileSystem.cs | 20 +- .../Library/CoreResolutionIgnoreRule.cs | 2 +- .../Library/LibraryManager.cs | 48 +- .../Library/MediaSourceManager.cs | 9 +- .../Library/Resolvers/BaseVideoResolver.cs | 17 +- .../Library/Resolvers/Movies/MovieResolver.cs | 12 +- .../Localization/LocalizationManager.cs | 34 +- .../Serialization/MyXmlSerializer.cs | 3 +- .../ServerApplicationPaths.cs | 10 +- .../BaseItemManager/BaseItemManager.cs | 9 +- MediaBrowser.Controller/Entities/BaseItem.cs | 31 +- .../Extensions/StringExtensions.cs | 21 + .../Library/ILibraryManager.cs | 7 + .../MediaBrowser.Controller.csproj | 5 +- .../Providers/DirectoryService.cs | 26 +- .../Providers/IDirectoryService.cs | 4 +- .../Providers/MetadataResult.cs | 18 +- .../Images/EpisodeLocalImageProvider.cs | 26 +- .../Manager/ItemImageProvider.cs | 42 +- .../Manager/MetadataService.cs | 18 +- .../MediaInfo/SubtitleResolver.cs | 25 +- RSSDP/SsdpCommunicationsServer.cs | 7 +- .../Video/CleanDateTimeTests.cs | 2 +- .../Video/CleanStringTests.cs | 6 +- .../Jellyfin.Naming.Tests/Video/ExtraTests.cs | 7 - .../Video/Format3DTests.cs | 9 +- .../Video/MultiVersionTests.cs | 208 ++++---- .../Jellyfin.Naming.Tests/Video/StubTests.cs | 3 +- .../Video/VideoListResolverTests.cs | 242 +++++---- .../Video/VideoResolverTests.cs | 32 +- .../Data/SqliteItemRepositoryTests.cs | 49 ++ 43 files changed, 1095 insertions(+), 906 deletions(-) delete mode 100644 Emby.Naming/Video/FlagParser.cs diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs index c63aec64e..5e952e47b 100644 --- a/Emby.Naming/TV/EpisodeResolver.cs +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -16,7 +16,7 @@ namespace Emby.Naming.TV /// /// Initializes a new instance of the class. /// - /// object containing VideoFileExtensions and passed to , , and . + /// object containing VideoFileExtensions and passed to , and . public EpisodeResolver(NamingOptions options) { _options = options; @@ -62,8 +62,7 @@ namespace Emby.Naming.TV container = extension.TrimStart('.'); } - var flags = new FlagParser(_options).GetFlags(path); - var format3DResult = new Format3DParser(_options).Parse(flags); + var format3DResult = Format3DParser.Parse(path, _options); var parsingResult = new EpisodePathParser(_options) .Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index f9d06c09b..1fade985b 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -44,7 +44,7 @@ namespace Emby.Naming.Video } else if (rule.MediaType == MediaType.Video) { - if (!new VideoResolver(_options).IsVideoFile(path)) + if (!VideoResolver.IsVideoFile(path, _options)) { continue; } diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs deleted file mode 100644 index 439de1813..000000000 --- a/Emby.Naming/Video/FlagParser.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.IO; -using Emby.Naming.Common; - -namespace Emby.Naming.Video -{ - /// - /// Parses list of flags from filename based on delimiters. - /// - public class FlagParser - { - private readonly NamingOptions _options; - - /// - /// Initializes a new instance of the class. - /// - /// object containing VideoFlagDelimiters. - public FlagParser(NamingOptions options) - { - _options = options; - } - - /// - /// Calls GetFlags function with _options.VideoFlagDelimiters parameter. - /// - /// Path to file. - /// List of found flags. - public string[] GetFlags(string path) - { - return GetFlags(path, _options.VideoFlagDelimiters); - } - - /// - /// Parses flags from filename based on delimiters. - /// - /// Path to file. - /// Delimiters used to extract flags. - /// List of found flags. - public string[] GetFlags(string path, char[] delimiters) - { - if (string.IsNullOrEmpty(path)) - { - return Array.Empty(); - } - - // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _. - - var file = Path.GetFileName(path); - - return file.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); - } - } -} diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs index 4fd5d78ba..190ff9918 100644 --- a/Emby.Naming/Video/Format3DParser.cs +++ b/Emby.Naming/Video/Format3DParser.cs @@ -1,45 +1,37 @@ using System; -using System.Linq; using Emby.Naming.Common; namespace Emby.Naming.Video { /// - /// Parste 3D format related flags. + /// Parse 3D format related flags. /// - public class Format3DParser + public static class Format3DParser { - private readonly NamingOptions _options; - - /// - /// Initializes a new instance of the class. - /// - /// object containing VideoFlagDelimiters and passes options to . - public Format3DParser(NamingOptions options) - { - _options = options; - } + // Static default result to save on allocation costs. + private static readonly Format3DResult _defaultResult = new (false, null); /// /// Parse 3D format related flags. /// /// Path to file. + /// The naming options. /// Returns object. - public Format3DResult Parse(string path) + public static Format3DResult Parse(string path, NamingOptions namingOptions) { - int oldLen = _options.VideoFlagDelimiters.Length; + int oldLen = namingOptions.VideoFlagDelimiters.Length; var delimiters = new char[oldLen + 1]; - _options.VideoFlagDelimiters.CopyTo(delimiters, 0); + namingOptions.VideoFlagDelimiters.CopyTo(delimiters, 0); delimiters[oldLen] = ' '; - return Parse(new FlagParser(_options).GetFlags(path, delimiters)); + return Parse(path, delimiters, namingOptions); } - internal Format3DResult Parse(string[] videoFlags) + private static Format3DResult Parse(ReadOnlySpan path, char[] delimiters, NamingOptions namingOptions) { - foreach (var rule in _options.Format3DRules) + foreach (var rule in namingOptions.Format3DRules) { - var result = Parse(videoFlags, rule); + var result = Parse(path, rule, delimiters); if (result.Is3D) { @@ -47,51 +39,43 @@ namespace Emby.Naming.Video } } - return new Format3DResult(); + return _defaultResult; } - private static Format3DResult Parse(string[] videoFlags, Format3DRule rule) + private static Format3DResult Parse(ReadOnlySpan path, Format3DRule rule, char[] delimiters) { - var result = new Format3DResult(); + bool is3D = false; + string? format3D = null; - if (string.IsNullOrEmpty(rule.PrecedingToken)) + // If there's no preceding token we just consider it found + var foundPrefix = string.IsNullOrEmpty(rule.PrecedingToken); + while (path.Length > 0) { - result.Format3D = new[] { rule.Token }.FirstOrDefault(i => videoFlags.Contains(i, StringComparer.OrdinalIgnoreCase)); - result.Is3D = !string.IsNullOrEmpty(result.Format3D); - - if (result.Is3D) + var index = path.IndexOfAny(delimiters); + if (index == -1) { - result.Tokens.Add(rule.Token); - } - } - else - { - var foundPrefix = false; - string? format = null; - - foreach (var flag in videoFlags) - { - if (foundPrefix) - { - result.Tokens.Add(rule.PrecedingToken); - - if (string.Equals(rule.Token, flag, StringComparison.OrdinalIgnoreCase)) - { - format = flag; - result.Tokens.Add(rule.Token); - } - - break; - } - - foundPrefix = string.Equals(flag, rule.PrecedingToken, StringComparison.OrdinalIgnoreCase); + index = path.Length - 1; } - result.Is3D = foundPrefix && !string.IsNullOrEmpty(format); - result.Format3D = format; + var currentSlice = path[..index]; + path = path[(index + 1)..]; + + if (!foundPrefix) + { + foundPrefix = currentSlice.Equals(rule.PrecedingToken, StringComparison.OrdinalIgnoreCase); + continue; + } + + is3D = foundPrefix && currentSlice.Equals(rule.Token, StringComparison.OrdinalIgnoreCase); + + if (is3D) + { + format3D = rule.Token; + break; + } } - return result; + return is3D ? new Format3DResult(true, format3D) : _defaultResult; } } } diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs index ac935f203..aac959c13 100644 --- a/Emby.Naming/Video/Format3DResult.cs +++ b/Emby.Naming/Video/Format3DResult.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace Emby.Naming.Video { /// @@ -10,27 +8,24 @@ namespace Emby.Naming.Video /// /// Initializes a new instance of the class. /// - public Format3DResult() + /// A value indicating whether the parsed string contains 3D tokens. + /// The 3D format. Value might be null if [is3D] is false. + public Format3DResult(bool is3D, string? format3D) { - Tokens = new List(); + Is3D = is3D; + Format3D = format3D; } /// - /// Gets or sets a value indicating whether [is3 d]. + /// Gets a value indicating whether [is3 d]. /// /// true if [is3 d]; otherwise, false. - public bool Is3D { get; set; } + public bool Is3D { get; } /// - /// Gets or sets the format3 d. + /// Gets the format3 d. /// /// The format3 d. - public string? Format3D { get; set; } - - /// - /// Gets or sets the tokens. - /// - /// The tokens. - public List Tokens { get; set; } + public string? Format3D { get; } } } diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs index 550c42961..36f65a562 100644 --- a/Emby.Naming/Video/StackResolver.cs +++ b/Emby.Naming/Video/StackResolver.cs @@ -85,10 +85,8 @@ namespace Emby.Naming.Video /// Enumerable of videos. public IEnumerable Resolve(IEnumerable files) { - var resolver = new VideoResolver(_options); - var list = files - .Where(i => i.IsDirectory || resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName)) + .Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options)) .OrderBy(i => i.FullName) .ToList(); diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index 1457db737..481773ff6 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -1,3 +1,4 @@ +using System; using MediaBrowser.Model.Entities; namespace Emby.Naming.Video @@ -106,9 +107,9 @@ namespace Emby.Naming.Video /// Gets the file name without extension. /// /// The file name without extension. - public string FileNameWithoutExtension => !IsDirectory - ? System.IO.Path.GetFileNameWithoutExtension(Path) - : System.IO.Path.GetFileName(Path); + public ReadOnlySpan FileNameWithoutExtension => !IsDirectory + ? System.IO.Path.GetFileNameWithoutExtension(Path.AsSpan()) + : System.IO.Path.GetFileName(Path.AsSpan()); /// public override string ToString() diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 7b6a1705b..65cf7c928 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -12,31 +12,19 @@ namespace Emby.Naming.Video /// /// Resolves alternative versions and extras from list of video files. /// - public class VideoListResolver + public static class VideoListResolver { - private readonly NamingOptions _options; - - /// - /// Initializes a new instance of the class. - /// - /// object containing CleanStringRegexes and VideoFlagDelimiters and passes options to and . - public VideoListResolver(NamingOptions options) - { - _options = options; - } - /// /// Resolves alternative versions and extras from list of video files. /// /// List of related video files. + /// The naming options. /// Indication we should consider multi-versions of content. /// Returns enumerable of which groups files together when related. - public IEnumerable Resolve(List files, bool supportMultiVersion = true) + public static IEnumerable Resolve(List files, NamingOptions namingOptions, bool supportMultiVersion = true) { - var videoResolver = new VideoResolver(_options); - var videoInfos = files - .Select(i => videoResolver.Resolve(i.FullName, i.IsDirectory)) + .Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions)) .OfType() .ToList(); @@ -46,7 +34,7 @@ namespace Emby.Naming.Video .Where(i => i.ExtraType == null) .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); - var stackResult = new StackResolver(_options) + var stackResult = new StackResolver(namingOptions) .Resolve(nonExtras).ToList(); var remainingFiles = videoInfos @@ -59,23 +47,17 @@ namespace Emby.Naming.Video { var info = new VideoInfo(stack.Name) { - Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)) + Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions)) .OfType() .ToList() }; info.Year = info.Files[0].Year; - var extraBaseNames = new List { stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0]) }; - - var extras = GetExtras(remainingFiles, extraBaseNames); + var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters); if (extras.Count > 0) { - remainingFiles = remainingFiles - .Except(extras) - .ToList(); - info.Extras = extras; } @@ -88,15 +70,12 @@ namespace Emby.Naming.Video foreach (var media in standaloneMedia) { - var info = new VideoInfo(media.Name) { Files = new List { media } }; + var info = new VideoInfo(media.Name) { Files = new[] { media } }; info.Year = info.Files[0].Year; - var extras = GetExtras(remainingFiles, new List { media.FileNameWithoutExtension }); - - remainingFiles = remainingFiles - .Except(extras.Concat(new[] { media })) - .ToList(); + remainingFiles.Remove(media); + var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters); info.Extras = extras; @@ -105,8 +84,7 @@ namespace Emby.Naming.Video if (supportMultiVersion) { - list = GetVideosGroupedByVersion(list) - .ToList(); + list = GetVideosGroupedByVersion(list, namingOptions); } // If there's only one resolved video, use the folder name as well to find extras @@ -114,19 +92,14 @@ namespace Emby.Naming.Video { var info = list[0]; var videoPath = list[0].Files[0].Path; - var parentPath = Path.GetDirectoryName(videoPath); + var parentPath = Path.GetDirectoryName(videoPath.AsSpan()); - if (!string.IsNullOrEmpty(parentPath)) + if (!parentPath.IsEmpty) { var folderName = Path.GetFileName(parentPath); - if (!string.IsNullOrEmpty(folderName)) + if (!folderName.IsEmpty) { - var extras = GetExtras(remainingFiles, new List { folderName }); - - remainingFiles = remainingFiles - .Except(extras) - .ToList(); - + var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters); extras.AddRange(info.Extras); info.Extras = extras; } @@ -164,96 +137,169 @@ namespace Emby.Naming.Video // Whatever files are left, just add them list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name) { - Files = new List { i }, + Files = new[] { i }, Year = i.Year })); return list; } - private IEnumerable GetVideosGroupedByVersion(List videos) + private static List GetVideosGroupedByVersion(List videos, NamingOptions namingOptions) { if (videos.Count == 0) { return videos; } - var list = new List(); + var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path.AsSpan())); - var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path)); - - if (!string.IsNullOrEmpty(folderName) - && folderName.Length > 1 - && videos.All(i => i.Files.Count == 1 - && IsEligibleForMultiVersion(folderName, i.Files[0].Path)) - && HaveSameYear(videos)) + if (folderName.Length <= 1 || !HaveSameYear(videos)) { - var ordered = videos.OrderBy(i => i.Name).ToList(); - - list.Add(ordered[0]); - - var alternateVersionsLen = ordered.Count - 1; - var alternateVersions = new VideoFileInfo[alternateVersionsLen]; - for (int i = 0; i < alternateVersionsLen; i++) - { - alternateVersions[i] = ordered[i + 1].Files[0]; - } - - list[0].AlternateVersions = alternateVersions; - list[0].Name = folderName; - var extras = ordered.Skip(1).SelectMany(i => i.Extras).ToList(); - extras.AddRange(list[0].Extras); - list[0].Extras = extras; - - return list; + return videos; } - return videos; - } - - private bool HaveSameYear(List videos) - { - return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2; - } - - private bool IsEligibleForMultiVersion(string folderName, string testFilePath) - { - string testFilename = Path.GetFileNameWithoutExtension(testFilePath); - if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) + // Cannot use Span inside local functions and delegates thus we cannot use LINQ here nor merge with the above [if] + for (var i = 0; i < videos.Count; i++) { - // Remove the folder name before cleaning as we don't care about cleaning that part - if (folderName.Length <= testFilename.Length) + var video = videos[i]; + if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions)) { - testFilename = testFilename.Substring(folderName.Length).Trim(); + return videos; } - - if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName)) - { - testFilename = cleanName.Trim().ToString(); - } - - // The CleanStringParser should have removed common keywords etc. - return string.IsNullOrEmpty(testFilename) - || testFilename[0] == '-' - || Regex.IsMatch(testFilename, @"^\[([^]]*)\]"); } - return false; - } + // The list is created and overwritten in the caller, so we are allowed to do in-place sorting + videos.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal)); - private List GetExtras(IEnumerable remainingFiles, List baseNames) - { - foreach (var name in baseNames.ToList()) + var list = new List { - var trimmedName = name.TrimEnd().TrimEnd(_options.VideoFlagDelimiters).TrimEnd(); - baseNames.Add(trimmedName); + videos[0] + }; + + var alternateVersionsLen = videos.Count - 1; + var alternateVersions = new VideoFileInfo[alternateVersionsLen]; + var extras = new List(list[0].Extras); + for (int i = 0; i < alternateVersionsLen; i++) + { + var video = videos[i + 1]; + alternateVersions[i] = video.Files[0]; + extras.AddRange(video.Extras); } - return remainingFiles - .Where(i => i.ExtraType != null) - .Where(i => baseNames.Any(b => - i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) - .ToList(); + list[0].AlternateVersions = alternateVersions; + list[0].Name = folderName.ToString(); + list[0].Extras = extras; + + return list; + } + + private static bool HaveSameYear(IReadOnlyList videos) + { + if (videos.Count == 1) + { + return true; + } + + var firstYear = videos[0].Year ?? -1; + for (var i = 1; i < videos.Count; i++) + { + if ((videos[i].Year ?? -1) != firstYear) + { + return false; + } + } + + return true; + } + + private static bool IsEligibleForMultiVersion(ReadOnlySpan folderName, string testFilePath, NamingOptions namingOptions) + { + var testFilename = Path.GetFileNameWithoutExtension(testFilePath.AsSpan()); + if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Remove the folder name before cleaning as we don't care about cleaning that part + if (folderName.Length <= testFilename.Length) + { + testFilename = testFilename[folderName.Length..].Trim(); + } + + // There are no span overloads for regex unfortunately + var tmpTestFilename = testFilename.ToString(); + if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName)) + { + tmpTestFilename = cleanName.Trim().ToString(); + } + + // The CleanStringParser should have removed common keywords etc. + return string.IsNullOrEmpty(tmpTestFilename) + || testFilename[0] == '-' + || Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled); + } + + private static ReadOnlySpan TrimFilenameDelimiters(ReadOnlySpan name, ReadOnlySpan videoFlagDelimiters) + { + return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd(); + } + + private static bool StartsWith(ReadOnlySpan fileName, ReadOnlySpan baseName, ReadOnlySpan trimmedBaseName) + { + if (baseName.IsEmpty) + { + return false; + } + + return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase) + || (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles]. + /// + /// The list of remaining filenames. + /// The base name to use for the comparison. + /// The video flag delimiters. + /// A list of video extras for [baseName]. + private static List ExtractExtras(IList remainingFiles, ReadOnlySpan baseName, ReadOnlySpan videoFlagDelimiters) + { + return ExtractExtras(remainingFiles, baseName, ReadOnlySpan.Empty, videoFlagDelimiters); + } + + /// + /// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles]. + /// + /// The list of remaining filenames. + /// The first base name to use for the comparison. + /// The second base name to use for the comparison. + /// The video flag delimiters. + /// A list of video extras for [firstBaseName] and [secondBaseName]. + private static List ExtractExtras(IList remainingFiles, ReadOnlySpan firstBaseName, ReadOnlySpan secondBaseName, ReadOnlySpan videoFlagDelimiters) + { + var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters); + var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters); + + var result = new List(); + var pos = remainingFiles.Count - 1; + for (; pos >= 0; pos--) + { + var file = remainingFiles[pos]; + if (file.ExtraType == null) + { + continue; + } + + var filename = file.FileNameWithoutExtension; + if (StartsWith(filename, firstBaseName, trimmedFirstBaseName) + || StartsWith(filename, secondBaseName, trimmedSecondBaseName)) + { + result.Add(file); + remainingFiles.RemoveAt(pos); + } + } + + return result; } } } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 27e73208c..c4ac5fdc6 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -9,38 +9,28 @@ namespace Emby.Naming.Video /// /// Resolves from file path. /// - public class VideoResolver + public static class VideoResolver { - private readonly NamingOptions _options; - - /// - /// Initializes a new instance of the class. - /// - /// object containing VideoFileExtensions, StubFileExtensions, CleanStringRegexes and CleanDateTimeRegexes - /// and passes options in , , and . - public VideoResolver(NamingOptions options) - { - _options = options; - } - /// /// Resolves the directory. /// /// The path. + /// The naming options. /// VideoFileInfo. - public VideoFileInfo? ResolveDirectory(string? path) + public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions) { - return Resolve(path, true); + return Resolve(path, true, namingOptions); } /// /// Resolves the file. /// /// The path. + /// The naming options. /// VideoFileInfo. - public VideoFileInfo? ResolveFile(string? path) + public static VideoFileInfo? ResolveFile(string? path, NamingOptions namingOptions) { - return Resolve(path, false); + return Resolve(path, false, namingOptions); } /// @@ -48,10 +38,11 @@ namespace Emby.Naming.Video /// /// The path. /// if set to true [is folder]. + /// The naming options. /// Whether or not the name should be parsed for info. /// VideoFileInfo. /// path is null. - public VideoFileInfo? Resolve(string? path, bool isDirectory, bool parseName = true) + public static VideoFileInfo? Resolve(string? path, bool isDirectory, NamingOptions namingOptions, bool parseName = true) { if (string.IsNullOrEmpty(path)) { @@ -67,10 +58,10 @@ namespace Emby.Naming.Video var extension = Path.GetExtension(path.AsSpan()); // Check supported extensions - if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + if (!namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { // It's not supported. Check stub extensions - if (!StubResolver.TryResolveFile(path, _options, out stubType)) + if (!StubResolver.TryResolveFile(path, namingOptions, out stubType)) { return null; } @@ -81,10 +72,9 @@ namespace Emby.Naming.Video container = extension.TrimStart('.'); } - var flags = new FlagParser(_options).GetFlags(path); - var format3DResult = new Format3DParser(_options).Parse(flags); + var format3DResult = Format3DParser.Parse(path, namingOptions); - var extraResult = new ExtraResolver(_options).GetExtraInfo(path); + var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path); var name = Path.GetFileNameWithoutExtension(path); @@ -92,12 +82,12 @@ namespace Emby.Naming.Video if (parseName) { - var cleanDateTimeResult = CleanDateTime(name); + var cleanDateTimeResult = CleanDateTime(name, namingOptions); name = cleanDateTimeResult.Name; year = cleanDateTimeResult.Year; if (extraResult.ExtraType == null - && TryCleanString(name, out ReadOnlySpan newName)) + && TryCleanString(name, namingOptions, out ReadOnlySpan newName)) { name = newName.ToString(); } @@ -121,43 +111,47 @@ namespace Emby.Naming.Video /// Determines if path is video file based on extension. ///
/// Path to file. + /// The naming options. /// True if is video file. - public bool IsVideoFile(string path) + public static bool IsVideoFile(string path, NamingOptions namingOptions) { var extension = Path.GetExtension(path.AsSpan()); - return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); + return namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); } /// /// Determines if path is video file stub based on extension. /// /// Path to file. + /// The naming options. /// True if is video file stub. - public bool IsStubFile(string path) + public static bool IsStubFile(string path, NamingOptions namingOptions) { var extension = Path.GetExtension(path.AsSpan()); - return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); + return namingOptions.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase); } /// /// Tries to clean name of clutter. /// /// Raw name. + /// The naming options. /// Clean name. /// True if cleaning of name was successful. - public bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan newName) + public static bool TryCleanString([NotNullWhen(true)] string? name, NamingOptions namingOptions, out ReadOnlySpan newName) { - return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName); + return CleanStringParser.TryClean(name, namingOptions.CleanStringRegexes, out newName); } /// /// Tries to get name and year from raw name. /// /// Raw name. + /// The naming options. /// Returns with name and optional year. - public CleanDateTimeResult CleanDateTime(string name) + public static CleanDateTimeResult CleanDateTime(string name, NamingOptions namingOptions) { - return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes); + return CleanDateTimeParser.Clean(name, namingOptions.CleanDateTimeRegexes); } } } diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 8c919db43..fab085dbc 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -299,25 +299,29 @@ namespace Emby.Server.Implementations.AppBase /// public object GetConfiguration(string key) { - return _configurations.GetOrAdd(key, k => - { - var file = GetConfigurationFile(key); - - var configurationInfo = _configurationStores - .FirstOrDefault(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase)); - - if (configurationInfo == null) + return _configurations.GetOrAdd( + key, + (k, configurationManager) => { - throw new ResourceNotFoundException("Configuration with key " + key + " not found."); - } + var file = configurationManager.GetConfigurationFile(k); - var configurationType = configurationInfo.ConfigurationType; + var configurationInfo = Array.Find( + configurationManager._configurationStores, + i => string.Equals(i.Key, k, StringComparison.OrdinalIgnoreCase)); - lock (_configurationSyncLock) - { - return LoadConfiguration(file, configurationType); - } - }); + if (configurationInfo == null) + { + throw new ResourceNotFoundException("Configuration with key " + k + " not found."); + } + + var configurationType = configurationInfo.ConfigurationType; + + lock (configurationManager._configurationSyncLock) + { + return configurationManager.LoadConfiguration(file, configurationType); + } + }, + this); } private object LoadConfiguration(string path, Type configurationType) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 2d060dd65..1480600cf 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -43,6 +43,7 @@ namespace Emby.Server.Implementations.Data ///
public class SqliteItemRepository : BaseSqliteRepository, IItemRepository { + private const string FromText = " from TypedBaseItems A"; private const string ChaptersTableName = "Chapters2"; private readonly IServerConfigurationManager _config; @@ -1045,18 +1046,37 @@ namespace Emby.Server.Implementations.Data return Array.Empty(); } - var list = new List(); - foreach (var part in value.SpanSplit('|')) + // TODO The following is an ugly performance optimization, but it's extremely unlikely that the data in the database would be malformed + var valueSpan = value.AsSpan(); + var count = valueSpan.CountOccurrences('|') + 1; + + var position = 0; + var result = new ItemImageInfo[count]; + foreach (var part in valueSpan.Split('|')) { var image = ItemImageInfoFromValueString(part); if (image != null) { - list.Add(image); + result[position++] = image; } } - return list.ToArray(); + if (position == count) + { + return result; + } + + if (position == 0) + { + return Array.Empty(); + } + + // Extremely unlikely, but somehow one or more of the image strings were malformed. Cut the array. + var newResult = new ItemImageInfo[position]; + Array.Copy(result, newResult, position); + + return newResult; } private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image) @@ -2250,10 +2270,8 @@ namespace Emby.Server.Implementations.Data return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x)); } - private List GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable startColumns) + private List GetFinalColumnsToSelect(InternalItemsQuery query, List columns) { - var list = startColumns.ToList(); - foreach (var field in _allFields) { if (!HasField(query, field)) @@ -2261,28 +2279,28 @@ namespace Emby.Server.Implementations.Data switch (field) { case ItemFields.Settings: - list.Remove("IsLocked"); - list.Remove("PreferredMetadataCountryCode"); - list.Remove("PreferredMetadataLanguage"); - list.Remove("LockedFields"); + columns.Remove("IsLocked"); + columns.Remove("PreferredMetadataCountryCode"); + columns.Remove("PreferredMetadataLanguage"); + columns.Remove("LockedFields"); break; case ItemFields.ServiceName: - list.Remove("ExternalServiceId"); + columns.Remove("ExternalServiceId"); break; case ItemFields.SortName: - list.Remove("ForcedSortName"); + columns.Remove("ForcedSortName"); break; case ItemFields.Taglines: - list.Remove("Tagline"); + columns.Remove("Tagline"); break; case ItemFields.Tags: - list.Remove("Tags"); + columns.Remove("Tags"); break; case ItemFields.IsHD: // do nothing break; default: - list.Remove(field.ToString()); + columns.Remove(field.ToString()); break; } } @@ -2290,60 +2308,60 @@ namespace Emby.Server.Implementations.Data if (!HasProgramAttributes(query)) { - list.Remove("IsMovie"); - list.Remove("IsSeries"); - list.Remove("EpisodeTitle"); - list.Remove("IsRepeat"); - list.Remove("ShowId"); + columns.Remove("IsMovie"); + columns.Remove("IsSeries"); + columns.Remove("EpisodeTitle"); + columns.Remove("IsRepeat"); + columns.Remove("ShowId"); } if (!HasEpisodeAttributes(query)) { - list.Remove("SeasonName"); - list.Remove("SeasonId"); + columns.Remove("SeasonName"); + columns.Remove("SeasonId"); } if (!HasStartDate(query)) { - list.Remove("StartDate"); + columns.Remove("StartDate"); } if (!HasTrailerTypes(query)) { - list.Remove("TrailerTypes"); + columns.Remove("TrailerTypes"); } if (!HasArtistFields(query)) { - list.Remove("AlbumArtists"); - list.Remove("Artists"); + columns.Remove("AlbumArtists"); + columns.Remove("Artists"); } if (!HasSeriesFields(query)) { - list.Remove("SeriesId"); + columns.Remove("SeriesId"); } if (!HasEpisodeAttributes(query)) { - list.Remove("SeasonName"); - list.Remove("SeasonId"); + columns.Remove("SeasonName"); + columns.Remove("SeasonId"); } if (!query.DtoOptions.EnableImages) { - list.Remove("Images"); + columns.Remove("Images"); } if (EnableJoinUserData(query)) { - list.Add("UserDatas.UserId"); - list.Add("UserDatas.lastPlayedDate"); - list.Add("UserDatas.playbackPositionTicks"); - list.Add("UserDatas.playcount"); - list.Add("UserDatas.isFavorite"); - list.Add("UserDatas.played"); - list.Add("UserDatas.rating"); + columns.Add("UserDatas.UserId"); + columns.Add("UserDatas.lastPlayedDate"); + columns.Add("UserDatas.playbackPositionTicks"); + columns.Add("UserDatas.playcount"); + columns.Add("UserDatas.isFavorite"); + columns.Add("UserDatas.played"); + columns.Add("UserDatas.rating"); } if (query.SimilarTo != null) @@ -2391,7 +2409,7 @@ namespace Emby.Server.Implementations.Data builder.Append(") as SimilarityScore"); - list.Add(builder.ToString()); + columns.Add(builder.ToString()); var oldLen = query.ExcludeItemIds.Length; var newLen = oldLen + item.ExtraIds.Length + 1; @@ -2418,10 +2436,10 @@ namespace Emby.Server.Implementations.Data builder.Append(") as SearchScore"); - list.Add(builder.ToString()); + columns.Add(builder.ToString()); } - return list; + return columns; } private void BindSearchParams(InternalItemsQuery query, IStatement statement) @@ -2487,31 +2505,25 @@ namespace Emby.Server.Implementations.Data private string GetGroupBy(InternalItemsQuery query) { - var groups = new List(); - - if (EnableGroupByPresentationUniqueKey(query)) + var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(query); + if (enableGroupByPresentationUniqueKey && query.GroupBySeriesPresentationUniqueKey) { - groups.Add("PresentationUniqueKey"); + return " Group by PresentationUniqueKey, SeriesPresentationUniqueKey"; + } + + if (enableGroupByPresentationUniqueKey) + { + return " Group by PresentationUniqueKey"; } if (query.GroupBySeriesPresentationUniqueKey) { - groups.Add("SeriesPresentationUniqueKey"); - } - - if (groups.Count > 0) - { - return " Group by " + string.Join(',', groups); + return " Group by SeriesPresentationUniqueKey"; } return string.Empty; } - private string GetFromText(string alias = "A") - { - return " from TypedBaseItems " + alias; - } - public int GetCount(InternalItemsQuery query) { if (query == null) @@ -2529,17 +2541,19 @@ namespace Emby.Server.Implementations.Data query.Limit = query.Limit.Value + 4; } - var commandText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) - + GetFromText() - + GetJoinUserDataText(query); + var commandTextBuilder = new StringBuilder("select ") + .AppendJoin(',', GetFinalColumnsToSelect(query, new List { "count(distinct PresentationUniqueKey)" })) + .Append(FromText) + .Append(GetJoinUserDataText(query)); var whereClauses = GetWhereClauses(query, null); if (whereClauses.Count != 0) { - commandText += " where " + string.Join(" AND ", whereClauses); + commandTextBuilder.Append(" where ") + .AppendJoin(" AND ", whereClauses); } + var commandText = commandTextBuilder.ToString(); int count; using (var connection = GetConnection(true)) { @@ -2581,20 +2595,21 @@ namespace Emby.Server.Implementations.Data query.Limit = query.Limit.Value + 4; } - var commandText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns)) - + GetFromText() - + GetJoinUserDataText(query); + var commandTextBuilder = new StringBuilder("select ") + .AppendJoin(',', GetFinalColumnsToSelect(query, _retriveItemColumns.ToList())) + .Append(FromText) + .Append(GetJoinUserDataText(query)); var whereClauses = GetWhereClauses(query, null); if (whereClauses.Count != 0) { - commandText += " where " + string.Join(" AND ", whereClauses); + commandTextBuilder.Append(" where ") + .AppendJoin(" AND ", whereClauses); } - commandText += GetGroupBy(query) - + GetOrderByText(query); + commandTextBuilder.Append(GetGroupBy(query)) + .Append(GetOrderByText(query)); if (query.Limit.HasValue || query.StartIndex.HasValue) { @@ -2602,15 +2617,18 @@ namespace Emby.Server.Implementations.Data if (query.Limit.HasValue || offset > 0) { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); + commandTextBuilder.Append(" LIMIT ") + .Append((query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture)); } if (offset > 0) { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); + commandTextBuilder.Append(" OFFSET ") + .Append(offset.ToString(CultureInfo.InvariantCulture)); } } + var commandText = commandTextBuilder.ToString(); var items = new List(); using (var connection = GetConnection(true)) { @@ -2766,20 +2784,25 @@ namespace Emby.Server.Implementations.Data query.Limit = query.Limit.Value + 4; } - var commandText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns)) - + GetFromText() - + GetJoinUserDataText(query); + var commandTextBuilder = new StringBuilder("select ") + .AppendJoin(',', GetFinalColumnsToSelect(query, _retriveItemColumns.ToList())) + .Append(FromText) + .Append(GetJoinUserDataText(query)); var whereClauses = GetWhereClauses(query, null); var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses); + string.Join(" AND ", whereClauses); - commandText += whereText - + GetGroupBy(query) - + GetOrderByText(query); + if (!string.IsNullOrEmpty(whereText)) + { + commandTextBuilder.Append(" where ") + .Append(whereText); + } + + commandTextBuilder.Append(GetGroupBy(query)) + .Append(GetOrderByText(query)); if (query.Limit.HasValue || query.StartIndex.HasValue) { @@ -2787,43 +2810,54 @@ namespace Emby.Server.Implementations.Data if (query.Limit.HasValue || offset > 0) { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); + commandTextBuilder.Append(" LIMIT ") + .Append((query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture)); } if (offset > 0) { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); + commandTextBuilder.Append(" OFFSET ") + .Append(offset.ToString(CultureInfo.InvariantCulture)); } } var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0; - var statementTexts = new List(); + var itemQuery = string.Empty; + var totalRecordCountQuery = string.Empty; if (!isReturningZeroItems) { - statementTexts.Add(commandText); + itemQuery = commandTextBuilder.ToString(); } if (query.EnableTotalRecordCount) { - commandText = string.Empty; + commandTextBuilder.Clear(); + + commandTextBuilder.Append(" select "); if (EnableGroupByPresentationUniqueKey(query)) { - commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText(); + commandTextBuilder.AppendJoin(',', GetFinalColumnsToSelect(query, new List { "count (distinct PresentationUniqueKey)" })); } else if (query.GroupBySeriesPresentationUniqueKey) { - commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText(); + commandTextBuilder.AppendJoin(',', GetFinalColumnsToSelect(query, new List { "count (distinct SeriesPresentationUniqueKey)" })); } else { - commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText(); + commandTextBuilder.AppendJoin(',', GetFinalColumnsToSelect(query, new List { "count (guid)" })); } - commandText += GetJoinUserDataText(query) - + whereText; - statementTexts.Add(commandText); + commandTextBuilder.Append(FromText) + .Append(GetJoinUserDataText(query)); + if (!string.IsNullOrEmpty(whereText)) + { + commandTextBuilder.Append(" where ") + .Append(whereText); + } + + totalRecordCountQuery = commandTextBuilder.ToString(); } var list = new List(); @@ -2833,11 +2867,12 @@ namespace Emby.Server.Implementations.Data connection.RunInTransaction( db => { - var statements = PrepareAll(db, statementTexts); + var itemQueryStatement = PrepareStatement(db, itemQuery); + var totalRecordCountQueryStatement = PrepareStatement(db, totalRecordCountQuery); if (!isReturningZeroItems) { - using (var statement = statements[0]) + using (var statement = itemQueryStatement) { if (EnableJoinUserData(query)) { @@ -2867,11 +2902,14 @@ namespace Emby.Server.Implementations.Data } } } + + LogQueryTime("GetItems.ItemQuery", itemQuery, now); } + now = DateTime.UtcNow; if (query.EnableTotalRecordCount) { - using (var statement = statements[statements.Length - 1]) + using (var statement = totalRecordCountQueryStatement) { if (EnableJoinUserData(query)) { @@ -2886,11 +2924,12 @@ namespace Emby.Server.Implementations.Data result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } + + LogQueryTime("GetItems.TotalRecordCount", totalRecordCountQuery, now); } }, ReadTransactionMode); } - LogQueryTime("GetItems", commandText, now); result.Items = list; return result; } @@ -3023,19 +3062,20 @@ namespace Emby.Server.Implementations.Data var now = DateTime.UtcNow; - var commandText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" })) - + GetFromText() - + GetJoinUserDataText(query); + var commandTextBuilder = new StringBuilder("select ") + .AppendJoin(',', GetFinalColumnsToSelect(query, new List { "guid" })) + .Append(FromText) + .Append(GetJoinUserDataText(query)); var whereClauses = GetWhereClauses(query, null); if (whereClauses.Count != 0) { - commandText += " where " + string.Join(" AND ", whereClauses); + commandTextBuilder.Append(" where ") + .AppendJoin(" AND ", whereClauses); } - commandText += GetGroupBy(query) - + GetOrderByText(query); + commandTextBuilder.Append(GetGroupBy(query)) + .Append(GetOrderByText(query)); if (query.Limit.HasValue || query.StartIndex.HasValue) { @@ -3043,15 +3083,18 @@ namespace Emby.Server.Implementations.Data if (query.Limit.HasValue || offset > 0) { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); + commandTextBuilder.Append(" LIMIT ") + .Append((query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture)); } if (offset > 0) { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); + commandTextBuilder.Append(" OFFSET ") + .Append(offset.ToString(CultureInfo.InvariantCulture)); } } + var commandText = commandTextBuilder.ToString(); var list = new List(); using (var connection = GetConnection(true)) { @@ -3090,7 +3133,7 @@ namespace Emby.Server.Implementations.Data var now = DateTime.UtcNow; - var commandText = "select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid", "path" })) + GetFromText(); + var commandText = "select " + string.Join(',', GetFinalColumnsToSelect(query, new List { "guid", "path" })) + FromText; var whereClauses = GetWhereClauses(query, null); if (whereClauses.Count != 0) @@ -3167,8 +3210,8 @@ namespace Emby.Server.Implementations.Data var now = DateTime.UtcNow; var commandText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" })) - + GetFromText() + + string.Join(',', GetFinalColumnsToSelect(query, new List { "guid" })) + + FromText + GetJoinUserDataText(query); var whereClauses = GetWhereClauses(query, null); @@ -3210,15 +3253,15 @@ namespace Emby.Server.Implementations.Data if (EnableGroupByPresentationUniqueKey(query)) { - commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText(); + commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new List { "count (distinct PresentationUniqueKey)" })) + FromText; } else if (query.GroupBySeriesPresentationUniqueKey) { - commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText(); + commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new List { "count (distinct SeriesPresentationUniqueKey)" })) + FromText; } else { - commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText(); + commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new List { "count (guid)" })) + FromText; } commandText += GetJoinUserDataText(query) @@ -4415,56 +4458,50 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb")); } - var includedItemByNameTypes = GetItemByNameTypesInQuery(query); - var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; - var queryTopParentIds = query.TopParentIds; - if (queryTopParentIds.Length == 1) + if (queryTopParentIds.Length > 0) { - if (enableItemsByName && includedItemByNameTypes.Count == 1) - { - whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)"); - if (statement != null) - { - statement.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]); - } - } - else if (enableItemsByName && includedItemByNameTypes.Count > 1) - { - var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'")); - whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))"); - } - else - { - whereClauses.Add("(TopParentId=@TopParentId)"); - } + var includedItemByNameTypes = GetItemByNameTypesInQuery(query); + var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; - if (statement != null) + if (queryTopParentIds.Length == 1) { - statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture)); - } - } - else if (queryTopParentIds.Length > 1) - { - var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); - - if (enableItemsByName && includedItemByNameTypes.Count == 1) - { - whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))"); - if (statement != null) + if (enableItemsByName && includedItemByNameTypes.Count == 1) { - statement.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]); + whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)"); + statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]); } + else if (enableItemsByName && includedItemByNameTypes.Count > 1) + { + var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'")); + whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))"); + } + else + { + whereClauses.Add("(TopParentId=@TopParentId)"); + } + + statement?.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture)); } - else if (enableItemsByName && includedItemByNameTypes.Count > 1) + else if (queryTopParentIds.Length > 1) { - var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'")); - whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))"); - } - else - { - whereClauses.Add("TopParentId in (" + val + ")"); + var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); + + if (enableItemsByName && includedItemByNameTypes.Count == 1) + { + whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))"); + statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]); + } + else if (enableItemsByName && includedItemByNameTypes.Count > 1) + { + var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'")); + whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))"); + } + else + { + whereClauses.Add("TopParentId in (" + val + ")"); + } } } @@ -4746,17 +4783,12 @@ namespace Emby.Server.Implementations.Data return true; } - var types = new[] - { - nameof(Episode), - nameof(Video), - nameof(Movie), - nameof(MusicVideo), - nameof(Series), - nameof(Season) - }; - - if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase))) + if (query.IncludeItemTypes.Contains(nameof(Episode), StringComparer.OrdinalIgnoreCase) + || query.IncludeItemTypes.Contains(nameof(Video), StringComparer.OrdinalIgnoreCase) + || query.IncludeItemTypes.Contains(nameof(Movie), StringComparer.OrdinalIgnoreCase) + || query.IncludeItemTypes.Contains(nameof(MusicVideo), StringComparer.OrdinalIgnoreCase) + || query.IncludeItemTypes.Contains(nameof(Series), StringComparer.OrdinalIgnoreCase) + || query.IncludeItemTypes.Contains(nameof(Season), StringComparer.OrdinalIgnoreCase)) { return true; } @@ -5200,37 +5232,45 @@ AND Type = @InternalPersonType)"); var now = DateTime.UtcNow; - var typeClause = itemValueTypes.Length == 1 ? - ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : - ("Type in (" + string.Join(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")"); - - var commandText = "Select Value From ItemValues where " + typeClause; + var stringBuilder = new StringBuilder("Select Value From ItemValues where Type"); + if (itemValueTypes.Length == 1) + { + stringBuilder.Append('=') + .Append(itemValueTypes[0].ToString(CultureInfo.InvariantCulture)); + } + else + { + stringBuilder.Append(" in (") + .AppendJoin(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + .Append(')'); + } if (withItemTypes.Count > 0) { - var typeString = string.Join(',', withItemTypes.Select(i => "'" + i + "'")); - commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))"; + stringBuilder.Append(" AND ItemId In (select guid from typedbaseitems where type in (") + .AppendJoin(',', withItemTypes.Select(i => "'" + i + "'")) + .Append("))"); } if (excludeItemTypes.Count > 0) { - var typeString = string.Join(',', excludeItemTypes.Select(i => "'" + i + "'")); - commandText += " AND ItemId not In (select guid from typedbaseitems where type in (" + typeString + "))"; + stringBuilder.Append(" AND ItemId not In (select guid from typedbaseitems where type in (") + .AppendJoin(',', excludeItemTypes.Select(i => "'" + i + "'")) + .Append("))"); } - commandText += " Group By CleanValue"; + stringBuilder.Append(" Group By CleanValue"); + var commandText = stringBuilder.ToString(); var list = new List(); using (var connection = GetConnection(true)) + using (var statement = PrepareStatement(connection, commandText)) { - using (var statement = PrepareStatement(connection, commandText)) + foreach (var row in statement.ExecuteQuery()) { - foreach (var row in statement.ExecuteQuery()) + if (row.TryGetString(0, out var result)) { - if (row.TryGetString(0, out var result)) - { - list.Add(result); - } + list.Add(result); } } } @@ -5261,13 +5301,14 @@ AND Type = @InternalPersonType)"); InternalItemsQuery typeSubQuery = null; - Dictionary itemCountColumns = null; + string itemCountColumns = null; + var stringBuilder = new StringBuilder(); var typesToCount = query.IncludeItemTypes; if (typesToCount.Length > 0) { - var itemCountColumnQuery = "select group_concat(type, '|')" + GetFromText("B"); + stringBuilder.Append("(select group_concat(type, '|') from TypedBaseItems B"); typeSubQuery = new InternalItemsQuery(query.User) { @@ -5283,20 +5324,21 @@ AND Type = @InternalPersonType)"); }; var whereClauses = GetWhereClauses(typeSubQuery, null); - whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")"); + stringBuilder.Append(" where ") + .AppendJoin(" AND ", whereClauses) + .Append("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND ") + .Append(typeClause) + .Append(")) as itemTypes"); - itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses); - - itemCountColumns = new Dictionary() - { - { "itemTypes", "(" + itemCountColumnQuery + ") as itemTypes" } - }; + itemCountColumns = stringBuilder.ToString(); + stringBuilder.Clear(); } List columns = _retriveItemColumns.ToList(); - if (itemCountColumns != null) + // Unfortunately we need to add it to columns to ensure the order of the columns in the select + if (!string.IsNullOrEmpty(itemCountColumns)) { - columns.AddRange(itemCountColumns.Values); + columns.Add(itemCountColumns); } // do this first before calling GetFinalColumnsToSelect, otherwise ExcludeItemIds will be set by SimilarTo @@ -5319,18 +5361,18 @@ AND Type = @InternalPersonType)"); columns = GetFinalColumnsToSelect(query, columns); - var commandText = "select " - + string.Join(',', columns) - + GetFromText() - + GetJoinUserDataText(query); - var innerWhereClauses = GetWhereClauses(innerQuery, null); - var innerWhereText = innerWhereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", innerWhereClauses); + stringBuilder.Append(" where Type=@SelectType And CleanName In (Select CleanValue from ItemValues where ") + .Append(typeClause) + .Append(" AND ItemId in (select guid from TypedBaseItems"); + if (innerWhereClauses.Count > 0) + { + stringBuilder.Append(" where ") + .AppendJoin(" AND ", innerWhereClauses); + } - var whereText = " where Type=@SelectType And CleanName In (Select CleanValue from ItemValues where " + typeClause + " AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))"; + stringBuilder.Append("))"); var outerQuery = new InternalItemsQuery(query.User) { @@ -5355,23 +5397,31 @@ AND Type = @InternalPersonType)"); }; var outerWhereClauses = GetWhereClauses(outerQuery, null); - if (outerWhereClauses.Count != 0) { - whereText += " AND " + string.Join(" AND ", outerWhereClauses); + stringBuilder.Append(" AND ") + .AppendJoin(" AND ", outerWhereClauses); } - commandText += whereText + " group by PresentationUniqueKey"; + var whereText = stringBuilder.ToString(); + stringBuilder.Clear(); + + stringBuilder.Append("select ") + .AppendJoin(',', columns) + .Append(FromText) + .Append(GetJoinUserDataText(query)) + .Append(whereText) + .Append(" group by PresentationUniqueKey"); if (query.OrderBy.Count != 0 || query.SimilarTo != null || !string.IsNullOrEmpty(query.SearchTerm)) { - commandText += GetOrderByText(query); + stringBuilder.Append(GetOrderByText(query)); } else { - commandText += " order by SortName"; + stringBuilder.Append(" order by SortName"); } if (query.Limit.HasValue || query.StartIndex.HasValue) @@ -5380,32 +5430,37 @@ AND Type = @InternalPersonType)"); if (query.Limit.HasValue || offset > 0) { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); + stringBuilder.Append(" LIMIT ") + .Append((query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture)); } if (offset > 0) { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); + stringBuilder.Append(" OFFSET ") + .Append(offset.ToString(CultureInfo.InvariantCulture)); } } var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0; - var statementTexts = new List(); + string commandText = string.Empty; + if (!isReturningZeroItems) { - statementTexts.Add(commandText); + commandText = stringBuilder.ToString(); } + string countText = string.Empty; if (query.EnableTotalRecordCount) { - var countText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) - + GetFromText() - + GetJoinUserDataText(query) - + whereText; + stringBuilder.Clear(); + stringBuilder.Append("select ") + .AppendJoin(',', GetFinalColumnsToSelect(query, new List { "count (distinct PresentationUniqueKey)" })) + .Append(FromText) + .Append(GetJoinUserDataText(query)) + .Append(whereText); - statementTexts.Add(countText); + countText = stringBuilder.ToString(); } var list = new List<(BaseItem, ItemCounts)>(); @@ -5415,11 +5470,9 @@ AND Type = @InternalPersonType)"); connection.RunInTransaction( db => { - var statements = PrepareAll(db, statementTexts); - if (!isReturningZeroItems) { - using (var statement = statements[0]) + using (var statement = PrepareStatement(db, commandText)) { statement.TryBind("@SelectType", returnType); if (EnableJoinUserData(query)) @@ -5460,13 +5513,7 @@ AND Type = @InternalPersonType)"); if (query.EnableTotalRecordCount) { - commandText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) - + GetFromText() - + GetJoinUserDataText(query) - + whereText; - - using (var statement = statements[statements.Length - 1]) + using (var statement = PrepareStatement(db, countText)) { statement.TryBind("@SelectType", returnType); if (EnableJoinUserData(query)) diff --git a/Emby.Server.Implementations/Data/TypeMapper.cs b/Emby.Server.Implementations/Data/TypeMapper.cs index 7f1306d15..064664e1f 100644 --- a/Emby.Server.Implementations/Data/TypeMapper.cs +++ b/Emby.Server.Implementations/Data/TypeMapper.cs @@ -28,19 +28,9 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(typeName)); } - return _typeMap.GetOrAdd(typeName, LookupType); - } - - /// - /// Lookups the type. - /// - /// Name of the type. - /// Type. - private Type? LookupType(string typeName) - { - return AppDomain.CurrentDomain.GetAssemblies() - .Select(a => a.GetType(typeName)) - .FirstOrDefault(t => t != null); + return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies() + .Select(a => a.GetType(k)) + .FirstOrDefault(t => t != null)); } } } diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 6a554e68a..64d802457 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; @@ -243,8 +244,8 @@ namespace Emby.Server.Implementations.IO { result.Length = fileInfo.Length; - // Issue #2354 get the size of files behind symbolic links - if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)) + // Issue #2354 get the size of files behind symbolic links. Also Enum.HasFlag is bad as it boxes! + if ((fileInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) { try { @@ -618,13 +619,13 @@ namespace Emby.Server.Implementations.IO { files = files.Where(i => { - var ext = i.Extension; - if (ext == null) + var ext = i.Extension.AsSpan(); + if (ext.IsEmpty) { return false; } - return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase); }); } @@ -636,8 +637,7 @@ namespace Emby.Server.Implementations.IO var directoryInfo = new DirectoryInfo(path); var enumerationOptions = GetEnumerationOptions(recursive); - return ToMetadata(directoryInfo.EnumerateDirectories("*", enumerationOptions)) - .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", enumerationOptions))); + return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", enumerationOptions)); } private IEnumerable ToMetadata(IEnumerable infos) @@ -672,13 +672,13 @@ namespace Emby.Server.Implementations.IO { files = files.Where(i => { - var ext = Path.GetExtension(i); - if (ext == null) + var ext = Path.GetExtension(i.AsSpan()); + if (ext.IsEmpty) { return false; } - return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase); }); } diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 3380e29d4..c7d113963 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Library if (parent != null) { // Don't resolve these into audio files - if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename, StringComparison.Ordinal) + if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFilename, StringComparison.Ordinal) && _libraryManager.IsAudioFile(filename)) { return true; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f8d8197d4..ffff3cfc5 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -696,25 +696,32 @@ namespace Emby.Server.Implementations.Library } private IEnumerable ResolveFileList( - IEnumerable fileList, + IReadOnlyList fileList, IDirectoryService directoryService, Folder parent, string collectionType, IItemResolver[] resolvers, LibraryOptions libraryOptions) { - return fileList.Select(f => + // Given that fileList is a list we can save enumerator allocations by indexing + for (var i = 0; i < fileList.Count; i++) { + var file = fileList[i]; + BaseItem result = null; try { - return ResolvePath(f, directoryService, resolvers, parent, collectionType, libraryOptions); + result = ResolvePath(file, directoryService, resolvers, parent, collectionType, libraryOptions); } catch (Exception ex) { - _logger.LogError(ex, "Error resolving path {path}", f.FullName); - return null; + _logger.LogError(ex, "Error resolving path {Path}", file.FullName); } - }).Where(i => i != null); + + if (result != null) + { + yield return result; + } + } } /// @@ -2076,7 +2083,7 @@ namespace Emby.Server.Implementations.Library return new List(); } - return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType().ToList()); + return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType()); } public List GetCollectionFolders(BaseItem item, List allUserRootChildren) @@ -2101,10 +2108,10 @@ namespace Emby.Server.Implementations.Library return GetCollectionFoldersInternal(item, allUserRootChildren); } - private static List GetCollectionFoldersInternal(BaseItem item, List allUserRootChildren) + private static List GetCollectionFoldersInternal(BaseItem item, IEnumerable allUserRootChildren) { return allUserRootChildren - .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase)) + .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path.AsSpan(), StringComparison.OrdinalIgnoreCase)) .ToList(); } @@ -2112,9 +2119,9 @@ namespace Emby.Server.Implementations.Library { if (!(item is CollectionFolder collectionFolder)) { + // List.Find is more performant than FirstOrDefault due to enumerator allocation collectionFolder = GetCollectionFolders(item) - .OfType() - .FirstOrDefault(); + .Find(folder => folder is CollectionFolder) as CollectionFolder; } return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); @@ -2500,8 +2507,7 @@ namespace Emby.Server.Implementations.Library /// public bool IsVideoFile(string path) { - var resolver = new VideoResolver(GetNamingOptions()); - return resolver.IsVideoFile(path); + return VideoResolver.IsVideoFile(path, GetNamingOptions()); } /// @@ -2679,6 +2685,7 @@ namespace Emby.Server.Implementations.Library return changed; } + /// public NamingOptions GetNamingOptions() { if (_namingOptions == null) @@ -2692,13 +2699,12 @@ namespace Emby.Server.Implementations.Library public ItemLookupInfo ParseName(string name) { - var resolver = new VideoResolver(GetNamingOptions()); - - var result = resolver.CleanDateTime(name); + var namingOptions = GetNamingOptions(); + var result = VideoResolver.CleanDateTime(name, namingOptions); return new ItemLookupInfo { - Name = resolver.TryCleanString(result.Name, out var newName) ? newName.ToString() : result.Name, + Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name, Year = result.Year }; } @@ -2712,9 +2718,7 @@ namespace Emby.Server.Implementations.Library .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); - var videoListResolver = new VideoListResolver(namingOptions); - - var videos = videoListResolver.Resolve(fileSystemChildren); + var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions); var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase)); @@ -2758,9 +2762,7 @@ namespace Emby.Server.Implementations.Library .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); - var videoListResolver = new VideoListResolver(namingOptions); - - var videos = videoListResolver.Resolve(fileSystemChildren); + var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions); var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase)); diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 38e81d14c..b812b6b61 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -352,7 +352,7 @@ namespace Emby.Server.Implementations.Library private string[] NormalizeLanguage(string language) { - if (language == null) + if (string.IsNullOrEmpty(language)) { return Array.Empty(); } @@ -381,8 +381,7 @@ namespace Emby.Server.Implementations.Library } } - var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference) - ? Array.Empty() : NormalizeLanguage(user.SubtitleLanguagePreference); + var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; var audioLangage = defaultAudioIndex == null @@ -411,9 +410,7 @@ namespace Emby.Server.Implementations.Library } } - var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference) - ? Array.Empty() - : NormalizeLanguage(user.AudioLanguagePreference); + var preferredAudio = NormalizeLanguage(user.AudioLanguagePreference); source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); } diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index a3dcdc944..cdb492022 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -47,11 +47,9 @@ namespace Emby.Server.Implementations.Library.Resolvers protected virtual TVideoType ResolveVideo(ItemResolveArgs args, bool parseName) where TVideoType : Video, new() { - var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); + var namingOptions = LibraryManager.GetNamingOptions(); // If the path is a file check for a matching extensions - var parser = new VideoResolver(namingOptions); - if (args.IsDirectory) { TVideoType video = null; @@ -66,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Resolvers { if (IsDvdDirectory(child.FullName, filename, args.DirectoryService)) { - videoInfo = parser.ResolveDirectory(args.Path); + videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); if (videoInfo == null) { @@ -84,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Resolvers if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService)) { - videoInfo = parser.ResolveDirectory(args.Path); + videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); if (videoInfo == null) { @@ -102,7 +100,7 @@ namespace Emby.Server.Implementations.Library.Resolvers } else if (IsDvdFile(filename)) { - videoInfo = parser.ResolveDirectory(args.Path); + videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions); if (videoInfo == null) { @@ -132,7 +130,7 @@ namespace Emby.Server.Implementations.Library.Resolvers } else { - var videoInfo = parser.Resolve(args.Path, false, false); + var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false); if (videoInfo == null) { @@ -252,10 +250,7 @@ namespace Emby.Server.Implementations.Library.Resolvers protected void Set3DFormat(Video video) { - var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); - - var resolver = new Format3DParser(namingOptions); - var result = resolver.Parse(video.Path); + var result = Format3DParser.Parse(video.Path, LibraryManager.GetNamingOptions()); Set3DFormat(video, result.Is3D, result.Format3D); } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 02c528764..97f96f746 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Video; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -257,10 +258,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } } - var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); + var namingOptions = LibraryManager.GetNamingOptions(); - var resolver = new VideoListResolver(namingOptions); - var resolverResult = resolver.Resolve(files, suppportMultiEditions).ToList(); + var resolverResult = VideoListResolver.Resolve(files, namingOptions, suppportMultiEditions).ToList(); var result = new MultiItemResolverResult { @@ -537,7 +537,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return returnVideo; } - private bool IsInvalid(Folder parent, string collectionType) + private bool IsInvalid(Folder parent, ReadOnlySpan collectionType) { if (parent != null) { @@ -547,12 +547,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } } - if (string.IsNullOrEmpty(collectionType)) + if (collectionType.IsEmpty) { return false; } - return !_validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase); + return !_validCollectionTypes.Contains(collectionType, StringComparison.OrdinalIgnoreCase); } } } diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index dd5dee1d1..b1ff28c2c 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Reflection; using System.Text.Json; using System.Threading.Tasks; @@ -169,12 +168,22 @@ namespace Emby.Server.Implementations.Localization /// public CultureDto FindLanguageInfo(string language) - => GetCultures() - .FirstOrDefault(i => - string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) - || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) - || i.ThreeLetterISOLanguageNames.Contains(language, StringComparer.OrdinalIgnoreCase) - || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); + { + // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs + for (var i = 0; i < _cultures.Count; i++) + { + var culture = _cultures[i]; + if (language.Equals(culture.DisplayName, StringComparison.OrdinalIgnoreCase) + || language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase) + || culture.ThreeLetterISOLanguageNames.Contains(language, StringComparison.OrdinalIgnoreCase) + || language.Equals(culture.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase)) + { + return culture; + } + } + + return default; + } /// public IEnumerable GetCountries() @@ -224,7 +233,7 @@ namespace Emby.Server.Implementations.Localization throw new ArgumentNullException(nameof(rating)); } - if (_unratedValues.Contains(rating, StringComparer.OrdinalIgnoreCase)) + if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase)) { return null; } @@ -252,11 +261,11 @@ namespace Emby.Server.Implementations.Localization var index = rating.IndexOf(':', StringComparison.Ordinal); if (index != -1) { - rating = rating.Substring(index).TrimStart(':').Trim(); + var trimmedRating = rating.AsSpan(index).TrimStart(':').Trim(); - if (!string.IsNullOrWhiteSpace(rating)) + if (!trimmedRating.IsEmpty) { - return GetRatingLevel(rating); + return GetRatingLevel(trimmedRating.ToString()); } } @@ -318,7 +327,8 @@ namespace Emby.Server.Implementations.Localization return _dictionaries.GetOrAdd( culture, - f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); + (key, localizationManager) => localizationManager.GetDictionary(Prefix, key, DefaultCulture + ".json").GetAwaiter().GetResult(), + this); } private async Task> GetDictionary(string prefix, string culture, string baseFilename) diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs index 8d8b82f0a..5ff73de81 100644 --- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -21,7 +21,8 @@ namespace Emby.Server.Implementations.Serialization private static XmlSerializer GetSerializer(Type type) => _serializers.GetOrAdd( type.FullName ?? throw new ArgumentException($"Invalid type {type}."), - _ => new XmlSerializer(type)); + (_, t) => new XmlSerializer(t), + type); /// /// Serializes to writer. diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index ac589b03c..d5483bf40 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -26,19 +26,23 @@ namespace Emby.Server.Implementations webDirectoryPath) { InternalMetadataPath = DefaultInternalMetadataPath; + // ProgramDataPath cannot change when the server is running, so cache these to avoid allocations. + RootFolderPath = Path.Join(ProgramDataPath, "root"); + DefaultUserViewsPath = Path.Combine(RootFolderPath, "default"); + DefaultInternalMetadataPath = Path.Combine(ProgramDataPath, "metadata"); } /// /// Gets the path to the base root media directory. /// /// The root folder path. - public string RootFolderPath => Path.Combine(ProgramDataPath, "root"); + public string RootFolderPath { get; } /// /// Gets the path to the default user view directory. Used if no specific user view is defined. /// /// The default user views path. - public string DefaultUserViewsPath => Path.Combine(RootFolderPath, "default"); + public string DefaultUserViewsPath { get; } /// /// Gets the path to the People directory. @@ -98,7 +102,7 @@ namespace Emby.Server.Implementations public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users"); /// - public string DefaultInternalMetadataPath => Path.Combine(ProgramDataPath, "metadata"); + public string DefaultInternalMetadataPath { get; } /// public string InternalMetadataPath { get; set; } diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index 68119cfed..ffc274c5d 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Threading; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -52,7 +53,7 @@ namespace MediaBrowser.Controller.BaseItemManager var typeOptions = libraryOptions.GetTypeOptions(baseItem.GetType().Name); if (typeOptions != null) { - return typeOptions.MetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); + return typeOptions.MetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); } if (!libraryOptions.EnableInternetProviders) @@ -62,7 +63,7 @@ namespace MediaBrowser.Controller.BaseItemManager var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase)); - return itemConfig == null || !itemConfig.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); + return itemConfig == null || !itemConfig.DisabledMetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); } /// @@ -83,7 +84,7 @@ namespace MediaBrowser.Controller.BaseItemManager var typeOptions = libraryOptions.GetTypeOptions(baseItem.GetType().Name); if (typeOptions != null) { - return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); + return typeOptions.ImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); } if (!libraryOptions.EnableInternetProviders) @@ -93,7 +94,7 @@ namespace MediaBrowser.Controller.BaseItemManager var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase)); - return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase); + return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); } /// diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 6e46b4cec..2574961b8 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -666,14 +666,12 @@ namespace MediaBrowser.Controller.Entities { if (SourceType == SourceType.Channel) { - return System.IO.Path.Combine(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); + return System.IO.Path.Join(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); } ReadOnlySpan idString = Id.ToString("N", CultureInfo.InvariantCulture); - basePath = System.IO.Path.Combine(basePath, "library"); - - return System.IO.Path.Join(basePath, idString.Slice(0, 2), idString); + return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString); } /// @@ -1258,7 +1256,7 @@ namespace MediaBrowser.Controller.Entities // Support plex/xbmc convention files.AddRange(fileSystemChildren - .Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))); + .Where(i => !i.IsDirectory && System.IO.Path.GetFileNameWithoutExtension(i.FullName.AsSpan()).Equals(ThemeSongFilename, StringComparison.OrdinalIgnoreCase))); return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions()) .OfType() @@ -1319,14 +1317,16 @@ namespace MediaBrowser.Controller.Entities { var extras = new List