Selaa lähdekoodia

Minor improvements

Bond_009 3 vuotta sitten
vanhempi
sitoutus
19824bff94

+ 1 - 1
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -880,7 +880,7 @@ namespace Emby.Server.Implementations.Channels
             }
         }
 
-        private async Task CacheResponse(object result, string path)
+        private async Task CacheResponse(ChannelItemResult result, string path)
         {
             try
             {

+ 13 - 19
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -63,13 +61,13 @@ namespace Emby.Server.Implementations.Collections
         }
 
         /// <inheritdoc />
-        public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
+        public event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
 
         /// <inheritdoc />
-        public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
+        public event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
 
         /// <inheritdoc />
-        public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
+        public event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
 
         private IEnumerable<Folder> FindFolders(string path)
         {
@@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Collections
                 .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path));
         }
 
-        internal async Task<Folder> EnsureLibraryFolder(string path, bool createIfNeeded)
+        internal async Task<Folder?> EnsureLibraryFolder(string path, bool createIfNeeded)
         {
             var existingFolder = FindFolders(path).FirstOrDefault();
             if (existingFolder != null)
@@ -114,7 +112,7 @@ namespace Emby.Server.Implementations.Collections
             return Path.Combine(_appPaths.DataPath, "collections");
         }
 
-        private Task<Folder> GetCollectionsFolder(bool createIfNeeded)
+        private Task<Folder?> GetCollectionsFolder(bool createIfNeeded)
         {
             return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
         }
@@ -203,8 +201,7 @@ namespace Emby.Server.Implementations.Collections
 
         private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
         {
-            var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
-            if (collection == null)
+            if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
             {
                 throw new ArgumentException("No collection exists with the supplied Id");
             }
@@ -256,9 +253,7 @@ namespace Emby.Server.Implementations.Collections
         /// <inheritdoc />
         public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
         {
-            var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
-
-            if (collection == null)
+            if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
             {
                 throw new ArgumentException("No collection exists with the supplied Id");
             }
@@ -312,11 +307,7 @@ namespace Emby.Server.Implementations.Collections
 
             foreach (var item in items)
             {
-                if (item is not ISupportsBoxSetGrouping)
-                {
-                    results[item.Id] = item;
-                }
-                else
+                if (item is ISupportsBoxSetGrouping)
                 {
                     var itemId = item.Id;
 
@@ -340,6 +331,7 @@ namespace Emby.Server.Implementations.Collections
                     }
 
                     var alreadyInResults = false;
+
                     // this is kind of a performance hack because only Video has alternate versions that should be in a box set?
                     if (item is Video video)
                     {
@@ -355,11 +347,13 @@ namespace Emby.Server.Implementations.Collections
                         }
                     }
 
-                    if (!alreadyInResults)
+                    if (alreadyInResults)
                     {
-                        results[itemId] = item;
+                        continue;
                     }
                 }
+
+                results[item.Id] = item;
             }
 
             return results.Values;

+ 27 - 36
Emby.Server.Implementations/Localization/LocalizationManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -38,10 +36,10 @@ namespace Emby.Server.Implementations.Localization
         private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
             new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
 
-        private List<CultureDto> _cultures;
-
         private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 
+        private List<CultureDto> _cultures = new List<CultureDto>();
+
         /// <summary>
         /// Initializes a new instance of the <see cref="LocalizationManager" /> class.
         /// </summary>
@@ -72,8 +70,8 @@ namespace Emby.Server.Implementations.Localization
                 string countryCode = resource.Substring(RatingsPath.Length, 2);
                 var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
 
-                await using var str = _assembly.GetManifestResourceStream(resource);
-                using var reader = new StreamReader(str);
+                await using var stream = _assembly.GetManifestResourceStream(resource);
+                using var reader = new StreamReader(stream!); // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames()
                 await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
                 {
                     if (string.IsNullOrWhiteSpace(line))
@@ -113,7 +111,8 @@ namespace Emby.Server.Implementations.Localization
         {
             List<CultureDto> list = new List<CultureDto>();
 
-            await using var stream = _assembly.GetManifestResourceStream(CulturesPath);
+            await using var stream = _assembly.GetManifestResourceStream(CulturesPath)
+                ?? throw new InvalidOperationException($"Invalid resource path: '{CulturesPath}'");
             using var reader = new StreamReader(stream);
             await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
             {
@@ -162,7 +161,7 @@ namespace Emby.Server.Implementations.Localization
         }
 
         /// <inheritdoc />
-        public CultureDto FindLanguageInfo(string language)
+        public CultureDto? FindLanguageInfo(string language)
         {
             // TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
             for (var i = 0; i < _cultures.Count; i++)
@@ -183,9 +182,10 @@ namespace Emby.Server.Implementations.Localization
         /// <inheritdoc />
         public IEnumerable<CountryInfo> GetCountries()
         {
-            using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream(CountriesPath));
-
-            return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions);
+            using StreamReader reader = new StreamReader(
+                _assembly.GetManifestResourceStream(CountriesPath) ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'"));
+            return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions)
+                ?? throw new InvalidOperationException($"Resource contains invalid data: '{CountriesPath}'");
         }
 
         /// <inheritdoc />
@@ -205,7 +205,9 @@ namespace Emby.Server.Implementations.Localization
                 countryCode = "us";
             }
 
-            return GetRatings(countryCode) ?? GetRatings("us");
+            return GetRatings(countryCode)
+                ?? GetRatings("us")
+                ?? throw new InvalidOperationException($"Invalid resource path: '{CountriesPath}'");
         }
 
         /// <summary>
@@ -213,7 +215,7 @@ namespace Emby.Server.Implementations.Localization
         /// </summary>
         /// <param name="countryCode">The country code.</param>
         /// <returns>The ratings.</returns>
-        private Dictionary<string, ParentalRating> GetRatings(string countryCode)
+        private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
         {
             _allParentalRatings.TryGetValue(countryCode, out var value);
 
@@ -238,7 +240,7 @@ namespace Emby.Server.Implementations.Localization
 
             var ratingsDictionary = GetParentalRatingsDictionary();
 
-            if (ratingsDictionary.TryGetValue(rating, out ParentalRating value))
+            if (ratingsDictionary.TryGetValue(rating, out ParentalRating? value))
             {
                 return value.Value;
             }
@@ -268,20 +270,6 @@ namespace Emby.Server.Implementations.Localization
             return null;
         }
 
-        /// <inheritdoc />
-        public bool HasUnicodeCategory(string value, UnicodeCategory category)
-        {
-            foreach (var chr in value)
-            {
-                if (char.GetUnicodeCategory(chr) == category)
-                {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
         /// <inheritdoc />
         public string GetLocalizedString(string phrase)
         {
@@ -347,18 +335,21 @@ namespace Emby.Server.Implementations.Localization
         {
             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)
+            if (stream == null)
             {
-                var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
+                _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
+                return;
+            }
 
-                foreach (var key in dict.Keys)
-                {
-                    dictionary[key] = dict[key];
-                }
+            var dict = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, _jsonOptions).ConfigureAwait(false);
+            if (dict == null)
+            {
+                throw new InvalidOperationException($"Resource contains invalid data: '{stream}'");
             }
-            else
+
+            foreach (var key in dict.Keys)
             {
-                _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath);
+                dictionary[key] = dict[key];
             }
         }
 

+ 2 - 2
MediaBrowser.Common/Plugins/BasePluginOfT.cs

@@ -47,10 +47,10 @@ namespace MediaBrowser.Common.Plugins
             var assemblyFilePath = assembly.Location;
 
             var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
-            if (!Directory.Exists(dataFolderPath) && Version != null)
+            if (Version != null && !Directory.Exists(dataFolderPath))
             {
                 // Try again with the version number appended to the folder name.
-                dataFolderPath = dataFolderPath + "_" + Version.ToString();
+                dataFolderPath += "_" + Version.ToString();
             }
 
             SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);

+ 4 - 4
MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs

@@ -1,6 +1,5 @@
-#nullable disable
-
 using System;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Threading;
 using Jellyfin.Extensions;
@@ -16,7 +15,7 @@ namespace MediaBrowser.Controller.BaseItemManager
     {
         private readonly IServerConfigurationManager _serverConfigurationManager;
 
-        private int _metadataRefreshConcurrency = 0;
+        private int _metadataRefreshConcurrency;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseItemManager"/> class.
@@ -101,7 +100,7 @@ namespace MediaBrowser.Controller.BaseItemManager
         /// Called when the configuration is updated.
         /// It will refresh the metadata throttler if the relevant config changed.
         /// </summary>
-        private void OnConfigurationUpdated(object sender, EventArgs e)
+        private void OnConfigurationUpdated(object? sender, EventArgs e)
         {
             int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency();
             if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency)
@@ -114,6 +113,7 @@ namespace MediaBrowser.Controller.BaseItemManager
         /// <summary>
         /// Creates the metadata refresh throttler.
         /// </summary>
+        [MemberNotNull(nameof(MetadataRefreshThrottler))]
         private void SetupMetadataThrottler()
         {
             MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency);

+ 1 - 3
MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 using System.Threading;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Configuration;
@@ -34,4 +32,4 @@ namespace MediaBrowser.Controller.BaseItemManager
         /// <returns><c>true</c> if image fetcher is enabled, else false.</returns>
         bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
     }
-}
+}

+ 4 - 5
MediaBrowser.Controller/Channels/ChannelItemResult.cs

@@ -1,7 +1,6 @@
-#nullable disable
-
-#pragma warning disable CA1002, CA2227, CS1591
+#pragma warning disable CS1591
 
+using System;
 using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Channels
@@ -10,10 +9,10 @@ namespace MediaBrowser.Controller.Channels
     {
         public ChannelItemResult()
         {
-            Items = new List<ChannelItemInfo>();
+            Items = Array.Empty<ChannelItemInfo>();
         }
 
-        public List<ChannelItemInfo> Items { get; set; }
+        public IReadOnlyList<ChannelItemInfo> Items { get; set; }
 
         public int? TotalRecordCount { get; set; }
     }

+ 1 - 1
MediaBrowser.Controller/Collections/CollectionCreationOptions.cs

@@ -1,6 +1,6 @@
 #nullable disable
 
-#pragma warning disable CA2227, CS1591
+#pragma warning disable CS1591
 
 using System;
 using System.Collections.Generic;

+ 0 - 2
MediaBrowser.Controller/Collections/CollectionModifiedEventArgs.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 #pragma warning disable CS1591
 
 using System;

+ 3 - 5
MediaBrowser.Controller/Collections/ICollectionManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 #pragma warning disable CS1591
 
 using System;
@@ -16,17 +14,17 @@ namespace MediaBrowser.Controller.Collections
         /// <summary>
         /// Occurs when [collection created].
         /// </summary>
-        event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
+        event EventHandler<CollectionCreatedEventArgs>? CollectionCreated;
 
         /// <summary>
         /// Occurs when [items added to collection].
         /// </summary>
-        event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
+        event EventHandler<CollectionModifiedEventArgs>? ItemsAddedToCollection;
 
         /// <summary>
         /// Occurs when [items removed from collection].
         /// </summary>
-        event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
+        event EventHandler<CollectionModifiedEventArgs>? ItemsRemovedFromCollection;
 
         /// <summary>
         /// Creates the collection.

+ 0 - 2
MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Model.Configuration;
 

+ 0 - 3
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -7,7 +7,6 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
-using System.Runtime.InteropServices;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Threading;
@@ -16,9 +15,7 @@ using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
-using Microsoft.Extensions.Configuration;
 
 namespace MediaBrowser.Controller.MediaEncoding
 {

+ 1 - 10
MediaBrowser.Model/Globalization/ILocalizationManager.cs

@@ -1,4 +1,3 @@
-#nullable disable
 using System.Collections.Generic;
 using System.Globalization;
 using MediaBrowser.Model.Entities;
@@ -56,19 +55,11 @@ namespace MediaBrowser.Model.Globalization
         /// <returns><see cref="IEnumerable{LocalizatonOption}" />.</returns>
         IEnumerable<LocalizationOption> GetLocalizationOptions();
 
-        /// <summary>
-        /// Checks if the string contains a character with the specified unicode category.
-        /// </summary>
-        /// <param name="value">The string.</param>
-        /// <param name="category">The unicode category.</param>
-        /// <returns>Wether or not the string contains a character with the specified unicode category.</returns>
-        bool HasUnicodeCategory(string value, UnicodeCategory category);
-
         /// <summary>
         /// Returns the correct <see cref="CultureInfo" /> for the given language.
         /// </summary>
         /// <param name="language">The language.</param>
         /// <returns>The correct <see cref="CultureInfo" /> for the given language.</returns>
-        CultureDto FindLanguageInfo(string language);
+        CultureDto? FindLanguageInfo(string language);
     }
 }

+ 49 - 53
MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs

@@ -148,80 +148,76 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                 return;
             }
 
-            using (var fileStream = File.OpenRead(metadataFile))
-            using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
-            {
-                item.ResetPeople();
-
-                // Need to handle a url after the xml data
-                // http://kodi.wiki/view/NFO_files/movies
+            item.ResetPeople();
 
-                var xml = streamReader.ReadToEnd();
+            // Need to handle a url after the xml data
+            // http://kodi.wiki/view/NFO_files/movies
 
-                // Find last closing Tag
-                // Need to do this in two steps to account for random > characters after the closing xml
-                var index = xml.LastIndexOf(@"</", StringComparison.Ordinal);
+            var xml = File.ReadAllText(metadataFile);
 
-                // If closing tag exists, move to end of Tag
-                if (index != -1)
-                {
-                    index = xml.IndexOf('>', index);
-                }
+            // Find last closing Tag
+            // Need to do this in two steps to account for random > characters after the closing xml
+            var index = xml.LastIndexOf(@"</", StringComparison.Ordinal);
 
-                if (index != -1)
-                {
-                    var endingXml = xml.Substring(index);
+            // If closing tag exists, move to end of Tag
+            if (index != -1)
+            {
+                index = xml.IndexOf('>', index);
+            }
 
-                    ParseProviderLinks(item.Item, endingXml);
+            if (index != -1)
+            {
+                var endingXml = xml.AsSpan().Slice(index);
 
-                    // If the file is just an imdb url, don't go any further
-                    if (index == 0)
-                    {
-                        return;
-                    }
+                ParseProviderLinks(item.Item, endingXml);
 
-                    xml = xml.Substring(0, index + 1);
-                }
-                else
+                // If the file is just an imdb url, don't go any further
+                if (index == 0)
                 {
-                    // If the file is just provider urls, handle that
-                    ParseProviderLinks(item.Item, xml);
-
                     return;
                 }
 
-                // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
-                try
+                xml = xml.Substring(0, index + 1);
+            }
+            else
+            {
+                // If the file is just provider urls, handle that
+                ParseProviderLinks(item.Item, xml);
+
+                return;
+            }
+
+            // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
+            try
+            {
+                using (var stringReader = new StringReader(xml))
+                using (var reader = XmlReader.Create(stringReader, settings))
                 {
-                    using (var stringReader = new StringReader(xml))
-                    using (var reader = XmlReader.Create(stringReader, settings))
+                    reader.MoveToContent();
+                    reader.Read();
+
+                    // Loop through each element
+                    while (!reader.EOF && reader.ReadState == ReadState.Interactive)
                     {
-                        reader.MoveToContent();
-                        reader.Read();
+                        cancellationToken.ThrowIfCancellationRequested();
 
-                        // Loop through each element
-                        while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+                        if (reader.NodeType == XmlNodeType.Element)
                         {
-                            cancellationToken.ThrowIfCancellationRequested();
-
-                            if (reader.NodeType == XmlNodeType.Element)
-                            {
-                                FetchDataFromXmlNode(reader, item);
-                            }
-                            else
-                            {
-                                reader.Read();
-                            }
+                            FetchDataFromXmlNode(reader, item);
+                        }
+                        else
+                        {
+                            reader.Read();
                         }
                     }
                 }
-                catch (XmlException)
-                {
-                }
+            }
+            catch (XmlException)
+            {
             }
         }
 
-        protected void ParseProviderLinks(T item, string xml)
+        protected void ParseProviderLinks(T item, ReadOnlySpan<char> xml)
         {
             if (ProviderIdParsers.TryFindImdbId(xml, out var imdbId))
             {

+ 44 - 48
MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs

@@ -40,72 +40,68 @@ namespace MediaBrowser.XbmcMetadata.Parsers
         /// <inheritdoc />
         protected override void Fetch(MetadataResult<Episode> item, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken)
         {
-            using (var fileStream = File.OpenRead(metadataFile))
-            using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
-            {
-                item.ResetPeople();
+            item.ResetPeople();
 
-                var xmlFile = streamReader.ReadToEnd();
+            var xmlFile = File.ReadAllText(metadataFile);
 
-                var srch = "</episodedetails>";
-                var index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
+            var srch = "</episodedetails>";
+            var index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
 
-                var xml = xmlFile;
+            var xml = xmlFile;
 
-                if (index != -1)
-                {
-                    xml = xmlFile.Substring(0, index + srch.Length);
-                    xmlFile = xmlFile.Substring(index + srch.Length);
-                }
+            if (index != -1)
+            {
+                xml = xmlFile.Substring(0, index + srch.Length);
+                xmlFile = xmlFile.Substring(index + srch.Length);
+            }
 
-                // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
-                try
+            // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
+            try
+            {
+                // Extract episode details from the first episodedetails block
+                using (var stringReader = new StringReader(xml))
+                using (var reader = XmlReader.Create(stringReader, settings))
                 {
-                    // Extract episode details from the first episodedetails block
-                    using (var stringReader = new StringReader(xml))
-                    using (var reader = XmlReader.Create(stringReader, settings))
+                    reader.MoveToContent();
+                    reader.Read();
+
+                    // Loop through each element
+                    while (!reader.EOF && reader.ReadState == ReadState.Interactive)
                     {
-                        reader.MoveToContent();
-                        reader.Read();
+                        cancellationToken.ThrowIfCancellationRequested();
 
-                        // Loop through each element
-                        while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+                        if (reader.NodeType == XmlNodeType.Element)
                         {
-                            cancellationToken.ThrowIfCancellationRequested();
-
-                            if (reader.NodeType == XmlNodeType.Element)
-                            {
-                                FetchDataFromXmlNode(reader, item);
-                            }
-                            else
-                            {
-                                reader.Read();
-                            }
+                            FetchDataFromXmlNode(reader, item);
+                        }
+                        else
+                        {
+                            reader.Read();
                         }
                     }
+                }
 
-                    // Extract the last episode number from nfo
-                    // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag
-                    while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1)
+                // Extract the last episode number from nfo
+                // This is needed because XBMC metadata uses multiple episodedetails blocks instead of episodenumberend tag
+                while ((index = xmlFile.IndexOf(srch, StringComparison.OrdinalIgnoreCase)) != -1)
+                {
+                    xml = xmlFile.Substring(0, index + srch.Length);
+                    xmlFile = xmlFile.Substring(index + srch.Length);
+
+                    using (var stringReader = new StringReader(xml))
+                    using (var reader = XmlReader.Create(stringReader, settings))
                     {
-                        xml = xmlFile.Substring(0, index + srch.Length);
-                        xmlFile = xmlFile.Substring(index + srch.Length);
+                        reader.MoveToContent();
 
-                        using (var stringReader = new StringReader(xml))
-                        using (var reader = XmlReader.Create(stringReader, settings))
+                        if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num))
                         {
-                            reader.MoveToContent();
-
-                            if (reader.ReadToDescendant("episode") && int.TryParse(reader.ReadElementContentAsString(), out var num))
-                            {
-                                item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
-                            }
+                            item.Item.IndexNumberEnd = Math.Max(num, item.Item.IndexNumberEnd ?? num);
                         }
                     }
                 }
-                catch (XmlException)
-                {
-                }
+            }
+            catch (XmlException)
+            {
             }
         }
 

+ 1 - 1
tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs

@@ -66,7 +66,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
             var germany = localizationManager.FindLanguageInfo(identifier);
             Assert.NotNull(germany);
 
-            Assert.Equal("ger", germany.ThreeLetterISOLanguageName);
+            Assert.Equal("ger", germany!.ThreeLetterISOLanguageName);
             Assert.Equal("German", germany.DisplayName);
             Assert.Equal("German", germany.Name);
             Assert.Contains("deu", germany.ThreeLetterISOLanguageNames);