From 2e2a594e19038bc2fcea5fdbeda9d37e8394fff7 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 30 Nov 2021 23:53:34 +0100 Subject: [PATCH 001/639] Move Get*Providers definitions to interface --- .../Providers/IProviderManager.cs | 18 ++++++++++++++++++ .../Manager/MetadataService.cs | 4 ++-- .../Manager/ProviderManager.cs | 15 ++------------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 44bc4a50cb..32a7951f62 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -131,6 +131,24 @@ namespace MediaBrowser.Controller.Providers /// IEnumerable{ImageProviderInfo}. IEnumerable GetRemoteImageProviderInfo(BaseItem item); + /// + /// Gets the image providers for the provided item. + /// + /// The item. + /// The image refresh options. + /// The image providers for the item. + IEnumerable GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions); + + /// + /// Gets the metadata providers for the provided item. + /// + /// The item. + /// The library options. + /// The type of metadata provider. + /// The metadata providers. + IEnumerable> GetMetadataProviders(BaseItem item, LibraryOptions libraryOptions) + where T : BaseItem; + /// /// Gets all metadata plugins. /// diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 0c52d26736..01e2a5db9f 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Providers.Manager var localImagesFailed = false; - var allImageProviders = ((ProviderManager)ProviderManager).GetImageProviders(item, refreshOptions).ToList(); + var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList(); if (refreshOptions.RemoveOldMetadata && refreshOptions.ReplaceAllImages) { @@ -522,7 +522,7 @@ namespace MediaBrowser.Providers.Manager protected IEnumerable GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh) { // Get providers to refresh - var providers = ((ProviderManager)ProviderManager).GetMetadataProviders(item, libraryOptions).ToList(); + var providers = ProviderManager.GetMetadataProviders(item, libraryOptions).ToList(); var metadataRefreshMode = options.MetadataRefreshMode; diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 0c31d460ff..e644f0e74d 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -302,12 +302,7 @@ namespace MediaBrowser.Providers.Manager return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(item).ToArray())); } - /// - /// Gets the image providers for the provided item. - /// - /// The item. - /// The image refresh options. - /// The image providers for the item. + /// public IEnumerable GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions) { return GetImageProviders(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false); @@ -342,13 +337,7 @@ namespace MediaBrowser.Providers.Manager .ThenBy(GetOrder); } - /// - /// Gets the metadata providers for the provided item. - /// - /// The item. - /// The library options. - /// The type of metadata provider. - /// The metadata providers. + /// public IEnumerable> GetMetadataProviders(BaseItem item, LibraryOptions libraryOptions) where T : BaseItem { From 4ace7f5c532b655449f7121b660a2cb9e66570be Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 30 Nov 2021 23:55:53 +0100 Subject: [PATCH 002/639] Fix unused var, log typo --- MediaBrowser.Providers/Manager/ProviderManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index e644f0e74d..1b73574774 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -354,7 +354,7 @@ namespace MediaBrowser.Providers.Manager return _metadataProviders.OfType>() .Where(i => CanRefresh(i, item, libraryOptions, includeDisabled, forceEnableInternetMetadata)) - .OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, globalMetadataOptions)) + .OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, currentOptions)) .ThenBy(GetDefaultOrder); } @@ -908,7 +908,7 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - _logger.LogError(ex, "Error in {0}.Suports", i.GetType().Name); + _logger.LogError(ex, "Error in {0}.Supports", i.GetType().Name); return false; } }); From 785cc1bb6ed1cbd3d0c300b9842af6e15167e715 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sun, 5 Dec 2021 17:19:40 +0100 Subject: [PATCH 003/639] Implement sort test for ProviderManager.GetImageProviders --- .../Manager/ProviderManagerTests.cs | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs new file mode 100644 index 0000000000..31b1913346 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Providers.Manager; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.Manager +{ + public class ProviderManagerTests + { + private static TheoryData GetImageProvidersOrderData() + => new () + { + { 3, null, null, null, null, new[] { 0, 1, 2 } }, // no order options set + + // library options ordering + { 3, null, Array.Empty(), null, null, new[] { 0, 1, 2 } }, // no order provided + { 3, null, new[] { 1 }, null, null, new[] { 1, 0, 2 } }, // one item in order + { 3, null, new[] { 2, 1, 0 }, null, null, new[] { 2, 1, 0 } }, // full reverse order + + // server options ordering + { 3, null, null, Array.Empty(), null, new[] { 0, 1, 2 } }, // no order provided + { 3, null, null, new[] { 1 }, null, new[] { 1, 0, 2 } }, // one item in order + { 3, null, null, new[] { 2, 1, 0 }, null, new[] { 2, 1, 0 } }, // full reverse order + + // IHasOrder ordering + // TODO unintuitive - default if not IHasOrder is 0, not max + { 3, null, null, null, new int?[] { null, 0, null }, new[] { 0, 1, 2 } }, // one item with order 0, no change because default order value is 0 + { 3, null, null, null, new int?[] { null, 1, null }, new[] { 0, 2, 1 } }, // one item in order (goes to end, not beginning) + { 3, null, null, null, new int?[] { 2, 1, 0 }, new[] { 2, 1, 0 } }, // full reverse order + + // multiple orders set + // TODO should library fall through to server if both are set on different elements? + { 3, null, new[] { 1 }, new[] { 2, 0, 1 }, null, new[] { 1, 0, 2 } }, // library order first, server order ignored + { 3, null, new[] { 1 }, null, new int?[] { 2, 0, 1 }, new[] { 1, 2, 0 } }, // library order first, then orderby + { 3, null, new[] { 2, 1, 0 }, new[] { 1, 2, 0 }, new int?[] { 2, 0, 1 }, new[] { 2, 1, 0 } }, // library order wins + + // ordering with ILocalImageProvider + // TODO what is the value of testing for ILocalImageProvider on the sort, should this be removed? Behavior is unintuitive + { 3, new[] { false, true, false }, new[] { 1, 0, 2 }, null, null, new[] { 0, 2, 1 } }, // ILocalImageProvider - sorts to end even when set first + { 3, new[] { false, true, false }, new[] { 1 }, null, null, new[] { 0, 1, 2 } }, // ILocalImageProvider - set order ignored when only value set + { 2, new[] { true, true }, new[] { 1, 0 }, null, null, new[] { 0, 1 } }, // ILocalImageProvider - set order ignored + { 2, new[] { true, true }, null, null, new int?[] { 1, 0 }, new[] { 1, 0 } }, // ILocalImageProvider - IHasOrder applies + }; + + [Theory] + [MemberData(nameof(GetImageProvidersOrderData))] + public void GetImageProviders_ProviderOrder_MatchesExpected(int providerCount, bool[]? localImageProvider, int[]? libraryOrder, int[]? serverOrder, int?[]? hasOrderOrder, int[] expectedOrder) + { + var item = new Movie(); + + var nameProvider = new Func(i => "Provider" + i); + + var providerList = new List(); + for (var i = 0; i < providerCount; i++) + { + var order = hasOrderOrder?[i]; + if (localImageProvider != null && localImageProvider[i]) + { + providerList.Add(MockIImageProvider(nameProvider(i), item, order)); + } + else + { + providerList.Add(MockIImageProvider(nameProvider(i), item, order)); + } + } + + var libraryOptions = new LibraryOptions(); + if (libraryOrder != null) + { + libraryOptions.TypeOptions = new[] + { + new TypeOptions + { + Type = item.GetType().Name, + ImageFetcherOrder = libraryOrder.Select(nameProvider).ToArray() + } + }; + } + + var serverConfiguration = new ServerConfiguration(); + if (serverOrder != null) + { + serverConfiguration.MetadataOptions = new[] + { + new MetadataOptions + { + ItemType = item.GetType().Name, + ImageFetcherOrder = serverOrder.Select(nameProvider).ToArray() + } + }; + } + + var providerManager = GetProviderManager(serverConfiguration: serverConfiguration, libraryOptions: libraryOptions); + AddParts(providerManager, imageProviders: providerList); + + var refreshOptions = new ImageRefreshOptions(Mock.Of(MockBehavior.Strict)); + var actualProviders = providerManager.GetImageProviders(item, refreshOptions).ToList(); + + Assert.Equal(providerList.Count, actualProviders.Count); + for (var i = 0; i < providerList.Count; i++) + { + Assert.Equal(i, actualProviders.IndexOf(providerList[expectedOrder[i]])); + } + } + + private static IImageProvider MockIImageProvider(string name, BaseItem supportedType, int? order = null) + where T : class, IImageProvider + { + Mock? hasOrder = null; + if (order != null) + { + hasOrder = new Mock(MockBehavior.Strict); + hasOrder.Setup(i => i.Order) + .Returns((int)order); + } + + var provider = hasOrder == null + ? new Mock(MockBehavior.Strict) + : hasOrder.As(); + provider.Setup(p => p.Name) + .Returns(name); + provider.Setup(p => p.Supports(supportedType)) + .Returns(true); + return provider.Object; + } + + private static ProviderManager GetProviderManager(ServerConfiguration? serverConfiguration = null, LibraryOptions? libraryOptions = null) + { + var serverConfigurationManager = new Mock(MockBehavior.Strict); + serverConfigurationManager.Setup(i => i.Configuration) + .Returns(serverConfiguration ?? new ServerConfiguration()); + + var libraryManager = new Mock(MockBehavior.Strict); + libraryManager.Setup(i => i.GetLibraryOptions(It.IsAny())) + .Returns(libraryOptions ?? new LibraryOptions()); + + var providerManager = new ProviderManager( + null, + null, + serverConfigurationManager.Object, + null, + new NullLogger(), + null, + null, + libraryManager.Object, + null); + + return providerManager; + } + + private static void AddParts( + ProviderManager providerManager, + IEnumerable? imageProviders = null, + IEnumerable? metadataServices = null, + IEnumerable? metadataProviders = null, + IEnumerable? metadataSavers = null, + IEnumerable? externalIds = null) + { + imageProviders ??= Array.Empty(); + metadataServices ??= Array.Empty(); + metadataProviders ??= Array.Empty(); + metadataSavers ??= Array.Empty(); + externalIds ??= Array.Empty(); + + providerManager.AddParts(imageProviders, metadataServices, metadataProviders, metadataSavers, externalIds); + } + } +} From 8515e8fbd111278cad95430e50904d6721be3a63 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sun, 5 Dec 2021 21:33:31 +0100 Subject: [PATCH 004/639] Improve image provider sorting Remove irrelevant check for ILocalImageProvider Providers that are not IHasOrder default to middle, not beginning --- .../Manager/ProviderManager.cs | 60 ++++++++----------- .../Manager/ProviderManagerTests.cs | 47 +++++---------- 2 files changed, 40 insertions(+), 67 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 1b73574774..855c467208 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -310,31 +310,25 @@ namespace MediaBrowser.Providers.Manager private IEnumerable GetImageProviders(BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled) { - // Avoid implicitly captured closure - var currentOptions = options; - var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); - var typeFetcherOrder = typeOptions?.ImageFetcherOrder; + var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder; return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, refreshOptions, includeDisabled)) - .OrderBy(i => - { - // See if there's a user-defined order - if (i is not ILocalImageProvider) - { - var fetcherOrder = typeFetcherOrder ?? currentOptions.ImageFetcherOrder; - var index = Array.IndexOf(fetcherOrder, i.Name); + .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name)) + .ThenBy(GetOrder); + } - if (index != -1) - { - return index; - } - } + private static int GetConfiguredOrder(string[] order, string providerName) + { + var index = Array.IndexOf(order, providerName); - // Not configured. Just return some high number to put it at the end. - return 100; - }) - .ThenBy(GetOrder); + if (index != -1) + { + return index; + } + + // default to end + return int.MaxValue; } /// @@ -450,21 +444,6 @@ namespace MediaBrowser.Providers.Manager } } - /// - /// Gets the order. - /// - /// The provider. - /// System.Int32. - private int GetOrder(IImageProvider provider) - { - if (provider is not IHasOrder hasOrder) - { - return 0; - } - - return hasOrder.Order; - } - private int GetConfiguredOrder(BaseItem item, IMetadataProvider provider, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions) { // See if there's a user-defined order @@ -500,6 +479,17 @@ namespace MediaBrowser.Providers.Manager return 100; } + private static int GetOrder(object provider) + { + if (provider is IHasOrder hasOrder) + { + return hasOrder.Order; + } + + // after items that want to be first (~0) but before items that want to be last (~100) + return 50; + } + private int GetDefaultOrder(IMetadataProvider provider) { if (provider is IHasOrder hasOrder) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index 31b1913346..590f50b256 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -16,44 +16,34 @@ namespace Jellyfin.Providers.Tests.Manager { public class ProviderManagerTests { - private static TheoryData GetImageProvidersOrderData() + private static TheoryData GetImageProvidersOrderData() => new () { - { 3, null, null, null, null, new[] { 0, 1, 2 } }, // no order options set + { 3, null, null, null, new[] { 0, 1, 2 } }, // no order options set // library options ordering - { 3, null, Array.Empty(), null, null, new[] { 0, 1, 2 } }, // no order provided - { 3, null, new[] { 1 }, null, null, new[] { 1, 0, 2 } }, // one item in order - { 3, null, new[] { 2, 1, 0 }, null, null, new[] { 2, 1, 0 } }, // full reverse order + { 3, Array.Empty(), null, null, new[] { 0, 1, 2 } }, // no order provided + { 3, new[] { 1 }, null, null, new[] { 1, 0, 2 } }, // one item in order + { 3, new[] { 2, 1, 0 }, null, null, new[] { 2, 1, 0 } }, // full reverse order // server options ordering - { 3, null, null, Array.Empty(), null, new[] { 0, 1, 2 } }, // no order provided - { 3, null, null, new[] { 1 }, null, new[] { 1, 0, 2 } }, // one item in order - { 3, null, null, new[] { 2, 1, 0 }, null, new[] { 2, 1, 0 } }, // full reverse order + { 3, null, Array.Empty(), null, new[] { 0, 1, 2 } }, // no order provided + { 3, null, new[] { 1 }, null, new[] { 1, 0, 2 } }, // one item in order + { 3, null, new[] { 2, 1, 0 }, null, new[] { 2, 1, 0 } }, // full reverse order // IHasOrder ordering - // TODO unintuitive - default if not IHasOrder is 0, not max - { 3, null, null, null, new int?[] { null, 0, null }, new[] { 0, 1, 2 } }, // one item with order 0, no change because default order value is 0 - { 3, null, null, null, new int?[] { null, 1, null }, new[] { 0, 2, 1 } }, // one item in order (goes to end, not beginning) - { 3, null, null, null, new int?[] { 2, 1, 0 }, new[] { 2, 1, 0 } }, // full reverse order + { 3, null, null, new int?[] { null, 1, null }, new[] { 1, 0, 2 } }, // one item with defined order + { 3, null, null, new int?[] { 2, 1, 0 }, new[] { 2, 1, 0 } }, // full reverse order // multiple orders set - // TODO should library fall through to server if both are set on different elements? - { 3, null, new[] { 1 }, new[] { 2, 0, 1 }, null, new[] { 1, 0, 2 } }, // library order first, server order ignored - { 3, null, new[] { 1 }, null, new int?[] { 2, 0, 1 }, new[] { 1, 2, 0 } }, // library order first, then orderby - { 3, null, new[] { 2, 1, 0 }, new[] { 1, 2, 0 }, new int?[] { 2, 0, 1 }, new[] { 2, 1, 0 } }, // library order wins - - // ordering with ILocalImageProvider - // TODO what is the value of testing for ILocalImageProvider on the sort, should this be removed? Behavior is unintuitive - { 3, new[] { false, true, false }, new[] { 1, 0, 2 }, null, null, new[] { 0, 2, 1 } }, // ILocalImageProvider - sorts to end even when set first - { 3, new[] { false, true, false }, new[] { 1 }, null, null, new[] { 0, 1, 2 } }, // ILocalImageProvider - set order ignored when only value set - { 2, new[] { true, true }, new[] { 1, 0 }, null, null, new[] { 0, 1 } }, // ILocalImageProvider - set order ignored - { 2, new[] { true, true }, null, null, new int?[] { 1, 0 }, new[] { 1, 0 } }, // ILocalImageProvider - IHasOrder applies + { 3, new[] { 1 }, new[] { 2, 0, 1 }, null, new[] { 1, 0, 2 } }, // library order first, server order ignored + { 3, new[] { 1 }, null, new int?[] { 2, 0, 1 }, new[] { 1, 2, 0 } }, // library order first, then orderby + { 3, new[] { 2, 1, 0 }, new[] { 1, 2, 0 }, new int?[] { 2, 0, 1 }, new[] { 2, 1, 0 } }, // library order wins }; [Theory] [MemberData(nameof(GetImageProvidersOrderData))] - public void GetImageProviders_ProviderOrder_MatchesExpected(int providerCount, bool[]? localImageProvider, int[]? libraryOrder, int[]? serverOrder, int?[]? hasOrderOrder, int[] expectedOrder) + public void GetImageProviders_ProviderOrder_MatchesExpected(int providerCount, int[]? libraryOrder, int[]? serverOrder, int?[]? hasOrderOrder, int[] expectedOrder) { var item = new Movie(); @@ -63,14 +53,7 @@ namespace Jellyfin.Providers.Tests.Manager for (var i = 0; i < providerCount; i++) { var order = hasOrderOrder?[i]; - if (localImageProvider != null && localImageProvider[i]) - { - providerList.Add(MockIImageProvider(nameProvider(i), item, order)); - } - else - { - providerList.Add(MockIImageProvider(nameProvider(i), item, order)); - } + providerList.Add(MockIImageProvider(nameProvider(i), item, order)); } var libraryOptions = new LibraryOptions(); From 6221991c630bcbd688316ad35121781c7a52c591 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sun, 5 Dec 2021 21:43:59 +0100 Subject: [PATCH 005/639] Add nullable annotations --- .../Manager/ProviderManager.cs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 855c467208..9bae738010 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -47,7 +45,7 @@ namespace MediaBrowser.Providers.Manager /// public class ProviderManager : IProviderManager, IDisposable { - private readonly object _refreshQueueLock = new object(); + private readonly object _refreshQueueLock = new (); private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryMonitor _libraryMonitor; @@ -57,11 +55,11 @@ namespace MediaBrowser.Providers.Manager private readonly ISubtitleManager _subtitleManager; private readonly IServerConfigurationManager _configurationManager; private readonly IBaseItemManager _baseItemManager; - private readonly ConcurrentDictionary _activeRefreshes = new ConcurrentDictionary(); - private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); - private readonly SimplePriorityQueue> _refreshQueue = - new SimplePriorityQueue>(); + private readonly ConcurrentDictionary _activeRefreshes = new (); + private readonly CancellationTokenSource _disposeCancellationTokenSource = new (); + private readonly SimplePriorityQueue> _refreshQueue = new (); + private IImageProvider[] _imageProviders = Array.Empty(); private IMetadataService[] _metadataServices = Array.Empty(); private IMetadataProvider[] _metadataProviders = Array.Empty(); private IMetadataSaver[] _savers = Array.Empty(); @@ -104,15 +102,13 @@ namespace MediaBrowser.Providers.Manager } /// - public event EventHandler> RefreshStarted; + public event EventHandler>? RefreshStarted; /// - public event EventHandler> RefreshCompleted; + public event EventHandler>? RefreshCompleted; /// - public event EventHandler>> RefreshProgress; - - private IImageProvider[] ImageProviders { get; set; } + public event EventHandler>>? RefreshProgress; /// public void AddParts( @@ -122,8 +118,7 @@ namespace MediaBrowser.Providers.Manager IEnumerable metadataSavers, IEnumerable externalIds) { - ImageProviders = imageProviders.ToArray(); - + _imageProviders = imageProviders.ToArray(); _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray(); _metadataProviders = metadataProviders.ToArray(); _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray(); @@ -313,7 +308,7 @@ namespace MediaBrowser.Providers.Manager var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder; - return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, refreshOptions, includeDisabled)) + return _imageProviders.Where(i => CanRefresh(i, item, libraryOptions, refreshOptions, includeDisabled)) .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name)) .ThenBy(GetOrder); } @@ -758,7 +753,7 @@ namespace MediaBrowser.Providers.Manager where TItemType : BaseItem, new() where TLookupType : ItemLookupInfo { - BaseItem referenceItem = null; + BaseItem? referenceItem = null; if (!searchInfo.ItemId.Equals(default)) { @@ -768,7 +763,7 @@ namespace MediaBrowser.Providers.Manager return GetRemoteSearchResults(searchInfo, referenceItem, cancellationToken); } - private async Task> GetRemoteSearchResults(RemoteSearchQuery searchInfo, BaseItem referenceItem, CancellationToken cancellationToken) + private async Task> GetRemoteSearchResults(RemoteSearchQuery searchInfo, BaseItem? referenceItem, CancellationToken cancellationToken) where TItemType : BaseItem, new() where TLookupType : ItemLookupInfo { @@ -930,7 +925,8 @@ namespace MediaBrowser.Providers.Manager i.UrlFormatString, value) }; - }).Where(i => i != null).Concat(item.GetRelatedUrls()); + }).Where(i => i != null) + .Concat(item.GetRelatedUrls())!; // We just filtered out all the nulls } /// From 56900d0fc3bc791fd3c0a92bda22ca2f23f28be1 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 6 Dec 2021 00:09:46 +0100 Subject: [PATCH 006/639] Implement CanRefresh tests for ProviderManager.GetImageProviders --- .../Manager/ProviderManagerTests.cs | 93 +++++++++++++++++-- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index 590f50b256..4a1b90895f 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -53,7 +54,7 @@ namespace Jellyfin.Providers.Tests.Manager for (var i = 0; i < providerCount; i++) { var order = hasOrderOrder?[i]; - providerList.Add(MockIImageProvider(nameProvider(i), item, order)); + providerList.Add(MockIImageProvider(nameProvider(i), item, order: order)); } var libraryOptions = new LibraryOptions(); @@ -95,7 +96,78 @@ namespace Jellyfin.Providers.Tests.Manager } } - private static IImageProvider MockIImageProvider(string name, BaseItem supportedType, int? order = null) + [Theory] + [InlineData(true, false, true)] + [InlineData(false, false, false)] + [InlineData(true, true, false)] + public void GetImageProviders_CanRefreshBasic_WhenSupportsWithoutError(bool supports, bool errorOnSupported, bool expected) + { + GetImageProviders_CanRefresh_Tester(typeof(IImageProvider), supports, expected, errorOnSupported: errorOnSupported); + } + + [Theory] + [InlineData(typeof(ILocalImageProvider), false, true)] + [InlineData(typeof(ILocalImageProvider), true, true)] + [InlineData(typeof(IImageProvider), false, false)] + [InlineData(typeof(IImageProvider), true, true)] + public void GetImageProviders_CanRefreshLocked_WhenLocalOrFullRefresh(Type providerType, bool fullRefresh, bool expected) + { + GetImageProviders_CanRefresh_Tester(providerType, true, expected, itemLocked: true, fullRefresh: fullRefresh); + } + + [Theory] + [InlineData(typeof(ILocalImageProvider), false, true)] + [InlineData(typeof(IRemoteImageProvider), true, true)] + [InlineData(typeof(IDynamicImageProvider), true, true)] + [InlineData(typeof(IRemoteImageProvider), false, false)] + [InlineData(typeof(IDynamicImageProvider), false, false)] + public void GetImageProviders_CanRefreshEnabled_WhenLocalOrEnabled(Type providerType, bool enabled, bool expected) + { + GetImageProviders_CanRefresh_Tester(providerType, true, expected, baseItemEnabled: enabled); + } + + private static void GetImageProviders_CanRefresh_Tester(Type providerType, bool supports, bool expected, bool errorOnSupported = false, bool itemLocked = false, bool fullRefresh = false, bool baseItemEnabled = true) + { + var item = new Movie + { + IsLocked = itemLocked + }; + + var providerName = "provider"; + IImageProvider provider = providerType.Name switch + { + "IImageProvider" => MockIImageProvider(providerName, item, supports: supports, errorOnSupported: errorOnSupported), + "ILocalImageProvider" => MockIImageProvider(providerName, item, supports: supports, errorOnSupported: errorOnSupported), + "IRemoteImageProvider" => MockIImageProvider(providerName, item, supports: supports, errorOnSupported: errorOnSupported), + "IDynamicImageProvider" => MockIImageProvider(providerName, item, supports: supports, errorOnSupported: errorOnSupported), + _ => throw new ArgumentException("Unexpected provider type") + }; + + var refreshOptions = new ImageRefreshOptions(Mock.Of(MockBehavior.Strict)) + { + ImageRefreshMode = fullRefresh ? MetadataRefreshMode.FullRefresh : MetadataRefreshMode.Default + }; + + var baseItemManager = new Mock(MockBehavior.Strict); + baseItemManager.Setup(i => i.IsImageFetcherEnabled(item, It.IsAny(), providerName)) + .Returns(baseItemEnabled); + + var providerManager = GetProviderManager(baseItemManager: baseItemManager.Object); + AddParts(providerManager, imageProviders: new[] { provider }); + + var actualProviders = providerManager.GetImageProviders(item, refreshOptions); + + if (expected) + { + Assert.Single(actualProviders); + } + else + { + Assert.Empty(actualProviders); + } + } + + private static IImageProvider MockIImageProvider(string name, BaseItem expectedType, bool supports = true, int? order = null, bool errorOnSupported = false) where T : class, IImageProvider { Mock? hasOrder = null; @@ -111,12 +183,21 @@ namespace Jellyfin.Providers.Tests.Manager : hasOrder.As(); provider.Setup(p => p.Name) .Returns(name); - provider.Setup(p => p.Supports(supportedType)) - .Returns(true); + if (errorOnSupported) + { + provider.Setup(p => p.Supports(It.IsAny())) + .Throws(new ArgumentException()); + } + else + { + provider.Setup(p => p.Supports(expectedType)) + .Returns(supports); + } + return provider.Object; } - private static ProviderManager GetProviderManager(ServerConfiguration? serverConfiguration = null, LibraryOptions? libraryOptions = null) + private static ProviderManager GetProviderManager(ServerConfiguration? serverConfiguration = null, LibraryOptions? libraryOptions = null, IBaseItemManager? baseItemManager = null) { var serverConfigurationManager = new Mock(MockBehavior.Strict); serverConfigurationManager.Setup(i => i.Configuration) @@ -135,7 +216,7 @@ namespace Jellyfin.Providers.Tests.Manager null, null, libraryManager.Object, - null); + baseItemManager); return providerManager; } From 11c7c24f0ef06e6366c075062928976ae0d30600 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 6 Dec 2021 22:31:16 +0100 Subject: [PATCH 007/639] Clarify naming, minor method ordering improvement --- .../Manager/ProviderManager.cs | 40 +++++++++---------- .../Manager/ProviderManagerTests.cs | 14 +++---- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 9bae738010..633b3b1db7 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -297,18 +297,31 @@ namespace MediaBrowser.Providers.Manager return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(item).ToArray())); } + private IEnumerable GetRemoteImageProviders(BaseItem item, bool includeDisabled) + { + var options = GetMetadataOptions(item); + var libraryOptions = _libraryManager.GetLibraryOptions(item); + + return GetImageProvidersInternal( + item, + libraryOptions, + options, + new ImageRefreshOptions(new DirectoryService(_fileSystem)), + includeDisabled).OfType(); + } + /// public IEnumerable GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions) { - return GetImageProviders(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false); + return GetImageProvidersInternal(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false); } - private IEnumerable GetImageProviders(BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled) + private IEnumerable GetImageProvidersInternal(BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled) { var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder; - return _imageProviders.Where(i => CanRefresh(i, item, libraryOptions, refreshOptions, includeDisabled)) + return _imageProviders.Where(i => CanRefreshImages(i, item, libraryOptions, refreshOptions, includeDisabled)) .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name)) .ThenBy(GetOrder); } @@ -342,25 +355,12 @@ namespace MediaBrowser.Providers.Manager var currentOptions = globalMetadataOptions; return _metadataProviders.OfType>() - .Where(i => CanRefresh(i, item, libraryOptions, includeDisabled, forceEnableInternetMetadata)) + .Where(i => CanRefreshMetadata(i, item, libraryOptions, includeDisabled, forceEnableInternetMetadata)) .OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, currentOptions)) .ThenBy(GetDefaultOrder); } - private IEnumerable GetRemoteImageProviders(BaseItem item, bool includeDisabled) - { - var options = GetMetadataOptions(item); - var libraryOptions = _libraryManager.GetLibraryOptions(item); - - return GetImageProviders( - item, - libraryOptions, - options, - new ImageRefreshOptions(new DirectoryService(_fileSystem)), - includeDisabled).OfType(); - } - - private bool CanRefresh( + private bool CanRefreshMetadata( IMetadataProvider provider, BaseItem item, LibraryOptions libraryOptions, @@ -401,7 +401,7 @@ namespace MediaBrowser.Providers.Manager return true; } - private bool CanRefresh( + private bool CanRefreshImages( IImageProvider provider, BaseItem item, LibraryOptions libraryOptions, @@ -535,7 +535,7 @@ namespace MediaBrowser.Providers.Manager var libraryOptions = new LibraryOptions(); - var imageProviders = GetImageProviders( + var imageProviders = GetImageProvidersInternal( dummy, libraryOptions, options, diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index 4a1b90895f..98c1e19b20 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -100,9 +100,9 @@ namespace Jellyfin.Providers.Tests.Manager [InlineData(true, false, true)] [InlineData(false, false, false)] [InlineData(true, true, false)] - public void GetImageProviders_CanRefreshBasic_WhenSupportsWithoutError(bool supports, bool errorOnSupported, bool expected) + public void GetImageProviders_CanRefreshImagesBasic_WhenSupportsWithoutError(bool supports, bool errorOnSupported, bool expected) { - GetImageProviders_CanRefresh_Tester(typeof(IImageProvider), supports, expected, errorOnSupported: errorOnSupported); + GetImageProviders_CanRefreshImages_Tester(typeof(IImageProvider), supports, expected, errorOnSupported: errorOnSupported); } [Theory] @@ -110,9 +110,9 @@ namespace Jellyfin.Providers.Tests.Manager [InlineData(typeof(ILocalImageProvider), true, true)] [InlineData(typeof(IImageProvider), false, false)] [InlineData(typeof(IImageProvider), true, true)] - public void GetImageProviders_CanRefreshLocked_WhenLocalOrFullRefresh(Type providerType, bool fullRefresh, bool expected) + public void GetImageProviders_CanRefreshImagesLocked_WhenLocalOrFullRefresh(Type providerType, bool fullRefresh, bool expected) { - GetImageProviders_CanRefresh_Tester(providerType, true, expected, itemLocked: true, fullRefresh: fullRefresh); + GetImageProviders_CanRefreshImages_Tester(providerType, true, expected, itemLocked: true, fullRefresh: fullRefresh); } [Theory] @@ -121,12 +121,12 @@ namespace Jellyfin.Providers.Tests.Manager [InlineData(typeof(IDynamicImageProvider), true, true)] [InlineData(typeof(IRemoteImageProvider), false, false)] [InlineData(typeof(IDynamicImageProvider), false, false)] - public void GetImageProviders_CanRefreshEnabled_WhenLocalOrEnabled(Type providerType, bool enabled, bool expected) + public void GetImageProviders_CanRefreshImagesEnabled_WhenLocalOrEnabled(Type providerType, bool enabled, bool expected) { - GetImageProviders_CanRefresh_Tester(providerType, true, expected, baseItemEnabled: enabled); + GetImageProviders_CanRefreshImages_Tester(providerType, true, expected, baseItemEnabled: enabled); } - private static void GetImageProviders_CanRefresh_Tester(Type providerType, bool supports, bool expected, bool errorOnSupported = false, bool itemLocked = false, bool fullRefresh = false, bool baseItemEnabled = true) + private static void GetImageProviders_CanRefreshImages_Tester(Type providerType, bool supports, bool expected, bool errorOnSupported = false, bool itemLocked = false, bool fullRefresh = false, bool baseItemEnabled = true) { var item = new Movie { From 91e706d3873440a28f107da04143a374d4277b9a Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Wed, 8 Dec 2021 00:52:57 +0100 Subject: [PATCH 008/639] Implement sort test for ProviderManager.GetMetadataProviders --- .../Manager/ProviderManagerTests.cs | 186 +++++++++++++++++- 1 file changed, 177 insertions(+), 9 deletions(-) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index 98c1e19b20..7a542f0eba 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -37,7 +37,7 @@ namespace Jellyfin.Providers.Tests.Manager { 3, null, null, new int?[] { 2, 1, 0 }, new[] { 2, 1, 0 } }, // full reverse order // multiple orders set - { 3, new[] { 1 }, new[] { 2, 0, 1 }, null, new[] { 1, 0, 2 } }, // library order first, server order ignored + { 3, new[] { 1 }, new[] { 2, 0, 1 }, null, new[] { 1, 0, 2 } }, // partial library order first, server order ignored { 3, new[] { 1 }, null, new int?[] { 2, 0, 1 }, new[] { 1, 2, 0 } }, // library order first, then orderby { 3, new[] { 2, 1, 0 }, new[] { 1, 2, 0 }, new int?[] { 2, 0, 1 }, new[] { 2, 1, 0 } }, // library order wins }; @@ -90,10 +90,8 @@ namespace Jellyfin.Providers.Tests.Manager var actualProviders = providerManager.GetImageProviders(item, refreshOptions).ToList(); Assert.Equal(providerList.Count, actualProviders.Count); - for (var i = 0; i < providerList.Count; i++) - { - Assert.Equal(i, actualProviders.IndexOf(providerList[expectedOrder[i]])); - } + var actualOrder = actualProviders.Select(i => providerList.IndexOf(i)).ToArray(); + Assert.Equal(expectedOrder, actualOrder); } [Theory] @@ -167,8 +165,129 @@ namespace Jellyfin.Providers.Tests.Manager } } - private static IImageProvider MockIImageProvider(string name, BaseItem expectedType, bool supports = true, int? order = null, bool errorOnSupported = false) - where T : class, IImageProvider + private static TheoryData GetMetadataProvidersOrderData() + { + var l = "local"; + var r = "remote"; + return new () + { + { new[] { l, l, r, r }, null, null, null, null, null, new[] { 0, 1, 2, 3 } }, // no order options set + + // library options ordering + { new[] { l, l, r, r }, Array.Empty(), Array.Empty(), null, null, null, new[] { 0, 1, 2, 3 } }, // no order provided + // local only + { new[] { r, l, l, l }, new[] { 2 }, null, null, null, null, new[] { 2, 0, 1, 3 } }, // one item in order + { new[] { r, l, l, l }, new[] { 3, 2, 1 }, null, null, null, null, new[] { 3, 2, 1, 0 } }, // full reverse order + // remote only + { new[] { l, r, r, r }, null, new[] { 2 }, null, null, null, new[] { 2, 0, 1, 3 } }, // one item in order + { new[] { l, r, r, r }, null, new[] { 3, 2, 1 }, null, null, null, new[] { 3, 2, 1, 0 } }, // full reverse order + // local and remote, note that results will be interleaved (odd but expected) + { new[] { l, l, r, r }, new[] { 1 }, new[] { 3 }, null, null, null, new[] { 1, 3, 0, 2 } }, // one item in each order + { new[] { l, l, l, r, r, r }, new[] { 2, 1, 0 }, new[] { 5, 4, 3 }, null, null, null, new[] { 2, 5, 1, 4, 0, 3 } }, // full reverse order + + // // server options ordering + { new[] { l, l, r, r }, null, null, Array.Empty(), Array.Empty(), null, new[] { 0, 1, 2, 3 } }, // no order provided + // local only + { new[] { r, l, l, l }, null, null, new[] { 2 }, null, null, new[] { 2, 0, 1, 3 } }, // one item in order + { new[] { r, l, l, l }, null, null, new[] { 3, 2, 1 }, null, null, new[] { 3, 2, 1, 0 } }, // full reverse order + // remote only + { new[] { l, r, r, r }, null, null, null, new[] { 2 }, null, new[] { 2, 0, 1, 3 } }, // one item in order + { new[] { l, r, r, r }, null, null, null, new[] { 3, 2, 1 }, null, new[] { 3, 2, 1, 0 } }, // full reverse order + // local and remote, note that results will be interleaved (odd but expected) + { new[] { l, l, r, r }, null, null, new[] { 1 }, new[] { 3 }, null, new[] { 1, 3, 0, 2 } }, // one item in each order + { new[] { l, l, l, r, r, r }, null, null, new[] { 2, 1, 0 }, new[] { 5, 4, 3 }, null, new[] { 2, 5, 1, 4, 0, 3 } }, // full reverse order + + // IHasOrder ordering (not interleaved, doesn't care about types) + // TODO unset goes to beginning, not end + { new[] { l, l, r, r }, null, null, null, null, new int?[] { 2, null, 1, null }, new[] { 1, 3, 2, 0 } }, // partially defined + { new[] { l, l, r, r }, null, null, null, null, new int?[] { 3, 2, 1, 0 }, new[] { 3, 2, 1, 0 } }, // full reverse order + // note odd interaction - orderby determines order of slot when local and remote both have a slot 0 + { new[] { l, l, r, r }, new[] { 1 }, new[] { 3 }, null, null, new int?[] { null, 2, null, 1 }, new[] { 3, 1, 0, 2 } }, // sorts interleaved results + + // multiple orders set + { new[] { l, l, l, r, r, r }, new[] { 1 }, new[] { 4 }, new[] { 2, 1, 0 }, new[] { 5, 4, 3 }, null, new[] { 1, 4, 0, 2, 3, 5 } }, // partial library order first, server order ignored + { new[] { l, l, l }, new[] { 1 }, null, null, null, new int?[] { 2, 0, 1 }, new[] { 1, 2, 0 } }, // library order first, then orderby + { new[] { l, l, l, r, r, r }, new[] { 2, 1, 0 }, new[] { 5, 4, 3 }, new[] { 1, 2, 0 }, new[] { 4, 5, 3 }, new int?[] { 5, 4, 1, 6, 3, 2 }, new[] { 2, 5, 4, 1, 0, 3 } }, // library order wins (with orderby between local/remote) + }; + } + + [Theory] + [MemberData(nameof(GetMetadataProvidersOrderData))] + public void GetMetadataProviders_ProviderOrder_MatchesExpected(string[] providers, int[]? libraryLocalOrder, int[]? libraryRemoteOrder, int[]? serverLocalOrder, int[]? serverRemoteOrder, int?[]? hasOrderOrder, int[] expectedOrder) + { + var item = new MetadataTestItem(); + var typeNames = new Dictionary + { + { "remote", nameof(IRemoteMetadataProvider) }, + { "local", nameof(ILocalMetadataProvider) }, + { "custom", nameof(ICustomMetadataProvider) } + }; + + var nameProvider = new Func(i => "Provider" + i); + + var providerList = new List>(); + for (var i = 0; i < providers.Length; i++) + { + var order = hasOrderOrder?[i]; + providerList.Add(MockIMetadataProviderMapper(typeNames[providers[i]], nameProvider(i), order: order)); + } + + var libraryOptions = new LibraryOptions(); + if (libraryLocalOrder != null) + { + libraryOptions.LocalMetadataReaderOrder = libraryLocalOrder.Select(nameProvider).ToArray(); + } + + if (libraryRemoteOrder != null) + { + libraryOptions.TypeOptions = new[] + { + new TypeOptions + { + Type = item.GetType().Name, + MetadataFetcherOrder = libraryRemoteOrder.Select(nameProvider).ToArray() + } + }; + } + + var serverConfiguration = new ServerConfiguration(); + if (serverLocalOrder != null || serverRemoteOrder != null) + { + serverConfiguration.MetadataOptions = new[] + { + new MetadataOptions + { + ItemType = item.GetType().Name + } + }; + if (serverLocalOrder != null) + { + serverConfiguration.MetadataOptions[0].LocalMetadataReaderOrder = serverLocalOrder.Select(nameProvider).ToArray(); + } + + if (serverRemoteOrder != null) + { + serverConfiguration.MetadataOptions[0].MetadataFetcherOrder = serverRemoteOrder.Select(nameProvider).ToArray(); + } + } + + var baseItemManager = new Mock(MockBehavior.Strict); + baseItemManager.Setup(i => i.IsMetadataFetcherEnabled(item, It.IsAny(), It.IsAny())) + .Returns(true); + + var providerManager = GetProviderManager(serverConfiguration: serverConfiguration, baseItemManager: baseItemManager.Object); + AddParts(providerManager, metadataProviders: providerList); + + // TODO why does this take libraryOptions directly while GetImageProviders did not? + var actualProviders = providerManager.GetMetadataProviders(item, libraryOptions).ToList(); + + Assert.Equal(providerList.Count, actualProviders.Count); + var actualOrder = actualProviders.Select(i => providerList.IndexOf(i)).ToArray(); + Assert.Equal(expectedOrder, actualOrder); + } + + private static IImageProvider MockIImageProvider(string name, BaseItem expectedType, bool supports = true, int? order = null, bool errorOnSupported = false) + where TProviderType : class, IImageProvider { Mock? hasOrder = null; if (order != null) @@ -179,8 +298,8 @@ namespace Jellyfin.Providers.Tests.Manager } var provider = hasOrder == null - ? new Mock(MockBehavior.Strict) - : hasOrder.As(); + ? new Mock(MockBehavior.Strict) + : hasOrder.As(); provider.Setup(p => p.Name) .Returns(name); if (errorOnSupported) @@ -197,6 +316,38 @@ namespace Jellyfin.Providers.Tests.Manager return provider.Object; } + private static IMetadataProvider MockIMetadataProviderMapper(string typeName, string providerName, int? order = null) + where TItemType : BaseItem, IHasLookupInfo + where TLookupInfoType : ItemLookupInfo, new() + => typeName switch + { + "ILocalMetadataProvider" => MockIMetadataProvider, TItemType>(providerName, order), + "IRemoteMetadataProvider" => MockIMetadataProvider, TItemType>(providerName, order), + "ICustomMetadataProvider" => MockIMetadataProvider, TItemType>(providerName, order), + _ => MockIMetadataProvider, TItemType>(providerName, order) + }; + + private static IMetadataProvider MockIMetadataProvider(string name, int? order = null) + where TProviderType : class, IMetadataProvider + where TItemType : BaseItem + { + Mock? hasOrder = null; + if (order != null) + { + hasOrder = new Mock(MockBehavior.Strict); + hasOrder.Setup(i => i.Order) + .Returns((int)order); + } + + var provider = hasOrder == null + ? new Mock(MockBehavior.Strict) + : hasOrder.As(); + provider.Setup(p => p.Name) + .Returns(name); + + return provider.Object; + } + private static ProviderManager GetProviderManager(ServerConfiguration? serverConfiguration = null, LibraryOptions? libraryOptions = null, IBaseItemManager? baseItemManager = null) { var serverConfigurationManager = new Mock(MockBehavior.Strict); @@ -237,5 +388,22 @@ namespace Jellyfin.Providers.Tests.Manager providerManager.AddParts(imageProviders, metadataServices, metadataProviders, metadataSavers, externalIds); } + + /// + /// Simple extension to force SupportsLocalMetadata to true. + /// + public class MetadataTestItem : BaseItem, IHasLookupInfo + { + public override bool SupportsLocalMetadata => true; + + public MetadataTestItemInfo GetLookupInfo() + { + return GetItemLookupInfo(); + } + } + + public class MetadataTestItemInfo : ItemLookupInfo + { + } } } From e7df72de497f25deb7f77bf9de39aeaba1159d11 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Wed, 8 Dec 2021 16:49:09 +0100 Subject: [PATCH 009/639] Improve metadata provider sorting Extract configured order up front instead of for each provider Non-IHasOrder providers default to middle, not beginning Merge image and metadata sort helper methods --- .../Manager/ProviderManager.cs | 80 ++++++------------- .../Manager/ProviderManagerTests.cs | 16 +--- 2 files changed, 27 insertions(+), 69 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 633b3b1db7..82d633e234 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -323,20 +323,7 @@ namespace MediaBrowser.Providers.Manager return _imageProviders.Where(i => CanRefreshImages(i, item, libraryOptions, refreshOptions, includeDisabled)) .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name)) - .ThenBy(GetOrder); - } - - private static int GetConfiguredOrder(string[] order, string providerName) - { - var index = Array.IndexOf(order, providerName); - - if (index != -1) - { - return index; - } - - // default to end - return int.MaxValue; + .ThenBy(GetDefaultOrder); } /// @@ -351,12 +338,23 @@ namespace MediaBrowser.Providers.Manager private IEnumerable> GetMetadataProvidersInternal(BaseItem item, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions, bool includeDisabled, bool forceEnableInternetMetadata) where T : BaseItem { - // Avoid implicitly captured closure - var currentOptions = globalMetadataOptions; + var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadataReaderOrder; + var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); + var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder; return _metadataProviders.OfType>() .Where(i => CanRefreshMetadata(i, item, libraryOptions, includeDisabled, forceEnableInternetMetadata)) - .OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, currentOptions)) + .OrderBy(i => + { + // local and remote providers will be interleaved in the final order + // only relative order within a type matters: consumers of the list filter to one or the other + switch (i) + { + case ILocalMetadataProvider: return GetConfiguredOrder(localMetadataReaderOrder, i.Name); + case IRemoteMetadataProvider: return GetConfiguredOrder(metadataFetcherOrder, i.Name); + default: return int.MaxValue; // default to end + } + }) .ThenBy(GetDefaultOrder); } @@ -439,42 +437,20 @@ namespace MediaBrowser.Providers.Manager } } - private int GetConfiguredOrder(BaseItem item, IMetadataProvider provider, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions) + private static int GetConfiguredOrder(string[] order, string providerName) { - // See if there's a user-defined order - if (provider is ILocalMetadataProvider) + var index = Array.IndexOf(order, providerName); + + if (index != -1) { - var configuredOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadataReaderOrder; - - var index = Array.IndexOf(configuredOrder, provider.Name); - - if (index != -1) - { - return index; - } + return index; } - // See if there's a user-defined order - if (provider is IRemoteMetadataProvider) - { - var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); - var typeFetcherOrder = typeOptions?.MetadataFetcherOrder; - - var fetcherOrder = typeFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder; - - var index = Array.IndexOf(fetcherOrder, provider.Name); - - if (index != -1) - { - return index; - } - } - - // Not configured. Just return some high number to put it at the end. - return 100; + // default to end + return int.MaxValue; } - private static int GetOrder(object provider) + private static int GetDefaultOrder(object provider) { if (provider is IHasOrder hasOrder) { @@ -485,16 +461,6 @@ namespace MediaBrowser.Providers.Manager return 50; } - private int GetDefaultOrder(IMetadataProvider provider) - { - if (provider is IHasOrder hasOrder) - { - return hasOrder.Order; - } - - return 0; - } - /// public MetadataPluginSummary[] GetAllMetadataPlugins() { diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index 7a542f0eba..d59e4070f9 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -167,8 +167,8 @@ namespace Jellyfin.Providers.Tests.Manager private static TheoryData GetMetadataProvidersOrderData() { - var l = "local"; - var r = "remote"; + var l = nameof(ILocalMetadataProvider); + var r = nameof(IRemoteMetadataProvider); return new () { { new[] { l, l, r, r }, null, null, null, null, null, new[] { 0, 1, 2, 3 } }, // no order options set @@ -198,8 +198,7 @@ namespace Jellyfin.Providers.Tests.Manager { new[] { l, l, l, r, r, r }, null, null, new[] { 2, 1, 0 }, new[] { 5, 4, 3 }, null, new[] { 2, 5, 1, 4, 0, 3 } }, // full reverse order // IHasOrder ordering (not interleaved, doesn't care about types) - // TODO unset goes to beginning, not end - { new[] { l, l, r, r }, null, null, null, null, new int?[] { 2, null, 1, null }, new[] { 1, 3, 2, 0 } }, // partially defined + { new[] { l, l, r, r }, null, null, null, null, new int?[] { 2, null, 1, null }, new[] { 2, 0, 1, 3 } }, // partially defined { new[] { l, l, r, r }, null, null, null, null, new int?[] { 3, 2, 1, 0 }, new[] { 3, 2, 1, 0 } }, // full reverse order // note odd interaction - orderby determines order of slot when local and remote both have a slot 0 { new[] { l, l, r, r }, new[] { 1 }, new[] { 3 }, null, null, new int?[] { null, 2, null, 1 }, new[] { 3, 1, 0, 2 } }, // sorts interleaved results @@ -216,12 +215,6 @@ namespace Jellyfin.Providers.Tests.Manager public void GetMetadataProviders_ProviderOrder_MatchesExpected(string[] providers, int[]? libraryLocalOrder, int[]? libraryRemoteOrder, int[]? serverLocalOrder, int[]? serverRemoteOrder, int?[]? hasOrderOrder, int[] expectedOrder) { var item = new MetadataTestItem(); - var typeNames = new Dictionary - { - { "remote", nameof(IRemoteMetadataProvider) }, - { "local", nameof(ILocalMetadataProvider) }, - { "custom", nameof(ICustomMetadataProvider) } - }; var nameProvider = new Func(i => "Provider" + i); @@ -229,7 +222,7 @@ namespace Jellyfin.Providers.Tests.Manager for (var i = 0; i < providers.Length; i++) { var order = hasOrderOrder?[i]; - providerList.Add(MockIMetadataProviderMapper(typeNames[providers[i]], nameProvider(i), order: order)); + providerList.Add(MockIMetadataProviderMapper(providers[i], nameProvider(i), order: order)); } var libraryOptions = new LibraryOptions(); @@ -278,7 +271,6 @@ namespace Jellyfin.Providers.Tests.Manager var providerManager = GetProviderManager(serverConfiguration: serverConfiguration, baseItemManager: baseItemManager.Object); AddParts(providerManager, metadataProviders: providerList); - // TODO why does this take libraryOptions directly while GetImageProviders did not? var actualProviders = providerManager.GetMetadataProviders(item, libraryOptions).ToList(); Assert.Equal(providerList.Count, actualProviders.Count); From d5e2c2fb5e8a1328a2e938a7100a1ceb29b28fa7 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Fri, 10 Dec 2021 00:38:41 +0100 Subject: [PATCH 010/639] Implement CanRefreshMetadata tests for GetMetadataProviders Cleanup tests, extract common blocks --- .../Manager/ProviderManagerTests.cs | 285 ++++++++++++------ 1 file changed, 196 insertions(+), 89 deletions(-) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index d59e4070f9..ba91f5ed2d 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -57,33 +57,10 @@ namespace Jellyfin.Providers.Tests.Manager providerList.Add(MockIImageProvider(nameProvider(i), item, order: order)); } - var libraryOptions = new LibraryOptions(); - if (libraryOrder != null) - { - libraryOptions.TypeOptions = new[] - { - new TypeOptions - { - Type = item.GetType().Name, - ImageFetcherOrder = libraryOrder.Select(nameProvider).ToArray() - } - }; - } + var libraryOptions = CreateLibraryOptions(item.GetType().Name, imageFetcherOrder: libraryOrder?.Select(nameProvider).ToArray()); + var serverConfiguration = CreateServerConfiguration(item.GetType().Name, imageFetcherOrder: serverOrder?.Select(nameProvider).ToArray()); - var serverConfiguration = new ServerConfiguration(); - if (serverOrder != null) - { - serverConfiguration.MetadataOptions = new[] - { - new MetadataOptions - { - ItemType = item.GetType().Name, - ImageFetcherOrder = serverOrder.Select(nameProvider).ToArray() - } - }; - } - - var providerManager = GetProviderManager(serverConfiguration: serverConfiguration, libraryOptions: libraryOptions); + using var providerManager = GetProviderManager(serverConfiguration: serverConfiguration, libraryOptions: libraryOptions); AddParts(providerManager, imageProviders: providerList); var refreshOptions = new ImageRefreshOptions(Mock.Of(MockBehavior.Strict)); @@ -119,12 +96,19 @@ namespace Jellyfin.Providers.Tests.Manager [InlineData(typeof(IDynamicImageProvider), true, true)] [InlineData(typeof(IRemoteImageProvider), false, false)] [InlineData(typeof(IDynamicImageProvider), false, false)] - public void GetImageProviders_CanRefreshImagesEnabled_WhenLocalOrEnabled(Type providerType, bool enabled, bool expected) + public void GetImageProviders_CanRefreshImagesBaseItemEnabled_WhenLocalOrEnabled(Type providerType, bool enabled, bool expected) { GetImageProviders_CanRefreshImages_Tester(providerType, true, expected, baseItemEnabled: enabled); } - private static void GetImageProviders_CanRefreshImages_Tester(Type providerType, bool supports, bool expected, bool errorOnSupported = false, bool itemLocked = false, bool fullRefresh = false, bool baseItemEnabled = true) + private static void GetImageProviders_CanRefreshImages_Tester( + Type providerType, + bool supports, + bool expected, + bool errorOnSupported = false, + bool itemLocked = false, + bool fullRefresh = false, + bool baseItemEnabled = true) { var item = new Movie { @@ -150,19 +134,12 @@ namespace Jellyfin.Providers.Tests.Manager baseItemManager.Setup(i => i.IsImageFetcherEnabled(item, It.IsAny(), providerName)) .Returns(baseItemEnabled); - var providerManager = GetProviderManager(baseItemManager: baseItemManager.Object); + using var providerManager = GetProviderManager(baseItemManager: baseItemManager.Object); AddParts(providerManager, imageProviders: new[] { provider }); - var actualProviders = providerManager.GetImageProviders(item, refreshOptions); + var actualProviders = providerManager.GetImageProviders(item, refreshOptions).ToArray(); - if (expected) - { - Assert.Single(actualProviders); - } - else - { - Assert.Empty(actualProviders); - } + Assert.Equal(expected ? 1 : 0, actualProviders.Length); } private static TheoryData GetMetadataProvidersOrderData() @@ -212,7 +189,14 @@ namespace Jellyfin.Providers.Tests.Manager [Theory] [MemberData(nameof(GetMetadataProvidersOrderData))] - public void GetMetadataProviders_ProviderOrder_MatchesExpected(string[] providers, int[]? libraryLocalOrder, int[]? libraryRemoteOrder, int[]? serverLocalOrder, int[]? serverRemoteOrder, int?[]? hasOrderOrder, int[] expectedOrder) + public void GetMetadataProviders_ProviderOrder_MatchesExpected( + string[] providers, + int[]? libraryLocalOrder, + int[]? libraryRemoteOrder, + int[]? serverLocalOrder, + int[]? serverRemoteOrder, + int?[]? hasOrderOrder, + int[] expectedOrder) { var item = new MetadataTestItem(); @@ -225,50 +209,20 @@ namespace Jellyfin.Providers.Tests.Manager providerList.Add(MockIMetadataProviderMapper(providers[i], nameProvider(i), order: order)); } - var libraryOptions = new LibraryOptions(); - if (libraryLocalOrder != null) - { - libraryOptions.LocalMetadataReaderOrder = libraryLocalOrder.Select(nameProvider).ToArray(); - } - - if (libraryRemoteOrder != null) - { - libraryOptions.TypeOptions = new[] - { - new TypeOptions - { - Type = item.GetType().Name, - MetadataFetcherOrder = libraryRemoteOrder.Select(nameProvider).ToArray() - } - }; - } - - var serverConfiguration = new ServerConfiguration(); - if (serverLocalOrder != null || serverRemoteOrder != null) - { - serverConfiguration.MetadataOptions = new[] - { - new MetadataOptions - { - ItemType = item.GetType().Name - } - }; - if (serverLocalOrder != null) - { - serverConfiguration.MetadataOptions[0].LocalMetadataReaderOrder = serverLocalOrder.Select(nameProvider).ToArray(); - } - - if (serverRemoteOrder != null) - { - serverConfiguration.MetadataOptions[0].MetadataFetcherOrder = serverRemoteOrder.Select(nameProvider).ToArray(); - } - } + var libraryOptions = CreateLibraryOptions( + item.GetType().Name, + localMetadataReaderOrder: libraryLocalOrder?.Select(nameProvider).ToArray(), + metadataFetcherOrder: libraryRemoteOrder?.Select(nameProvider).ToArray()); + var serverConfiguration = CreateServerConfiguration( + item.GetType().Name, + localMetadataReaderOrder: serverLocalOrder?.Select(nameProvider).ToArray(), + metadataFetcherOrder: serverRemoteOrder?.Select(nameProvider).ToArray()); var baseItemManager = new Mock(MockBehavior.Strict); baseItemManager.Setup(i => i.IsMetadataFetcherEnabled(item, It.IsAny(), It.IsAny())) .Returns(true); - var providerManager = GetProviderManager(serverConfiguration: serverConfiguration, baseItemManager: baseItemManager.Object); + using var providerManager = GetProviderManager(serverConfiguration: serverConfiguration, baseItemManager: baseItemManager.Object); AddParts(providerManager, metadataProviders: providerList); var actualProviders = providerManager.GetMetadataProviders(item, libraryOptions).ToList(); @@ -278,6 +232,87 @@ namespace Jellyfin.Providers.Tests.Manager Assert.Equal(expectedOrder, actualOrder); } + [Theory] + [InlineData(typeof(IMetadataProvider))] + [InlineData(typeof(ILocalMetadataProvider))] + [InlineData(typeof(IRemoteMetadataProvider))] + [InlineData(typeof(ICustomMetadataProvider))] + public void GetMetadataProviders_CanRefreshMetadataBasic_ReturnsTrue(Type providerType) + { + GetMetadataProviders_CanRefreshMetadata_Tester(providerType, true); + } + + [Theory] + [InlineData(typeof(ILocalMetadataProvider), false, true)] + [InlineData(typeof(IRemoteMetadataProvider), false, false)] + [InlineData(typeof(ICustomMetadataProvider), false, false)] + [InlineData(typeof(ILocalMetadataProvider), true, true)] + [InlineData(typeof(ICustomMetadataProvider), true, false)] + public void GetMetadataProviders_CanRefreshMetadataLocked_WhenLocalOrForced(Type providerType, bool forced, bool expected) + { + GetMetadataProviders_CanRefreshMetadata_Tester(providerType, expected, itemLocked: true, providerForced: forced); + } + + [Theory] + [InlineData(typeof(ILocalMetadataProvider), false, true)] + [InlineData(typeof(ICustomMetadataProvider), false, true)] + [InlineData(typeof(IRemoteMetadataProvider), false, false)] + [InlineData(typeof(IRemoteMetadataProvider), true, true)] + public void GetMetadataProviders_CanRefreshMetadataBaseItemEnabled_WhenEnabledOrNotRemote(Type providerType, bool baseItemEnabled, bool expected) + { + GetMetadataProviders_CanRefreshMetadata_Tester(providerType, expected, baseItemEnabled: baseItemEnabled); + } + + [Theory] + [InlineData(typeof(IRemoteMetadataProvider), false, true)] + [InlineData(typeof(ICustomMetadataProvider), false, true)] + [InlineData(typeof(ILocalMetadataProvider), false, false)] + [InlineData(typeof(ILocalMetadataProvider), true, true)] + public void GetMetadataProviders_CanRefreshMetadataSupportsLocal_WhenSupportsOrNotLocal(Type providerType, bool supportsLocalMetadata, bool expected) + { + GetMetadataProviders_CanRefreshMetadata_Tester(providerType, expected, supportsLocalMetadata: supportsLocalMetadata); + } + + [Theory] + [InlineData(typeof(ICustomMetadataProvider), true)] + [InlineData(typeof(IRemoteMetadataProvider), false)] + [InlineData(typeof(ILocalMetadataProvider), false)] + public void GetMetadataProviders_CanRefreshMetadataOwned_WhenNotLocal(Type providerType, bool expected) + { + GetMetadataProviders_CanRefreshMetadata_Tester(providerType, expected, ownedItem: true); + } + + private static void GetMetadataProviders_CanRefreshMetadata_Tester( + Type providerType, + bool expected, + bool itemLocked = false, + bool baseItemEnabled = true, + bool providerForced = false, + bool supportsLocalMetadata = true, + bool ownedItem = false) + { + var item = new MetadataTestItem + { + IsLocked = itemLocked, + OwnerId = ownedItem ? Guid.NewGuid() : Guid.Empty, + EnableLocalMetadata = supportsLocalMetadata + }; + + var providerName = "provider"; + var provider = MockIMetadataProviderMapper(providerType.Name, providerName, forced: providerForced); + + var baseItemManager = new Mock(MockBehavior.Strict); + baseItemManager.Setup(i => i.IsMetadataFetcherEnabled(item, It.IsAny(), providerName)) + .Returns(baseItemEnabled); + + using var providerManager = GetProviderManager(baseItemManager: baseItemManager.Object); + AddParts(providerManager, metadataProviders: new[] { provider }); + + var actualProviders = providerManager.GetMetadataProviders(item, new LibraryOptions()).ToArray(); + + Assert.Equal(expected ? 1 : 0, actualProviders.Length); + } + private static IImageProvider MockIImageProvider(string name, BaseItem expectedType, bool supports = true, int? order = null, bool errorOnSupported = false) where TProviderType : class, IImageProvider { @@ -297,7 +332,7 @@ namespace Jellyfin.Providers.Tests.Manager if (errorOnSupported) { provider.Setup(p => p.Supports(It.IsAny())) - .Throws(new ArgumentException()); + .Throws(new ArgumentException("Provider threw exception on Supports(item)")); } else { @@ -308,25 +343,31 @@ namespace Jellyfin.Providers.Tests.Manager return provider.Object; } - private static IMetadataProvider MockIMetadataProviderMapper(string typeName, string providerName, int? order = null) + private static IMetadataProvider MockIMetadataProviderMapper(string typeName, string providerName, int? order = null, bool forced = false) where TItemType : BaseItem, IHasLookupInfo where TLookupInfoType : ItemLookupInfo, new() => typeName switch { - "ILocalMetadataProvider" => MockIMetadataProvider, TItemType>(providerName, order), - "IRemoteMetadataProvider" => MockIMetadataProvider, TItemType>(providerName, order), - "ICustomMetadataProvider" => MockIMetadataProvider, TItemType>(providerName, order), - _ => MockIMetadataProvider, TItemType>(providerName, order) + "ILocalMetadataProvider" => MockIMetadataProvider, TItemType>(providerName, order, forced), + "IRemoteMetadataProvider" => MockIMetadataProvider, TItemType>(providerName, order, forced), + "ICustomMetadataProvider" => MockIMetadataProvider, TItemType>(providerName, order, forced), + _ => MockIMetadataProvider, TItemType>(providerName, order, forced) }; - private static IMetadataProvider MockIMetadataProvider(string name, int? order = null) + private static IMetadataProvider MockIMetadataProvider(string name, int? order = null, bool forced = false) where TProviderType : class, IMetadataProvider where TItemType : BaseItem { + Mock? forcedProvider = null; + if (forced) + { + forcedProvider = new Mock(); + } + Mock? hasOrder = null; if (order != null) { - hasOrder = new Mock(MockBehavior.Strict); + hasOrder = forcedProvider == null ? new Mock() : forcedProvider.As(); hasOrder.Setup(i => i.Order) .Returns((int)order); } @@ -340,7 +381,71 @@ namespace Jellyfin.Providers.Tests.Manager return provider.Object; } - private static ProviderManager GetProviderManager(ServerConfiguration? serverConfiguration = null, LibraryOptions? libraryOptions = null, IBaseItemManager? baseItemManager = null) + private static LibraryOptions CreateLibraryOptions( + string typeName, + string[]? imageFetcherOrder = null, + string[]? localMetadataReaderOrder = null, + string[]? metadataFetcherOrder = null) + { + var libraryOptions = new LibraryOptions + { + LocalMetadataReaderOrder = localMetadataReaderOrder + }; + + // only create type options if populating it with something + if (imageFetcherOrder != null || metadataFetcherOrder != null) + { + imageFetcherOrder ??= Array.Empty(); + metadataFetcherOrder ??= Array.Empty(); + + libraryOptions.TypeOptions = new[] + { + new TypeOptions + { + Type = typeName, + ImageFetcherOrder = imageFetcherOrder, + MetadataFetcherOrder = metadataFetcherOrder + } + }; + } + + return libraryOptions; + } + + private static ServerConfiguration CreateServerConfiguration( + string typeName, + string[]? imageFetcherOrder = null, + string[]? localMetadataReaderOrder = null, + string[]? metadataFetcherOrder = null) + { + var serverConfiguration = new ServerConfiguration(); + + // only create type options if populating it with something + if (imageFetcherOrder != null || localMetadataReaderOrder != null || metadataFetcherOrder != null) + { + imageFetcherOrder ??= Array.Empty(); + localMetadataReaderOrder ??= Array.Empty(); + metadataFetcherOrder ??= Array.Empty(); + + serverConfiguration.MetadataOptions = new[] + { + new MetadataOptions + { + ItemType = typeName, + ImageFetcherOrder = imageFetcherOrder, + LocalMetadataReaderOrder = localMetadataReaderOrder, + MetadataFetcherOrder = metadataFetcherOrder + } + }; + } + + return serverConfiguration; + } + + private static ProviderManager GetProviderManager( + ServerConfiguration? serverConfiguration = null, + LibraryOptions? libraryOptions = null, + IBaseItemManager? baseItemManager = null) { var serverConfigurationManager = new Mock(MockBehavior.Strict); serverConfigurationManager.Setup(i => i.Configuration) @@ -382,11 +487,13 @@ namespace Jellyfin.Providers.Tests.Manager } /// - /// Simple extension to force SupportsLocalMetadata to true. + /// Simple extension to make SupportsLocalMetadata directly settable. /// public class MetadataTestItem : BaseItem, IHasLookupInfo { - public override bool SupportsLocalMetadata => true; + public bool EnableLocalMetadata { get; set; } = true; + + public override bool SupportsLocalMetadata => EnableLocalMetadata; public MetadataTestItemInfo GetLookupInfo() { From a7c009e2eb3e21b7b5c07984866419bb8136423f Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sat, 18 Dec 2021 21:40:27 +0100 Subject: [PATCH 011/639] Pass TypeOptions instead of full LibraryOptions --- .../BaseItemManager/BaseItemManager.cs | 14 ++++---- .../BaseItemManager/IBaseItemManager.cs | 8 ++--- .../Manager/ProviderManager.cs | 12 +++---- .../BaseItemManagerTests.cs | 32 +++++++------------ .../Manager/ProviderManagerTests.cs | 6 ++-- 5 files changed, 31 insertions(+), 41 deletions(-) diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index d273b54fc6..61539cae56 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.BaseItemManager public SemaphoreSlim MetadataRefreshThrottler { get; private set; } /// - public bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name) + public bool IsMetadataFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name) { if (baseItem is Channel) { @@ -49,10 +49,9 @@ namespace MediaBrowser.Controller.BaseItemManager return !baseItem.EnableMediaSourceDisplay; } - var typeOptions = libraryOptions.GetTypeOptions(baseItem.GetType().Name); - if (typeOptions != null) + if (libraryTypeOptions != null) { - return typeOptions.MetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); + return libraryTypeOptions.MetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); } var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, baseItem.GetType().Name, StringComparison.OrdinalIgnoreCase)); @@ -61,7 +60,7 @@ namespace MediaBrowser.Controller.BaseItemManager } /// - public bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name) + public bool IsImageFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name) { if (baseItem is Channel) { @@ -75,10 +74,9 @@ namespace MediaBrowser.Controller.BaseItemManager return !baseItem.EnableMediaSourceDisplay; } - var typeOptions = libraryOptions.GetTypeOptions(baseItem.GetType().Name); - if (typeOptions != null) + if (libraryTypeOptions != null) { - return typeOptions.ImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); + return libraryTypeOptions.ImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase); } var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, baseItem.GetType().Name, StringComparison.OrdinalIgnoreCase)); diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs index e18994214e..b07c80879d 100644 --- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -18,18 +18,18 @@ namespace MediaBrowser.Controller.BaseItemManager /// Is metadata fetcher enabled. /// /// The base item. - /// The library options. + /// The type options for baseItem from the library (if defined). /// The metadata fetcher name. /// true if metadata fetcher is enabled, else false. - bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name); + bool IsMetadataFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name); /// /// Is image fetcher enabled. /// /// The base item. - /// The library options. + /// The type options for baseItem from the library (if defined). /// The image fetcher name. /// true if image fetcher is enabled, else false. - bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name); + bool IsImageFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name); } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 82d633e234..d1a5831f98 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -321,7 +321,7 @@ namespace MediaBrowser.Providers.Manager var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder; - return _imageProviders.Where(i => CanRefreshImages(i, item, libraryOptions, refreshOptions, includeDisabled)) + return _imageProviders.Where(i => CanRefreshImages(i, item, typeOptions, refreshOptions, includeDisabled)) .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name)) .ThenBy(GetDefaultOrder); } @@ -343,7 +343,7 @@ namespace MediaBrowser.Providers.Manager var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder; return _metadataProviders.OfType>() - .Where(i => CanRefreshMetadata(i, item, libraryOptions, includeDisabled, forceEnableInternetMetadata)) + .Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInternetMetadata)) .OrderBy(i => { // local and remote providers will be interleaved in the final order @@ -361,7 +361,7 @@ namespace MediaBrowser.Providers.Manager private bool CanRefreshMetadata( IMetadataProvider provider, BaseItem item, - LibraryOptions libraryOptions, + TypeOptions? libraryTypeOptions, bool includeDisabled, bool forceEnableInternetMetadata) { @@ -375,7 +375,7 @@ namespace MediaBrowser.Providers.Manager if (provider is IRemoteMetadataProvider) { - if (!forceEnableInternetMetadata && !_baseItemManager.IsMetadataFetcherEnabled(item, libraryOptions, provider.Name)) + if (!forceEnableInternetMetadata && !_baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name)) { return false; } @@ -402,7 +402,7 @@ namespace MediaBrowser.Providers.Manager private bool CanRefreshImages( IImageProvider provider, BaseItem item, - LibraryOptions libraryOptions, + TypeOptions? libraryTypeOptions, ImageRefreshOptions refreshOptions, bool includeDisabled) { @@ -419,7 +419,7 @@ namespace MediaBrowser.Providers.Manager if (provider is IRemoteImageProvider || provider is IDynamicImageProvider) { - if (!_baseItemManager.IsImageFetcherEnabled(item, libraryOptions, provider.Name)) + if (!_baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, provider.Name)) { return false; } diff --git a/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs b/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs index 463e17ad36..f67e6d1ef0 100644 --- a/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs +++ b/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs @@ -20,17 +20,13 @@ namespace Jellyfin.Controller.Tests { BaseItem item = (BaseItem)Activator.CreateInstance(itemType)!; - var libraryOptions = new LibraryOptions - { - TypeOptions = new[] + var libraryTypeOptions = itemType == typeof(Book) + ? new TypeOptions { - new TypeOptions - { - Type = "Book", - MetadataFetchers = new[] { "LibraryEnabled" } - } + Type = "Book", + MetadataFetchers = new[] { "LibraryEnabled" } } - }; + : null; var serverConfiguration = new ServerConfiguration(); foreach (var typeConfig in serverConfiguration.MetadataOptions) @@ -43,7 +39,7 @@ namespace Jellyfin.Controller.Tests .Returns(serverConfiguration); var baseItemManager = new BaseItemManager(serverConfigurationManager.Object); - var actual = baseItemManager.IsMetadataFetcherEnabled(item, libraryOptions, fetcherName); + var actual = baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, fetcherName); Assert.Equal(expected, actual); } @@ -57,17 +53,13 @@ namespace Jellyfin.Controller.Tests { BaseItem item = (BaseItem)Activator.CreateInstance(itemType)!; - var libraryOptions = new LibraryOptions - { - TypeOptions = new[] + var libraryTypeOptions = itemType == typeof(Book) + ? new TypeOptions { - new TypeOptions - { - Type = "Book", - ImageFetchers = new[] { "LibraryEnabled" } - } + Type = "Book", + ImageFetchers = new[] { "LibraryEnabled" } } - }; + : null; var serverConfiguration = new ServerConfiguration(); foreach (var typeConfig in serverConfiguration.MetadataOptions) @@ -80,7 +72,7 @@ namespace Jellyfin.Controller.Tests .Returns(serverConfiguration); var baseItemManager = new BaseItemManager(serverConfigurationManager.Object); - var actual = baseItemManager.IsImageFetcherEnabled(item, libraryOptions, fetcherName); + var actual = baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, fetcherName); Assert.Equal(expected, actual); } diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index ba91f5ed2d..560b50f091 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -131,7 +131,7 @@ namespace Jellyfin.Providers.Tests.Manager }; var baseItemManager = new Mock(MockBehavior.Strict); - baseItemManager.Setup(i => i.IsImageFetcherEnabled(item, It.IsAny(), providerName)) + baseItemManager.Setup(i => i.IsImageFetcherEnabled(item, It.IsAny(), providerName)) .Returns(baseItemEnabled); using var providerManager = GetProviderManager(baseItemManager: baseItemManager.Object); @@ -219,7 +219,7 @@ namespace Jellyfin.Providers.Tests.Manager metadataFetcherOrder: serverRemoteOrder?.Select(nameProvider).ToArray()); var baseItemManager = new Mock(MockBehavior.Strict); - baseItemManager.Setup(i => i.IsMetadataFetcherEnabled(item, It.IsAny(), It.IsAny())) + baseItemManager.Setup(i => i.IsMetadataFetcherEnabled(item, It.IsAny(), It.IsAny())) .Returns(true); using var providerManager = GetProviderManager(serverConfiguration: serverConfiguration, baseItemManager: baseItemManager.Object); @@ -302,7 +302,7 @@ namespace Jellyfin.Providers.Tests.Manager var provider = MockIMetadataProviderMapper(providerType.Name, providerName, forced: providerForced); var baseItemManager = new Mock(MockBehavior.Strict); - baseItemManager.Setup(i => i.IsMetadataFetcherEnabled(item, It.IsAny(), providerName)) + baseItemManager.Setup(i => i.IsMetadataFetcherEnabled(item, It.IsAny(), providerName)) .Returns(baseItemEnabled); using var providerManager = GetProviderManager(baseItemManager: baseItemManager.Object); From 6ab64f4930f61f7f0d0968b88da687a95e0035ad Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sun, 19 Dec 2021 23:33:27 +0100 Subject: [PATCH 012/639] Switch to nameof to simplify theory signatures --- .../Manager/ProviderManagerTests.cs | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index 560b50f091..d76d411a78 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -77,32 +77,32 @@ namespace Jellyfin.Providers.Tests.Manager [InlineData(true, true, false)] public void GetImageProviders_CanRefreshImagesBasic_WhenSupportsWithoutError(bool supports, bool errorOnSupported, bool expected) { - GetImageProviders_CanRefreshImages_Tester(typeof(IImageProvider), supports, expected, errorOnSupported: errorOnSupported); + GetImageProviders_CanRefreshImages_Tester(nameof(IImageProvider), supports, expected, errorOnSupported: errorOnSupported); } [Theory] - [InlineData(typeof(ILocalImageProvider), false, true)] - [InlineData(typeof(ILocalImageProvider), true, true)] - [InlineData(typeof(IImageProvider), false, false)] - [InlineData(typeof(IImageProvider), true, true)] - public void GetImageProviders_CanRefreshImagesLocked_WhenLocalOrFullRefresh(Type providerType, bool fullRefresh, bool expected) + [InlineData(nameof(ILocalImageProvider), false, true)] + [InlineData(nameof(ILocalImageProvider), true, true)] + [InlineData(nameof(IImageProvider), false, false)] + [InlineData(nameof(IImageProvider), true, true)] + public void GetImageProviders_CanRefreshImagesLocked_WhenLocalOrFullRefresh(string providerType, bool fullRefresh, bool expected) { GetImageProviders_CanRefreshImages_Tester(providerType, true, expected, itemLocked: true, fullRefresh: fullRefresh); } [Theory] - [InlineData(typeof(ILocalImageProvider), false, true)] - [InlineData(typeof(IRemoteImageProvider), true, true)] - [InlineData(typeof(IDynamicImageProvider), true, true)] - [InlineData(typeof(IRemoteImageProvider), false, false)] - [InlineData(typeof(IDynamicImageProvider), false, false)] - public void GetImageProviders_CanRefreshImagesBaseItemEnabled_WhenLocalOrEnabled(Type providerType, bool enabled, bool expected) + [InlineData(nameof(ILocalImageProvider), false, true)] + [InlineData(nameof(IRemoteImageProvider), true, true)] + [InlineData(nameof(IDynamicImageProvider), true, true)] + [InlineData(nameof(IRemoteImageProvider), false, false)] + [InlineData(nameof(IDynamicImageProvider), false, false)] + public void GetImageProviders_CanRefreshImagesBaseItemEnabled_WhenLocalOrEnabled(string providerType, bool enabled, bool expected) { GetImageProviders_CanRefreshImages_Tester(providerType, true, expected, baseItemEnabled: enabled); } private static void GetImageProviders_CanRefreshImages_Tester( - Type providerType, + string providerType, bool supports, bool expected, bool errorOnSupported = false, @@ -116,7 +116,7 @@ namespace Jellyfin.Providers.Tests.Manager }; var providerName = "provider"; - IImageProvider provider = providerType.Name switch + IImageProvider provider = providerType switch { "IImageProvider" => MockIImageProvider(providerName, item, supports: supports, errorOnSupported: errorOnSupported), "ILocalImageProvider" => MockIImageProvider(providerName, item, supports: supports, errorOnSupported: errorOnSupported), @@ -233,57 +233,57 @@ namespace Jellyfin.Providers.Tests.Manager } [Theory] - [InlineData(typeof(IMetadataProvider))] - [InlineData(typeof(ILocalMetadataProvider))] - [InlineData(typeof(IRemoteMetadataProvider))] - [InlineData(typeof(ICustomMetadataProvider))] - public void GetMetadataProviders_CanRefreshMetadataBasic_ReturnsTrue(Type providerType) + [InlineData(nameof(IMetadataProvider))] + [InlineData(nameof(ILocalMetadataProvider))] + [InlineData(nameof(IRemoteMetadataProvider))] + [InlineData(nameof(ICustomMetadataProvider))] + public void GetMetadataProviders_CanRefreshMetadataBasic_ReturnsTrue(string providerType) { GetMetadataProviders_CanRefreshMetadata_Tester(providerType, true); } [Theory] - [InlineData(typeof(ILocalMetadataProvider), false, true)] - [InlineData(typeof(IRemoteMetadataProvider), false, false)] - [InlineData(typeof(ICustomMetadataProvider), false, false)] - [InlineData(typeof(ILocalMetadataProvider), true, true)] - [InlineData(typeof(ICustomMetadataProvider), true, false)] - public void GetMetadataProviders_CanRefreshMetadataLocked_WhenLocalOrForced(Type providerType, bool forced, bool expected) + [InlineData(nameof(ILocalMetadataProvider), false, true)] + [InlineData(nameof(IRemoteMetadataProvider), false, false)] + [InlineData(nameof(ICustomMetadataProvider), false, false)] + [InlineData(nameof(ILocalMetadataProvider), true, true)] + [InlineData(nameof(ICustomMetadataProvider), true, false)] + public void GetMetadataProviders_CanRefreshMetadataLocked_WhenLocalOrForced(string providerType, bool forced, bool expected) { GetMetadataProviders_CanRefreshMetadata_Tester(providerType, expected, itemLocked: true, providerForced: forced); } [Theory] - [InlineData(typeof(ILocalMetadataProvider), false, true)] - [InlineData(typeof(ICustomMetadataProvider), false, true)] - [InlineData(typeof(IRemoteMetadataProvider), false, false)] - [InlineData(typeof(IRemoteMetadataProvider), true, true)] - public void GetMetadataProviders_CanRefreshMetadataBaseItemEnabled_WhenEnabledOrNotRemote(Type providerType, bool baseItemEnabled, bool expected) + [InlineData(nameof(ILocalMetadataProvider), false, true)] + [InlineData(nameof(ICustomMetadataProvider), false, true)] + [InlineData(nameof(IRemoteMetadataProvider), false, false)] + [InlineData(nameof(IRemoteMetadataProvider), true, true)] + public void GetMetadataProviders_CanRefreshMetadataBaseItemEnabled_WhenEnabledOrNotRemote(string providerType, bool baseItemEnabled, bool expected) { GetMetadataProviders_CanRefreshMetadata_Tester(providerType, expected, baseItemEnabled: baseItemEnabled); } [Theory] - [InlineData(typeof(IRemoteMetadataProvider), false, true)] - [InlineData(typeof(ICustomMetadataProvider), false, true)] - [InlineData(typeof(ILocalMetadataProvider), false, false)] - [InlineData(typeof(ILocalMetadataProvider), true, true)] - public void GetMetadataProviders_CanRefreshMetadataSupportsLocal_WhenSupportsOrNotLocal(Type providerType, bool supportsLocalMetadata, bool expected) + [InlineData(nameof(IRemoteMetadataProvider), false, true)] + [InlineData(nameof(ICustomMetadataProvider), false, true)] + [InlineData(nameof(ILocalMetadataProvider), false, false)] + [InlineData(nameof(ILocalMetadataProvider), true, true)] + public void GetMetadataProviders_CanRefreshMetadataSupportsLocal_WhenSupportsOrNotLocal(string providerType, bool supportsLocalMetadata, bool expected) { GetMetadataProviders_CanRefreshMetadata_Tester(providerType, expected, supportsLocalMetadata: supportsLocalMetadata); } [Theory] - [InlineData(typeof(ICustomMetadataProvider), true)] - [InlineData(typeof(IRemoteMetadataProvider), false)] - [InlineData(typeof(ILocalMetadataProvider), false)] - public void GetMetadataProviders_CanRefreshMetadataOwned_WhenNotLocal(Type providerType, bool expected) + [InlineData(nameof(ICustomMetadataProvider), true)] + [InlineData(nameof(IRemoteMetadataProvider), false)] + [InlineData(nameof(ILocalMetadataProvider), false)] + public void GetMetadataProviders_CanRefreshMetadataOwned_WhenNotLocal(string providerType, bool expected) { GetMetadataProviders_CanRefreshMetadata_Tester(providerType, expected, ownedItem: true); } private static void GetMetadataProviders_CanRefreshMetadata_Tester( - Type providerType, + string providerType, bool expected, bool itemLocked = false, bool baseItemEnabled = true, @@ -299,7 +299,7 @@ namespace Jellyfin.Providers.Tests.Manager }; var providerName = "provider"; - var provider = MockIMetadataProviderMapper(providerType.Name, providerName, forced: providerForced); + var provider = MockIMetadataProviderMapper(providerType, providerName, forced: providerForced); var baseItemManager = new Mock(MockBehavior.Strict); baseItemManager.Setup(i => i.IsMetadataFetcherEnabled(item, It.IsAny(), providerName)) From bdce435b09b88329dd7f23ff5f4e9bb7998763b5 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sat, 18 Dec 2021 22:30:06 +0100 Subject: [PATCH 013/639] Reorder and flatten provider filtering --- .../Manager/ProviderManager.cs | 115 ++++++++---------- .../Manager/ProviderManagerTests.cs | 4 +- 2 files changed, 55 insertions(+), 64 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index d1a5831f98..e2882ee06e 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -326,6 +326,39 @@ namespace MediaBrowser.Providers.Manager .ThenBy(GetDefaultOrder); } + private bool CanRefreshImages( + IImageProvider provider, + BaseItem item, + TypeOptions? libraryTypeOptions, + ImageRefreshOptions refreshOptions, + bool includeDisabled) + { + try + { + if (!provider.Supports(item)) + { + return false; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path); + return false; + } + + if (includeDisabled || provider is ILocalImageProvider) + { + return true; + } + + if (item.IsLocked && refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh) + { + return false; + } + + return _baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, provider.Name); + } + /// public IEnumerable> GetMetadataProviders(BaseItem item, LibraryOptions libraryOptions) where T : BaseItem @@ -365,76 +398,34 @@ namespace MediaBrowser.Providers.Manager bool includeDisabled, bool forceEnableInternetMetadata) { - if (!includeDisabled) - { - // If locked only allow local providers - if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider) - { - return false; - } - - if (provider is IRemoteMetadataProvider) - { - if (!forceEnableInternetMetadata && !_baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name)) - { - return false; - } - } - } - if (!item.SupportsLocalMetadata && provider is ILocalMetadataProvider) { return false; } - // If this restriction is ever lifted, movie xml providers will have to be updated to prevent owned items like trailers from reading those files - if (!item.OwnerId.Equals(default)) + // Prevent owned items from reading the same local metadata file as their owner + if (!item.OwnerId.Equals(default) && provider is ILocalMetadataProvider) { - if (provider is ILocalMetadataProvider || provider is IRemoteMetadataProvider) - { - return false; - } - } - - return true; - } - - private bool CanRefreshImages( - IImageProvider provider, - BaseItem item, - TypeOptions? libraryTypeOptions, - ImageRefreshOptions refreshOptions, - bool includeDisabled) - { - if (!includeDisabled) - { - // If locked only allow local providers - if (item.IsLocked && provider is not ILocalImageProvider) - { - if (refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh) - { - return false; - } - } - - if (provider is IRemoteImageProvider || provider is IDynamicImageProvider) - { - if (!_baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, provider.Name)) - { - return false; - } - } - } - - try - { - return provider.Supports(item); - } - catch (Exception ex) - { - _logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path); return false; } + + if (includeDisabled) + { + return true; + } + + // If locked only allow local providers + if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider) + { + return false; + } + + if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider) + { + return true; + } + + return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name); } private static int GetConfiguredOrder(string[] order, string providerName) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index d76d411a78..8100dcfa6f 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -54,7 +54,7 @@ namespace Jellyfin.Providers.Tests.Manager for (var i = 0; i < providerCount; i++) { var order = hasOrderOrder?[i]; - providerList.Add(MockIImageProvider(nameProvider(i), item, order: order)); + providerList.Add(MockIImageProvider(nameProvider(i), item, order: order)); } var libraryOptions = CreateLibraryOptions(item.GetType().Name, imageFetcherOrder: libraryOrder?.Select(nameProvider).ToArray()); @@ -275,7 +275,7 @@ namespace Jellyfin.Providers.Tests.Manager [Theory] [InlineData(nameof(ICustomMetadataProvider), true)] - [InlineData(nameof(IRemoteMetadataProvider), false)] + [InlineData(nameof(IRemoteMetadataProvider), true)] [InlineData(nameof(ILocalMetadataProvider), false)] public void GetMetadataProviders_CanRefreshMetadataOwned_WhenNotLocal(string providerType, bool expected) { From ee5bd0daa62e68f4f93e7603017c1f028781e387 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 21 Dec 2021 00:24:07 +0100 Subject: [PATCH 014/639] Implement tests on ProviderManager.RefreshSingleItem --- .../Providers/IMetadataService.cs | 5 + .../Manager/ProviderManagerTests.cs | 110 +++++++++++++++++- 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IMetadataService.cs b/MediaBrowser.Controller/Providers/IMetadataService.cs index 05fbb18ee4..f0f1d18624 100644 --- a/MediaBrowser.Controller/Providers/IMetadataService.cs +++ b/MediaBrowser.Controller/Providers/IMetadataService.cs @@ -23,6 +23,11 @@ namespace MediaBrowser.Controller.Providers /// true if this instance can refresh the specified item. bool CanRefresh(BaseItem item); + /// + /// Determines whether this instance primarily targets the specified type. + /// + /// The type. + /// true if this instance primarily targets the specified type. bool CanRefreshPrimary(Type type); /// diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index 8100dcfa6f..5845b31be8 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -9,6 +11,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Providers.Manager; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; @@ -17,6 +20,95 @@ namespace Jellyfin.Providers.Tests.Manager { public class ProviderManagerTests { + private static readonly ILogger _logger = new NullLogger(); + + private static TheoryData[], int> RefreshSingleItemOrderData() + => new () + { + // no order set, uses provided order + { + new[] + { + MockIMetadataService(true, true), + MockIMetadataService(true, true) + }, + 0 + }, + // sort order sets priority when all match + { + new[] + { + MockIMetadataService(true, true, 1), + MockIMetadataService(true, true, 0), + MockIMetadataService(true, true, 2) + }, + 1 + }, + // CanRefreshPrimary prioritized + { + new[] + { + MockIMetadataService(false, true), + MockIMetadataService(true, true), + }, + 1 + }, + // falls back to CanRefresh + { + new[] + { + MockIMetadataService(false, false), + MockIMetadataService(false, true) + }, + 1 + }, + }; + + [Theory] + [MemberData(nameof(RefreshSingleItemOrderData))] + public void RefreshSingleItem_ServiceOrdering_FollowsPriority(Mock[] servicesList, int expectedIndex) + { + var item = new Movie(); + + using var providerManager = GetProviderManager(); + AddParts(providerManager, metadataServices: servicesList.Select(s => s.Object).ToArray()); + + var refreshOptions = new MetadataRefreshOptions(Mock.Of(MockBehavior.Strict)); + var actual = providerManager.RefreshSingleItem(item, refreshOptions, CancellationToken.None); + + Assert.Equal(ItemUpdateType.MetadataDownload, actual.Result); + for (var i = 0; i < servicesList.Length; i++) + { + if (i == expectedIndex) + { + servicesList[i].Verify(mock => mock.RefreshMetadata(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); + } + else + { + servicesList[i].Verify(mock => mock.RefreshMetadata(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); + } + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void RefreshSingleItem_RefreshMetadata_WhenServiceFound(bool serviceFound) + { + var item = new Movie(); + + var servicesList = new[] { MockIMetadataService(false, serviceFound) }; + + using var providerManager = GetProviderManager(); + AddParts(providerManager, metadataServices: servicesList.Select(s => s.Object).ToArray()); + + var refreshOptions = new MetadataRefreshOptions(Mock.Of(MockBehavior.Strict)); + var actual = providerManager.RefreshSingleItem(item, refreshOptions, CancellationToken.None); + + var expectedResult = serviceFound ? ItemUpdateType.MetadataDownload : ItemUpdateType.None; + Assert.Equal(expectedResult, actual.Result); + } + private static TheoryData GetImageProvidersOrderData() => new () { @@ -313,6 +405,20 @@ namespace Jellyfin.Providers.Tests.Manager Assert.Equal(expected ? 1 : 0, actualProviders.Length); } + private static Mock MockIMetadataService(bool refreshPrimary, bool canRefresh, int order = 0) + { + var service = new Mock(MockBehavior.Strict); + service.Setup(s => s.Order) + .Returns(order); + service.Setup(s => s.CanRefreshPrimary(It.IsAny())) + .Returns(refreshPrimary); + service.Setup(s => s.CanRefresh(It.IsAny())) + .Returns(canRefresh); + service.Setup(s => s.RefreshMetadata(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(ItemUpdateType.MetadataDownload)); + return service; + } + private static IImageProvider MockIImageProvider(string name, BaseItem expectedType, bool supports = true, int? order = null, bool errorOnSupported = false) where TProviderType : class, IImageProvider { @@ -460,11 +566,11 @@ namespace Jellyfin.Providers.Tests.Manager null, serverConfigurationManager.Object, null, - new NullLogger(), + _logger, null, null, libraryManager.Object, - baseItemManager); + baseItemManager!); return providerManager; } From ac675318f858e1be6e156e6d9d217cb662ba5c31 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Tue, 21 Dec 2021 00:25:35 +0100 Subject: [PATCH 015/639] Simplify RefreshSingleItem --- .../Manager/ProviderManager.cs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index e2882ee06e..135b69a95b 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -132,26 +132,15 @@ namespace MediaBrowser.Providers.Manager var type = item.GetType(); var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type)); + service ??= _metadataServices.FirstOrDefault(current => current.CanRefresh(item)); if (service == null) { - foreach (var current in _metadataServices) - { - if (current.CanRefresh(item)) - { - service = current; - break; - } - } + _logger.LogError("Unable to find a metadata service for item of type {TypeName}", item.GetType().Name); + return Task.FromResult(ItemUpdateType.None); } - if (service != null) - { - return service.RefreshMetadata(item, options, cancellationToken); - } - - _logger.LogError("Unable to find a metadata service for item of type {TypeName}", item.GetType().Name); - return Task.FromResult(ItemUpdateType.None); + return service.RefreshMetadata(item, options, cancellationToken); } /// From 6e4710d048767292ec027a4992ad3448d826e42e Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@users.noreply.github.com> Date: Fri, 24 Dec 2021 09:50:58 +0100 Subject: [PATCH 016/639] Fix review comment Co-authored-by: Cody Robibero --- MediaBrowser.Providers/Manager/ProviderManager.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 135b69a95b..9be4bc479b 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -367,16 +367,15 @@ namespace MediaBrowser.Providers.Manager return _metadataProviders.OfType>() .Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInternetMetadata)) .OrderBy(i => - { // local and remote providers will be interleaved in the final order // only relative order within a type matters: consumers of the list filter to one or the other - switch (i) + i switch { - case ILocalMetadataProvider: return GetConfiguredOrder(localMetadataReaderOrder, i.Name); - case IRemoteMetadataProvider: return GetConfiguredOrder(metadataFetcherOrder, i.Name); - default: return int.MaxValue; // default to end - } - }) + ILocalMetadataProvider => GetConfiguredOrder(localMetadataReaderOrder, i.Name), + IRemoteMetadataProvider => GetConfiguredOrder(metadataFetcherOrder, i.Name), + // Default to end + _ => int.MaxValue + }) .ThenBy(GetDefaultOrder); } From b03f56c3d6e005b615780bcdc676bcd632e80395 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Sat, 1 Jan 2022 00:22:46 +0100 Subject: [PATCH 017/639] Remove warnings --- .../Manager/ProviderManager.cs | 26 +++++++++------- .../Manager/ProviderManagerTests.cs | 30 +++++++++++-------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 9be4bc479b..eb4bc3587f 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Providers.Manager /// public class ProviderManager : IProviderManager, IDisposable { - private readonly object _refreshQueueLock = new (); + private readonly object _refreshQueueLock = new(); private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryMonitor _libraryMonitor; @@ -55,9 +55,9 @@ namespace MediaBrowser.Providers.Manager private readonly ISubtitleManager _subtitleManager; private readonly IServerConfigurationManager _configurationManager; private readonly IBaseItemManager _baseItemManager; - private readonly ConcurrentDictionary _activeRefreshes = new (); - private readonly CancellationTokenSource _disposeCancellationTokenSource = new (); - private readonly SimplePriorityQueue> _refreshQueue = new (); + private readonly ConcurrentDictionary _activeRefreshes = new(); + private readonly CancellationTokenSource _disposeCancellationTokenSource = new(); + private readonly SimplePriorityQueue> _refreshQueue = new(); private IImageProvider[] _imageProviders = Array.Empty(); private IMetadataService[] _metadataServices = Array.Empty(); @@ -164,6 +164,10 @@ namespace MediaBrowser.Providers.Manager { contentType = "image/png"; } + else + { + throw new HttpRequestException("Invalid image received: contentType not set.", null, response.StatusCode); + } } // thetvdb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons... @@ -589,7 +593,7 @@ namespace MediaBrowser.Providers.Manager foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false))) { - _logger.LogDebug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name); + _logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name); if (saver is IMetadataFileSaver fileSaver) { @@ -601,7 +605,7 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - _logger.LogError(ex, "Error in {0} GetSavePath", saver.Name); + _logger.LogError(ex, "Error in {Saver} GetSavePath", saver.Name); continue; } @@ -688,7 +692,7 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - _logger.LogError(ex, "Error in {0}.IsEnabledFor", saver.Name); + _logger.LogError(ex, "Error in {Saver}.IsEnabledFor", saver.Name); return false; } } @@ -838,7 +842,7 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - _logger.LogError(ex, "Error in {0}.Supports", i.GetType().Name); + _logger.LogError(ex, "Error in {Type}.Supports", i.GetType().Name); return false; } }); @@ -904,7 +908,7 @@ namespace MediaBrowser.Providers.Manager /// public void OnRefreshStart(BaseItem item) { - _logger.LogDebug("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); + _logger.LogDebug("OnRefreshStart {Item}", item.Id.ToString("N", CultureInfo.InvariantCulture)); _activeRefreshes[item.Id] = 0; RefreshStarted?.Invoke(this, new GenericEventArgs(item)); } @@ -912,7 +916,7 @@ namespace MediaBrowser.Providers.Manager /// public void OnRefreshComplete(BaseItem item) { - _logger.LogDebug("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); + _logger.LogDebug("OnRefreshComplete {Item}", item.Id.ToString("N", CultureInfo.InvariantCulture)); _activeRefreshes.Remove(item.Id, out _); @@ -934,7 +938,7 @@ namespace MediaBrowser.Providers.Manager public void OnRefreshProgress(BaseItem item, double progress) { var id = item.Id; - _logger.LogDebug("OnRefreshProgress {0} {1}", id.ToString("N", CultureInfo.InvariantCulture), progress); + _logger.LogDebug("OnRefreshProgress {Id} {Progress}", id.ToString("N", CultureInfo.InvariantCulture), progress); // TODO: Need to hunt down the conditions for this happening _activeRefreshes.AddOrUpdate( diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index 5845b31be8..6179e31359 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -1,21 +1,29 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller; using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; +// Allow Moq to see internal class +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] + namespace Jellyfin.Providers.Tests.Manager { public class ProviderManagerTests @@ -23,7 +31,7 @@ namespace Jellyfin.Providers.Tests.Manager private static readonly ILogger _logger = new NullLogger(); private static TheoryData[], int> RefreshSingleItemOrderData() - => new () + => new() { // no order set, uses provided order { @@ -110,7 +118,7 @@ namespace Jellyfin.Providers.Tests.Manager } private static TheoryData GetImageProvidersOrderData() - => new () + => new() { { 3, null, null, null, new[] { 0, 1, 2 } }, // no order options set @@ -238,7 +246,7 @@ namespace Jellyfin.Providers.Tests.Manager { var l = nameof(ILocalMetadataProvider); var r = nameof(IRemoteMetadataProvider); - return new () + return new() { { new[] { l, l, r, r }, null, null, null, null, null, new[] { 0, 1, 2, 3 } }, // no order options set @@ -269,8 +277,6 @@ namespace Jellyfin.Providers.Tests.Manager // IHasOrder ordering (not interleaved, doesn't care about types) { new[] { l, l, r, r }, null, null, null, null, new int?[] { 2, null, 1, null }, new[] { 2, 0, 1, 3 } }, // partially defined { new[] { l, l, r, r }, null, null, null, null, new int?[] { 3, 2, 1, 0 }, new[] { 3, 2, 1, 0 } }, // full reverse order - // note odd interaction - orderby determines order of slot when local and remote both have a slot 0 - { new[] { l, l, r, r }, new[] { 1 }, new[] { 3 }, null, null, new int?[] { null, 2, null, 1 }, new[] { 3, 1, 0, 2 } }, // sorts interleaved results // multiple orders set { new[] { l, l, l, r, r, r }, new[] { 1 }, new[] { 4 }, new[] { 2, 1, 0 }, new[] { 5, 4, 3 }, null, new[] { 1, 4, 0, 2, 3, 5 } }, // partial library order first, server order ignored @@ -562,13 +568,13 @@ namespace Jellyfin.Providers.Tests.Manager .Returns(libraryOptions ?? new LibraryOptions()); var providerManager = new ProviderManager( - null, - null, + Mock.Of(), + Mock.Of(), serverConfigurationManager.Object, - null, + Mock.Of(), _logger, - null, - null, + Mock.Of(), + Mock.Of(), libraryManager.Object, baseItemManager!); @@ -595,7 +601,7 @@ namespace Jellyfin.Providers.Tests.Manager /// /// Simple extension to make SupportsLocalMetadata directly settable. /// - public class MetadataTestItem : BaseItem, IHasLookupInfo + internal class MetadataTestItem : BaseItem, IHasLookupInfo { public bool EnableLocalMetadata { get; set; } = true; @@ -607,7 +613,7 @@ namespace Jellyfin.Providers.Tests.Manager } } - public class MetadataTestItemInfo : ItemLookupInfo + internal class MetadataTestItemInfo : ItemLookupInfo { } } From 6bf71c0fd355e9c95a1e142019d9bc5cce34200d Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@users.noreply.github.com> Date: Sun, 16 Jan 2022 14:18:44 +0100 Subject: [PATCH 018/639] Combine verify calls Co-authored-by: Claus Vium --- .../Manager/ProviderManagerTests.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index 6179e31359..a5673ad838 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -87,14 +87,8 @@ namespace Jellyfin.Providers.Tests.Manager Assert.Equal(ItemUpdateType.MetadataDownload, actual.Result); for (var i = 0; i < servicesList.Length; i++) { - if (i == expectedIndex) - { - servicesList[i].Verify(mock => mock.RefreshMetadata(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); - } - else - { - servicesList[i].Verify(mock => mock.RefreshMetadata(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); - } + var times = i == expectedIndex ? Times.Once() : Times.Never(); + servicesList[i].Verify(mock => mock.RefreshMetadata(It.IsAny(), It.IsAny(), It.IsAny()), times); } } From 12ec0e285ddf8a5360859b5c49bb4b7b916569d2 Mon Sep 17 00:00:00 2001 From: "Negulici-R. Barnabas" Date: Mon, 18 Jul 2022 17:50:52 +0300 Subject: [PATCH 019/639] Chapter Images: - chapter image extraction intervals, limit count and resolutions can be set by the user from the server general settings; --- .../Encoder/MediaEncoder.cs | 30 ++++++++++++- .../Configuration/ServerConfiguration.cs | 18 ++++++++ MediaBrowser.Model/Drawing/ImageResolution.cs | 43 +++++++++++++++++++ .../MediaInfo/FFProbeVideoInfo.cs | 9 ++-- 4 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 MediaBrowser.Model/Drawing/ImageResolution.cs diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 77b97c9b48..5e2d318290 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -51,6 +51,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; private readonly IConfiguration _config; + private readonly IServerConfigurationManager _serverConfig; private readonly string _startupOptionFFmpegPath; private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2); @@ -81,13 +82,15 @@ namespace MediaBrowser.MediaEncoding.Encoder IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, - IConfiguration config) + IConfiguration config, + IServerConfigurationManager serverConfig) { _logger = logger; _configurationManager = configurationManager; _fileSystem = fileSystem; _localization = localization; _config = config; + _serverConfig = serverConfig; _startupOptionFFmpegPath = config.GetValue(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty; _jsonSerializerOptions = JsonDefaults.Options; } @@ -598,6 +601,29 @@ namespace MediaBrowser.MediaEncoding.Encoder _ => ".jpg" }; + bool enumConversionStatus = Enum.TryParse(_serverConfig.Configuration.ChapterImageResolution, out ImageResolution resolution); + var outputResolution = string.Empty; + + if (enumConversionStatus) + { + outputResolution = resolution switch + { + ImageResolution.P240 => "426x240", + ImageResolution.P360 => "640x360", + ImageResolution.P480 => "854x480", + ImageResolution.P720 => "1280x720", + ImageResolution.P1080 => "1920x1080", + ImageResolution.P1440 => "2560x1440", + ImageResolution.P2160 => "3840x2160", + _ => string.Empty + }; + + if (!string.IsNullOrEmpty(outputResolution)) + { + outputResolution = " -s " + outputResolution; + } + } + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); @@ -651,7 +677,7 @@ namespace MediaBrowser.MediaEncoding.Encoder var vf = string.Join(',', filters); var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; - var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads); + var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2}{5} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads, outputResolution); if (offset.HasValue) { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index e61b896b9d..c37c167eb6 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -240,5 +240,23 @@ namespace MediaBrowser.Model.Configuration /// Gets or sets a value indicating whether clients should be allowed to upload logs. /// public bool AllowClientLogUpload { get; set; } = true; + + /// + /// Gets or sets the dummy chapters duration in seconds. + /// + /// The dummy chapters duration. + public int DummyChapterDuration { get; set; } = 300; + + /// + /// Gets or sets the dummy chapter count. + /// + /// The dummy chapter count. + public int DummyChapterCount { get; set; } = 100; + + /// + /// Gets or sets the chapter image resolution. + /// + /// The chapter image resolution. + public string ChapterImageResolution { get; set; } = "Match Source"; } } diff --git a/MediaBrowser.Model/Drawing/ImageResolution.cs b/MediaBrowser.Model/Drawing/ImageResolution.cs new file mode 100644 index 0000000000..7e1c54f5a3 --- /dev/null +++ b/MediaBrowser.Model/Drawing/ImageResolution.cs @@ -0,0 +1,43 @@ +namespace MediaBrowser.Model.Drawing +{ + /// + /// Enum ImageResolution. + /// + public enum ImageResolution + { + /// + /// 240p. + /// + P240, + + /// + /// 360p. + /// + P360, + + /// + /// 480p. + /// + P480, + + /// + /// 720p. + /// + P720, + + /// + /// 1080p. + /// + P1080, + + /// + /// 1440p. + /// + P1440, + + /// + /// 2160p. + /// + P2160 + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 8c08ab30ef..c8ff5de69a 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -48,8 +48,6 @@ namespace MediaBrowser.Providers.MediaInfo private readonly SubtitleResolver _subtitleResolver; private readonly IMediaSourceManager _mediaSourceManager; - private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; - public FFProbeVideoInfo( ILogger logger, IMediaSourceManager mediaSourceManager, @@ -651,6 +649,7 @@ namespace MediaBrowser.Providers.MediaInfo private ChapterInfo[] CreateDummyChapters(Video video) { var runtime = video.RunTimeTicks ?? 0; + long dummyChapterDuration = TimeSpan.FromSeconds(_config.Configuration.DummyChapterDuration).Ticks; if (runtime < 0) { @@ -662,13 +661,13 @@ namespace MediaBrowser.Providers.MediaInfo runtime)); } - if (runtime < _dummyChapterDuration) + if (runtime < dummyChapterDuration) { return Array.Empty(); } // Limit to 100 chapters just in case there's some incorrect metadata here - int chapterCount = (int)Math.Min(runtime / _dummyChapterDuration, 100); + int chapterCount = (int)Math.Min(runtime / dummyChapterDuration, _config.Configuration.DummyChapterCount); var chapters = new ChapterInfo[chapterCount]; long currentChapterTicks = 0; @@ -679,7 +678,7 @@ namespace MediaBrowser.Providers.MediaInfo StartPositionTicks = currentChapterTicks }; - currentChapterTicks += _dummyChapterDuration; + currentChapterTicks += dummyChapterDuration; } return chapters; From 37daeffafa69ceffbcf531057c49d38a2fa064e5 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 13 Oct 2022 18:49:25 +0800 Subject: [PATCH 020/639] Add support for OPUS and fixes for FLAC case issue in HLS Signed-off-by: nyanmisaka --- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 42 ++++++++++++++++++- Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs | 16 ++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index fa392e5674..853fbe89d5 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -197,6 +197,13 @@ namespace Jellyfin.Api.Helpers if (state.VideoStream != null && state.VideoRequest != null) { + // Provide a workaround for the case issue between flac and fLaC. + var flacWaPlaylist = ApplyFlacCaseWorkaround(state, basicPlaylist.ToString()); + if (!String.IsNullOrEmpty(flacWaPlaylist)) + { + builder.Append(flacWaPlaylist); + } + // Provide SDR HEVC entrance for backward compatibility. if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && !string.IsNullOrEmpty(state.VideoStream.VideoRange) @@ -215,7 +222,14 @@ namespace Jellyfin.Api.Helpers var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0; var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; - AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); + var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); + + // Provide a workaround for the case issue between flac and fLaC. + flacWaPlaylist = ApplyFlacCaseWorkaround(state, sdrPlaylist.ToString()); + if (!String.IsNullOrEmpty(flacWaPlaylist)) + { + builder.Append(flacWaPlaylist); + } // Restore the video codec state.OutputVideoCodec = "copy"; @@ -245,6 +259,13 @@ namespace Jellyfin.Api.Helpers state.VideoStream.Level = originalLevel; var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField); builder.Append(newPlaylist); + + // Provide a workaround for the case issue between flac and fLaC. + flacWaPlaylist = ApplyFlacCaseWorkaround(state, newPlaylist); + if (!String.IsNullOrEmpty(flacWaPlaylist)) + { + builder.Append(flacWaPlaylist); + } } } @@ -603,6 +624,11 @@ namespace Jellyfin.Api.Helpers return HlsCodecStringHelpers.GetALACString(); } + if (string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringHelpers.GetOPUSString(); + } + return string.Empty; } @@ -701,7 +727,19 @@ namespace Jellyfin.Api.Helpers return oldPlaylist.Replace( oldValue.ToString(), newValue.ToString(), - StringComparison.OrdinalIgnoreCase); + StringComparison.Ordinal); + } + + private string ApplyFlacCaseWorkaround(StreamState state, string srcPlaylist) + { + if (!string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)) + { + return string.Empty; + } + + var newPlaylist = srcPlaylist.Replace(",flac\"", ",fLaC\"", StringComparison.Ordinal); + + return newPlaylist.Contains(",fLaC\"", StringComparison.Ordinal) ? newPlaylist : string.Empty; } } } diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs index a5369c441c..cbe82979bc 100644 --- a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs @@ -27,13 +27,18 @@ namespace Jellyfin.Api.Helpers /// /// Codec name for FLAC. /// - public const string FLAC = "fLaC"; + public const string FLAC = "flac"; /// /// Codec name for ALAC. /// public const string ALAC = "alac"; + /// + /// Codec name for OPUS. + /// + public const string OPUS = "opus"; + /// /// Gets a MP3 codec string. /// @@ -101,6 +106,15 @@ namespace Jellyfin.Api.Helpers return ALAC; } + /// + /// Gets an OPUS codec string. + /// + /// OPUS codec string. + public static string GetOPUSString() + { + return OPUS; + } + /// /// Gets a H.264 codec string. /// From c9a387943f05cbbd11c5b92d900ff850055ed4f3 Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:17:56 -0400 Subject: [PATCH 021/639] Add IPv4 fallback from IPv6 failure. Co-authored-by: BaronGreenback --- Emby.Dlna/Eventing/DlnaEventManager.cs | 2 +- .../HappyEyeballs/HttpClientExtension.cs | 117 ++++++++++++++++++ Jellyfin.Networking/Manager/NetworkManager.cs | 17 ++- Jellyfin.Server/Startup.cs | 20 ++- MediaBrowser.Common/Net/NamedClient.cs | 9 +- 5 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index d17e238715..d2c0017ec6 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -165,7 +165,7 @@ namespace Emby.Dlna.Eventing try { - using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + using var response = await _httpClientFactory.CreateClient(NamedClient.DirectIp) .SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); } catch (OperationCanceledException) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs new file mode 100644 index 0000000000..12b42cc6e1 --- /dev/null +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -0,0 +1,117 @@ +using System.Diagnostics.Metrics; +using System.IO; +using System.Net.Http; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +/* + Copyright (c) 2021 ppy Pty Ltd . + 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. +*/ + +namespace Jellyfin.Networking.HappyEyeballs +{ + /// + /// Defines the class. + /// + /// Implementation taken from https://github.com/ppy/osu-framework/pull/4191 . + /// + public static class HttpClientExtension + { + private const int ConnectionEstablishTimeout = 2000; + + /// + /// Gets a value indicating whether the initial IPv6 check has been performed (to determine whether v6 is available or not). + /// + private static bool _hasResolvedIPv6Availability; + + /// + /// Gets or sets a value indicating whether IPv6 should be preferred. Value may change based on runtime failures. + /// + public static bool? UseIPv6 { get; set; } = null; + + /// + /// Implements the httpclient callback method. + /// + /// The instance. + /// The instance. + /// The http steam. + public static async ValueTask OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken) + { + // Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), + // let's make IPv4 fallback work in a simple way. This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 + // and expected to be fixed in .NET 6. + if (UseIPv6 == true) + { + try + { + var localToken = cancellationToken; + + if (!_hasResolvedIPv6Availability) + { + // to make things move fast, use a very low timeout for the initial ipv6 attempt. + using var quickFailCts = new CancellationTokenSource(ConnectionEstablishTimeout); + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, quickFailCts.Token); + + localToken = linkedTokenSource.Token; + } + + return await AttemptConnection(AddressFamily.InterNetworkV6, context, localToken).ConfigureAwait(false); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch +#pragma warning restore CA1031 // Do not catch general exception types + { + // very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt. + // Network manager will reset this value in the case of physical network changes / interruptions. + UseIPv6 = false; + } + finally + { + _hasResolvedIPv6Availability = true; + } + } + + // fallback to IPv4. + return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false); + } + + private static async ValueTask AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken) + { + // The following socket constructor will create a dual-mode socket on systems where IPV6 is available. + var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp) + { + // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios. + NoDelay = true + }; + + try + { + await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false); + // The stream should take the ownership of the underlying socket, + // closing it when it's disposed. + return new NetworkStream(socket, ownsSocket: true); + } + catch + { + socket.Dispose(); + throw; + } + } + } +} diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 9e06cdfe71..631e9cdb8d 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -594,6 +594,7 @@ namespace Jellyfin.Networking.Manager IsIP4Enabled = Socket.OSSupportsIPv4 && config.EnableIPV4; IsIP6Enabled = Socket.OSSupportsIPv6 && config.EnableIPV6; + HappyEyeballs.HttpClientExtension.UseIPv6 = IsIP6Enabled; if (!IsIP6Enabled && !IsIP4Enabled) { @@ -838,9 +839,19 @@ namespace Jellyfin.Networking.Manager try { await Task.Delay(2000).ConfigureAwait(false); - InitialiseInterfaces(); - // Recalculate LAN caches. - InitialiseLAN(_configurationManager.GetNetworkConfiguration()); + + var config = _configurationManager.GetNetworkConfiguration(); + // Have we lost IPv6 capability? + if (IsIP6Enabled && !Socket.OSSupportsIPv6) + { + UpdateSettings(config); + } + else + { + InitialiseInterfaces(); + // Recalculate LAN caches. + InitialiseLAN(config); + } NetworkChanged?.Invoke(this, EventArgs.Empty); } diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 1954a5c558..4417bb7226 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -7,6 +7,7 @@ using System.Net.Mime; using System.Text; using Jellyfin.MediaEncoding.Hls.Extensions; using Jellyfin.Networking.Configuration; +using Jellyfin.Networking.HappyEyeballs; using Jellyfin.Server.Extensions; using Jellyfin.Server.Implementations; using Jellyfin.Server.Infrastructure; @@ -24,6 +25,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; +using Microsoft.VisualBasic; using Prometheus; namespace Jellyfin.Server @@ -79,6 +81,13 @@ namespace Jellyfin.Server var acceptJsonHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json, 1.0); var acceptXmlHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Xml, 0.9); var acceptAnyHeader = new MediaTypeWithQualityHeaderValue("*/*", 0.8); + Func eyeballsHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() + { + AutomaticDecompression = DecompressionMethods.All, + RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8, + ConnectCallback = HttpClientExtension.OnConnect + }; + Func defaultHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() { AutomaticDecompression = DecompressionMethods.All, @@ -93,7 +102,7 @@ namespace Jellyfin.Server c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); }) - .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); + .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); services.AddHttpClient(NamedClient.MusicBrainz, c => { @@ -102,6 +111,15 @@ namespace Jellyfin.Server c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); }) + .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); + + services.AddHttpClient(NamedClient.DirectIp, c => + { + c.DefaultRequestHeaders.UserAgent.Add(productHeader); + c.DefaultRequestHeaders.Accept.Add(acceptJsonHeader); + c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); + c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); + }) .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); services.AddHttpClient(NamedClient.Dlna, c => diff --git a/MediaBrowser.Common/Net/NamedClient.cs b/MediaBrowser.Common/Net/NamedClient.cs index a6cacd4f17..9c5544b0ff 100644 --- a/MediaBrowser.Common/Net/NamedClient.cs +++ b/MediaBrowser.Common/Net/NamedClient.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Common.Net +namespace MediaBrowser.Common.Net { /// /// Registered http client names. @@ -6,7 +6,7 @@ public static class NamedClient { /// - /// Gets the value for the default named http client. + /// Gets the value for the default named http client which implements happy eyeballs. /// public const string Default = nameof(Default); @@ -19,5 +19,10 @@ /// Gets the value for the DLNA named http client. /// public const string Dlna = nameof(Dlna); + + /// + /// Non happy eyeballs implementation. + /// + public const string DirectIp = nameof(DirectIp); } } From 6c479dfb36d305444bae7d4b3ad78bfb2112549b Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:38:28 -0400 Subject: [PATCH 022/639] Ensure IPv6 property is threadsafe. --- .../HappyEyeballs/HttpClientExtension.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index 12b42cc6e1..6760869107 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -35,6 +35,11 @@ namespace Jellyfin.Networking.HappyEyeballs { private const int ConnectionEstablishTimeout = 2000; + /// + /// Interlocked doesn't support bool types. + /// + private static int _useIPv6 = 0; + /// /// Gets a value indicating whether the initial IPv6 check has been performed (to determine whether v6 is available or not). /// @@ -43,7 +48,21 @@ namespace Jellyfin.Networking.HappyEyeballs /// /// Gets or sets a value indicating whether IPv6 should be preferred. Value may change based on runtime failures. /// - public static bool? UseIPv6 { get; set; } = null; + public static bool UseIPv6 + { + get => Interlocked.CompareExchange(ref _useIPv6, 1, 1) == 1; + set + { + if (value) + { + Interlocked.CompareExchange(ref _useIPv6, 1, 0); + } + else + { + Interlocked.CompareExchange(ref _useIPv6, 0, 1); + } + } + } /// /// Implements the httpclient callback method. @@ -56,7 +75,7 @@ namespace Jellyfin.Networking.HappyEyeballs // Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), // let's make IPv4 fallback work in a simple way. This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 // and expected to be fixed in .NET 6. - if (UseIPv6 == true) + if (UseIPv6) { try { From 6caabe68ff05f4100db63a3a98d6eeb02347f689 Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:17:41 -0400 Subject: [PATCH 023/639] Change method to call IPv6 and IPv4 in parallel. --- .../HappyEyeballs/HttpClientExtension.cs | 119 ++++++------------ 1 file changed, 38 insertions(+), 81 deletions(-) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index 6760869107..049c8dd150 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -1,29 +1,9 @@ -using System.Diagnostics.Metrics; using System.IO; using System.Net.Http; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -/* - Copyright (c) 2021 ppy Pty Ltd . - 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. -*/ - namespace Jellyfin.Networking.HappyEyeballs { /// @@ -33,36 +13,12 @@ namespace Jellyfin.Networking.HappyEyeballs /// public static class HttpClientExtension { - private const int ConnectionEstablishTimeout = 2000; - /// - /// Interlocked doesn't support bool types. + /// Gets or sets a value indicating whether the client should use IPv6. /// - private static int _useIPv6 = 0; + public static bool UseIPv6 { get; set; } = true; - /// - /// Gets a value indicating whether the initial IPv6 check has been performed (to determine whether v6 is available or not). - /// - private static bool _hasResolvedIPv6Availability; - - /// - /// Gets or sets a value indicating whether IPv6 should be preferred. Value may change based on runtime failures. - /// - public static bool UseIPv6 - { - get => Interlocked.CompareExchange(ref _useIPv6, 1, 1) == 1; - set - { - if (value) - { - Interlocked.CompareExchange(ref _useIPv6, 1, 0); - } - else - { - Interlocked.CompareExchange(ref _useIPv6, 0, 1); - } - } - } + // Implementation taken from https://github.com/rmkerr/corefx/blob/SocketsHttpHandler_Connect_HappyEyeballs/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs /// /// Implements the httpclient callback method. @@ -72,45 +28,46 @@ namespace Jellyfin.Networking.HappyEyeballs /// The http steam. public static async ValueTask OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken) { - // Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), - // let's make IPv4 fallback work in a simple way. This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 - // and expected to be fixed in .NET 6. - if (UseIPv6) + if (!UseIPv6) { - try - { - var localToken = cancellationToken; - - if (!_hasResolvedIPv6Availability) - { - // to make things move fast, use a very low timeout for the initial ipv6 attempt. - using var quickFailCts = new CancellationTokenSource(ConnectionEstablishTimeout); - using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, quickFailCts.Token); - - localToken = linkedTokenSource.Token; - } - - return await AttemptConnection(AddressFamily.InterNetworkV6, context, localToken).ConfigureAwait(false); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch -#pragma warning restore CA1031 // Do not catch general exception types - { - // very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt. - // Network manager will reset this value in the case of physical network changes / interruptions. - UseIPv6 = false; - } - finally - { - _hasResolvedIPv6Availability = true; - } + return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false); } - // fallback to IPv4. - return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false); + using var cancelIPv6 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var tryConnectAsyncIPv6 = AttemptConnection(AddressFamily.InterNetworkV6, context, cancelIPv6.Token); + + if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConnectAsyncIPv6 && tryConnectAsyncIPv6.IsCompletedSuccessfully) + { + cancelIPv6.Cancel(); + return tryConnectAsyncIPv6.GetAwaiter().GetResult(); + } + + using var cancelIPv4 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var tryConnectAsyncIPv4 = AttemptConnection(AddressFamily.InterNetwork, context, cancelIPv4.Token); + + if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6) + { + if (tryConnectAsyncIPv6.IsCompletedSuccessfully) + { + cancelIPv4.Cancel(); + return tryConnectAsyncIPv6.GetAwaiter().GetResult(); + } + + return tryConnectAsyncIPv4.GetAwaiter().GetResult(); + } + else + { + if (tryConnectAsyncIPv4.IsCompletedSuccessfully) + { + cancelIPv6.Cancel(); + return tryConnectAsyncIPv4.GetAwaiter().GetResult(); + } + + return tryConnectAsyncIPv6.GetAwaiter().GetResult(); + } } - private static async ValueTask AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken) + private static async Task AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken) { // The following socket constructor will create a dual-mode socket on systems where IPV6 is available. var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp) From 06d89ff46020108f54e35840ee1ebcd17d00826b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 21:12:35 +0000 Subject: [PATCH 024/639] chore(deps): update dependency sharpfuzz to v2 --- .../Emby.Server.Implementations.Fuzz.csproj | 2 +- fuzz/Jellyfin.Server.Fuzz/Jellyfin.Server.Fuzz.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj b/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj index 81c8f2ba93..e6196e8472 100644 --- a/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj +++ b/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj @@ -19,7 +19,7 @@ - + diff --git a/fuzz/Jellyfin.Server.Fuzz/Jellyfin.Server.Fuzz.csproj b/fuzz/Jellyfin.Server.Fuzz/Jellyfin.Server.Fuzz.csproj index facd8b7bbc..6ffc17ff91 100644 --- a/fuzz/Jellyfin.Server.Fuzz/Jellyfin.Server.Fuzz.csproj +++ b/fuzz/Jellyfin.Server.Fuzz/Jellyfin.Server.Fuzz.csproj @@ -16,7 +16,7 @@ - + From 6621121c1b958f0e3726f3b4f1d9c2604e398504 Mon Sep 17 00:00:00 2001 From: Stefan Hendricks <38368299+Neuheit@users.noreply.github.com> Date: Sat, 29 Oct 2022 18:54:11 -0400 Subject: [PATCH 025/639] Add .NET MIT License --- .../HappyEyeballs/HttpClientExtension.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index 049c8dd150..bab02505dc 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -1,3 +1,29 @@ +/* +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +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. +*/ + using System.IO; using System.Net.Http; using System.Net.Sockets; @@ -18,8 +44,6 @@ namespace Jellyfin.Networking.HappyEyeballs /// public static bool UseIPv6 { get; set; } = true; - // Implementation taken from https://github.com/rmkerr/corefx/blob/SocketsHttpHandler_Connect_HappyEyeballs/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs - /// /// Implements the httpclient callback method. /// From 21072310e7d8425fba581bc407447cf5e947946e Mon Sep 17 00:00:00 2001 From: Jendrik Weise Date: Fri, 4 Nov 2022 15:16:27 +0100 Subject: [PATCH 026/639] Sort external files when scanning Sorts files such as external subtitles or audio as well as metadata Useful for deterministic display in the UI. --- MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs index 1bc2edfd88..bb2d584c10 100644 --- a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs @@ -175,12 +175,12 @@ namespace MediaBrowser.Providers.MediaInfo return Array.Empty(); } - var files = directoryService.GetFilePaths(folder, clearCache).ToList(); + var files = directoryService.GetFilePaths(folder, clearCache, true).ToList(); files.Remove(video.Path); var internalMetadataPath = video.GetInternalMetadataPath(); if (_fileSystem.DirectoryExists(internalMetadataPath)) { - files.AddRange(directoryService.GetFilePaths(internalMetadataPath, clearCache)); + files.AddRange(directoryService.GetFilePaths(internalMetadataPath, clearCache, true)); } if (!files.Any()) From a5f8d36b5d1c6fe0e391d3533d6df3bbd005c0fb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Nov 2022 02:10:48 +0000 Subject: [PATCH 027/639] chore(deps): update dependency prometheus-net to v7 --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 6d77aa1df2..15ae380e64 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -39,7 +39,7 @@ - + From d7f0596d5dcc6c6eddee05dbddd1d5e493c3580d Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 11 Nov 2022 08:32:29 -0700 Subject: [PATCH 028/639] Don't auto-update if plugin is pending restart --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index ec4e0dbeb9..3f7d46822e 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -715,6 +715,7 @@ namespace Emby.Server.Implementations.Plugins { // This value is memory only - so that the web will show restart required. plugin.Manifest.Status = PluginStatus.Restart; + plugin.Manifest.AutoUpdate = false; return; } @@ -729,6 +730,7 @@ namespace Emby.Server.Implementations.Plugins // This value is memory only - so that the web will show restart required. plugin.Manifest.Status = PluginStatus.Restart; + plugin.Manifest.AutoUpdate = false; } } } From cf060ee6643279473af93012e5be056cc852ba9a Mon Sep 17 00:00:00 2001 From: TheBlueKingLP <12997043+TheBlueKingLP@users.noreply.github.com> Date: Sun, 13 Nov 2022 00:53:38 +0900 Subject: [PATCH 029/639] Correcting LocalizationOption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changing from 体(the simplified variant) to 體(the traditional variant) in the LocalizationOption for the "Traditional Chinese" language. --- Emby.Server.Implementations/Localization/LocalizationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 22b283b8a0..4eab040a4a 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -435,7 +435,7 @@ namespace Emby.Server.Implementations.Localization yield return new LocalizationOption("اُردُو", "ur_PK"); yield return new LocalizationOption("Tiếng Việt", "vi"); yield return new LocalizationOption("汉语 (简化字)", "zh-CN"); - yield return new LocalizationOption("漢語 (繁体字)", "zh-TW"); + yield return new LocalizationOption("漢語 (繁體字)", "zh-TW"); yield return new LocalizationOption("廣東話 (香港)", "zh-HK"); } } From 7db1813cc8358ac7d4c7a78022553e1e3215679e Mon Sep 17 00:00:00 2001 From: "Negulici-R. Barnabas" Date: Sun, 13 Nov 2022 16:23:21 +0200 Subject: [PATCH 030/639] changed ChapterImageResolution in model to enum type; added 144p to the ImageResolution enum; updated chapters limit comment inside FFProbeVideoInfo.cs; --- .../Encoder/MediaEncoder.cs | 51 ++++++++++--------- .../Configuration/ServerConfiguration.cs | 3 +- MediaBrowser.Model/Drawing/ImageResolution.cs | 10 ++++ .../MediaInfo/FFProbeVideoInfo.cs | 2 +- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index cc6b51f58e..b7c49ed99b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -609,6 +609,32 @@ namespace MediaBrowser.MediaEncoding.Encoder return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, targetFormat, cancellationToken).ConfigureAwait(false); } + private string GetImageResolutionParameter() + { + string imageResolutionParameter; + + imageResolutionParameter = _serverConfig.Configuration.ChapterImageResolution switch + { + ImageResolution.P144 => "256x144", + ImageResolution.P240 => "426x240", + ImageResolution.P360 => "640x360", + ImageResolution.P480 => "854x480", + ImageResolution.P720 => "1280x720", + ImageResolution.P1080 => "1920x1080", + ImageResolution.P1440 => "2560x1440", + ImageResolution.P2160 => "3840x2160", + _ => string.Empty + }; + + if (!string.IsNullOrEmpty(imageResolutionParameter)) + { + imageResolutionParameter = " -s " + imageResolutionParameter; + } + + return imageResolutionParameter; + } + + private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, ImageFormat? targetFormat, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) @@ -626,29 +652,6 @@ namespace MediaBrowser.MediaEncoding.Encoder _ => ".jpg" }; - bool enumConversionStatus = Enum.TryParse(_serverConfig.Configuration.ChapterImageResolution, out ImageResolution resolution); - var outputResolution = string.Empty; - - if (enumConversionStatus) - { - outputResolution = resolution switch - { - ImageResolution.P240 => "426x240", - ImageResolution.P360 => "640x360", - ImageResolution.P480 => "854x480", - ImageResolution.P720 => "1280x720", - ImageResolution.P1080 => "1920x1080", - ImageResolution.P1440 => "2560x1440", - ImageResolution.P2160 => "3840x2160", - _ => string.Empty - }; - - if (!string.IsNullOrEmpty(outputResolution)) - { - outputResolution = " -s " + outputResolution; - } - } - var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); @@ -702,7 +705,7 @@ namespace MediaBrowser.MediaEncoding.Encoder var vf = string.Join(',', filters); var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; - var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2}{5} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads, outputResolution); + var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2}{5} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads, GetImageResolutionParameter()); if (offset.HasValue) { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index c37c167eb6..a07ab7121e 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Updates; @@ -257,6 +258,6 @@ namespace MediaBrowser.Model.Configuration /// Gets or sets the chapter image resolution. /// /// The chapter image resolution. - public string ChapterImageResolution { get; set; } = "Match Source"; + public ImageResolution ChapterImageResolution { get; set; } = ImageResolution.MatchSource; } } diff --git a/MediaBrowser.Model/Drawing/ImageResolution.cs b/MediaBrowser.Model/Drawing/ImageResolution.cs index 7e1c54f5a3..99967da45e 100644 --- a/MediaBrowser.Model/Drawing/ImageResolution.cs +++ b/MediaBrowser.Model/Drawing/ImageResolution.cs @@ -5,6 +5,16 @@ namespace MediaBrowser.Model.Drawing /// public enum ImageResolution { + /// + /// MatchSource. + /// + MatchSource, + + /// + /// 144p. + /// + P144, + /// /// 240p. /// diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index c8ff5de69a..686f68c5d7 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -666,7 +666,7 @@ namespace MediaBrowser.Providers.MediaInfo return Array.Empty(); } - // Limit to 100 chapters just in case there's some incorrect metadata here + // Limit the chapters just in case there's some incorrect metadata here int chapterCount = (int)Math.Min(runtime / dummyChapterDuration, _config.Configuration.DummyChapterCount); var chapters = new ChapterInfo[chapterCount]; From 2036f58b184e7ae13a0e9564e44daed8d361fbeb Mon Sep 17 00:00:00 2001 From: "Negulici-R. Barnabas" Date: Mon, 14 Nov 2022 16:48:45 +0200 Subject: [PATCH 031/639] added associated value to ImageResolution enum; --- MediaBrowser.Model/Drawing/ImageResolution.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/MediaBrowser.Model/Drawing/ImageResolution.cs b/MediaBrowser.Model/Drawing/ImageResolution.cs index 99967da45e..5834fa4a03 100644 --- a/MediaBrowser.Model/Drawing/ImageResolution.cs +++ b/MediaBrowser.Model/Drawing/ImageResolution.cs @@ -8,46 +8,46 @@ namespace MediaBrowser.Model.Drawing /// /// MatchSource. /// - MatchSource, + MatchSource = 0, /// /// 144p. /// - P144, + P144 = 1, /// /// 240p. /// - P240, + P240 = 2, /// /// 360p. /// - P360, + P360 = 3, /// /// 480p. /// - P480, + P480 = 4, /// /// 720p. /// - P720, + P720 = 5, /// /// 1080p. /// - P1080, + P1080 = 6, /// /// 1440p. /// - P1440, + P1440 = 7, /// /// 2160p. /// - P2160 + P2160 = 8 } } From d3580f25daf7c9512e83c2f4f86494a454143738 Mon Sep 17 00:00:00 2001 From: "Negulici-R. Barnabas" Date: Mon, 14 Nov 2022 16:57:30 +0200 Subject: [PATCH 032/639] fixed namescope for ImageResolution enum; --- MediaBrowser.Model/Drawing/ImageResolution.cs | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/MediaBrowser.Model/Drawing/ImageResolution.cs b/MediaBrowser.Model/Drawing/ImageResolution.cs index 5834fa4a03..34738b7990 100644 --- a/MediaBrowser.Model/Drawing/ImageResolution.cs +++ b/MediaBrowser.Model/Drawing/ImageResolution.cs @@ -1,53 +1,52 @@ -namespace MediaBrowser.Model.Drawing +namespace MediaBrowser.Model.Drawing; + +/// +/// Enum ImageResolution. +/// +public enum ImageResolution { /// - /// Enum ImageResolution. + /// MatchSource. /// - public enum ImageResolution - { - /// - /// MatchSource. - /// - MatchSource = 0, + MatchSource = 0, - /// - /// 144p. - /// - P144 = 1, + /// + /// 144p. + /// + P144 = 1, - /// - /// 240p. - /// - P240 = 2, + /// + /// 240p. + /// + P240 = 2, - /// - /// 360p. - /// - P360 = 3, + /// + /// 360p. + /// + P360 = 3, - /// - /// 480p. - /// - P480 = 4, + /// + /// 480p. + /// + P480 = 4, - /// - /// 720p. - /// - P720 = 5, + /// + /// 720p. + /// + P720 = 5, - /// - /// 1080p. - /// - P1080 = 6, + /// + /// 1080p. + /// + P1080 = 6, - /// - /// 1440p. - /// - P1440 = 7, + /// + /// 1440p. + /// + P1440 = 7, - /// - /// 2160p. - /// - P2160 = 8 - } + /// + /// 2160p. + /// + P2160 = 8 } From 9c06001aee93c289fbf6871f74f4d72266ad6ddb Mon Sep 17 00:00:00 2001 From: TheBlueKingLP <12997043+TheBlueKingLP@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:39:21 +0300 Subject: [PATCH 033/639] Change the Translation of "Simplified Chinese" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the translation of "Simplified Chinese" from "汉语 (简化字)" to "汉语 (简体字)" --- Emby.Server.Implementations/Localization/LocalizationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 4eab040a4a..b771681266 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -434,7 +434,7 @@ namespace Emby.Server.Implementations.Localization yield return new LocalizationOption("Українська", "uk"); yield return new LocalizationOption("اُردُو", "ur_PK"); yield return new LocalizationOption("Tiếng Việt", "vi"); - yield return new LocalizationOption("汉语 (简化字)", "zh-CN"); + yield return new LocalizationOption("汉语 (简体字)", "zh-CN"); yield return new LocalizationOption("漢語 (繁體字)", "zh-TW"); yield return new LocalizationOption("廣東話 (香港)", "zh-HK"); } From 712a3b006397bcf1789c21bd9c9dc2e744ffac92 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 14 Nov 2022 11:34:38 +0000 Subject: [PATCH 034/639] 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 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index 232b3ec934..e1c937b6cd 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -123,5 +123,6 @@ "TaskKeyframeExtractorDescription": "Iš vaizdo įrašo paruošia reikšminius kadrus, kad būtų sukuriamas tikslenis HLS grojaraštis. Šios užduoties vykdymas gali ilgai užtrukti.", "TaskKeyframeExtractor": "Pagrindinių kadrų ištraukėjas", "TaskOptimizeDatabaseDescription": "Suspaudžia duomenų bazę ir atlaisvina vietą. Paleidžiant šią užduotį, po bibliotekos skenavimo arba kitų veiksmų kurie galimai modifikuoja duomenų bazė, gali pagerinti greitaveiką.", - "External": "Išorinis" + "External": "Išorinis", + "HearingImpaired": "Su klausos sutrikimais" } From 74e54825ed0397da4a2110894ed520b46de4fa84 Mon Sep 17 00:00:00 2001 From: 5h4d Date: Mon, 14 Nov 2022 15:24:55 +0000 Subject: [PATCH 035/639] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sk/ --- Emby.Server.Implementations/Localization/Core/sk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index 7502969a64..858cc40dd8 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -123,5 +123,6 @@ "TaskOptimizeDatabase": "Optimalizovať databázu", "TaskKeyframeExtractorDescription": "Extrahuje kľúčové snímky z video súborov na vytvorenie presnejších HLS playlistov. Táto úloha môže trvať dlhšiu dobu.", "TaskKeyframeExtractor": "Extraktor kľúčových snímkov", - "External": "Externé" + "External": "Externé", + "HearingImpaired": "Sluchovo Postihnutý" } From ce7a542c1f30b056cb72028a40b7c2924790784a Mon Sep 17 00:00:00 2001 From: Pavel Petrescu Date: Sun, 13 Nov 2022 11:12:17 +0000 Subject: [PATCH 036/639] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ro/ --- .../Localization/Core/ro.json | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json index 53456269af..2c10bb4779 100644 --- a/Emby.Server.Implementations/Localization/Core/ro.json +++ b/Emby.Server.Implementations/Localization/Core/ro.json @@ -11,7 +11,7 @@ "UserOfflineFromDevice": "{0} s-a deconectat de la {1}", "UserLockedOutWithName": "Utilizatorul {0} a fost blocat", "UserDownloadingItemWithValues": "{0} descarcă {1}", - "UserDeletedWithName": "Utilizatorul {0} a fost eliminat", + "UserDeletedWithName": "Utilizatorul {0} a fost șters", "UserCreatedWithName": "Utilizatorul {0} a fost creat", "User": "Utilizator", "TvShows": "Seriale TV", @@ -20,33 +20,33 @@ "SubtitleDownloadFailureFromForItem": "Subtitrările nu au putut fi descărcate de la {0} pentru {1}", "StartupEmbyServerIsLoading": "Se încarcă serverul Jellyfin. Încercați din nou în scurt timp.", "Songs": "Melodii", - "Shows": "Spectacole", - "ServerNameNeedsToBeRestarted": "{0} trebuie repornit", + "Shows": "Seriale", + "ServerNameNeedsToBeRestarted": "{0} trebuie să fie repornit", "ScheduledTaskStartedWithName": "{0} pornit/ă", "ScheduledTaskFailedWithName": "{0} eșuat/ă", "ProviderValue": "Furnizor: {0}", "PluginUpdatedWithName": "{0} a fost actualizat/ă", "PluginUninstalledWithName": "{0} a fost dezinstalat", "PluginInstalledWithName": "{0} a fost instalat", - "Plugin": "Plugin", - "Playlists": "Liste redare", + "Plugin": "Extensie", + "Playlists": "Liste de redare", "Photos": "Fotografii", "NotificationOptionVideoPlaybackStopped": "Redarea video oprită", "NotificationOptionVideoPlayback": "Redare video începută", "NotificationOptionUserLockedOut": "Utilizatorul a fost blocat", - "NotificationOptionTaskFailed": "Activitate programata eșuată", + "NotificationOptionTaskFailed": "Activitate programată eșuată", "NotificationOptionServerRestartRequired": "Este necesară repornirea serverului", - "NotificationOptionPluginUpdateInstalled": "Actualizare plugin instalată", - "NotificationOptionPluginUninstalled": "Plugin dezinstalat", - "NotificationOptionPluginInstalled": "Plugin instalat", - "NotificationOptionPluginError": "Plugin-ul a eșuat", - "NotificationOptionNewLibraryContent": "Adăugat conținut nou", - "NotificationOptionInstallationFailed": "Eșec la instalare", - "NotificationOptionCameraImageUploaded": "Încarcată imagine cameră", + "NotificationOptionPluginUpdateInstalled": "Actualizarea extensiei este instalată", + "NotificationOptionPluginUninstalled": "Extensie dezinstalată", + "NotificationOptionPluginInstalled": "Extensie instalată", + "NotificationOptionPluginError": "Eroare de extensie", + "NotificationOptionNewLibraryContent": "A fost adăugat conținut nou", + "NotificationOptionInstallationFailed": "Instalare eșuată", + "NotificationOptionCameraImageUploaded": "Imagine încarcată", "NotificationOptionAudioPlaybackStopped": "Redare audio oprită", "NotificationOptionAudioPlayback": "A început redarea audio", "NotificationOptionApplicationUpdateInstalled": "Actualizarea aplicației a fost instalată", - "NotificationOptionApplicationUpdateAvailable": "Disponibilă o actualizare a aplicației", + "NotificationOptionApplicationUpdateAvailable": "Este disponibilă o actualizare a aplicației", "NewVersionIsAvailable": "O nouă versiune a Jellyfin Server este disponibilă pentru descărcare.", "NameSeasonUnknown": "Sezon Necunoscut", "NameSeasonNumber": "Sezonul {0}", @@ -54,8 +54,8 @@ "MusicVideos": "Videoclipuri muzicale", "Music": "Muzică", "Movies": "Filme", - "MixedContent": "Conținut mixt", - "MessageServerConfigurationUpdated": "Configurația serverului a fost actualizată", + "MixedContent": "Conținut amestecat", + "MessageServerConfigurationUpdated": "Configurarea serverului a fost actualizată", "MessageNamedServerConfigurationUpdatedWithValue": "Secțiunea de configurare a serverului {0} a fost acualizata", "MessageApplicationUpdatedTo": "Jellyfin Server a fost actualizat la {0}", "MessageApplicationUpdated": "Jellyfin Server a fost actualizat", @@ -69,7 +69,7 @@ "HeaderRecordingGroups": "Grupuri de înregistrare", "HeaderLiveTV": "TV în Direct", "HeaderFavoriteSongs": "Melodii Favorite", - "HeaderFavoriteShows": "Spectacole Favorite", + "HeaderFavoriteShows": "Seriale TV Favorite", "HeaderFavoriteEpisodes": "Episoade Favorite", "HeaderFavoriteArtists": "Artiști Favoriți", "HeaderFavoriteAlbums": "Albume Favorite", @@ -97,10 +97,10 @@ "TaskRefreshChannels": "Actualizează canale", "TaskCleanTranscodeDescription": "Șterge fișierele de transcodare mai vechi de o zi.", "TaskCleanTranscode": "Curățați directorul de transcodare", - "TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru pluginuri care sunt configurate să se actualizeze automat.", - "TaskUpdatePlugins": "Actualizați plugin-uri", + "TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru extensiile care sunt configurate să se actualizeze automat.", + "TaskUpdatePlugins": "Actualizați Extensile", "TaskRefreshPeopleDescription": "Actualizează metadatele pentru actori și regizori din biblioteca media.", - "TaskRefreshPeople": "Actualizează oamenii", + "TaskRefreshPeople": "Actualizează Persoanele", "TaskCleanLogsDescription": "Șterge fișierele jurnal care au mai mult de {0} zile.", "TaskCleanLogs": "Curățare director jurnal", "TaskRefreshLibraryDescription": "Scanează biblioteca media pentru fișiere noi și reîmprospătează metadatele.", @@ -114,13 +114,14 @@ "TasksLibraryCategory": "Librărie", "TasksMaintenanceCategory": "Mentenanță", "TaskCleanActivityLogDescription": "Șterge intrările din jurnalul de activitate mai vechi de data configurată.", - "TaskCleanActivityLog": "Curăță Jurnalul de Activitate", + "TaskCleanActivityLog": "Curăță Jurnalul de Activități", "Undefined": "Nedefinit", "Forced": "Forțat", "Default": "Implicit", - "TaskOptimizeDatabaseDescription": "Compactează baza de date și trunchiază spațiul liber. Rularea acestei sarcini după scanarea bibliotecii sau după efectuarea altor modificări care implică modificări ale bazei de date poate îmbunătăți performanța.", + "TaskOptimizeDatabaseDescription": "Comprimă baza de date și trunchiază spațiul liber. Rularea acestei sarcini după scanarea bibliotecii sau după efectuarea altor modificări care implică modificări ale bazei de date poate îmbunătăți performanța.", "TaskOptimizeDatabase": "Optimizează baza de date", "TaskKeyframeExtractorDescription": "Extrage cadrele cheie din fișierele video pentru a crea liste de redare HLS mai precise. Această sarcină poate rula o perioadă lungă de timp.", "External": "Extern", - "TaskKeyframeExtractor": "Extractor de cadre cheie" + "TaskKeyframeExtractor": "Extractor de cadre cheie", + "HearingImpaired": "Ascultare Impară" } From 469b01e18e0a1aed92de9032d20e0afb76714dcb Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Sun, 13 Nov 2022 10:40:22 +0000 Subject: [PATCH 037/639] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index b9e2f1e6c2..44ce4ac5b2 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -122,5 +122,6 @@ "TaskOptimizeDatabase": "Tối ưu hóa cơ sở dữ liệu", "TaskKeyframeExtractor": "Trích Xuất Khung Hình", "TaskKeyframeExtractorDescription": "Trích xuất khung hình chính từ các tệp video để tạo danh sách phát HLS chính xác hơn. Tác vụ này có thể chạy trong một thời gian dài.", - "External": "Bên ngoài" + "External": "Bên ngoài", + "HearingImpaired": "Khiếm Thính" } From 072651c4be3914f0ffb5e0be8f57e714d4303fe1 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 15 Apr 2022 19:27:38 +0200 Subject: [PATCH 038/639] Add xmldocs for TMDb provider, correct provider spelling --- .../Library/LibraryManager.cs | 4 +- .../Library/Resolvers/Movies/MovieResolver.cs | 4 +- Jellyfin.Api/Controllers/ItemsController.cs | 16 +++--- Jellyfin.Api/Controllers/MoviesController.cs | 20 +++---- .../Controllers/TrailersController.cs | 8 +-- .../Providers/ProviderIdParsers.cs | 4 +- .../Entities/Movies/Movie.cs | 4 +- .../Providers/IHasOrder.cs | 9 +++- .../Providers/IRemoteMetadataProvider.cs | 23 +++++++- .../Entities/MetadataProvider.cs | 52 ++++++++++++++++--- MediaBrowser.Model/Querying/ItemFields.cs | 2 +- .../Manager/ProviderManager.cs | 2 +- .../Plugins/Tmdb/Api/TmdbController.cs | 2 +- .../Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 2 +- .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 16 +++++- .../Tmdb/BoxSets/TmdbBoxSetProvider.cs | 15 +++++- .../Tmdb/Movies/TmdbMovieExternalId.cs | 2 +- .../Tmdb/Movies/TmdbMovieImageProvider.cs | 16 +++++- .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 19 ++++--- .../Tmdb/People/TmdbPersonExternalId.cs | 2 +- .../Tmdb/People/TmdbPersonImageProvider.cs | 14 ++++- .../Plugins/Tmdb/People/TmdbPersonProvider.cs | 14 ++++- .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 27 +++++++--- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 16 ++++-- .../Tmdb/TV/TmdbSeasonImageProvider.cs | 45 +++++++++++----- .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 14 ++++- .../Plugins/Tmdb/TV/TmdbSeriesExternalId.cs | 2 +- .../Tmdb/TV/TmdbSeriesImageProvider.cs | 17 ++++-- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 17 ++++-- .../Plugins/Tmdb/TmdbUtils.cs | 23 ++++---- .../Parsers/BaseNfoParser.cs | 26 +++++----- 31 files changed, 316 insertions(+), 121 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index cef82ebbcc..b688af5286 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2590,9 +2590,9 @@ namespace Emby.Server.Implementations.Library { /* Anime series don't generally have a season in their file name, however, - tvdb needs a season to correctly get the metadata. + TVDb needs a season to correctly get the metadata. Hence, a null season needs to be filled with something. */ - // FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified + // FIXME perhaps this would be better for TVDb parser to ask for season 1 if no season is specified episode.ParentIndexNumber = 1; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 8f9e5f01b1..d6ae8aba85 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 if (!justName.IsEmpty) { - // check for tmdb id + // Check for TMDb id var tmdbid = justName.GetAttributeValue("tmdbid"); if (!string.IsNullOrWhiteSpace(tmdbid)) @@ -387,7 +387,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (!string.IsNullOrEmpty(item.Path)) { - // check for imdb id - we use full media path, as we can assume, that this will match in any use case (either id in parent dir or in file name) + // Check for IMDb id - we use full media path, as we can assume that this will match in any use case (wither id in parent dir or in file name) var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid"); if (!string.IsNullOrWhiteSpace(imdbid)) diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 33b67b3898..3ee5b8d737 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -87,9 +87,9 @@ namespace Jellyfin.Api.Controllers /// Optional. The minimum last saved date for the current user. Format = ISO. /// Optional. The maximum premiere date. Format = ISO. /// Optional filter by items that have an overview or not. - /// Optional filter by items that have an imdb id or not. - /// Optional filter by items that have a tmdb id or not. - /// Optional filter by items that have a tvdb id or not. + /// Optional filter by items that have an IMDb id or not. + /// Optional filter by items that have a TMDb id or not. + /// Optional filter by items that have a TVDb id or not. /// Optional filter for live tv movies. /// Optional filter for live tv series. /// Optional filter for live tv news. @@ -100,7 +100,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// When searching within folders, this determines whether or not the search will be recursive. true/false. /// Optional. Filter based on a search term. - /// Sort Order - Ascending,Descending. + /// Sort Order - Ascending, Descending. /// Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. @@ -536,9 +536,9 @@ namespace Jellyfin.Api.Controllers /// Optional. The minimum last saved date for the current user. Format = ISO. /// Optional. The maximum premiere date. Format = ISO. /// Optional filter by items that have an overview or not. - /// Optional filter by items that have an imdb id or not. - /// Optional filter by items that have a tmdb id or not. - /// Optional filter by items that have a tvdb id or not. + /// Optional filter by items that have an IMDb id or not. + /// Optional filter by items that have a TMDb id or not. + /// Optional filter by items that have a TVDb id or not. /// Optional filter for live tv movies. /// Optional filter for live tv series. /// Optional filter for live tv news. @@ -549,7 +549,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// When searching within folders, this determines whether or not the search will be recursive. true/false. /// Optional. Filter based on a search term. - /// Sort Order - Ascending,Descending. + /// Sort Order - Ascending, Descending. /// Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index 8195fc7609..03f864b4a7 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers new InternalItemsQuery(user) { Person = name, - // Account for duplicates by imdb id, since the database doesn't support this yet + // Account for duplicates by IMDb id, since the database doesn't support this yet Limit = itemLimit + 2, PersonTypes = new[] { PersonType.Director }, IncludeItemTypes = itemTypes.ToArray(), @@ -232,15 +232,15 @@ namespace Jellyfin.Api.Controllers foreach (var name in names) { var items = _libraryManager.GetItemList(new InternalItemsQuery(user) - { - Person = name, - // Account for duplicates by imdb id, since the database doesn't support this yet - Limit = itemLimit + 2, - IncludeItemTypes = itemTypes.ToArray(), - IsMovie = true, - EnableGroupByMetadataKey = true, - DtoOptions = dtoOptions - }).GroupBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) + { + Person = name, + // Account for duplicates by IMDb id, since the database doesn't support this yet + Limit = itemLimit + 2, + IncludeItemTypes = itemTypes.ToArray(), + IsMovie = true, + EnableGroupByMetadataKey = true, + DtoOptions = dtoOptions + }).GroupBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) .Select(x => x.First()) .Take(itemLimit) .ToList(); diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index b296d1c960..53a839e431 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -55,9 +55,9 @@ namespace Jellyfin.Api.Controllers /// Optional. The minimum last saved date for the current user. Format = ISO. /// Optional. The maximum premiere date. Format = ISO. /// Optional filter by items that have an overview or not. - /// Optional filter by items that have an imdb id or not. - /// Optional filter by items that have a tmdb id or not. - /// Optional filter by items that have a tvdb id or not. + /// Optional filter by items that have an IMDb id or not. + /// Optional filter by items that have a TMDb id or not. + /// Optional filter by items that have a TVDb id or not. /// Optional filter for live tv movies. /// Optional filter for live tv series. /// Optional filter for live tv news. @@ -68,7 +68,7 @@ namespace Jellyfin.Api.Controllers /// Optional. The maximum number of records to return. /// When searching within folders, this determines whether or not the search will be recursive. true/false. /// Optional. Filter based on a search term. - /// Sort Order - Ascending,Descending. + /// Sort Order - Ascending, Descending. /// Specify this to localize the search to a specific item or folder. Omit to use the root. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs index 487b5a6d29..d569167b1d 100644 --- a/MediaBrowser.Common/Providers/ProviderIdParsers.cs +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Common.Providers /// True if parsing was successful, false otherwise. public static bool TryFindImdbId(ReadOnlySpan text, out ReadOnlySpan imdbId) { - // imdb id is at least 9 chars (tt + 7 numbers) + // IMDb id is at least 9 chars (tt + 7 numbers) while (text.Length >= 2 + ImdbMinNumbers) { var ttPos = text.IndexOf(ImdbPrefix); @@ -42,7 +42,7 @@ namespace MediaBrowser.Common.Providers } } - // skip if more than 8 digits + 2 chars for tt + // Skip if more than 8 digits + 2 chars for tt if (i <= ImdbMaxNumbers + 2 && i >= ImdbMinNumbers + 2) { imdbId = text.Slice(0, i); diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 77e70f8fbd..3c12acd90d 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -33,9 +33,9 @@ namespace MediaBrowser.Controller.Entities.Movies .ToArray(); /// - /// Gets or sets the name of the TMDB collection. + /// Gets or sets the name of the TMDb collection. /// - /// The name of the TMDB collection. + /// The name of the TMDb collection. public string TmdbCollectionName { get; set; } [JsonIgnore] diff --git a/MediaBrowser.Controller/Providers/IHasOrder.cs b/MediaBrowser.Controller/Providers/IHasOrder.cs index 9fde0e6958..77b0407a20 100644 --- a/MediaBrowser.Controller/Providers/IHasOrder.cs +++ b/MediaBrowser.Controller/Providers/IHasOrder.cs @@ -1,9 +1,14 @@ -#pragma warning disable CS1591 - namespace MediaBrowser.Controller.Providers { + /// + /// Interface IHasOrder. + /// public interface IHasOrder { + /// + /// Gets the order. + /// + /// The order. int Order { get; } } } diff --git a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs index f146decb60..2c943d9e77 100644 --- a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8,20 +6,41 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Controller.Providers { + /// + /// Interface IRemoteMetadataProvider. + /// public interface IRemoteMetadataProvider : IMetadataProvider { } + /// + /// Interface IRemoteMetadataProvider. + /// public interface IRemoteMetadataProvider : IMetadataProvider, IRemoteMetadataProvider, IRemoteSearchProvider where TItemType : BaseItem, IHasLookupInfo where TLookupInfoType : ItemLookupInfo, new() { + /// + /// Gets the metadata for a specific LookupInfoType. + /// + /// The LookupInfoType to get metadata for. + /// The . + /// Task{MetadataResult{TItemType}}. Task> GetMetadata(TLookupInfoType info, CancellationToken cancellationToken); } + /// + /// Interface IRemoteMetadataProvider. + /// public interface IRemoteSearchProvider : IRemoteSearchProvider where TLookupInfoType : ItemLookupInfo { + /// + /// Gets the list of for a specific LookupInfoType. + /// + /// The LookupInfoType to search for. + /// The . + /// Task{IEnumerable{RemoteSearchResult}}. Task> GetSearchResults(TLookupInfoType searchInfo, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs index 37e3d88645..a34bbd3c8f 100644 --- a/MediaBrowser.Model/Entities/MetadataProvider.cs +++ b/MediaBrowser.Model/Entities/MetadataProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - namespace MediaBrowser.Model.Entities { /// @@ -14,38 +12,78 @@ namespace MediaBrowser.Model.Entities Custom = 0, /// - /// The imdb. + /// The IMDb id. /// Imdb = 2, /// - /// The TMDB. + /// The TMDb id. /// Tmdb = 3, /// - /// The TVDB. + /// The TVDb id. /// Tvdb = 4, /// - /// The tvcom. + /// The tvcom id. /// Tvcom = 5, /// - /// Tmdb Collection Id. + /// TMDb collection id. /// TmdbCollection = 7, + + /// + /// The MusicBrainz album id. + /// MusicBrainzAlbum = 8, + + /// + /// The MusicBrainz album artist id. + /// MusicBrainzAlbumArtist = 9, + + /// + /// The MusicBrainz artist id. + /// MusicBrainzArtist = 10, + + /// + /// The MusicBrainz release group id. + /// MusicBrainzReleaseGroup = 11, + + /// + /// The Zap2It id. + /// Zap2It = 12, + + /// + /// The TvRage id. + /// TvRage = 15, + + /// + /// The AudioDb artist id. + /// AudioDbArtist = 16, + + /// + /// The AudioDb collection id. + /// AudioDbAlbum = 17, + + /// + /// The MusicBrainz track id. + /// MusicBrainzTrack = 18, + + /// + /// The TvMaze id. + /// TvMaze = 19 } } diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index e6c3a6c260..6fa1d778ad 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Model.Querying ProductionLocations, /// - /// Imdb, tmdb, etc. + /// The ids from IMDb, TMDb, etc. /// ProviderIds, diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index bbb33ddf0e..552ded0c45 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -183,7 +183,7 @@ namespace MediaBrowser.Providers.Manager } } - // thetvdb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons... + // TVDb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons... if (contentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase)) { throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs index 0bab7c3cad..ac3df1d5d6 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs @@ -8,7 +8,7 @@ using TMDbLib.Objects.General; namespace MediaBrowser.Providers.Plugins.Tmdb.Api { /// - /// The TMDb api controller. + /// The TMDb API controller. /// [ApiController] [Authorize(Policy = "DefaultAuthorization")] diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index 3217ac2f13..0e768bb832 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { /// - /// External ID for a TMDB box set. + /// External id for a TMDb box set. /// public class TmdbBoxSetExternalId : IExternalId { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index 29a557c315..ef878e6707 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -18,26 +16,38 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { + /// + /// BoxSet image provider powered by TMDb. + /// public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// public string Name => TmdbUtils.ProviderName; + /// public int Order => 0; + /// public bool Supports(BaseItem item) { return item is BoxSet; } + /// public IEnumerable GetSupportedImages(BaseItem item) { return new List @@ -47,6 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets }; } + /// public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { var tmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); @@ -76,6 +87,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets return remoteImages; } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs index 62bc9c65f9..90f2aa88f7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -18,12 +16,21 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { + /// + /// BoxSet provider powered by TMDb. + /// public class TmdbBoxSetProvider : IRemoteMetadataProvider { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; private readonly ILibraryManager _libraryManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager, ILibraryManager libraryManager) { _httpClientFactory = httpClientFactory; @@ -31,8 +38,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets _libraryManager = libraryManager; } + /// public string Name => TmdbUtils.ProviderName; + /// public async Task> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken) { var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); @@ -81,6 +90,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets return collections; } + /// public async Task> GetMetadata(BoxSetInfo info, CancellationToken cancellationToken) { var tmdbId = Convert.ToInt32(info.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); @@ -124,6 +134,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets return result; } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs index 31310a8d41..38d2c5c69a 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { /// - /// External ID for a TMBD movie. + /// External id for a TMDb movie. /// public class TmdbMovieExternalId : IExternalId { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs index 16f0089f8f..1646a93d22 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -19,26 +17,38 @@ using TMDbLib.Objects.Find; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { + /// + /// Movie image provider powered by TMDb. + /// public class TmdbMovieImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . public TmdbMovieImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// public int Order => 0; + /// public string Name => TmdbUtils.ProviderName; + /// public bool Supports(BaseItem item) { return item is Movie || item is Trailer; } + /// public IEnumerable GetSupportedImages(BaseItem item) { return new List @@ -49,6 +59,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies }; } + /// public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { var language = item.GetPreferredMetadataLanguage(); @@ -96,6 +107,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies return remoteImages; } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index f14f31858c..dd2d5d97d9 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -23,7 +21,7 @@ using TMDbLib.Objects.Search; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { /// - /// Class MovieDbProvider. + /// Movie provider powered by TMDb. /// public class TmdbMovieProvider : IRemoteMetadataProvider, IHasOrder { @@ -31,6 +29,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies private readonly ILibraryManager _libraryManager; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . public TmdbMovieProvider( ILibraryManager libraryManager, TmdbClientManager tmdbClientManager, @@ -41,11 +45,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies _httpClientFactory = httpClientFactory; } - public string Name => TmdbUtils.ProviderName; - /// public int Order => 1; + /// + public string Name => TmdbUtils.ProviderName; + + /// public async Task> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken) { if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var id)) @@ -133,6 +139,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies return remoteSearchResults; } + /// public async Task> GetMetadata(MovieInfo info, CancellationToken cancellationToken) { var tmdbId = info.GetProviderId(MetadataProvider.Tmdb); @@ -144,7 +151,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies // Caller provides the filename with extension stripped and NOT the parsed filename var parsedName = _libraryManager.ParseName(info.Name); var cleanedName = TmdbUtils.CleanName(parsedName.Name); - var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); if (searchResults.Count > 0) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs index 9804d60bdd..027399aec2 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.People { /// - /// External ID for a TMDB person. + /// External id for a TMDb person. /// public class TmdbPersonExternalId : IExternalId { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index 7ce4cfe676..d7f5c99dd2 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -14,11 +12,19 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.People { + /// + /// Person image provider powered by TMDb. + /// public class TmdbPersonImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . public TmdbPersonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; @@ -31,11 +37,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People /// public int Order => 0; + /// public bool Supports(BaseItem item) { return item is Person; } + /// public IEnumerable GetSupportedImages(BaseItem item) { return new List @@ -44,6 +52,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People }; } + /// public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { var person = (Person)item; @@ -68,6 +77,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People return remoteImages; } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 8790e37592..d760ad1426 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -16,19 +14,29 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.People { + /// + /// Person image provider powered by TMDb. + /// public class TmdbPersonProvider : IRemoteMetadataProvider { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . public TmdbPersonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// public string Name => TmdbUtils.ProviderName; + /// public async Task> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken) { if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId)) @@ -79,6 +87,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People return remoteSearchResults; } + /// public async Task> GetMetadata(PersonLookupInfo info, CancellationToken cancellationToken) { var personTmdbId = Convert.ToInt32(info.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); @@ -131,6 +140,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People return result; } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index 5eec776b5b..e568bc4d3d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -17,22 +15,38 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// + /// TV episode iage provider powered by TheMovieDb. + /// public class TmdbEpisodeImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } - // After TheTvDb + /// public int Order => 1; + /// public string Name => TmdbUtils.ProviderName; + /// + public bool Supports(BaseItem item) + { + return item is Controller.Entities.TV.Episode; + } + + /// public IEnumerable GetSupportedImages(BaseItem item) { return new List @@ -41,6 +55,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV }; } + /// public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { var episode = (Controller.Entities.TV.Episode)item; @@ -81,14 +96,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return remoteImages; } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } - - public bool Supports(BaseItem item) - { - return item is Controller.Entities.TV.Episode; - } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index f50f158772..e20284e6f8 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -19,22 +17,32 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// + /// TV episode provider powered by TheMovieDb. + /// public class TmdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . public TmdbEpisodeProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } - // After TheTvDb + /// public int Order => 1; + /// public string Name => TmdbUtils.ProviderName; + /// public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) { // The search query must either provide an episode number or date @@ -68,6 +76,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV }; } + /// public async Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) { var metadataResult = new MetadataResult(); @@ -209,6 +218,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return metadataResult; } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index 4446fa9665..dea89f1d2c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -16,26 +14,52 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// + /// TV season image provider powered by TheMovieDb. + /// public class TmdbSeasonImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + public TmdbSeasonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// + /// The order. + /// public int Order => 1; + /// + /// The name. + /// public string Name => TmdbUtils.ProviderName; - public Task GetImageResponse(string url, CancellationToken cancellationToken) + /// + public bool Supports(BaseItem item) { - return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); + return item is Season; } + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + return new List + { + ImageType.Primary + }; + } + + /// public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { var season = (Season)item; @@ -68,17 +92,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return remoteImages; } - public IEnumerable GetSupportedImages(BaseItem item) + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) { - return new List - { - ImageType.Primary - }; - } - - public bool Supports(BaseItem item) - { - return item is Season; + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 64ed3f408d..2cf0f399e0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -17,19 +15,29 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// + /// TV season provider powered by TheMovieDb. + /// public class TmdbSeasonProvider : IRemoteMetadataProvider { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . public TmdbSeasonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// public string Name => TmdbUtils.ProviderName; + /// public async Task> GetMetadata(SeasonInfo info, CancellationToken cancellationToken) { var result = new MetadataResult(); @@ -114,11 +122,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return result; } + /// public Task> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken) { return Task.FromResult(Enumerable.Empty()); } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs index 8a2be80cde..df04cb2e73 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { /// - /// External ID for a TMDB series. + /// External id for a TMDb series. /// public class TmdbSeriesExternalId : IExternalId { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index 130d6ce448..e96b680b48 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -16,27 +14,38 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// + /// TV series image provider powered by TheMovieDb. + /// public class TmdbSeriesImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . public TmdbSeriesImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// public string Name => TmdbUtils.ProviderName; - // After tvdb and fanart + /// public int Order => 2; + /// public bool Supports(BaseItem item) { return item is Series; } + /// public IEnumerable GetSupportedImages(BaseItem item) { return new List @@ -47,6 +56,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV }; } + /// public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { var tmdbId = item.GetProviderId(MetadataProvider.Tmdb); @@ -80,6 +90,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return remoteImages; } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 4d26052faf..4e8fdf0ee8 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -23,12 +21,21 @@ using TMDbLib.Objects.TvShows; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// + /// TV series provider powered by TheMovieDb. + /// public class TmdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryManager _libraryManager; private readonly TmdbClientManager _tmdbClientManager; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . public TmdbSeriesProvider( ILibraryManager libraryManager, IHttpClientFactory httpClientFactory, @@ -39,11 +46,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV _tmdbClientManager = tmdbClientManager; } + /// public string Name => TmdbUtils.ProviderName; - // After TheTVDB + /// public int Order => 1; + /// public async Task> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) { if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var tmdbId)) @@ -159,6 +168,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return remoteResult; } + /// public async Task> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) { var result = new MetadataResult @@ -383,6 +393,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 685eb222f7..44c2c81f44 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb private static readonly Regex _nonWords = new(@"[\W_]+", RegexOptions.Compiled); /// - /// URL of the TMDB instance to use. + /// URL of the TMDb instance to use. /// public const string BaseTmdbUrl = "https://www.themoviedb.org/"; @@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } /// - /// Maps the TMDB provided roles for crew members to Jellyfin roles. + /// Maps the TMDb provided roles for crew members to Jellyfin roles. /// /// Crew member to map against the Jellyfin person types. /// The Jellyfin person type. @@ -103,9 +103,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb languages.Add(preferredLanguage); - if (preferredLanguage.Length == 5) // like en-US + if (preferredLanguage.Length == 5) // Like en-US { - // Currently, TMDB supports 2-letter language codes only + // Currently, TMDb supports 2-letter language codes only. // They are planning to change this in the future, thus we're // supplying both codes if we're having a 5-letter code. languages.Add(preferredLanguage.Substring(0, 2)); @@ -114,6 +114,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb languages.Add("null"); + // Always add English as fallback language if (!string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase)) { languages.Add("en"); @@ -134,14 +135,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return language; } - // They require this to be uppercase - // Everything after the hyphen must be written in uppercase due to a way TMDB wrote their api. + // TMDb requires this to be uppercase + // Everything after the hyphen must be written in uppercase due to a way TMDb wrote their API. // See here: https://www.themoviedb.org/talk/5119221d760ee36c642af4ad?page=3#56e372a0c3a3685a9e0019ab var parts = language.Split('-'); 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 + // 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.OrdinalIgnoreCase)) { return parts[0]; @@ -174,14 +175,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } /// - /// Combines the metadata country code and the parental rating from the Api into the value we store in our database. + /// 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 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. + // 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; diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index da348239a1..0d03876f24 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -170,7 +170,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers ParseProviderLinks(item.Item, endingXml); - // If the file is just an imdb url, don't go any further + // If the file is just an IMDb url, don't go any further if (index == 0) { return; @@ -1136,21 +1136,21 @@ namespace MediaBrowser.XbmcMetadata.Parsers switch (reader.Name) { case "rating": - { - if (reader.IsEmptyElement) { - reader.Read(); - continue; + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + + var ratingName = reader.GetAttribute("name"); + + using var subtree = reader.ReadSubtree(); + FetchFromRatingNode(subtree, item, ratingName); + + break; } - var ratingName = reader.GetAttribute("name"); - - using var subtree = reader.ReadSubtree(); - FetchFromRatingNode(subtree, item, ratingName); - - break; - } - default: reader.Skip(); break; From 4b1654ae3bb2977e1ec1978a3ea07d6f2e96b477 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 15 Apr 2022 20:10:37 +0200 Subject: [PATCH 039/639] Add xmldocs for studio image provider --- .../Configuration/PluginConfiguration.cs | 10 ++++--- .../Plugins/StudioImages/Plugin.cs | 24 ++++++++++++++++- .../StudioImages/StudiosImageProvider.cs | 27 +++++++++++++++++-- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs index cb422ef3d6..0bfab98245 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs @@ -1,13 +1,17 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Plugins; namespace MediaBrowser.Providers.Plugins.StudioImages.Configuration { + /// + /// Plugin configuration class for the studio image provider. + /// public class PluginConfiguration : BasePluginConfiguration { private string _repository = Plugin.DefaultServer; + /// + /// Gets or sets the studio image repository URL. + /// public string RepositoryUrl { get diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs index 5e653d039f..f5ea6d103d 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs @@ -1,5 +1,4 @@ #nullable disable -#pragma warning disable CS1591 using System; using System.Collections.Generic; @@ -11,27 +10,50 @@ using MediaBrowser.Providers.Plugins.StudioImages.Configuration; namespace MediaBrowser.Providers.Plugins.StudioImages { + /// + /// Artwork Plugin class. + /// public class Plugin : BasePlugin, IHasWebPages { + /// + /// Artwork repository URL. + /// public const string DefaultServer = "https://raw.github.com/jellyfin/emby-artwork/master/studios"; + /// + /// Initializes a new instance of the class. + /// + /// application paths. + /// xml serializer. public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { Instance = this; } + /// + /// Gets the instance of Artwork plugin. + /// public static Plugin Instance { get; private set; } + /// public override Guid Id => new Guid("872a7849-1171-458d-a6fb-3de3d442ad30"); + /// public override string Name => "Studio Images"; + /// public override string Description => "Get artwork for studios from any Jellyfin-compatible repository."; // TODO remove when plugin removed from server. + + /// public override string ConfigurationFileName => "Jellyfin.Plugin.StudioImages.xml"; + /// + /// Return the plugin configuration page. + /// + /// PluginPageInfo. public IEnumerable GetPages() { yield return new PluginPageInfo diff --git a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs index ef822a22ad..88bbdadb46 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -21,12 +19,21 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.StudioImages { + /// + /// Studio image provider. + /// public class StudiosImageProvider : IRemoteImageProvider { private readonly IServerConfigurationManager _config; private readonly IHttpClientFactory _httpClientFactory; private readonly IFileSystem _fileSystem; + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem) { _config = config; @@ -34,13 +41,16 @@ namespace MediaBrowser.Providers.Plugins.StudioImages _fileSystem = fileSystem; } + /// public string Name => "Artwork Repository"; + /// public bool Supports(BaseItem item) { return item is Studio; } + /// public IEnumerable GetSupportedImages(BaseItem item) { return new List @@ -49,6 +59,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages }; } + /// public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudiothumbs.txt"); @@ -103,6 +114,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages return EnsureList(url, file, _fileSystem, cancellationToken); } + /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); @@ -134,6 +146,12 @@ namespace MediaBrowser.Providers.Plugins.StudioImages return file; } + /// + /// Get matching image for an item. + /// + /// The . + /// The enumerable of image strings. + /// String. public string FindMatch(BaseItem item, IEnumerable images) { var name = GetComparableName(item.Name); @@ -151,6 +169,11 @@ namespace MediaBrowser.Providers.Plugins.StudioImages .Replace("/", string.Empty, StringComparison.Ordinal); } + /// + /// Get available images for a file. + /// + /// The file. + /// IEnumerable{string}. public IEnumerable GetAvailableImages(string file) { using var fileStream = File.OpenRead(file); From 2e639c77c73439901abf64fa3439191f181b0b60 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Wed, 27 Apr 2022 13:08:54 +0200 Subject: [PATCH 040/639] Apply review suggestions --- .../Library/Resolvers/Movies/MovieResolver.cs | 2 +- .../Providers/IRemoteMetadataProvider.cs | 4 +- .../Entities/MetadataProvider.cs | 30 +++++----- .../Plugins/StudioImages/Plugin.cs | 5 +- .../StudioImages/StudiosImageProvider.cs | 10 ++-- .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 2 +- .../Tmdb/TV/TmdbSeasonImageProvider.cs | 9 +-- .../Parsers/BaseNfoParser.cs | 55 ++++++++++++------- 8 files changed, 62 insertions(+), 55 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index d6ae8aba85..84d4688aff 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -387,7 +387,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (!string.IsNullOrEmpty(item.Path)) { - // Check for IMDb id - we use full media path, as we can assume that this will match in any use case (wither id in parent dir or in file name) + // Check for IMDb id - we use full media path, as we can assume that this will match in any use case (whether id in parent dir or in file name) var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid"); if (!string.IsNullOrWhiteSpace(imdbid)) diff --git a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs index 2c943d9e77..888ca6c72c 100644 --- a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Providers /// /// The LookupInfoType to get metadata for. /// The . - /// Task{MetadataResult{TItemType}}. + /// A task returning a MetadataResult for the specific LookupInfoType. Task> GetMetadata(TLookupInfoType info, CancellationToken cancellationToken); } @@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers /// /// The LookupInfoType to search for. /// The . - /// Task{IEnumerable{RemoteSearchResult}}. + /// A task returning RemoteSearchResults for the searchInfo. Task> GetSearchResults(TLookupInfoType searchInfo, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs index a34bbd3c8f..bd8db99416 100644 --- a/MediaBrowser.Model/Entities/MetadataProvider.cs +++ b/MediaBrowser.Model/Entities/MetadataProvider.cs @@ -12,77 +12,77 @@ namespace MediaBrowser.Model.Entities Custom = 0, /// - /// The IMDb id. + /// The IMDb provider. /// Imdb = 2, /// - /// The TMDb id. + /// The TMDb provider. /// Tmdb = 3, /// - /// The TVDb id. + /// The TVDb provider. /// Tvdb = 4, /// - /// The tvcom id. + /// The tvcom providerd. /// Tvcom = 5, /// - /// TMDb collection id. + /// TMDb collection provider. /// TmdbCollection = 7, /// - /// The MusicBrainz album id. + /// The MusicBrainz album provider. /// MusicBrainzAlbum = 8, /// - /// The MusicBrainz album artist id. + /// The MusicBrainz album artist provider. /// MusicBrainzAlbumArtist = 9, /// - /// The MusicBrainz artist id. + /// The MusicBrainz artist provider. /// MusicBrainzArtist = 10, /// - /// The MusicBrainz release group id. + /// The MusicBrainz release group provider. /// MusicBrainzReleaseGroup = 11, /// - /// The Zap2It id. + /// The Zap2It provider. /// Zap2It = 12, /// - /// The TvRage id. + /// The TvRage provider. /// TvRage = 15, /// - /// The AudioDb artist id. + /// The AudioDb artist provider. /// AudioDbArtist = 16, /// - /// The AudioDb collection id. + /// The AudioDb collection provider. /// AudioDbAlbum = 17, /// - /// The MusicBrainz track id. + /// The MusicBrainz track provider. /// MusicBrainzTrack = 18, /// - /// The TvMaze id. + /// The TvMaze provider. /// TvMaze = 19 } diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs index f5ea6d103d..78150153ad 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs @@ -50,10 +50,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages /// public override string ConfigurationFileName => "Jellyfin.Plugin.StudioImages.xml"; - /// - /// Return the plugin configuration page. - /// - /// PluginPageInfo. + /// public IEnumerable GetPages() { yield return new PluginPageInfo diff --git a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs index 88bbdadb46..ffbb338e84 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs @@ -122,13 +122,13 @@ namespace MediaBrowser.Providers.Plugins.StudioImages } /// - /// Ensures the list. + /// Ensures the existence of a file listing. /// /// The URL. /// The file. /// The file system. /// The cancellation token. - /// Task. + /// A Task to ensure existence of a file listing. public async Task EnsureList(string url, string file, IFileSystem fileSystem, CancellationToken cancellationToken) { var fileInfo = fileSystem.GetFileInfo(file); @@ -151,7 +151,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages /// /// The . /// The enumerable of image strings. - /// String. + /// The matching image string. public string FindMatch(BaseItem item, IEnumerable images) { var name = GetComparableName(item.Name); @@ -170,10 +170,10 @@ namespace MediaBrowser.Providers.Plugins.StudioImages } /// - /// Get available images for a file. + /// Get available image strings for a file. /// /// The file. - /// IEnumerable{string}. + /// All images strings of a file. public IEnumerable GetAvailableImages(string file) { using var fileStream = File.OpenRead(file); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index e568bc4d3d..943a3a75b2 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -16,7 +16,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { /// - /// TV episode iage provider powered by TheMovieDb. + /// TV episode image provider powered by TheMovieDb. /// public class TmdbEpisodeImageProvider : IRemoteImageProvider, IHasOrder { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index dea89f1d2c..da32ea4081 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -27,21 +27,16 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV /// /// The . /// The . - public TmdbSeasonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } - /// - /// The order. - /// + /// public int Order => 1; - /// - /// The name. - /// + /// public string Name => TmdbUtils.ProviderName; /// diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 0d03876f24..9e197e737b 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -23,6 +21,10 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers { + /// + /// The BaseNfoParser class. + /// + /// The type. public class BaseNfoParser where T : BaseItem { @@ -63,16 +65,22 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// protected ILogger Logger { get; } + /// + /// Gets the provider manager. + /// protected IProviderManager ProviderManager { get; } + /// + /// Gets a value indicating whether URLs after a closing XML tag are supporrted. + /// protected virtual bool SupportsUrlAfterClosingXmlTag => false; /// /// Fetches metadata for an item from one xml file. /// - /// The item. + /// The . /// The metadata file. - /// The cancellation token. + /// The . /// item is null. /// metadataFile is null or empty. public void Fetch(MetadataResult item, string metadataFile, CancellationToken cancellationToken) @@ -111,10 +119,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// /// Fetches the specified item. /// - /// The item. + /// The . /// The metadata file. - /// The settings. - /// The cancellation token. + /// The . + /// The . protected virtual void Fetch(MetadataResult item, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken) { if (!SupportsUrlAfterClosingXmlTag) @@ -216,6 +224,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } + /// + /// Parses a XML tag to a provider id. + /// + /// The item. + /// The xml tag. protected void ParseProviderLinks(T item, ReadOnlySpan xml) { if (ProviderIdParsers.TryFindImdbId(xml, out var imdbId)) @@ -245,6 +258,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } + /// + /// Fetches metadata from an XML node. + /// + /// The . + /// The . protected virtual void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) { var item = itemResult.Item; @@ -1100,17 +1118,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers switch (reader.Name) { case "language": + _ = reader.ReadElementContentAsString(); + if (item is Video video) { - _ = reader.ReadElementContentAsString(); - - if (item is Video video) - { - video.HasSubtitles = true; - } - - break; + video.HasSubtitles = true; } + break; + default: reader.Skip(); break; @@ -1210,9 +1225,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers } /// - /// Gets the persons from XML node. + /// Gets the persons from a XML node. /// - /// The reader. + /// The . /// IEnumerable{PersonInfo}. private PersonInfo GetPersonFromXmlNode(XmlReader reader) { @@ -1348,10 +1363,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers } /// - /// Parses the ImageType from the nfo aspect property. + /// Parses the from the NFO aspect property. /// - /// The nfo aspect property. - /// The image type. + /// The NFO aspect property. + /// The . private static ImageType GetImageType(string aspect) { return aspect switch From b5f9a093ddf2460d42f5910fd741dd2c57a47b61 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Sat, 19 Nov 2022 08:12:24 -0600 Subject: [PATCH 041/639] Don't cancel DVR recordings when adjusting settings (#8752) Fixes https://github.com/jellyfin/jellyfin/issues/3523 --- .../LiveTv/EmbyTV/EmbyTV.cs | 42 ++++--------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 74321a256e..a0ae328a47 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2201,7 +2201,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private void SearchForDuplicateShowIds(List timers) + private void SearchForDuplicateShowIds(IEnumerable timers) { var groups = timers.ToLookup(i => i.ShowId ?? string.Empty).ToList(); @@ -2282,39 +2282,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (updateTimerSettings) { - // Only update if not currently active - test both new timer and existing in case Id's are different - // Id's could be different if the timer was created manually prior to series timer creation - if (!_activeRecordings.TryGetValue(timer.Id, out _) && !_activeRecordings.TryGetValue(existingTimer.Id, out _)) - { - UpdateExistingTimerWithNewMetadata(existingTimer, timer); - - // Needed by ShouldCancelTimerForSeriesTimer - timer.IsManual = existingTimer.IsManual; - - if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) - { - existingTimer.Status = RecordingStatus.Cancelled; - } - else if (!existingTimer.IsManual) - { - existingTimer.Status = RecordingStatus.New; - } - - if (existingTimer.Status != RecordingStatus.Cancelled) - { - enabledTimersForSeries.Add(existingTimer); - } - - existingTimer.KeepUntil = seriesTimer.KeepUntil; - existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired; - existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired; - existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds; - existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds; - existingTimer.Priority = seriesTimer.Priority; - existingTimer.SeriesTimerId = seriesTimer.Id; - - _timerProvider.Update(existingTimer); - } + existingTimer.KeepUntil = seriesTimer.KeepUntil; + existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired; + existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired; + existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds; + existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds; + existingTimer.Priority = seriesTimer.Priority; + existingTimer.SeriesTimerId = seriesTimer.Id; } existingTimer.SeriesTimerId = seriesTimer.Id; From 159a244654aa533a149fe00636c9c8e8fa5d2c01 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Sat, 19 Nov 2022 14:14:41 -0600 Subject: [PATCH 042/639] Add Options to disable DVR NFO and image saving - SaveRecordingNFO and SaveRecordingImages default to true. Maintains current behavior. - Episode.FillMissingEpisodeNumbersFromPath for live tv so external metadata can be pulled when recording starts. --- .../LiveTv/EmbyTV/EmbyTV.cs | 30 +++++++++++-------- .../Entities/TV/Episode.cs | 2 +- MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 4 +++ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index a0ae328a47..6739c10c1e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1814,21 +1814,27 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV program.AddGenre("News"); } - if (timer.IsProgramSeries) + if (GetConfiguration().SaveRecordingNFO) { - await SaveSeriesNfoAsync(timer, seriesPath).ConfigureAwait(false); - await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); - } - else if (!timer.IsMovie || timer.IsSports || timer.IsNews) - { - await SaveVideoNfoAsync(timer, recordingPath, program, true).ConfigureAwait(false); - } - else - { - await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); + if (timer.IsProgramSeries) + { + await SaveSeriesNfoAsync(timer, seriesPath).ConfigureAwait(false); + await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); + } + else if (!timer.IsMovie || timer.IsSports || timer.IsNews) + { + await SaveVideoNfoAsync(timer, recordingPath, program, true).ConfigureAwait(false); + } + else + { + await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false); + } } - await SaveRecordingImages(recordingPath, program).ConfigureAwait(false); + if (GetConfiguration().SaveRecordingImages) + { + await SaveRecordingImages(recordingPath, program).ConfigureAwait(false); + } } catch (Exception ex) { diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 15b721fe63..285d3ef085 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -320,7 +320,7 @@ namespace MediaBrowser.Controller.Entities.TV if (!IsLocked) { - if (SourceType == SourceType.Library) + if (SourceType == SourceType.Library || SourceType == SourceType.LiveTV) { try { diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 4cece941cf..25e5c77969 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -40,5 +40,9 @@ namespace MediaBrowser.Model.LiveTv public string RecordingPostProcessor { get; set; } public string RecordingPostProcessorArguments { get; set; } + + public bool SaveRecordingNFO { get; set; } = true; + + public bool SaveRecordingImages { get; set; } = true; } } From b77922668b64991b5e043c277c911ea0ee39475b Mon Sep 17 00:00:00 2001 From: Akira Li Date: Sun, 20 Nov 2022 04:52:58 +0000 Subject: [PATCH 043/639] Translated using Weblate (Chinese (Traditional, Hong Kong)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant_HK/ --- Emby.Server.Implementations/Localization/Core/zh-HK.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 6c8bf76274..baa9ecc1c1 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -123,5 +123,6 @@ "TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。", "TaskKeyframeExtractorDescription": "提取關鍵格以創建更準確的HLS播放列表。次指示可能用時很長。", "TaskKeyframeExtractor": "關鍵幀提取器", - "External": "外部" + "External": "外部", + "HearingImpaired": "聽力障礙" } From bebc003e5ae19b582ca42aaf5abb2ebc009b9786 Mon Sep 17 00:00:00 2001 From: Akira Li Date: Sun, 20 Nov 2022 04:52:07 +0000 Subject: [PATCH 044/639] 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 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index 102a266f8e..a0d1e0b045 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -122,5 +122,6 @@ "TaskOptimizeDatabase": "最佳化資料庫", "TaskKeyframeExtractorDescription": "將關鍵幀從影片檔案提取出來並建立更精準的HLS播放清單。這可能需要很長時間。", "TaskKeyframeExtractor": "關鍵幀提取器", - "External": "外部" + "External": "外部", + "HearingImpaired": "聽力障礙" } From 5443708c4238d77f38346d63d2e0701c7dfb65b2 Mon Sep 17 00:00:00 2001 From: jhih_yu Date: Mon, 21 Nov 2022 15:14:17 +0000 Subject: [PATCH 045/639] 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 a0d1e0b045..4949c5ab6d 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": "音樂錄影帶", + "MusicVideos": "MV", "NameInstallFailed": "{0} 安裝失敗", "NameSeasonNumber": "第 {0} 季", "NameSeasonUnknown": "未知季數", From 75c96e6e76d7ba3e68f12108856c291623c819e7 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Tue, 22 Nov 2022 15:02:00 -0600 Subject: [PATCH 046/639] DVR: Prefer HD channels then earliest showing when handling duplicate showings. (#8768) Co-authored-by: Bond-009 --- .../LiveTv/EmbyTV/EmbyTV.cs | 5 ++-- .../LiveTv/EmbyTV/TimerManager.cs | 23 ++++++++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index a0ae328a47..cf9be5a54a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2192,10 +2192,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private void HandleDuplicateShowIds(List timers) { - foreach (var timer in timers.Skip(1)) + // sort showings by HD channels first, then by startDate, record earliest showing possible + foreach (var timer in timers.OrderByDescending(t => _liveTvManager.GetLiveTvChannel(t, this).IsHD).ThenBy(t => t.StartDate).Skip(1)) { - // TODO: Get smarter, prefer HD, etc - timer.Status = RecordingStatus.Cancelled; _timerProvider.Update(timer); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index a861e6ae44..f612565d1b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -122,11 +122,28 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (_timers.TryAdd(item.Id, timer)) { - Logger.LogInformation( - "Creating recording timer for {Id}, {Name}. Timer will fire in {Minutes} minutes", + if (item.IsSeries) + { + Logger.LogInformation( + "Creating recording timer for {Id}, {Name} {SeasonNumber}x{EpisodeNumber:D2} on channel {ChannelId}. Timer will fire in {Minutes} minutes at {StartDate}", item.Id, item.Name, - dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + item.SeasonNumber, + item.EpisodeNumber, + item.ChannelId, + dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture), + item.StartDate); + } + else + { + Logger.LogInformation( + "Creating recording timer for {Id}, {Name} on channel {ChannelId}. Timer will fire in {Minutes} minutes at {StartDate}", + item.Id, + item.Name, + item.ChannelId, + dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture), + item.StartDate); + } } else { From 6252bc399a3a56fc684f11523218093f8ff5f2b0 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:59:50 +0100 Subject: [PATCH 047/639] Fix unit tests after merge from master Co-authored-by: Bond-009 --- .../Manager/ProviderManagerTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index a5673ad838..725e295b9a 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -30,7 +30,7 @@ namespace Jellyfin.Providers.Tests.Manager { private static readonly ILogger _logger = new NullLogger(); - private static TheoryData[], int> RefreshSingleItemOrderData() + public static TheoryData[], int> RefreshSingleItemOrderData() => new() { // no order set, uses provided order @@ -74,7 +74,7 @@ namespace Jellyfin.Providers.Tests.Manager [Theory] [MemberData(nameof(RefreshSingleItemOrderData))] - public void RefreshSingleItem_ServiceOrdering_FollowsPriority(Mock[] servicesList, int expectedIndex) + public async Task RefreshSingleItem_ServiceOrdering_FollowsPriority(Mock[] servicesList, int expectedIndex) { var item = new Movie(); @@ -82,9 +82,9 @@ namespace Jellyfin.Providers.Tests.Manager AddParts(providerManager, metadataServices: servicesList.Select(s => s.Object).ToArray()); var refreshOptions = new MetadataRefreshOptions(Mock.Of(MockBehavior.Strict)); - var actual = providerManager.RefreshSingleItem(item, refreshOptions, CancellationToken.None); + var actual = await providerManager.RefreshSingleItem(item, refreshOptions, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(ItemUpdateType.MetadataDownload, actual.Result); + Assert.Equal(ItemUpdateType.MetadataDownload, actual); for (var i = 0; i < servicesList.Length; i++) { var times = i == expectedIndex ? Times.Once() : Times.Never(); @@ -95,7 +95,7 @@ namespace Jellyfin.Providers.Tests.Manager [Theory] [InlineData(true)] [InlineData(false)] - public void RefreshSingleItem_RefreshMetadata_WhenServiceFound(bool serviceFound) + public async Task RefreshSingleItem_RefreshMetadata_WhenServiceFound(bool serviceFound) { var item = new Movie(); @@ -105,13 +105,13 @@ namespace Jellyfin.Providers.Tests.Manager AddParts(providerManager, metadataServices: servicesList.Select(s => s.Object).ToArray()); var refreshOptions = new MetadataRefreshOptions(Mock.Of(MockBehavior.Strict)); - var actual = providerManager.RefreshSingleItem(item, refreshOptions, CancellationToken.None); + var actual = await providerManager.RefreshSingleItem(item, refreshOptions, CancellationToken.None).ConfigureAwait(false); var expectedResult = serviceFound ? ItemUpdateType.MetadataDownload : ItemUpdateType.None; - Assert.Equal(expectedResult, actual.Result); + Assert.Equal(expectedResult, actual); } - private static TheoryData GetImageProvidersOrderData() + public static TheoryData GetImageProvidersOrderData() => new() { { 3, null, null, null, new[] { 0, 1, 2 } }, // no order options set @@ -236,7 +236,7 @@ namespace Jellyfin.Providers.Tests.Manager Assert.Equal(expected ? 1 : 0, actualProviders.Length); } - private static TheoryData GetMetadataProvidersOrderData() + public static TheoryData GetMetadataProvidersOrderData() { var l = nameof(ILocalMetadataProvider); var r = nameof(IRemoteMetadataProvider); From 8fb5a1a12f7d392619ab54704eec09098faa3dfa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Nov 2022 17:24:37 +0000 Subject: [PATCH 048/639] chore(deps): update dependency efcoresecondlevelcacheinterceptor to v3.7.5 --- .../Jellyfin.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 5caac45233..8c7eee5052 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -26,7 +26,7 @@ - + From 7bf89502bccd350c4f2e0577f1f48fe32af03c54 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Nov 2022 17:24:50 +0000 Subject: [PATCH 049/639] chore(deps): update dependency prometheus-net.aspnetcore to v7 --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 15ae380e64..44f92cf833 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -40,7 +40,7 @@ - + From a84ab072caa3f84db075cf5a8c3186039348a9a1 Mon Sep 17 00:00:00 2001 From: andr8009 Date: Wed, 23 Nov 2022 18:20:02 +0000 Subject: [PATCH 050/639] 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 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 57455587d2..34655ace69 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -123,5 +123,6 @@ "TaskOptimizeDatabase": "Optimér database", "TaskKeyframeExtractorDescription": "Udtrækker billeder fra videofiler for at lave mere præcise HLS playlister. Denne opgave kan godt tage lang tid.", "TaskKeyframeExtractor": "Billedramme udtrækker", - "External": "Ekstern" + "External": "Ekstern", + "HearingImpaired": "Hørehæmmet" } From 5cef9799c365f3179ef4e4192bb861a0ca83a1e3 Mon Sep 17 00:00:00 2001 From: drlovesan Date: Wed, 23 Nov 2022 19:20:16 +0000 Subject: [PATCH 051/639] Translated using Weblate (Urdu (Pakistan)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ur_PK/ --- .../Localization/Core/ur_PK.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json index e7f3e492c6..5413346d41 100644 --- a/Emby.Server.Implementations/Localization/Core/ur_PK.json +++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json @@ -5,18 +5,18 @@ "HeaderAlbumArtists": "البم کے فنکار", "Movies": "فلمیں", "HeaderFavoriteEpisodes": "پسندیدہ اقساط", - "Collections": "مجموعہ", + "Collections": "مجموعے", "Folders": "فولڈرز", "HeaderLiveTV": "براہ راست ٹی وی", "Channels": "چینلز", "HeaderContinueWatching": "دیکھنا جاری رکھیں", "Playlists": "پلے لسٹس", - "ValueSpecialEpisodeName": "خاص - {0}", - "Shows": "شوز", + "ValueSpecialEpisodeName": "خصوصی - {0}", + "Shows": "دکھاتا ہے۔", "Genres": "انواع", "Artists": "فنکار", - "Sync": "مطابقت", - "Photos": "تصوریں", + "Sync": "مطابقت پذیری", + "Photos": "تصاویر", "Albums": "البمز", "Favorites": "پسندیدہ", "Songs": "گانے", From 18d7ac1a2aa795cd649e315bad37cbde3b4eed0d Mon Sep 17 00:00:00 2001 From: Pedro Barreiro Date: Fri, 25 Nov 2022 13:25:50 +0000 Subject: [PATCH 052/639] Translated using Weblate (Portuguese (Portugal)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/ --- Emby.Server.Implementations/Localization/Core/pt-PT.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index 7047f1c282..a75182f220 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -123,5 +123,6 @@ "TaskOptimizeDatabase": "Otimizar base de dados", "TaskKeyframeExtractorDescription": "Extrai quadros-chave de ficheiros de video para criar listas de reprodução HLS mais precisas. Esta tarefa pode demorar algum tempo.", "TaskKeyframeExtractor": "Extrator de Quadros-chave", - "External": "Externo" + "External": "Externo", + "HearingImpaired": "Surdo" } From 89772032e8d99c1bc936f41f02d1a876473a926f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 26 Nov 2022 17:56:15 -0700 Subject: [PATCH 053/639] chore(deps): update github/codeql-action digest to 312e093 (#8786) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 39ba5ea4d8..adca9680fb 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '6.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # tag=v2 + uses: github/codeql-action/init@312e093a1892bd801f026f1090904ee8e460b9b6 # v2 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # tag=v2 + uses: github/codeql-action/autobuild@312e093a1892bd801f026f1090904ee8e460b9b6 # v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # tag=v2 + uses: github/codeql-action/analyze@312e093a1892bd801f026f1090904ee8e460b9b6 # v2 From 692a62ab4f6bc5987d5e0d25e9016673b27611bc Mon Sep 17 00:00:00 2001 From: Terrance Date: Sun, 27 Nov 2022 01:59:25 +0000 Subject: [PATCH 054/639] Add missing format providers (fix CA1305 errors) (#8745) --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 2 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +- Jellyfin.Server/Program.cs | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 64ee5680ce..14fd7eb3c1 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -178,7 +178,7 @@ namespace Jellyfin.Api.Controllers foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("homesection", StringComparison.OrdinalIgnoreCase))) { - var order = int.Parse(key.AsSpan().Slice("homesection".Length)); + var order = int.Parse(key.AsSpan().Slice("homesection".Length), NumberStyles.Any, CultureInfo.InvariantCulture); if (!Enum.TryParse(displayPreferences.CustomPrefs[key], true, out var type)) { type = order < 8 ? defaults[order] : HomeSectionType.None; diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 3705737739..c8e62999c4 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -351,7 +351,7 @@ namespace Jellyfin.Api.Helpers try { // Parses npt times in the format of '10:19:25.7' - return TimeSpan.Parse(value).Ticks; + return TimeSpan.Parse(value, CultureInfo.InvariantCulture).Ticks; } catch { diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index cb763dfa33..7ed8388256 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -612,11 +613,14 @@ namespace Jellyfin.Server catch (Exception ex) { Log.Logger = new LoggerConfiguration() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}") + .WriteTo.Console( + outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}", + formatProvider: CultureInfo.InvariantCulture) .WriteTo.Async(x => x.File( Path.Combine(appPaths.LogDirectoryPath, "log_.log"), rollingInterval: RollingInterval.Day, outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}", + formatProvider: CultureInfo.InvariantCulture, encoding: Encoding.UTF8)) .Enrich.FromLogContext() .Enrich.WithThreadId() From f9d3ce0e452fe5ee9fb94b4a5685a5529e096e28 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 27 Nov 2022 14:09:07 +0100 Subject: [PATCH 055/639] chore(deps): update dependency prometheus-net.dotnetruntime to v4.4.0 (#8793) --- 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 a0bbd0c496..eeaa3346d9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -31,7 +31,7 @@ - + From e1bd5684e597b1719beabd6b1d32ebf61a23a434 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 27 Nov 2022 14:09:24 +0100 Subject: [PATCH 056/639] chore(deps): update dependency newtonsoft.json to v13.0.2 (#8792) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index b00c036e5a..eb921e6f52 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -21,7 +21,7 @@ - + From 556cc8062debd5370ef907b0c78e8636356a8068 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 23 Nov 2022 15:58:11 +0100 Subject: [PATCH 057/639] Investigate some TODO comments --- .gitignore | 2 -- Emby.Dlna/PlayTo/PlayToController.cs | 1 - .../Library/Resolvers/PlaylistResolver.cs | 20 ++++++++++--------- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 14 ++++++------- .../MediaEncoding/EncodingHelper.cs | 19 ------------------ .../Net/BasePeriodicWebSocketListener.cs | 3 ++- .../AlphanumericComparator.cs | 2 -- .../Dlna/StreamBuilderTests.cs | 12 +++++------ 8 files changed, 26 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index c2ae76c1e3..9e9fae7bfc 100644 --- a/.gitignore +++ b/.gitignore @@ -150,8 +150,6 @@ publish/ *.pubxml # NuGet Packages Directory -## TODO: If you have NuGet Package Restore enabled, uncomment the next line -# packages/ dlls/ dllssigned/ diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index b73ce00b6f..65367e24f2 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -338,7 +338,6 @@ namespace Emby.Dlna.PlayTo SubtitleStreamIndex = info.SubtitleStreamIndex, VolumeLevel = _device.Volume, - // TODO CanSeek = true, PlayMethod = info.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index 6b0dfe9864..7a2b3da3a9 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -31,16 +31,18 @@ namespace Emby.Server.Implementations.Library.Resolvers if (args.IsDirectory) { // It's a boxset if the path is a directory with [playlist] in it's the name - // TODO: Should this use Path.GetDirectoryName() instead? - bool isBoxSet = Path.GetFileName(args.Path) - ?.Contains("[playlist]", StringComparison.OrdinalIgnoreCase) - ?? false; - if (isBoxSet) + var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path)); + if (string.IsNullOrEmpty(filename)) + { + return null; + } + + if (filename.Contains("[playlist]", StringComparison.OrdinalIgnoreCase)) { return new Playlist { Path = args.Path, - Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim() + Name = filename.Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim() }; } @@ -51,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Resolvers return new Playlist { Path = args.Path, - Name = Path.GetFileName(args.Path) + Name = filename }; } } @@ -60,8 +62,8 @@ namespace Emby.Server.Implementations.Library.Resolvers // It should have the correct collection type and a supported file extension else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { - var extension = Path.GetExtension(args.Path); - if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + var extension = Path.GetExtension(args.Path.AsSpan()); + if (Playlist.SupportedExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) { return new Playlist { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 0bf0838fac..6106ae6c40 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -16,6 +16,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { @@ -24,15 +25,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// public class ChapterImagesTask : IScheduledTask { - /// - /// The _library manager. - /// + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; - private readonly IItemRepository _itemRepo; - private readonly IApplicationPaths _appPaths; - private readonly IEncodingManager _encodingManager; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; @@ -40,6 +36,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// /// Initializes a new instance of the class. /// + /// The logger.. /// The library manager.. /// The item repository. /// The application paths. @@ -47,6 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// The filesystem. /// The localization manager. public ChapterImagesTask( + ILogger logger, ILibraryManager libraryManager, IItemRepository itemRepo, IApplicationPaths appPaths, @@ -54,6 +52,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks IFileSystem fileSystem, ILocalizationManager localization) { + _logger = logger; _libraryManager = libraryManager; _itemRepo = itemRepo; _appPaths = appPaths; @@ -167,9 +166,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks progress.Report(100 * percent); } - catch (ObjectDisposedException) + catch (ObjectDisposedException ex) { // TODO Investigate and properly fix. + _logger.LogError(ex, "Object Disposed"); break; } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index cee08eedac..74abb91b28 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1176,24 +1176,6 @@ namespace MediaBrowser.Controller.MediaEncoding ":fontsdir='{0}'", _mediaEncoder.EscapeSubtitleFilterPath(fontPath)); - // TODO - // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf"); - // string fallbackFontParam = string.Empty; - - // if (!File.Exists(fallbackFontPath)) - // { - // _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(fallbackFontPath)); - // using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), GetType().Namespace + ".DroidSansFallback.ttf")) - // { - // using (var fileStream = new FileStream(fallbackFontPath, FileMode.Create, FileAccess.Write, FileShare.Read)) - // { - // stream.CopyTo(fileStream); - // } - // } - // } - - // fallbackFontParam = string.Format(CultureInfo.InvariantCulture, ":force_style='FontName=Droid Sans Fallback':fontsdir='{0}'", _mediaEncoder.EscapeSubtitleFilterPath(_fileSystem.GetDirectoryName(fallbackFontPath))); - if (state.SubtitleStream.IsExternal) { var charsetParam = string.Empty; @@ -1221,7 +1203,6 @@ namespace MediaBrowser.Controller.MediaEncoding alphaParam, sub2videoParam, fontParam, - // fallbackFontParam, setPtsParam); } diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 647de50030..2fe3a54723 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -227,9 +227,10 @@ namespace MediaBrowser.Controller.Net connection.Item2.Cancel(); connection.Item2.Dispose(); } - catch (ObjectDisposedException) + catch (ObjectDisposedException ex) { // TODO Investigate and properly fix. + Logger.LogError(ex, "Object Disposed"); } lock (_activeConnections) diff --git a/src/Jellyfin.Extensions/AlphanumericComparator.cs b/src/Jellyfin.Extensions/AlphanumericComparator.cs index e3c81eba8c..98a32d5b20 100644 --- a/src/Jellyfin.Extensions/AlphanumericComparator.cs +++ b/src/Jellyfin.Extensions/AlphanumericComparator.cs @@ -128,9 +128,7 @@ namespace Jellyfin.Extensions return result; } } -#pragma warning disable SA1500 // TODO remove with StyleCop.Analyzers v1.2.0 https://github.com/DotNetAnalyzers/StyleCopAnalyzers/pull/3196 } while (pos1 < len1 && pos2 < len2); -#pragma warning restore SA1500 return len1 - len2; } diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs index c279b6b4bb..e1bd2fe0f3 100644 --- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs +++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs @@ -359,7 +359,7 @@ namespace Jellyfin.Model.Tests Assert.Single(val.TargetAudioCodec); // Assert.Single(val.AudioCodecs); - if (transcodeMode == "DirectStream") + if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal)) { Assert.Equal(val.Container, uri.Extension); } @@ -371,14 +371,14 @@ namespace Jellyfin.Model.Tests Assert.NotEmpty(val.AudioCodecs); // Check expected container (todo: this could be a test param) - if (transcodeProtocol == "http") + if (transcodeProtocol.Equals("http", StringComparison.Ordinal)) { // Assert.Equal("webm", val.Container); Assert.Equal(val.Container, uri.Extension); Assert.Equal("stream", uri.Filename); Assert.Equal("http", val.SubProtocol); } - else if (transcodeProtocol == "HLS.mp4") + else if (transcodeProtocol.Equals("HLS.mp4", StringComparison.Ordinal)) { Assert.Equal("mp4", val.Container); Assert.Equal("m3u8", uri.Extension); @@ -394,7 +394,7 @@ namespace Jellyfin.Model.Tests } // Full transcode - if (transcodeMode == "Transcode") + if (transcodeMode.Equals("Transcode", StringComparison.Ordinal)) { if ((val.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == 0) { @@ -413,7 +413,7 @@ namespace Jellyfin.Model.Tests Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); Assert.Single(val.TargetVideoCodec); - if (transcodeMode == "DirectStream") + if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal)) { // Check expected audio codecs (1) if (!targetAudioStream.IsExternal) @@ -428,7 +428,7 @@ namespace Jellyfin.Model.Tests } } } - else if (transcodeMode == "Remux") + else if (transcodeMode.Equals("Remux", StringComparison.Ordinal)) { // Check expected audio codecs (1) Assert.Contains(targetAudioStream.Codec, val.AudioCodecs); From 060fb5f13cd4842c6655a6286ad2c76b3c1e1349 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 23 Nov 2022 16:56:41 +0100 Subject: [PATCH 058/639] Add stylecop.json file --- .gitignore | 1 - Directory.Build.props | 1 + stylecop.json | 9 +++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 stylecop.json diff --git a/.gitignore b/.gitignore index 9e9fae7bfc..fd8e1f314b 100644 --- a/.gitignore +++ b/.gitignore @@ -164,7 +164,6 @@ AppPackages/ sql/ *.Cache ClientBin/ -[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl diff --git a/Directory.Build.props b/Directory.Build.props index efcfb72243..f812f44190 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,6 +16,7 @@ + diff --git a/stylecop.json b/stylecop.json new file mode 100644 index 0000000000..6da4bf51df --- /dev/null +++ b/stylecop.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "layoutRules": { + "newlineAtEndOfFile": "require", + "allowDoWhileOnClosingBrace": true + } + } +} From fb3e97d7acf74adc1dda7dceb5c70271d3552770 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 27 Nov 2022 14:35:07 +0100 Subject: [PATCH 059/639] Use typed logger --- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 2 +- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 4 ++-- MediaBrowser.Providers/MediaInfo/ProbeProvider.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 6106ae6c40..da7c8732a8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// public class ChapterImagesTask : IScheduledTask { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IItemRepository _itemRepo; private readonly IApplicationPaths _appPaths; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 8c08ab30ef..f00023947d 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Providers.MediaInfo { public class FFProbeVideoInfo { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IMediaEncoder _mediaEncoder; private readonly IItemRepository _itemRepo; private readonly IBlurayExaminer _blurayExaminer; @@ -51,7 +51,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; public FFProbeVideoInfo( - ILogger logger, + ILogger logger, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, diff --git a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs index 6591366076..75f997a281 100644 --- a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs @@ -83,7 +83,7 @@ namespace MediaBrowser.Providers.MediaInfo _audioResolver = new AudioResolver(loggerFactory.CreateLogger(), localization, mediaEncoder, fileSystem, namingOptions); _subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger(), localization, mediaEncoder, fileSystem, namingOptions); _videoProber = new FFProbeVideoInfo( - _logger, + loggerFactory.CreateLogger(), mediaSourceManager, mediaEncoder, itemRepo, From 9c1da522c66aa4ec5e46eea943dd1ef60531bfa6 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sun, 27 Nov 2022 14:49:21 +0100 Subject: [PATCH 060/639] Fix last CA1305 error (#8806) --- .../JellyfinApplicationFactory.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 48c49bf848..c38faeda17 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Globalization; using System.IO; using System.Threading; using Emby.Server.Implementations; @@ -28,7 +29,9 @@ namespace Jellyfin.Server.Integration.Tests static JellyfinApplicationFactory() { // Perform static initialization that only needs to happen once per test-run - Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger(); + Log.Logger = new LoggerConfiguration() + .WriteTo.Console(formatProvider: CultureInfo.InvariantCulture) + .CreateLogger(); Program.PerformStaticInitialization(); } From 5787ab15dc297da6d2e1ec984ccf19751ed85db6 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 27 Nov 2022 08:02:32 -0700 Subject: [PATCH 061/639] Apply suggestions from code review Co-authored-by: Bond-009 --- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 853fbe89d5..ee672cab4e 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -199,7 +199,7 @@ namespace Jellyfin.Api.Helpers { // Provide a workaround for the case issue between flac and fLaC. var flacWaPlaylist = ApplyFlacCaseWorkaround(state, basicPlaylist.ToString()); - if (!String.IsNullOrEmpty(flacWaPlaylist)) + if (!string.IsNullOrEmpty(flacWaPlaylist)) { builder.Append(flacWaPlaylist); } @@ -226,7 +226,7 @@ namespace Jellyfin.Api.Helpers // Provide a workaround for the case issue between flac and fLaC. flacWaPlaylist = ApplyFlacCaseWorkaround(state, sdrPlaylist.ToString()); - if (!String.IsNullOrEmpty(flacWaPlaylist)) + if (!string.IsNullOrEmpty(flacWaPlaylist)) { builder.Append(flacWaPlaylist); } @@ -262,7 +262,7 @@ namespace Jellyfin.Api.Helpers // Provide a workaround for the case issue between flac and fLaC. flacWaPlaylist = ApplyFlacCaseWorkaround(state, newPlaylist); - if (!String.IsNullOrEmpty(flacWaPlaylist)) + if (!string.IsNullOrEmpty(flacWaPlaylist)) { builder.Append(flacWaPlaylist); } From 036382debbd61207799f17bffcd6379a4c1b3795 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 27 Nov 2022 08:52:43 -0700 Subject: [PATCH 062/639] chore(deps): update dependency efcoresecondlevelcacheinterceptor to v3.8.0 (#8805) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../Jellyfin.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 8c7eee5052..73e9a5882a 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -26,7 +26,7 @@ - + From 87d4ef7403f6601151397da977f4e591524c1709 Mon Sep 17 00:00:00 2001 From: SteveTheGrey <118358130+SteveTheGrey@users.noreply.github.com> Date: Sun, 27 Nov 2022 16:03:28 +0000 Subject: [PATCH 063/639] Minor search update - full word titles matches first (#8757) --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 7 +++++++ MediaBrowser.Model/Querying/ItemSortBy.cs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 371111dffe..4f0a15df18 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2456,6 +2456,8 @@ namespace Emby.Server.Implementations.Data builder.Append('('); builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)"); + builder.Append("+ ((CleanName = @SearchTermStartsWith COLLATE NOCASE or (OriginalTitle not null and OriginalTitle = @SearchTermStartsWith COLLATE NOCASE)) * 10)"); + if (query.SearchTerm.Length > 1) { @@ -3154,6 +3156,11 @@ namespace Emby.Server.Implementations.Data return ItemSortBy.SimilarityScore; } + if (string.Equals(name, ItemSortBy.SearchScore, StringComparison.OrdinalIgnoreCase)) + { + return ItemSortBy.SearchScore; + } + // Unknown SortBy, just sort by the SortName. return ItemSortBy.SortName; } diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index 470507c530..1a7c9a63b0 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -154,5 +154,10 @@ namespace MediaBrowser.Model.Querying /// The similarity score. /// public const string SimilarityScore = "SimilarityScore"; + + /// + /// The search score. + /// + public const string SearchScore = "SearchScore"; } } From 8f4ac1cb811ac4d62512149a5feb01703004b058 Mon Sep 17 00:00:00 2001 From: SenorSmartyPants Date: Sun, 27 Nov 2022 13:13:11 -0600 Subject: [PATCH 064/639] Call GetConfiguration just once in function --- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 6739c10c1e..f27978a449 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1814,7 +1814,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV program.AddGenre("News"); } - if (GetConfiguration().SaveRecordingNFO) + var config = GetConfiguration(); + + if (config.SaveRecordingNFO) { if (timer.IsProgramSeries) { @@ -1831,7 +1833,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - if (GetConfiguration().SaveRecordingImages) + if (config.SaveRecordingImages) { await SaveRecordingImages(recordingPath, program).ConfigureAwait(false); } From 63a72f995b8be6f1b3c3517c2b6e526cf325375d Mon Sep 17 00:00:00 2001 From: photonconvergence <116527579+photonconvergence@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:28:43 -0800 Subject: [PATCH 065/639] Offset Played Indicator to correct position --- Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs index db4f78398c..2a37299428 100644 --- a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs +++ b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs @@ -42,7 +42,7 @@ namespace Jellyfin.Drawing.Skia // ask the font manager for a font with that character paint.Typeface = SKFontManager.Default.MatchCharacter(emojiChar); - canvas.DrawText(Text, (float)x - 20, OffsetFromTopRightCorner + 12, paint); + canvas.DrawText(Text, (float)x - 12, OffsetFromTopRightCorner + 12, paint); } } } From 0a56a45a4ac31e6a735c295bfc0d1d09e29b648f Mon Sep 17 00:00:00 2001 From: Pierre Penninckx Date: Mon, 28 Nov 2022 04:34:34 +0000 Subject: [PATCH 066/639] 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 768245a09b..4877bcd7a7 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 d'album", + "HeaderAlbumArtists": "Artistes de l'album", "HeaderContinueWatching": "Reprendre le visionnage", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes préférés", From 11707f8f082f3bce81a06bb024be720b43d503c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Mart=C3=ADn=20P=C3=A9rez?= Date: Tue, 29 Nov 2022 14:14:25 +0000 Subject: [PATCH 067/639] Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/ --- .../Localization/Core/ca.json | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index ab04693ccf..c0ed01fdff 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -40,16 +40,16 @@ "Movies": "Pel·lícules", "Music": "Música", "MusicVideos": "Vídeos Musicals", - "NameInstallFailed": "Instalació de {0} fallida", + "NameInstallFailed": "Instal·lació de {0} fallida", "NameSeasonNumber": "Temporada {0}", "NameSeasonUnknown": "Temporada Desconeguda", "NewVersionIsAvailable": "Una nova versió del Servidor Jellyfin està disponible per descarregar.", "NotificationOptionApplicationUpdateAvailable": "Actualització d'aplicació disponible", "NotificationOptionApplicationUpdateInstalled": "Actualització d'aplicació instal·lada", - "NotificationOptionAudioPlayback": "Reproducció d'audio iniciada", - "NotificationOptionAudioPlaybackStopped": "Reproducció d'audio aturada", + "NotificationOptionAudioPlayback": "Reproducció d'àudio iniciada", + "NotificationOptionAudioPlaybackStopped": "Reproducció d'àudio aturada", "NotificationOptionCameraImageUploaded": "Imatge de càmera pujada", - "NotificationOptionInstallationFailed": "Instalació fallida", + "NotificationOptionInstallationFailed": "Instal·lació fallida", "NotificationOptionNewLibraryContent": "Nou contingut afegit", "NotificationOptionPluginError": "Un connector ha fallat", "NotificationOptionPluginInstalled": "Connector instal·lat", @@ -72,13 +72,13 @@ "ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciat", "Shows": "Sèries", "Songs": "Cançons", - "StartupEmbyServerIsLoading": "El Servidor d'Jellyfin està carregant. Si et plau, prova de nou en breus.", + "StartupEmbyServerIsLoading": "El Servidor de Jellyfin està carregant. Si et plau, prova de nou ben aviat.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}", "Sync": "Sincronitzar", "System": "Sistema", - "TvShows": "Espectacles de TV", - "User": "User", + "TvShows": "Sèries de TV", + "User": "Usuari", "UserCreatedWithName": "S'ha creat l'usuari {0}", "UserDeletedWithName": "L'usuari {0} ha estat eliminat", "UserDownloadingItemWithValues": "{0} està descarregant {1}", @@ -89,12 +89,12 @@ "UserPolicyUpdatedWithName": "La política d'usuari s'ha actualitzat per {0}", "UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1}", "UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}", - "ValueHasBeenAddedToLibrary": "{0} ha sigut afegit a la teva llibreria", + "ValueHasBeenAddedToLibrary": "{0} ha sigut afegit a la teva biblioteca", "ValueSpecialEpisodeName": "Especial - {0}", "VersionNumber": "Versió {0}", "TaskDownloadMissingSubtitlesDescription": "Cerca a internet els subtítols que faltin a partir de la configuració de metadades.", "TaskDownloadMissingSubtitles": "Descarrega els subtítols que faltin", - "TaskRefreshChannelsDescription": "Actualitza la informació dels canals d'internet.", + "TaskRefreshChannelsDescription": "Actualitza la informació dels canals d'Internet.", "TaskRefreshChannels": "Actualitza Canals", "TaskCleanTranscodeDescription": "Elimina els arxius temporals de transcodificacions que tinguin més d'un dia.", "TaskCleanTranscode": "Neteja les transcodificacions", @@ -110,7 +110,7 @@ "TaskRefreshChapterImages": "Extreure les imatges dels capítols", "TaskCleanCacheDescription": "Elimina els arxius temporals que ja no són necessaris per al servidor.", "TaskCleanCache": "Elimina arxius temporals", - "TasksChannelsCategory": "Canals d'internet", + "TasksChannelsCategory": "Canals d'Internet", "TasksApplicationCategory": "Aplicació", "TasksLibraryCategory": "Biblioteca", "TasksMaintenanceCategory": "Manteniment", @@ -118,7 +118,7 @@ "TaskCleanActivityLog": "Buidar Registre d'Activitat", "Undefined": "Indefinit", "Forced": "Forçat", - "Default": "Defecto", + "Default": "Defecte", "TaskOptimizeDatabaseDescription": "Compacta la base de dades i trunca l'espai lliure. Executar aquesta tasca després d’escanejar la biblioteca o fer altres canvis que impliquin modificacions a la base de dades pot millorar el rendiment.", "TaskOptimizeDatabase": "Optimitzar la base de dades", "TaskKeyframeExtractorDescription": "Extreu fotogrames clau dels fitxers de vídeo per crear llistes de reproducció HLS més precises. Aquesta tasca pot durar molt de temps.", From fea01a1ccb44dc995c6034ebdc72f0b75c86cfb7 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 30 Nov 2022 20:57:20 +0100 Subject: [PATCH 068/639] Fix CI (#8824) --- Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f812f44190..44a60ffb5c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,8 +15,8 @@ - - + + From da5913aa30d57567b9846d8856ccb6bd5ac9c504 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 30 Nov 2022 21:00:39 +0100 Subject: [PATCH 069/639] The -autoscale option was added in FFmpeg 4.4 (#8813) --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 74abb91b28..aa57f0480b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -977,7 +977,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Disable auto inserted SW scaler for HW decoders in case of changed resolution. var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options)); - if (!isSwDecoder) + if (!isSwDecoder && _mediaEncoder.EncoderVersion >= new Version(4, 4)) { arg.Append(" -autoscale 0"); } From b2ce70987c2f724436514e8f91f9f455be7d13ff Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 30 Nov 2022 22:07:34 +0100 Subject: [PATCH 070/639] Change log level for slow HTTP responses from WRN TO DBG The added log level check is there because Request.GetDisplayUrl() is a pretty expensive call, creating a StringBuilder and string which doesn't need to happen on most installs where debug logging is disabled --- Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs b/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs index 1c25696cd1..65b64da4f1 100644 --- a/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs +++ b/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs @@ -47,9 +47,10 @@ namespace Jellyfin.Server.Middleware context.Response.OnStarting(() => { watch.Stop(); - if (enableWarning && watch.ElapsedMilliseconds > warningThreshold) + var responseTimeForCompleteRequest = watch.ElapsedMilliseconds; + if (enableWarning && responseTimeForCompleteRequest > warningThreshold && _logger.IsEnabled(LogLevel.Debug)) { - _logger.LogWarning( + _logger.LogDebug( "Slow HTTP Response from {Url} to {RemoteIp} in {Elapsed:g} with Status Code {StatusCode}", context.Request.GetDisplayUrl(), context.GetNormalizedRemoteIp(), @@ -57,7 +58,6 @@ namespace Jellyfin.Server.Middleware context.Response.StatusCode); } - var responseTimeForCompleteRequest = watch.ElapsedMilliseconds; context.Response.Headers[ResponseHeaderResponseTime] = responseTimeForCompleteRequest.ToString(CultureInfo.InvariantCulture); return Task.CompletedTask; }); From 471ebec16adfd37cb0b30896964c537c425feb76 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 Dec 2022 15:26:18 +0100 Subject: [PATCH 071/639] chore(deps): update peter-evans/find-comment digest to f4499a7 (#8820) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index ca710fe834..390d140fd5 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -90,7 +90,7 @@ jobs: body="${body//$'\r'/'%0D'}" echo ::set-output name=body::$body - name: Find difference comment - uses: peter-evans/find-comment@b657a70ff16d17651703a84bee1cb9ad9d2be2ea # tag=v2 + uses: peter-evans/find-comment@f4499a714d59013c74a08789b48abe4b704364a0 # v2 id: find-comment with: issue-number: ${{ github.event.pull_request.number }} From 79f01834c16956ef158503a95d5bf9541fde353f Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Thu, 1 Dec 2022 15:31:59 -0500 Subject: [PATCH 072/639] Add delay_moov flag for progressive mp4 transcoding --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index aa57f0480b..5e924e4bfb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -5569,7 +5569,7 @@ namespace MediaBrowser.Controller.MediaEncoding && state.BaseRequest.Context == EncodingContext.Streaming) { // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js - format = " -f mp4 -movflags frag_keyframe+empty_moov"; + format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov"; } var threads = GetNumberOfThreads(state, encodingOptions, videoCodec); From 51c21143d477bece52f66d9d7c480546a9a26cf5 Mon Sep 17 00:00:00 2001 From: NorwayFun Date: Fri, 2 Dec 2022 07:15:20 -0500 Subject: [PATCH 073/639] Added translation using Weblate (Georgian) --- Emby.Server.Implementations/Localization/Core/ka.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/ka.json diff --git a/Emby.Server.Implementations/Localization/Core/ka.json b/Emby.Server.Implementations/Localization/Core/ka.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ka.json @@ -0,0 +1 @@ +{} From db2c0d4c91a952407ab7709d9ff5c86017e5753e Mon Sep 17 00:00:00 2001 From: NorwayFun Date: Fri, 2 Dec 2022 12:19:40 +0000 Subject: [PATCH 074/639] Translated using Weblate (Georgian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ka/ --- .../Localization/Core/ka.json | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ka.json b/Emby.Server.Implementations/Localization/Core/ka.json index 0967ef424b..78cfda3bdc 100644 --- a/Emby.Server.Implementations/Localization/Core/ka.json +++ b/Emby.Server.Implementations/Localization/Core/ka.json @@ -1 +1,31 @@ -{} +{ + "Genres": "ჟანრები", + "HeaderAlbumArtists": "ალბომის შემსრულებლები", + "HeaderFavoriteAlbums": "რჩეული ალბომები", + "TasksApplicationCategory": "აპლიკაცია", + "Albums": "ალბომები", + "AppDeviceValues": "აპი: {0}, მოწყობილობა: {1}", + "Application": "აპლიკაცია", + "Artists": "შემსრულებლები", + "AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია", + "Books": "წიგნები", + "Forced": "ძალით", + "Inherit": "მემკვიდრეობით", + "Latest": "უახლესი", + "Movies": "ფილმები", + "Music": "მუსიკა", + "Photos": "ფოტოები", + "Playlists": "დასაკრავი სიები", + "Plugin": "დამატება", + "Shows": "სერიალები", + "Songs": "სიმღერები", + "Sync": "სინქრონიზაცია", + "System": "სისტემა", + "Undefined": "აღუწერელი", + "User": "მომხმარებელი", + "TasksMaintenanceCategory": "რემონტი", + "TasksLibraryCategory": "ბიბლიოთეკა", + "ChapterNameValue": "თავი {0}", + "HeaderContinueWatching": "ყურების გაგრძელება", + "HeaderFavoriteArtists": "რჩეული შემსრულებლები" +} From fd73f346dc94a2b1a2c3421e9d83c0f6d9346d29 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 12 Nov 2022 10:19:52 +0100 Subject: [PATCH 075/639] Add userId parameter to AuthorizeQuickConnect --- Jellyfin.Api/Controllers/QuickConnectController.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs index 77d88475ff..aed4d93415 100644 --- a/Jellyfin.Api/Controllers/QuickConnectController.cs +++ b/Jellyfin.Api/Controllers/QuickConnectController.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Jellyfin.Api.Constants; @@ -96,6 +97,7 @@ namespace Jellyfin.Api.Controllers /// Authorizes a pending quick connect request. /// /// Quick connect code to authorize. + /// The user the authorize. Access to the requested user is required. /// Quick connect result authorized successfully. /// Unknown user id. /// Boolean indicating if the authorization was successful. @@ -103,17 +105,19 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> AuthorizeQuickConnect([FromQuery, Required] string code) + public async Task> AuthorizeQuickConnect([FromQuery, Required] string code, [FromQuery] Guid? userId = null) { - var userId = User.GetUserId(); - if (userId.Equals(default)) + var currentUserId = User.GetUserId(); + var actualUserId = userId ?? currentUserId; + + if (actualUserId.Equals(default) || (!userId.Equals(currentUserId) && !User.IsInRole(UserRoles.Administrator))) { - return StatusCode(StatusCodes.Status403Forbidden, "Unknown user id"); + return Forbid("Unknown user id"); } try { - return await _quickConnect.AuthorizeRequest(userId, code).ConfigureAwait(false); + return await _quickConnect.AuthorizeRequest(actualUserId, code).ConfigureAwait(false); } catch (AuthenticationException) { From 722ad3fe97e6fb1ef2bc99603c8fd84efe36ca79 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 12 Nov 2022 10:20:40 +0100 Subject: [PATCH 076/639] Change InitiateQuickConnect to use POST request Keep the GET request for compatibility --- Jellyfin.Api/Controllers/QuickConnectController.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs index aed4d93415..6dbcdae228 100644 --- a/Jellyfin.Api/Controllers/QuickConnectController.cs +++ b/Jellyfin.Api/Controllers/QuickConnectController.cs @@ -52,7 +52,7 @@ namespace Jellyfin.Api.Controllers /// Quick connect request successfully created. /// Quick connect is not active on this server. /// A with a secret and code for future use or an error message. - [HttpGet("Initiate")] + [HttpPost("Initiate")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> InitiateQuickConnect() { @@ -67,6 +67,16 @@ namespace Jellyfin.Api.Controllers } } + /// + /// Old version of using a GET method. + /// Still available to avoid breaking compatibility. + /// + /// The result of . + [Obsolete("Use POST request instead")] + [HttpGet("Initiate")] + [ApiExplorerSettings(IgnoreApi = true)] + public Task> InitiateQuickConnectLegacy() => InitiateQuickConnect(); + /// /// Attempts to retrieve authentication information. /// From e2cea6121a7b7f82693c05f63921a977ccd9a411 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 3 Dec 2022 17:47:30 +0200 Subject: [PATCH 077/639] Harden GitHub Workflows security (#8664) --- .github/workflows/automation.yml | 1 + .github/workflows/commands.yml | 4 ++++ .github/workflows/openapi.yml | 5 +++++ .github/workflows/repo-stale.yaml | 1 + 4 files changed, 11 insertions(+) diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index 0989df64b9..2dc7fb5a3e 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -7,6 +7,7 @@ on: pull_request_target: issue_comment: +permissions: {} jobs: label: name: Labeling diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index a29519b296..f7fbc4706d 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -9,6 +9,7 @@ on: - labeled - synchronize +permissions: {} jobs: rebase: name: Rebase @@ -34,6 +35,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }} check-backport: + permissions: + contents: read + name: Check Backport if: ${{ ( github.event.issue.pull_request && contains(github.event.comment.body, '@jellyfin-bot check backport') ) || github.event.label.name == 'stable backport' || contains(github.event.pull_request.labels.*.name, 'stable backport' ) }} runs-on: ubuntu-latest diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index 390d140fd5..a82579f1b1 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -5,6 +5,8 @@ on: - master pull_request_target: +permissions: {} + jobs: openapi-head: name: OpenAPI - HEAD @@ -55,6 +57,9 @@ jobs: path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json openapi-diff: + permissions: + pull-requests: write # to create or update comment (peter-evans/create-or-update-comment) + name: OpenAPI - Difference if: ${{ github.event_name == 'pull_request_target' }} runs-on: ubuntu-latest diff --git a/.github/workflows/repo-stale.yaml b/.github/workflows/repo-stale.yaml index f7a77f02b1..1c6fe1492f 100644 --- a/.github/workflows/repo-stale.yaml +++ b/.github/workflows/repo-stale.yaml @@ -5,6 +5,7 @@ on: - cron: '30 1 * * *' workflow_dispatch: +permissions: {} jobs: stale: runs-on: ubuntu-latest From fe3e7979b0e5301bd13565df5dba674386ea0541 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 3 Dec 2022 16:47:50 +0100 Subject: [PATCH 078/639] Add MusicBrainz server validation and fallback (#8833) --- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 25 ++++++++++++++++-- .../MusicBrainz/MusicBrainzArtistProvider.cs | 26 ++++++++++++++++--- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 4d9feca6d1..2b0c43d05f 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -13,6 +13,7 @@ using MediaBrowser.Providers.Music; using MetaBrainz.MusicBrainz; using MetaBrainz.MusicBrainz.Interfaces.Entities; using MetaBrainz.MusicBrainz.Interfaces.Searches; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Plugins.MusicBrainz; @@ -21,16 +22,36 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz; /// public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder, IDisposable { + private readonly ILogger _logger; private readonly Query _musicBrainzQuery; + private readonly string _musicBrainzDefaultUri = "https://musicbrainz.org"; /// /// Initializes a new instance of the class. /// - public MusicBrainzAlbumProvider() + /// The logger. + public MusicBrainzAlbumProvider(ILogger logger) { + _logger = logger; + MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) => { - Query.DefaultServer = MusicBrainz.Plugin.Instance.Configuration.Server; + if (Uri.TryCreate(MusicBrainz.Plugin.Instance.Configuration.Server, UriKind.Absolute, out var server)) + { + Query.DefaultServer = server.Host; + Query.DefaultPort = server.Port; + Query.DefaultUrlScheme = server.Scheme; + } + else + { + // Fallback to official server + _logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server"); + var defaultServer = new Uri(_musicBrainzDefaultUri); + Query.DefaultServer = defaultServer.Host; + Query.DefaultPort = defaultServer.Port; + Query.DefaultUrlScheme = defaultServer.Scheme; + } + Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit; }; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs index 2cc3a13bef..2b15154265 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using System.Xml; using Jellyfin.Extensions; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; @@ -14,6 +13,7 @@ using MediaBrowser.Providers.Music; using MetaBrainz.MusicBrainz; using MetaBrainz.MusicBrainz.Interfaces.Entities; using MetaBrainz.MusicBrainz.Interfaces.Searches; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Plugins.MusicBrainz; @@ -22,16 +22,36 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz; /// public class MusicBrainzArtistProvider : IRemoteMetadataProvider, IDisposable { + private readonly ILogger _logger; private readonly Query _musicBrainzQuery; + private readonly string _musicBrainzDefaultUri = "https://musicbrainz.org"; /// /// Initializes a new instance of the class. /// - public MusicBrainzArtistProvider() + /// The logger. + public MusicBrainzArtistProvider(ILogger logger) { + _logger = logger; + MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) => { - Query.DefaultServer = MusicBrainz.Plugin.Instance.Configuration.Server; + if (Uri.TryCreate(MusicBrainz.Plugin.Instance.Configuration.Server, UriKind.Absolute, out var server)) + { + Query.DefaultServer = server.Host; + Query.DefaultPort = server.Port; + Query.DefaultUrlScheme = server.Scheme; + } + else + { + // Fallback to official server + _logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server"); + var defaultServer = new Uri(_musicBrainzDefaultUri); + Query.DefaultServer = defaultServer.Host; + Query.DefaultPort = defaultServer.Port; + Query.DefaultUrlScheme = defaultServer.Scheme; + } + Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit; }; From df66816178ea1c90881ffb1fda920fa5d7e8b27f Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 3 Dec 2022 10:47:59 -0500 Subject: [PATCH 079/639] Allow non-ASCII in downloaded filenames (#8825) Fixes https://github.com/jellyfin/jellyfin/issues/8657 --- Jellyfin.Api/Controllers/LibraryController.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 7a57bf1a21..7d5cfc7ae5 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -637,22 +637,10 @@ namespace Jellyfin.Api.Controllers await LogDownloadAsync(item, user).ConfigureAwait(false); } - var path = item.Path; + // Quotes are valid in linux. They'll possibly cause issues here. + var filename = Path.GetFileName(item.Path)?.Replace("\"", string.Empty, StringComparison.Ordinal); - // Quotes are valid in linux. They'll possibly cause issues here - var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty, StringComparison.Ordinal); - if (!string.IsNullOrWhiteSpace(filename)) - { - // Kestrel doesn't support non-ASCII characters in headers - if (Regex.IsMatch(filename, @"[^\p{IsBasicLatin}]")) - { - // Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2 - filename = WebUtility.UrlEncode(filename); - } - } - - // TODO determine non-ASCII validity. - return PhysicalFile(path, MimeTypes.GetMimeType(path), filename, true); + return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), filename, true); } /// From be17e742f156a0fbb25b1438d6457b9690c59f13 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Dec 2022 08:48:05 -0700 Subject: [PATCH 080/639] chore(deps): update github/codeql-action digest to b2a92eb (#8834) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index adca9680fb..677bfc3dfc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '6.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@312e093a1892bd801f026f1090904ee8e460b9b6 # v2 + uses: github/codeql-action/init@b2a92eb56d8cb930006a1c6ed86b0782dd8a4297 # v2 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@312e093a1892bd801f026f1090904ee8e460b9b6 # v2 + uses: github/codeql-action/autobuild@b2a92eb56d8cb930006a1c6ed86b0782dd8a4297 # v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@312e093a1892bd801f026f1090904ee8e460b9b6 # v2 + uses: github/codeql-action/analyze@b2a92eb56d8cb930006a1c6ed86b0782dd8a4297 # v2 From 38e02e15c4f2f97f84ae5c30ac8c03ca59c58c0f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Dec 2022 08:48:12 -0700 Subject: [PATCH 081/639] chore(deps): update dependency sqlitepclraw.bundle_e_sqlite3 to v2.1.3 (#8837) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 44f92cf833..4cb83cdf0b 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -48,7 +48,7 @@ - + From 210a4921f2bc96b8f87a9ac7c1a348fcee792329 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 5 Dec 2022 13:54:28 +0100 Subject: [PATCH 082/639] Fix some warnings and only disable TreatWarningsAsErrors for CodeAnalysis (#8709) --- Emby.Dlna/Emby.Dlna.csproj | 2 +- Emby.Drawing/Emby.Drawing.csproj | 4 --- Emby.Naming/Emby.Naming.csproj | 2 +- .../Emby.Server.Implementations.csproj | 2 +- .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 +- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- .../Jellyfin.Server.Implementations.csproj | 2 +- .../MediaBrowser.Common.csproj | 2 +- .../MediaBrowser.Controller.csproj | 2 +- .../MediaEncoding/EncodingHelper.cs | 1 - .../MediaEncoding/IMediaEncoder.cs | 2 +- .../Encoder/MediaEncoder.cs | 3 ++- .../MediaBrowser.MediaEncoding.csproj | 2 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- .../MediaBrowser.Providers.csproj | 2 +- .../Music/AlbumMetadataService.cs | 1 + jellyfin.ruleset | 2 ++ .../Entities/BaseItemTests.cs | 25 +++++++++---------- 18 files changed, 29 insertions(+), 31 deletions(-) diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index d59b43ef03..66aded3b42 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -23,7 +23,7 @@ - false + false diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index 8f64b0b214..177172e8a5 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -11,10 +11,6 @@ true - - false - - diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index ca002b9816..fbf0e1cec2 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -16,7 +16,7 @@ - false + false diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index eeaa3346d9..de756e1e31 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -49,7 +49,7 @@ - false + false diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 82f0baf32e..264eec947a 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -243,7 +243,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { Id = c.Id, Name = c.DisplayName, - ImageUrl = c.Icon != null && !string.IsNullOrEmpty(c.Icon.Source) ? c.Icon.Source : null, + ImageUrl = string.IsNullOrEmpty(c.Icon.Source) ? null : c.Icon.Source, Number = string.IsNullOrWhiteSpace(c.Number) ? c.Id : c.Number }).ToList(); } diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index a4502b6129..c44fdae788 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -13,7 +13,7 @@ - false + false diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 73e9a5882a..ba5d6a3126 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -7,7 +7,7 @@ - false + false diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 7a55f398a1..f058fba60a 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -39,7 +39,7 @@ - false + false diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index d4e025a43e..62c17b48c8 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -14,7 +14,7 @@ - false + false diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5e924e4bfb..fae53acbfb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2814,7 +2814,6 @@ namespace MediaBrowser.Controller.MediaEncoding { algorithm = "bt.2390"; } - else if (string.Equals(options.TonemappingAlgorithm, "none", StringComparison.OrdinalIgnoreCase)) { algorithm = "clip"; diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 52c57b906e..fe8e9063ee 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.MediaEncoding Version EncoderVersion { get; } /// - /// Whether p key pausing is supported. + /// Gets a value indicating whether p key pausing is supported. /// /// true if p key pausing is supported, false otherwise. bool IsPkeyPauseSupported { get; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index b7c49ed99b..93c035c5cf 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -75,7 +75,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private bool _isVaapiDeviceInteli965 = false; private bool _isVaapiDeviceSupportVulkanFmtModifier = false; - private static string[] _vulkanFmtModifierExts = { + private static string[] _vulkanFmtModifierExts = + { "VK_KHR_sampler_ycbcr_conversion", "VK_EXT_image_drm_format_modifier", "VK_KHR_external_memory_fd", diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index afe4ff4e71..2ed654e1c6 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -12,7 +12,7 @@ - false + false diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 4172e9825e..7c5399b4de 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -24,7 +24,7 @@ - false + false diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index eb921e6f52..30f4a449e4 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -36,7 +36,7 @@ - false + false diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index ac40f0b3a4..58cd23aa34 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -152,6 +152,7 @@ namespace MediaBrowser.Providers.Music return ItemUpdateType.MetadataEdit; } } + return ItemUpdateType.None; } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 8144db93d5..71385cee2a 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -23,6 +23,8 @@ + + diff --git a/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs b/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs index 985bbcde1f..f3ada59dbc 100644 --- a/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs +++ b/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs @@ -1,18 +1,17 @@ using MediaBrowser.Controller.Entities; using Xunit; -namespace Jellyfin.Controller.Tests.Entities +namespace Jellyfin.Controller.Tests.Entities; + +public class BaseItemTests { - public class BaseItemTests - { - [Theory] - [InlineData("", "")] - [InlineData("1", "0000000001")] - [InlineData("t", "t")] - [InlineData("test", "test")] - [InlineData("test1", "test0000000001")] - [InlineData("1test 2", "0000000001test 0000000002")] - public void BaseItem_ModifySortChunks_Valid(string input, string expected) - => Assert.Equal(expected, BaseItem.ModifySortChunks(input)); - } + [Theory] + [InlineData("", "")] + [InlineData("1", "0000000001")] + [InlineData("t", "t")] + [InlineData("test", "test")] + [InlineData("test1", "test0000000001")] + [InlineData("1test 2", "0000000001test 0000000002")] + public void BaseItem_ModifySortChunks_Valid(string input, string expected) + => Assert.Equal(expected, BaseItem.ModifySortChunks(input)); } From 8eb688dc988bb287adf67af576dc5dcc4cf96979 Mon Sep 17 00:00:00 2001 From: shoddysheep Date: Sun, 4 Dec 2022 12:57:16 +0000 Subject: [PATCH 083/639] 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 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 34655ace69..0d0d0c8136 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -15,8 +15,8 @@ "Favorites": "Favoritter", "Folders": "Mapper", "Genres": "Genrer", - "HeaderAlbumArtists": "Kunstnerens album", - "HeaderContinueWatching": "Fortsæt Afspilning", + "HeaderAlbumArtists": "Albumkunstner", + "HeaderContinueWatching": "Fortsæt afspilning", "HeaderFavoriteAlbums": "Favoritalbummer", "HeaderFavoriteArtists": "Favoritkunstnere", "HeaderFavoriteEpisodes": "Favoritepisoder", @@ -42,7 +42,7 @@ "MusicVideos": "Musik videoer", "NameInstallFailed": "{0} installationen mislykkedes", "NameSeasonNumber": "Sæson {0}", - "NameSeasonUnknown": "Ukendt Sæson", + "NameSeasonUnknown": "Ukendt sæson", "NewVersionIsAvailable": "En ny version af Jellyfin Server er tilgængelig til download.", "NotificationOptionApplicationUpdateAvailable": "Opdatering til applikation tilgængelig", "NotificationOptionApplicationUpdateInstalled": "Opdatering til applikation installeret", @@ -77,7 +77,7 @@ "SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke downloades fra {0} til {1}", "Sync": "Synk", "System": "System", - "TvShows": "TV serier", + "TvShows": "Tv-serier", "User": "Bruger", "UserCreatedWithName": "Bruger {0} er blevet oprettet", "UserDeletedWithName": "Brugeren {0} er blevet slettet", From 3943ff03de54defc77e1621573506bcb09c19ac8 Mon Sep 17 00:00:00 2001 From: Weevild Date: Sun, 4 Dec 2022 12:47:05 +0000 Subject: [PATCH 084/639] 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 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index af5db1976c..318a0f3cf1 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -123,5 +123,6 @@ "TaskOptimizeDatabaseDescription": "Komprimerar databasen och trunkerar ledigt utrymme. Prestandan kan förbättras genom att köra denna aktivitet efter att du har skannat biblioteket eller gjort andra förändringar som indikerar att databasen har modifierats.", "TaskKeyframeExtractorDescription": "Exporterar nyckelbildrutor från videofiler för att skapa mer exakta HLS-spellistor. Denna rutin kan ta lång tid.", "TaskKeyframeExtractor": "Extraktor för nyckelbildrutor", - "External": "Extern" + "External": "Extern", + "HearingImpaired": "Hörselskadad" } From 9ec5782555ba19a476847e4222565a4381779a68 Mon Sep 17 00:00:00 2001 From: NorwayFun Date: Sat, 3 Dec 2022 18:23:45 +0000 Subject: [PATCH 085/639] Translated using Weblate (Georgian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ka/ --- .../Localization/Core/ka.json | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ka.json b/Emby.Server.Implementations/Localization/Core/ka.json index 78cfda3bdc..3a8b89f446 100644 --- a/Emby.Server.Implementations/Localization/Core/ka.json +++ b/Emby.Server.Implementations/Localization/Core/ka.json @@ -27,5 +27,86 @@ "TasksLibraryCategory": "ბიბლიოთეკა", "ChapterNameValue": "თავი {0}", "HeaderContinueWatching": "ყურების გაგრძელება", - "HeaderFavoriteArtists": "რჩეული შემსრულებლები" + "HeaderFavoriteArtists": "რჩეული შემსრულებლები", + "DeviceOfflineWithName": "{0} გაითიშა", + "External": "გარე", + "HeaderFavoriteEpisodes": "რჩეული ეპიზოდები", + "HeaderFavoriteSongs": "რჩეული სიმღერები", + "HeaderRecordingGroups": "ჩამწერი ჯგუფები", + "HearingImpaired": "სმენადაქვეითებული", + "LabelRunningTimeValue": "გაშვებულობის დრო: {0}", + "MessageApplicationUpdatedTo": "Jellyfin-ის სერვერი განახლდა {0}-ზე", + "MessageNamedServerConfigurationUpdatedWithValue": "სერვერის კონფიგურაციის სექცია {0} განახლდა", + "MixedContent": "შერეული შემცველობა", + "MusicVideos": "მუსიკის ვიდეოები", + "NotificationOptionInstallationFailed": "დაყენების შეცდომა", + "NotificationOptionApplicationUpdateInstalled": "აპლიკაციის განახლება დაყენებულია", + "NotificationOptionAudioPlayback": "აუდიოს დაკვრა დაწყებულია", + "NotificationOptionCameraImageUploaded": "კამერის გამოსახულება ატვირთულია", + "NotificationOptionVideoPlaybackStopped": "ვიდეოს დაკვრა გაჩერებულია", + "PluginUninstalledWithName": "{0} წაიშალა", + "ScheduledTaskStartedWithName": "{0} გაეშვა", + "VersionNumber": "ვერსია {0}", + "TasksChannelsCategory": "ინტერნეტ-არხები", + "ValueSpecialEpisodeName": "სპეციალური - {0}", + "TaskRefreshChannelsDescription": "ინტერნეტ-არხის ინფორმაციის განახლება.", + "Channels": "არხები", + "Collections": "კოლექციები", + "Default": "ნაგულისხმები", + "Favorites": "რჩეულები", + "Folders": "საქაღალდეები", + "HeaderFavoriteShows": "რჩეული სერიალები", + "HeaderLiveTV": "ცოცხალი TV", + "HeaderNextUp": "შემდეგი ზემოთ", + "HomeVideos": "სახლის ვიდეოები", + "NameSeasonNumber": "სეზონი {0}", + "NameSeasonUnknown": "სეზონი უცნობია", + "NotificationOptionPluginError": "დამატების შეცდომა", + "NotificationOptionPluginInstalled": "დამატება დაყენებულია", + "NotificationOptionPluginUninstalled": "დამატება წაიშალა", + "ProviderValue": "მომწოდებელი: {0}", + "ScheduledTaskFailedWithName": "{0} ავარიულია", + "TvShows": "TV სერიალები", + "TaskRefreshPeople": "ხალხის განახლება", + "TaskUpdatePlugins": "დამატებების განახლება", + "TaskRefreshChannels": "არხების განახლება", + "TaskOptimizeDatabase": "ბაზების ოპტიმიზაცია", + "TaskKeyframeExtractor": "საკვანძო კადრის გამომღები", + "DeviceOnlineWithName": "{0} შეერთებულია", + "LabelIpAddressValue": "IP მისამართი: {0}", + "NameInstallFailed": "{0}-ის დაყენების შეცდომა", + "NotificationOptionApplicationUpdateAvailable": "ხელმისაწვდომია აპლიკაციის განახლება", + "NotificationOptionAudioPlaybackStopped": "აუდიოს დაკვრა გაჩერებულია", + "NotificationOptionNewLibraryContent": "ახალი შემცველობა დამატებულია", + "NotificationOptionPluginUpdateInstalled": "დამატების განახლება დაყენებულია", + "NotificationOptionServerRestartRequired": "სერვერის გადატვირთვა აუცილებელია", + "NotificationOptionTaskFailed": "დაგეგმილი ამოცანის შეცდომა", + "NotificationOptionUserLockedOut": "მომხმარებელი დაიბლოკა", + "NotificationOptionVideoPlayback": "ვიდეოს დაკვრა დაწყებულია", + "PluginInstalledWithName": "{0} დაყენებულია", + "PluginUpdatedWithName": "{0} განახლდა", + "TaskCleanActivityLog": "აქტივობების ჟურნალის გასუფთავება", + "TaskCleanCache": "ქეშის საქაღალდის გასუფთავება", + "TaskRefreshChapterImages": "თავის სურათების გაშლა", + "TaskRefreshLibrary": "მედიის ბიბლიოთეკის სკანირება", + "TaskCleanLogs": "ჟურნალის საქაღალდის გასუფთავება", + "TaskCleanTranscode": "ტრანსკოდირების საქაღალდის გასუფთავება", + "TaskDownloadMissingSubtitles": "ნაკლული სუბტიტრების გადმოწერა", + "UserDownloadingItemWithValues": "{0} -ი {0}-ს იწერს", + "FailedLoginAttemptWithUserName": "{0}-დან შემოსვლის შეცდომა", + "MessageApplicationUpdated": "Jellyfin-ის სერვერი განახლდა", + "MessageServerConfigurationUpdated": "სერვერის კონფიგურაცია განახლდა", + "ServerNameNeedsToBeRestarted": "საჭიროა {0}-ის გადატვირთვა", + "UserCreatedWithName": "მომხმარებელი {0} შეიქმნა", + "UserDeletedWithName": "მომხმარებელი {0} წაშლილია", + "UserOnlineFromDevice": "{0}-ი ხაზზეა {1}-დან", + "UserOfflineFromDevice": "{0}-ი {1}-დან გაითიშა", + "ItemAddedWithName": "{0} ჩამატებულია ბიბლიოთეკაში", + "ItemRemovedWithName": "{0} წაშლილია ბიბლიოთეკიდან", + "UserLockedOutWithName": "მომხმარებელი {0} დაბლოკილია", + "UserStartedPlayingItemWithValues": "{0} თამაშობს {1}-ს {2}-ზე", + "UserPasswordChangedWithName": "მომხმარებლისთვის {0} პაროლი შეცვლილია", + "UserPolicyUpdatedWithName": "{0}-ის მომხმარებლის პოლიტიკა განახლდა", + "UserStoppedPlayingItemWithValues": "{0}-მა დაამთავრა {1}-ის დაკვრა {2}-ზე", + "TaskRefreshChapterImagesDescription": "თავების მქონე ვიდეოებისთვის მინიატურების შექმნა." } From b2def4c9ea6cf5e406bf5f865867d6cb5b54f640 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 5 Dec 2022 14:56:58 +0100 Subject: [PATCH 086/639] Fix build (#8859) --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 1 - MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs | 3 +++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 4f0a15df18..9d44fd9d18 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2458,7 +2458,6 @@ namespace Emby.Server.Implementations.Data builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)"); builder.Append("+ ((CleanName = @SearchTermStartsWith COLLATE NOCASE or (OriginalTitle not null and OriginalTitle = @SearchTermStartsWith COLLATE NOCASE)) * 10)"); - if (query.SearchTerm.Length > 1) { builder.Append("+ ((CleanName like @SearchTermContains or (OriginalTitle not null and OriginalTitle like @SearchTermContains)) * 10)"); diff --git a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs index 888ca6c72c..9ad0c26b0c 100644 --- a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs @@ -16,6 +16,8 @@ namespace MediaBrowser.Controller.Providers /// /// Interface IRemoteMetadataProvider. /// + /// The type of . + /// The type of . public interface IRemoteMetadataProvider : IMetadataProvider, IRemoteMetadataProvider, IRemoteSearchProvider where TItemType : BaseItem, IHasLookupInfo where TLookupInfoType : ItemLookupInfo, new() @@ -32,6 +34,7 @@ namespace MediaBrowser.Controller.Providers /// /// Interface IRemoteMetadataProvider. /// + /// The type of . public interface IRemoteSearchProvider : IRemoteSearchProvider where TLookupInfoType : ItemLookupInfo { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 93c035c5cf..e50aa679a3 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -635,7 +635,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return imageResolutionParameter; } - private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, ImageFormat? targetFormat, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) From c7d50d640e614a3c13699e3041fbfcb258861c5a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 5 Dec 2022 15:00:20 +0100 Subject: [PATCH 087/639] Replace == null with is null --- Emby.Dlna/Didl/DidlBuilder.cs | 20 ++-- Emby.Dlna/DlnaManager.cs | 8 +- Emby.Dlna/Main/DlnaEntryPoint.cs | 2 +- Emby.Dlna/PlayTo/Device.cs | 94 +++++++++---------- Emby.Dlna/PlayTo/PlayToController.cs | 8 +- Emby.Dlna/PlayTo/PlayToManager.cs | 4 +- Emby.Dlna/Ssdp/DeviceDiscovery.cs | 6 +- Emby.Drawing/ImageProcessor.cs | 2 +- .../AudioBook/AudioBookListResolver.cs | 2 +- Emby.Naming/AudioBook/AudioBookNameParser.cs | 2 +- .../ExternalFiles/ExternalPathParser.cs | 2 +- Emby.Naming/Video/VideoListResolver.cs | 4 +- Emby.Naming/Video/VideoResolver.cs | 2 +- Emby.Notifications/NotificationEntryPoint.cs | 2 +- Emby.Notifications/NotificationManager.cs | 2 +- .../AppBase/BaseConfigurationManager.cs | 4 +- .../AppBase/ConfigurationHelper.cs | 2 +- .../Channels/ChannelManager.cs | 16 ++-- .../Collections/CollectionManager.cs | 8 +- .../Data/SqliteExtensions.cs | 2 +- .../Data/SqliteItemRepository.cs | 18 ++-- Emby.Server.Implementations/Dto/DtoService.cs | 16 ++-- .../EntryPoints/LibraryChangedNotifier.cs | 6 +- .../EntryPoints/UserDataChangeNotifier.cs | 2 +- .../HttpServer/WebSocketConnection.cs | 4 +- .../IO/FileRefresher.cs | 6 +- .../Library/LibraryManager.cs | 46 ++++----- .../Library/LiveStreamHelper.cs | 4 +- .../Library/MediaSourceManager.cs | 14 +-- .../Library/ResolverHelper.cs | 2 +- .../Library/Resolvers/Audio/AudioResolver.cs | 4 +- .../Library/Resolvers/BaseVideoResolver.cs | 4 +- .../Library/Resolvers/ExtraResolver.cs | 2 +- .../Resolvers/GenericFolderResolver.cs | 2 +- .../Library/Resolvers/Movies/MovieResolver.cs | 8 +- .../Library/Resolvers/TV/EpisodeResolver.cs | 6 +- .../Library/UserViewManager.cs | 4 +- .../Validators/CollectionPostScanTask.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 22 ++--- .../LiveTv/EmbyTV/ItemDataProvider.cs | 2 +- .../LiveTv/Listings/SchedulesDirect.cs | 12 +-- .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 +- .../LiveTv/LiveTvDtoService.cs | 8 +- .../LiveTv/LiveTvManager.cs | 22 ++--- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 2 +- .../Localization/LocalizationManager.cs | 4 +- .../Playlists/PlaylistManager.cs | 4 +- .../Playlists/PlaylistsFolder.cs | 2 +- .../Plugins/PluginManager.cs | 10 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 2 +- .../ScheduledTasks/TaskManager.cs | 6 +- .../Triggers/IntervalTrigger.cs | 2 +- .../Session/SessionManager.cs | 32 +++---- .../Session/SessionWebSocketListener.cs | 2 +- .../Session/WebSocketController.cs | 2 +- .../Sorting/AiredEpisodeOrderComparer.cs | 6 +- .../Sorting/PlayCountComparer.cs | 2 +- .../Sorting/PremiereDateComparer.cs | 2 +- .../Sorting/ProductionYearComparer.cs | 2 +- Emby.Server.Implementations/SyncPlay/Group.cs | 4 +- .../SyncPlay/SyncPlayManager.cs | 22 ++--- .../TV/TVSeriesManager.cs | 4 +- .../Updates/InstallationManager.cs | 8 +- .../AnonymousLanAccessHandler.cs | 2 +- Jellyfin.Api/Auth/BaseAuthorizationHandler.cs | 2 +- .../Controllers/ConfigurationController.cs | 2 +- .../Controllers/DashboardController.cs | 4 +- Jellyfin.Api/Controllers/DevicesController.cs | 6 +- Jellyfin.Api/Controllers/DlnaController.cs | 6 +- .../Controllers/DlnaServerController.cs | 2 +- .../Controllers/DynamicHlsController.cs | 10 +- .../Controllers/EnvironmentController.cs | 2 +- Jellyfin.Api/Controllers/FilterController.cs | 2 +- .../Controllers/HlsSegmentController.cs | 2 +- .../Controllers/ImageByNameController.cs | 2 +- Jellyfin.Api/Controllers/ImageController.cs | 48 +++++----- .../Controllers/ItemLookupController.cs | 2 +- .../Controllers/ItemRefreshController.cs | 2 +- .../Controllers/ItemUpdateController.cs | 4 +- Jellyfin.Api/Controllers/LibraryController.cs | 14 +-- Jellyfin.Api/Controllers/LiveTvController.cs | 6 +- .../Controllers/MediaInfoController.cs | 2 +- Jellyfin.Api/Controllers/PackageController.cs | 4 +- Jellyfin.Api/Controllers/PersonsController.cs | 2 +- .../Controllers/PlaylistsController.cs | 2 +- .../Controllers/PlaystateController.cs | 2 +- Jellyfin.Api/Controllers/PluginsController.cs | 12 +-- .../Controllers/RemoteImageController.cs | 6 +- .../Controllers/ScheduledTasksController.cs | 8 +- Jellyfin.Api/Controllers/SearchController.cs | 2 +- Jellyfin.Api/Controllers/SessionController.cs | 2 +- .../Controllers/SubtitleController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 2 +- Jellyfin.Api/Controllers/UserController.cs | 12 +-- .../Controllers/UserLibraryController.cs | 4 +- .../Controllers/UserViewsController.cs | 2 +- .../Controllers/VideoAttachmentsController.cs | 2 +- Jellyfin.Api/Controllers/VideosController.cs | 6 +- Jellyfin.Api/Controllers/YearsController.cs | 2 +- Jellyfin.Api/Helpers/AudioHelper.cs | 4 +- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 6 +- Jellyfin.Api/Helpers/HlsHelpers.cs | 2 +- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 2 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 2 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 12 +-- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../NullableEnumModelBinderProvider.cs | 2 +- .../Models/PlaybackDtos/TranscodingJobDto.cs | 2 +- Jellyfin.Data/Entities/User.cs | 6 +- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 4 +- Jellyfin.Drawing.Skia/SplashscreenBuilder.cs | 2 +- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 4 +- Jellyfin.Networking/Manager/NetworkManager.cs | 10 +- .../Devices/DeviceManager.cs | 4 +- .../Consumers/Session/PlaybackStartLogger.cs | 2 +- .../Consumers/Session/PlaybackStopLogger.cs | 4 +- .../Events/EventManager.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../Security/AuthenticationManager.cs | 2 +- .../Users/DefaultAuthenticationProvider.cs | 4 +- .../Users/DisplayPreferencesManager.cs | 4 +- .../Users/UserManager.cs | 6 +- Jellyfin.Server/Filters/FileResponseFilter.cs | 2 +- .../Formatters/CssOutputFormatter.cs | 2 +- .../Formatters/XmlOutputFormatter.cs | 2 +- .../Routines/MigrateDisplayPreferencesDb.cs | 4 +- .../Migrations/Routines/MigrateUserDb.cs | 2 +- Jellyfin.Server/Program.cs | 2 +- .../Extensions/HttpContextExtensions.cs | 4 +- MediaBrowser.Common/Net/NetworkExtensions.cs | 4 +- MediaBrowser.Common/Plugins/BasePluginOfT.cs | 2 +- MediaBrowser.Common/Plugins/LocalPlugin.cs | 8 +- .../BaseItemManager/BaseItemManager.cs | 4 +- .../Drawing/ImageProcessorExtensions.cs | 2 +- .../Entities/AggregateFolder.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 36 +++---- .../Entities/BaseItemExtensions.cs | 2 +- .../Entities/Extensions.cs | 2 +- MediaBrowser.Controller/Entities/Folder.cs | 18 ++-- .../Entities/InternalItemsQuery.cs | 2 +- .../Entities/ItemImageInfo.cs | 2 +- .../Entities/PeopleHelper.cs | 4 +- .../Entities/TV/Episode.cs | 14 +-- MediaBrowser.Controller/Entities/TV/Season.cs | 8 +- MediaBrowser.Controller/Entities/TV/Series.cs | 4 +- .../Entities/UserRootFolder.cs | 2 +- .../Entities/UserViewBuilder.cs | 14 +-- .../Library/ItemResolveArgs.cs | 8 +- .../Library/NameExtensions.cs | 2 +- .../LiveTv/LiveTvChannel.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 30 +++--- .../MediaEncoding/EncodingJobInfo.cs | 8 +- .../Providers/MetadataResult.cs | 4 +- .../Session/SessionInfo.cs | 12 +-- MediaBrowser.LocalMetadata/BaseXmlProvider.cs | 4 +- .../Images/EpisodeLocalImageProvider.cs | 2 +- .../Images/LocalImageProvider.cs | 2 +- .../Attachments/AttachmentExtractor.cs | 4 +- .../BdInfo/BdInfoExaminer.cs | 2 +- .../Encoder/EncoderValidator.cs | 2 +- .../Encoder/MediaEncoder.cs | 2 +- .../Probing/ProbeResultNormalizer.cs | 16 ++-- MediaBrowser.Model/Dlna/AudioOptions.cs | 2 +- MediaBrowser.Model/Dlna/ContainerProfile.cs | 2 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 44 ++++----- MediaBrowser.Model/Dlna/StreamInfo.cs | 4 +- MediaBrowser.Model/Drawing/DrawingUtils.cs | 8 +- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 2 +- .../Entities/ProviderIdsExtensions.cs | 2 +- .../Notifications/NotificationOptions.cs | 2 +- MediaBrowser.Model/Updates/VersionInfo.cs | 2 +- .../BoxSets/BoxSetMetadataService.cs | 2 +- .../Manager/ItemImageProvider.cs | 6 +- .../Manager/MetadataService.cs | 8 +- .../Manager/ProviderManager.cs | 12 +-- .../MediaInfo/EmbeddedImageProvider.cs | 2 +- .../MediaInfo/FFProbeVideoInfo.cs | 4 +- .../MediaInfo/ProbeProvider.cs | 2 +- .../MediaInfo/SubtitleScheduledTask.cs | 4 +- .../MediaInfo/VideoImageProvider.cs | 4 +- .../JsonOmdbNotAvailableInt32Converter.cs | 2 +- .../Plugins/Omdb/OmdbProvider.cs | 6 +- .../StudioImages/StudiosImageProvider.cs | 2 +- .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 2 +- .../Tmdb/BoxSets/TmdbBoxSetProvider.cs | 2 +- .../Tmdb/Movies/TmdbMovieImageProvider.cs | 2 +- .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 2 +- .../Tmdb/People/TmdbPersonImageProvider.cs | 2 +- .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 2 +- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 2 +- .../Tmdb/TV/TmdbSeasonImageProvider.cs | 4 +- .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 2 +- .../Tmdb/TV/TmdbSeriesImageProvider.cs | 2 +- .../Plugins/Tmdb/TmdbClientManager.cs | 4 +- .../TV/SeriesMetadataService.cs | 4 +- .../Parsers/BaseNfoParser.cs | 4 +- .../Providers/BaseNfoProvider.cs | 4 +- .../Savers/BaseNfoSaver.cs | 2 +- .../AlphanumericComparator.cs | 6 +- .../Dlna/StreamBuilderTests.cs | 2 +- .../Video/Format3DTests.cs | 2 +- .../Video/MultiVersionTests.cs | 4 +- .../Manager/MetadataServiceTests.cs | 8 +- .../Manager/ProviderManagerTests.cs | 6 +- .../MediaInfo/EmbeddedImageProviderTests.cs | 4 +- .../TypedBaseItem/BaseItemKindTests.cs | 2 +- 206 files changed, 627 insertions(+), 627 deletions(-) diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 8e3a335c66..e2e3b2d8b6 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -191,7 +191,7 @@ namespace Emby.Dlna.Didl private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null) { - if (streamInfo == null) + if (streamInfo is null) { var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user); @@ -263,7 +263,7 @@ namespace Emby.Dlna.Didl .FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) && i.Method == SubtitleDeliveryMethod.External); - if (subtitleProfile == null) + if (subtitleProfile is null) { return false; } @@ -392,7 +392,7 @@ namespace Emby.Dlna.Didl var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal)); - var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType) + var mimeType = mediaProfile is null || string.IsNullOrEmpty(mediaProfile.MimeType) ? MimeTypes.GetMimeType(filename) : mediaProfile.MimeType; @@ -533,7 +533,7 @@ namespace Emby.Dlna.Didl { writer.WriteStartElement(string.Empty, "res", NsDidl); - if (streamInfo == null) + if (streamInfo is null) { var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user); @@ -598,7 +598,7 @@ namespace Emby.Dlna.Didl var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal)); - var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType) + var mimeType = mediaProfile is null || string.IsNullOrEmpty(mediaProfile.MimeType) ? MimeTypes.GetMimeType(filename) : mediaProfile.MimeType; @@ -695,7 +695,7 @@ namespace Emby.Dlna.Didl } // Not a samsung device - if (secAttribute == null) + if (secAttribute is null) { return; } @@ -909,7 +909,7 @@ namespace Emby.Dlna.Didl AddValue(writer, "dc", "creator", artist, NsDc); // If it doesn't support album artists (musicvideo), then tag as both - if (hasAlbumArtists == null) + if (hasAlbumArtists is null) { AddAlbumArtist(writer, artist); } @@ -973,7 +973,7 @@ namespace Emby.Dlna.Didl { ImageDownloadInfo imageInfo = GetImageInfo(item); - if (imageInfo == null) + if (imageInfo is null) { return; } @@ -1036,7 +1036,7 @@ namespace Emby.Dlna.Didl { var imageInfo = GetImageInfo(item); - if (imageInfo == null) + if (imageInfo is null) { return; } @@ -1116,7 +1116,7 @@ namespace Emby.Dlna.Didl private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item) { - if (item == null) + if (item is null) { return null; } diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 2ea2c130f2..31b999f64c 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)); - if (profile == null) + if (profile is null) { _logger.LogInformation("No matching device profile found. The default will need to be used. \n{@Profile}", deviceInfo); } @@ -172,7 +172,7 @@ namespace Emby.Dlna ArgumentNullException.ThrowIfNull(headers); var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification)); - if (profile == null) + if (profile is null) { _logger.LogDebug("No matching device profile found. {@Headers}", headers); } @@ -272,7 +272,7 @@ namespace Emby.Dlna var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase)); - if (info == null) + if (info is null) { return null; } @@ -470,7 +470,7 @@ namespace Emby.Dlna var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant(); var stream = _assembly.GetManifestResourceStream(resource); - if (stream == null) + if (stream is null) { return null; } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 15021c19d6..bcd7ece085 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -199,7 +199,7 @@ namespace Emby.Dlna.Main { try { - if (_communicationsServer == null) + if (_communicationsServer is null) { var enableMultiSocketBinding = OperatingSystem.IsWindows() || OperatingSystem.IsLinux(); diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 34981bd3f4..1c7730187b 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -220,14 +220,14 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); - if (command == null) + if (command is null) { return false; } var service = GetServiceRenderingControl(); - if (service == null) + if (service is null) { return false; } @@ -260,14 +260,14 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); - if (command == null) + if (command is null) { return; } var service = GetServiceRenderingControl(); - if (service == null) + if (service is null) { throw new InvalidOperationException("Unable to find service"); } @@ -291,14 +291,14 @@ namespace Emby.Dlna.PlayTo var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); - if (command == null) + if (command is null) { return; } var service = GetAvTransportService(); - if (service == null) + if (service is null) { throw new InvalidOperationException("Unable to find service"); } @@ -324,7 +324,7 @@ namespace Emby.Dlna.PlayTo _logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header); var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); - if (command == null) + if (command is null) { return; } @@ -337,7 +337,7 @@ namespace Emby.Dlna.PlayTo var service = GetAvTransportService(); - if (service == null) + if (service is null) { throw new InvalidOperationException("Unable to find service"); } @@ -381,7 +381,7 @@ namespace Emby.Dlna.PlayTo _logger.LogDebug("{PropertyName} - SetNextAvTransport Uri: {Url} DlnaHeaders: {Header}", Properties.Name, url, header); var command = avCommands.ServiceActions.FirstOrDefault(c => string.Equals(c.Name, "SetNextAVTransportURI", StringComparison.OrdinalIgnoreCase)); - if (command == null) + if (command is null) { return; } @@ -394,7 +394,7 @@ namespace Emby.Dlna.PlayTo var service = GetAvTransportService(); - if (service == null) + if (service is null) { throw new InvalidOperationException("Unable to find service"); } @@ -418,13 +418,13 @@ namespace Emby.Dlna.PlayTo private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken) { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play"); - if (command == null) + if (command is null) { return Task.CompletedTask; } var service = GetAvTransportService(); - if (service == null) + if (service is null) { throw new InvalidOperationException("Unable to find service"); } @@ -440,7 +440,7 @@ namespace Emby.Dlna.PlayTo public async Task SetPlay(CancellationToken cancellationToken) { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - if (avCommands == null) + if (avCommands is null) { return; } @@ -455,7 +455,7 @@ namespace Emby.Dlna.PlayTo var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); - if (command == null) + if (command is null) { return; } @@ -479,7 +479,7 @@ namespace Emby.Dlna.PlayTo var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); - if (command == null) + if (command is null) { return; } @@ -513,7 +513,7 @@ namespace Emby.Dlna.PlayTo var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - if (avCommands == null) + if (avCommands is null) { return; } @@ -538,7 +538,7 @@ namespace Emby.Dlna.PlayTo var currentObject = tuple.Track; - if (tuple.Success && currentObject == null) + if (tuple.Success && currentObject is null) { currentObject = await GetMediaInfo(avCommands, cancellationToken).ConfigureAwait(false); } @@ -607,14 +607,14 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); - if (command == null) + if (command is null) { return; } var service = GetServiceRenderingControl(); - if (service == null) + if (service is null) { return; } @@ -626,7 +626,7 @@ namespace Emby.Dlna.PlayTo rendererCommands.BuildPost(command, service.ServiceType), cancellationToken: cancellationToken).ConfigureAwait(false); - if (result == null || result.Document == null) + if (result is null || result.Document is null) { return; } @@ -657,14 +657,14 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); - if (command == null) + if (command is null) { return; } var service = GetServiceRenderingControl(); - if (service == null) + if (service is null) { return; } @@ -676,7 +676,7 @@ namespace Emby.Dlna.PlayTo rendererCommands.BuildPost(command, service.ServiceType), cancellationToken: cancellationToken).ConfigureAwait(false); - if (result == null || result.Document == null) + if (result is null || result.Document is null) { return; } @@ -691,13 +691,13 @@ namespace Emby.Dlna.PlayTo private async Task GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken) { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo"); - if (command == null) + if (command is null) { return null; } var service = GetAvTransportService(); - if (service == null) + if (service is null) { return null; } @@ -709,7 +709,7 @@ namespace Emby.Dlna.PlayTo avCommands.BuildPost(command, service.ServiceType), cancellationToken: cancellationToken).ConfigureAwait(false); - if (result == null || result.Document == null) + if (result is null || result.Document is null) { return null; } @@ -731,19 +731,19 @@ namespace Emby.Dlna.PlayTo private async Task GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken) { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo"); - if (command == null) + if (command is null) { return null; } var service = GetAvTransportService(); - if (service == null) + if (service is null) { throw new InvalidOperationException("Unable to find service"); } var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - if (rendererCommands == null) + if (rendererCommands is null) { return null; } @@ -755,14 +755,14 @@ namespace Emby.Dlna.PlayTo rendererCommands.BuildPost(command, service.ServiceType), cancellationToken: cancellationToken).ConfigureAwait(false); - if (result == null || result.Document == null) + if (result is null || result.Document is null) { return null; } var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault(); - if (track == null) + if (track is null) { return null; } @@ -778,7 +778,7 @@ namespace Emby.Dlna.PlayTo track = result.Document.Descendants("CurrentURI").FirstOrDefault(); - if (track == null) + if (track is null) { return null; } @@ -801,21 +801,21 @@ namespace Emby.Dlna.PlayTo private async Task<(bool Success, UBaseObject Track)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken) { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo"); - if (command == null) + if (command is null) { return (false, null); } var service = GetAvTransportService(); - if (service == null) + if (service is null) { throw new InvalidOperationException("Unable to find service"); } var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - if (rendererCommands == null) + if (rendererCommands is null) { return (false, null); } @@ -827,7 +827,7 @@ namespace Emby.Dlna.PlayTo rendererCommands.BuildPost(command, service.ServiceType), cancellationToken: cancellationToken).ConfigureAwait(false); - if (result == null || result.Document == null) + if (result is null || result.Document is null) { return (false, null); } @@ -858,7 +858,7 @@ namespace Emby.Dlna.PlayTo var track = result.Document.Descendants("TrackMetaData").FirstOrDefault(); - if (track == null) + if (track is null) { // If track is null, some vendors do this, use GetMediaInfo instead. return (true, null); @@ -882,7 +882,7 @@ namespace Emby.Dlna.PlayTo _logger.LogError(ex, "Uncaught exception while parsing xml"); } - if (uPnpResponse == null) + if (uPnpResponse is null) { _logger.LogError("Failed to parse xml: \n {Xml}", trackString); return (true, null); @@ -985,7 +985,7 @@ namespace Emby.Dlna.PlayTo } var avService = GetAvTransportService(); - if (avService == null) + if (avService is null) { return null; } @@ -995,7 +995,7 @@ namespace Emby.Dlna.PlayTo var httpClient = new DlnaHttpClient(_logger, _httpClientFactory); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); - if (document == null) + if (document is null) { return null; } @@ -1017,7 +1017,7 @@ namespace Emby.Dlna.PlayTo } var avService = GetServiceRenderingControl(); - if (avService == null) + if (avService is null) { throw new ArgumentException("Device AvService is null"); } @@ -1027,7 +1027,7 @@ namespace Emby.Dlna.PlayTo var httpClient = new DlnaHttpClient(_logger, _httpClientFactory); _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); - if (document == null) + if (document is null) { return null; } @@ -1062,7 +1062,7 @@ namespace Emby.Dlna.PlayTo var ssdpHttpClient = new DlnaHttpClient(logger, httpClientFactory); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); - if (document == null) + if (document is null) { return null; } @@ -1149,13 +1149,13 @@ namespace Emby.Dlna.PlayTo foreach (var services in document.Descendants(UPnpNamespaces.Ud.GetName("serviceList"))) { - if (services == null) + if (services is null) { continue; } var servicesList = services.Descendants(UPnpNamespaces.Ud.GetName("service")); - if (servicesList == null) + if (servicesList is null) { continue; } @@ -1212,14 +1212,14 @@ namespace Emby.Dlna.PlayTo var previousMediaInfo = CurrentMediaInfo; CurrentMediaInfo = mediaInfo; - if (mediaInfo == null) + if (mediaInfo is null) { if (previousMediaInfo != null) { OnPlaybackStop(previousMediaInfo); } } - else if (previousMediaInfo == null) + else if (previousMediaInfo is null) { if (state != TransportState.STOPPED) { diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 65367e24f2..fca5fc2a0d 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -164,7 +164,7 @@ namespace Emby.Dlna.PlayTo } streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item == null) + if (streamInfo.Item is null) { return; } @@ -199,7 +199,7 @@ namespace Emby.Dlna.PlayTo { var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item == null) + if (streamInfo.Item is null) { return; } @@ -210,7 +210,7 @@ namespace Emby.Dlna.PlayTo var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); - var duration = mediaSource == null + var duration = mediaSource is null ? _device.Duration?.Ticks : mediaSource.RunTimeTicks; @@ -865,7 +865,7 @@ namespace Emby.Dlna.PlayTo throw new ObjectDisposedException(GetType().Name); } - if (_device == null) + if (_device is null) { return Task.CompletedTask; } diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 294bda5b6a..f4a9a90af4 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -176,10 +176,10 @@ namespace Emby.Dlna.PlayTo var controller = sessionInfo.SessionControllers.OfType().FirstOrDefault(); - if (controller == null) + if (controller is null) { var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false); - if (device == null) + if (device is null) { _logger.LogError("Ignoring device as xml response is invalid."); return; diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index 391dda1479..bb58a5d1b6 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -71,7 +71,7 @@ namespace Emby.Dlna.Ssdp { lock (_syncLock) { - if (_listenerCount > 0 && _deviceLocator == null && _commsServer != null) + if (_listenerCount > 0 && _deviceLocator is null && _commsServer != null) { _deviceLocator = new SsdpDeviceLocator(_commsServer); @@ -97,7 +97,7 @@ namespace Emby.Dlna.Ssdp { var originalHeaders = e.DiscoveredDevice.ResponseHeaders; - var headerDict = originalHeaders == null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); + var headerDict = originalHeaders is null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); @@ -116,7 +116,7 @@ namespace Emby.Dlna.Ssdp { var originalHeaders = e.DiscoveredDevice.ResponseHeaders; - var headerDict = originalHeaders == null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); + var headerDict = originalHeaders is null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 11256dafde..d2c430415a 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -436,7 +436,7 @@ namespace Emby.Drawing /// public string? GetImageCacheTag(User user) { - if (user.ProfileImage == null) + if (user.ProfileImage is null) { return null; } diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs index 6e491185d8..7a241aab2b 100644 --- a/Emby.Naming/AudioBook/AudioBookListResolver.cs +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -75,7 +75,7 @@ namespace Emby.Naming.AudioBook foreach (var group in groupedBy) { - if (group.Key.ChapterNumber == null && group.Key.PartNumber == null) + if (group.Key.ChapterNumber is null && group.Key.PartNumber is null) { if (group.Count() > 1 || haveChaptersOrPages) { diff --git a/Emby.Naming/AudioBook/AudioBookNameParser.cs b/Emby.Naming/AudioBook/AudioBookNameParser.cs index 120482bc2c..97b34199e0 100644 --- a/Emby.Naming/AudioBook/AudioBookNameParser.cs +++ b/Emby.Naming/AudioBook/AudioBookNameParser.cs @@ -33,7 +33,7 @@ namespace Emby.Naming.AudioBook var match = new Regex(expression, RegexOptions.IgnoreCase).Match(name); if (match.Success) { - if (result.Name == null) + if (result.Name is null) { var value = match.Groups["name"]; if (value.Success) diff --git a/Emby.Naming/ExternalFiles/ExternalPathParser.cs b/Emby.Naming/ExternalFiles/ExternalPathParser.cs index 1fa4fa5371..cfe3256e3d 100644 --- a/Emby.Naming/ExternalFiles/ExternalPathParser.cs +++ b/Emby.Naming/ExternalFiles/ExternalPathParser.cs @@ -94,7 +94,7 @@ namespace Emby.Naming.ExternalFiles // Try to translate to three character code var culture = _localizationManager.FindLanguageInfo(currentSliceWithoutSeparator); - if (culture != null && pathInfo.Language == null) + if (culture != null && pathInfo.Language is null) { pathInfo.Language = culture.ThreeLetterISOLanguageName; extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 11f82525f3..1366bdb2c3 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -26,7 +26,7 @@ namespace Emby.Naming.Video // Filter out all extras, otherwise they could cause stacks to not be resolved // See the unit test TestStackedWithTrailer var nonExtras = videoInfos - .Where(i => i.ExtraType == null) + .Where(i => i.ExtraType is null) .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList(); @@ -42,7 +42,7 @@ namespace Emby.Naming.Video continue; } - if (current.ExtraType == null) + if (current.ExtraType is null) { standaloneMedia.Add(current); } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index de8e177d8b..858e9dd2f5 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -87,7 +87,7 @@ namespace Emby.Naming.Video name = cleanDateTimeResult.Name; year = cleanDateTimeResult.Year; - if (extraResult.ExtraType == null + if (extraResult.ExtraType is null && TryCleanString(name, namingOptions, out var newName)) { name = newName; diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs index 668c059b47..3763b1e922 100644 --- a/Emby.Notifications/NotificationEntryPoint.cs +++ b/Emby.Notifications/NotificationEntryPoint.cs @@ -141,7 +141,7 @@ namespace Emby.Notifications lock (_libraryChangedSyncLock) { - if (_libraryUpdateTimer == null) + if (_libraryUpdateTimer is null) { _libraryUpdateTimer = new Timer( LibraryUpdateTimerCallback, diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index ac90cc8ec5..2b9d2c5774 100644 --- a/Emby.Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -68,7 +68,7 @@ namespace Emby.Notifications var users = GetUserIds(request, options) .Select(i => _userManager.GetUserById(i)) - .Where(i => relatedItem == null || relatedItem.IsVisibleStandalone(i)) + .Where(i => relatedItem is null || relatedItem.IsVisibleStandalone(i)) .ToArray(); var title = request.Name; diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 26b4649dd8..5876c9b4c6 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.AppBase { IConfigurationFactory factory = Activator.CreateInstance(); - if (_configurationFactories == null) + if (_configurationFactories is null) { _configurationFactories = new[] { factory }; } @@ -306,7 +306,7 @@ namespace Emby.Server.Implementations.AppBase configurationManager._configurationStores, i => string.Equals(i.Key, k, StringComparison.OrdinalIgnoreCase)); - if (configurationInfo == null) + if (configurationInfo is null) { throw new ResourceNotFoundException("Configuration with key " + k + " not found."); } diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index f923e59efb..1c84776059 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.AppBase Span newBytes = stream.GetBuffer().AsSpan(0, (int)stream.Length); // If the file didn't exist before, or if something has changed, re-save - if (buffer == null || !newBytes.SequenceEqual(buffer)) + if (buffer is null || !newBytes.SequenceEqual(buffer)) { var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path)); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 6837cce5cc..d7eb7e3aca 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -129,7 +129,7 @@ namespace Emby.Server.Implementations.Channels public Task DeleteItem(BaseItem item) { var internalChannel = _libraryManager.GetItemById(item.ChannelId); - if (internalChannel == null) + if (internalChannel is null) { throw new ArgumentException(nameof(item.ChannelId)); } @@ -253,7 +253,7 @@ namespace Emby.Server.Implementations.Channels if (query.StartIndex.HasValue || query.Limit.HasValue) { int startIndex = query.StartIndex ?? 0; - int count = query.Limit == null ? totalCount - startIndex : Math.Min(query.Limit.Value, totalCount - startIndex); + int count = query.Limit is null ? totalCount - startIndex : Math.Min(query.Limit.Value, totalCount - startIndex); all = all.GetRange(startIndex, count); } @@ -355,7 +355,7 @@ namespace Emby.Server.Implementations.Channels { var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasourceinfos.json"); - if (mediaSources == null || mediaSources.Count == 0) + if (mediaSources is null || mediaSources.Count == 0) { try { @@ -447,7 +447,7 @@ namespace Emby.Server.Implementations.Channels var item = _libraryManager.GetItemById(id) as Channel; - if (item == null) + if (item is null) { item = new Channel { @@ -861,7 +861,7 @@ namespace Emby.Server.Implementations.Channels var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false); - if (result == null) + if (result is null) { throw new InvalidOperationException("Channel returned a null result from GetChannelItems"); } @@ -955,7 +955,7 @@ namespace Emby.Server.Implementations.Channels _logger.LogError(ex, "Error retrieving channel item from database"); } - if (item == null) + if (item is null) { item = new T(); isNew = true; @@ -1193,7 +1193,7 @@ namespace Emby.Server.Implementations.Channels var result = GetAllChannels() .FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(channel.ChannelId) || string.Equals(i.Name, channel.Name, StringComparison.OrdinalIgnoreCase)); - if (result == null) + if (result is null) { throw new ResourceNotFoundException("No channel provider found for channel " + channel.Name); } @@ -1206,7 +1206,7 @@ namespace Emby.Server.Implementations.Channels var result = GetAllChannels() .FirstOrDefault(i => internalChannelId.Equals(GetInternalChannelId(i.Name))); - if (result == null) + if (result is null) { throw new ResourceNotFoundException("No channel provider found for channel id " + internalChannelId); } diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 187e0c9b3b..701c7b61dc 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections { var folder = GetCollectionsFolder(false).GetAwaiter().GetResult(); - return folder == null + return folder is null ? Enumerable.Empty() : folder.GetChildren(user, true).OfType(); } @@ -138,7 +138,7 @@ namespace Emby.Server.Implementations.Collections var parentFolder = await GetCollectionsFolder(true).ConfigureAwait(false); - if (parentFolder == null) + if (parentFolder is null) { throw new ArgumentException(nameof(parentFolder)); } @@ -216,7 +216,7 @@ namespace Emby.Server.Implementations.Collections { var item = _libraryManager.GetItemById(id); - if (item == null) + if (item is null) { throw new ArgumentException("No item exists with the supplied Id"); } @@ -267,7 +267,7 @@ namespace Emby.Server.Implementations.Collections var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value.Equals(guidId)) || (childItem != null && string.Equals(childItem.Path, i.Path, StringComparison.OrdinalIgnoreCase))); - if (child == null) + if (child is null) { _logger.LogWarning("No collection title exists with the supplied Id"); continue; diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index 736b8125d7..4055b0ba17 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -253,7 +253,7 @@ namespace Emby.Server.Implementations.Data { if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam)) { - if (value == null) + if (value is null) { bindParam.BindNull(); } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 9d44fd9d18..36fdfc8f27 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -691,7 +691,7 @@ namespace Emby.Server.Implementations.Data private string GetPathToSave(string path) { - if (path == null) + if (path is null) { return null; } @@ -890,7 +890,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@UnratedType", item.GetBlockUnratedType().ToString()); - if (topParent == null) + if (topParent is null) { saveItemStatement.TryBindNull("@TopParentId"); } @@ -1414,7 +1414,7 @@ namespace Emby.Server.Implementations.Data var type = _typeMapper.GetType(typeString); - if (type == null) + if (type is null) { return null; } @@ -1433,7 +1433,7 @@ namespace Emby.Server.Implementations.Data } } - if (item == null) + if (item is null) { try { @@ -1444,7 +1444,7 @@ namespace Emby.Server.Implementations.Data } } - if (item == null) + if (item is null) { return null; } @@ -2151,7 +2151,7 @@ namespace Emby.Server.Implementations.Data private static bool EnableJoinUserData(InternalItemsQuery query) { - if (query.User == null) + if (query.User is null) { return false; } @@ -2497,7 +2497,7 @@ namespace Emby.Server.Implementations.Data { var item = query.SimilarTo; - if (item == null) + if (item is null) { return; } @@ -4522,7 +4522,7 @@ namespace Emby.Server.Implementations.Data if (query.ExcludeInheritedTags.Length > 0) { var paramName = "@ExcludeInheritedTags"; - if (statement == null) + if (statement is null) { int index = 0; string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++)); @@ -4732,7 +4732,7 @@ namespace Emby.Server.Implementations.Data return false; } - if (query.User == null) + if (query.User is null) { return false; } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index f6d37421a8..e0e3366b4f 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -235,14 +235,14 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.CanDelete)) { - dto.CanDelete = user == null + dto.CanDelete = user is null ? item.CanDelete() : item.CanDelete(user); } if (options.ContainsField(ItemFields.CanDownload)) { - dto.CanDownload = user == null + dto.CanDownload = user is null ? item.CanDownload() : item.CanDownload(user); } @@ -571,7 +571,7 @@ namespace Emby.Server.Implementations.Dto return null; } }).Where(i => i != null) - .Where(i => user == null ? + .Where(i => user is null ? true : i.IsVisible(user)) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) @@ -1143,7 +1143,7 @@ namespace Emby.Server.Implementations.Dto if (episodeSeries != null) { dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary); - if (dto.ImageTags == null || !dto.ImageTags.ContainsKey(ImageType.Primary)) + if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary)) { AttachPrimaryImageAspectRatio(dto, episodeSeries); } @@ -1193,7 +1193,7 @@ namespace Emby.Server.Implementations.Dto if (series != null) { dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary); - if (dto.ImageTags == null || !dto.ImageTags.ContainsKey(ImageType.Primary)) + if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary)) { AttachPrimaryImageAspectRatio(dto, series); } @@ -1276,7 +1276,7 @@ namespace Emby.Server.Implementations.Dto var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent(); - if (parent == null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is not AggregateFolder && originalItem is not ICollectionFolder && originalItem is not Channel) + if (parent is null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is not AggregateFolder && originalItem is not ICollectionFolder && originalItem is not Channel) { parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault(); } @@ -1315,7 +1315,7 @@ namespace Emby.Server.Implementations.Dto || parent is Series) { parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent; - if (parent == null) + if (parent is null) { break; } @@ -1403,7 +1403,7 @@ namespace Emby.Server.Implementations.Dto { var imageInfo = item.GetImageInfo(ImageType.Primary, 0); - if (imageInfo == null) + if (imageInfo is null) { return null; } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index d5e4a636ef..fb6f52332f 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.EntryPoints lock (_libraryChangedSyncLock) { - if (LibraryUpdateTimer == null) + if (LibraryUpdateTimer is null) { LibraryUpdateTimer = new Timer( LibraryUpdateTimerCallback, @@ -227,7 +227,7 @@ namespace Emby.Server.Implementations.EntryPoints lock (_libraryChangedSyncLock) { - if (LibraryUpdateTimer == null) + if (LibraryUpdateTimer is null) { LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite); } @@ -254,7 +254,7 @@ namespace Emby.Server.Implementations.EntryPoints lock (_libraryChangedSyncLock) { - if (LibraryUpdateTimer == null) + if (LibraryUpdateTimer is null) { LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite); } diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 82c8d3ab6d..ce81f9e42f 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.EntryPoints lock (_syncLock) { - if (_updateTimer == null) + if (_updateTimer is null) { _updateTimer = new Timer( UpdateTimerCallback, diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index d095248fab..b1a99853ad 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -164,7 +164,7 @@ namespace Emby.Server.Implementations.HttpServer ReadResult result = await reader.ReadAsync().ConfigureAwait(false); ReadOnlySequence buffer = result.Buffer; - if (OnReceive == null) + if (OnReceive is null) { // Tell the PipeReader how much of the buffer we have consumed reader.AdvanceTo(buffer.End); @@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (stub == null) + if (stub is null) { _logger.LogError("Error processing web socket message"); return; diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 6326208f71..6337952c12 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.IO return; } - if (_timer == null) + if (_timer is null) { _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); } @@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.IO { BaseItem? item = null; - while (item == null && !string.IsNullOrEmpty(path)) + while (item is null && !string.IsNullOrEmpty(path)) { item = _libraryManager.FindByPath(path, null); @@ -192,7 +192,7 @@ namespace Emby.Server.Implementations.IO { item = item.GetOwner() ?? item.GetParent(); - if (item == null) + if (item is null) { break; } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index b688af5286..e8f6d2d236 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.Library { get { - if (_rootFolder == null) + if (_rootFolder is null) { lock (_rootFolderSyncLock) { @@ -656,7 +656,7 @@ namespace Emby.Server.Implementations.Library if (parent != null) { - var multiItemResolvers = resolvers == null ? MultiItemResolvers : resolvers.OfType().ToArray(); + var multiItemResolvers = resolvers is null ? MultiItemResolvers : resolvers.OfType().ToArray(); foreach (var resolver in multiItemResolvers) { @@ -770,11 +770,11 @@ namespace Emby.Server.Implementations.Library public Folder GetUserRootFolder() { - if (_userRootFolder == null) + if (_userRootFolder is null) { lock (_userRootFolderSyncLock) { - if (_userRootFolder == null) + if (_userRootFolder is null) { var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; @@ -792,7 +792,7 @@ namespace Emby.Server.Implementations.Library _logger.LogError(ex, "Error creating UserRootFolder {Path}", newItemId); } - if (tmpItem == null) + if (tmpItem is null) { _logger.LogDebug("Creating new userRootFolder with DeepCopy"); tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy(); @@ -961,7 +961,7 @@ namespace Emby.Server.Implementations.Library var path = getPathFn(name); var id = GetItemByNameId(path); var item = GetItemById(id) as T; - if (item == null) + if (item is null) { item = new T { @@ -1627,7 +1627,7 @@ namespace Emby.Server.Implementations.Library // Get an existing item by Id video = GetItemById(info.ItemId.Value) as Video; - if (video == null) + if (video is null) { _logger.LogError("Unable to locate item with Id {ID}.", info.ItemId.Value); } @@ -1639,7 +1639,7 @@ namespace Emby.Server.Implementations.Library // Try to resolve the path into a video video = ResolvePath(_fileSystem.GetFileSystemInfo(info.Path)) as Video; - if (video == null) + if (video is null) { _logger.LogError("Intro resolver returned null for {Path}.", info.Path); } @@ -1711,7 +1711,7 @@ namespace Emby.Server.Implementations.Library foreach (var (name, sortOrder) in orderBy) { var comparer = GetComparer(name, user); - if (comparer == null) + if (comparer is null) { continue; } @@ -2010,7 +2010,7 @@ namespace Emby.Server.Implementations.Library { var parent = item.GetParent(); - if (parent == null || parent is AggregateFolder) + if (parent is null || parent is AggregateFolder) { break; } @@ -2018,7 +2018,7 @@ namespace Emby.Server.Implementations.Library item = parent; } - if (item == null) + if (item is null) { return new List(); } @@ -2032,7 +2032,7 @@ namespace Emby.Server.Implementations.Library { var parent = item.GetParent(); - if (parent == null || parent is AggregateFolder) + if (parent is null || parent is AggregateFolder) { break; } @@ -2040,7 +2040,7 @@ namespace Emby.Server.Implementations.Library item = parent; } - if (item == null) + if (item is null) { return new List(); } @@ -2064,7 +2064,7 @@ namespace Emby.Server.Implementations.Library .Find(folder => folder is CollectionFolder) as CollectionFolder; } - return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); + return collectionFolder is null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); } public string GetContentType(BaseItem item) @@ -2129,7 +2129,7 @@ namespace Emby.Server.Implementations.Library private string GetTopFolderContentType(BaseItem item) { - if (item == null) + if (item is null) { return null; } @@ -2137,7 +2137,7 @@ namespace Emby.Server.Implementations.Library while (!item.ParentId.Equals(default)) { var parent = item.GetParent(); - if (parent == null || parent is AggregateFolder) + if (parent is null || parent is AggregateFolder) { break; } @@ -2177,7 +2177,7 @@ namespace Emby.Server.Implementations.Library var refresh = false; - if (item == null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase)) + if (item is null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase)) { Directory.CreateDirectory(path); @@ -2225,7 +2225,7 @@ namespace Emby.Server.Implementations.Library var isNew = false; - if (item == null) + if (item is null) { Directory.CreateDirectory(path); @@ -2289,7 +2289,7 @@ namespace Emby.Server.Implementations.Library var isNew = false; - if (item == null) + if (item is null) { Directory.CreateDirectory(path); @@ -2362,7 +2362,7 @@ namespace Emby.Server.Implementations.Library var isNew = false; - if (item == null) + if (item is null) { Directory.CreateDirectory(path); @@ -2459,7 +2459,7 @@ namespace Emby.Server.Implementations.Library episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming); // Resolve from parent folder if it's not the Season folder var parent = episode.GetParent(); - if (episodeInfo == null && parent.GetType() == typeof(Folder)) + if (episodeInfo is null && parent.GetType() == typeof(Folder)) { episodeInfo = resolver.Resolve(parent.Path, true, null, null, isAbsoluteNaming); if (episodeInfo != null) @@ -2620,7 +2620,7 @@ namespace Emby.Server.Implementations.Library public IEnumerable FindExtras(BaseItem owner, IReadOnlyList fileSystemChildren, IDirectoryService directoryService) { var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions); - if (ownerVideoInfo == null) + if (ownerVideoInfo is null) { yield break; } @@ -2754,7 +2754,7 @@ namespace Emby.Server.Implementations.Library } }) .Where(i => i != null) - .Where(i => query.User == null ? + .Where(i => query.User is null ? true : i.IsVisible(query.User)) .ToList(); diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 20624cc7ae..1010335512 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.Library } } - if (mediaInfo == null) + if (mediaInfo is null) { if (addProbeDelay) { @@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.Library var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audioStream == null || audioStream.Index == -1) + if (audioStream is null || audioStream.Index == -1) { mediaSource.DefaultAudioStreamIndex = null; } diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index bfccc7db72..e2474f5081 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -383,7 +383,7 @@ namespace Emby.Server.Implementations.Library var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; - var audioLangage = defaultAudioIndex == null + var audioLangage = defaultAudioIndex is null ? null : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); @@ -417,13 +417,13 @@ namespace Emby.Server.Implementations.Library public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) { // Item would only be null if the app didn't supply ItemId as part of the live stream open request - var mediaType = item == null ? MediaType.Video : item.MediaType; + var mediaType = item is null ? MediaType.Video : item.MediaType; if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { - var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item); + var userData = item is null ? new UserItemData() : _userDataManager.GetUserData(user, item); - var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections; + var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections; SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection); SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); @@ -543,7 +543,7 @@ namespace Emby.Server.Implementations.Library var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audioStream == null || audioStream.Index == -1) + if (audioStream is null || audioStream.Index == -1) { mediaSource.DefaultAudioStreamIndex = null; } @@ -638,7 +638,7 @@ namespace Emby.Server.Implementations.Library } } - if (mediaInfo == null) + if (mediaInfo is null) { if (addProbeDelay) { @@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.Library var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audioStream == null || audioStream.Index == -1) + if (audioStream is null || audioStream.Index == -1) { mediaSource.DefaultAudioStreamIndex = null; } diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 4100a74a58..590d37b926 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.Library // Make sure DateCreated and DateModified have values var fileInfo = directoryService.GetFile(item.Path); - if (fileInfo == null) + if (fileInfo is null) { return false; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 7a6aea9c1f..bc1f5d08a8 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio // Use regular audio type for mixed libraries, owned items and music if (isMixedCollectionType || - args.Parent == null || + args.Parent is null || isMusicCollectionType) { item = new MediaBrowser.Controller.Entities.Audio.Audio(); @@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio // TODO: Allow GetMultiDiscMovie in here var result = ResolveMultipleAudio(args.Parent, args.GetActualFileSystemChildren(), parseName); - if (result == null || result.Items.Count != 1 || result.Items[0] is not AudioBook item) + if (result is null || result.Items.Count != 1 || result.Items[0] is not AudioBook item) { return null; } diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index b2a7abb1bd..cb377136ad 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.Library.Resolvers videoType = VideoType.Dvd; } - if (videoType == null) + if (videoType is null) { continue; } @@ -93,7 +93,7 @@ namespace Emby.Server.Implementations.Library.Resolvers videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName); } - if (videoInfo == null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions))) + if (videoInfo is null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions))) { return null; } diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs index 408e640f9d..30c52e19d3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs @@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.Library.Resolvers public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out ExtraType? extraType) { var extraResult = GetExtraInfo(path, _namingOptions); - if (extraResult.ExtraType == null) + if (extraResult.ExtraType is null) { extraType = null; return false; diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs index 0799622825..1c2de912aa 100644 --- a/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Library.Resolvers { base.SetInitialItemValues(item, args); - item.IsRoot = args.Parent == null; + item.IsRoot = args.Parent is null; } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 84d4688aff..6b18ad237f 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (string.IsNullOrEmpty(collectionType)) { // Owned items will be caught by the video extra resolver - if (args.Parent == null) + if (args.Parent is null) { return null; } @@ -127,10 +127,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } // ignore extras - return movie?.ExtraType == null ? movie : null; + return movie?.ExtraType is null ? movie : null; } - if (args.Parent == null) + if (args.Parent is null) { return base.Resolve(args); } @@ -205,7 +205,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (string.IsNullOrEmpty(collectionType)) { // Owned items should just use the plain video type - if (parent == null) + if (parent is null) { return ResolveVideos