diff options
139 files changed, 4134 insertions, 424 deletions
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 143873266..7bcaed70c 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -2,7 +2,7 @@ name: $(Date:yyyyMMdd)$(Rev:.r) variables: - name: TestProjects - value: 'tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj' + value: 'tests/**/*Tests.csproj' - name: RestoreBuildProjects value: 'Jellyfin.Server/Jellyfin.Server.csproj' @@ -132,12 +132,12 @@ jobs: inputs: packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion - - task: VSTest@2 inputs: testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun testAssemblyVer2: | # Required when testSelector == TestAssemblies - **\bin\$(BuildConfiguration)\**\*test*.dll + **\bin\$(BuildConfiguration)\**\*tests.dll + **\bin\$(BuildConfiguration)\**\*test.dll !**\obj\** !**\xunit.runner.visualstudio.testadapter.dll !**\xunit.runner.visualstudio.dotnetcore.testadapter.dll @@ -147,6 +147,8 @@ jobs: codeCoverageEnabled: True # Optional configuration: 'Debug' # Optional publishRunAttachments: true # Optional + testRunTitle: $(Agent.JobName) + otherConsoleOptions: '/platform:x64 /Framework:.NETCoreApp,Version=v3.1 /logger:console;verbosity="normal"' - job: main_build_win displayName: Publish Windows diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 69e68660d..a2105889b 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Video; +using MediaBrowser.Model.Entities; namespace Emby.Naming.Common { @@ -176,7 +177,7 @@ namespace Emby.Naming.Common CleanDateTimes = new[] { - @"(.+[^ _\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9][0-9]|20[0-1][0-9])([ _\,\.\(\)\[\]\-][^0-9]|$)" + @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](\d{4})([ _\,\.\(\)\[\]\-][^\d]|).*(\d{4})*" }; CleanStrings = new[] @@ -423,126 +424,126 @@ namespace Emby.Naming.Common { new ExtraRule { - ExtraType = "trailer", + ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Filename, Token = "trailer", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "trailer", + ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Suffix, Token = "-trailer", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "trailer", + ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Suffix, Token = ".trailer", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "trailer", + ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Suffix, Token = "_trailer", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "trailer", + ExtraType = ExtraType.Trailer, RuleType = ExtraRuleType.Suffix, Token = " trailer", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "sample", + ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Filename, Token = "sample", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "sample", + ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Suffix, Token = "-sample", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "sample", + ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Suffix, Token = ".sample", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "sample", + ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Suffix, Token = "_sample", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "sample", + ExtraType = ExtraType.Sample, RuleType = ExtraRuleType.Suffix, Token = " sample", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "themesong", + ExtraType = ExtraType.ThemeSong, RuleType = ExtraRuleType.Filename, Token = "theme", MediaType = MediaType.Audio }, new ExtraRule { - ExtraType = "scene", + ExtraType = ExtraType.Scene, RuleType = ExtraRuleType.Suffix, Token = "-scene", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "clip", + ExtraType = ExtraType.Clip, RuleType = ExtraRuleType.Suffix, Token = "-clip", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "interview", + ExtraType = ExtraType.Interview, RuleType = ExtraRuleType.Suffix, Token = "-interview", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "behindthescenes", + ExtraType = ExtraType.BehindTheScenes, RuleType = ExtraRuleType.Suffix, Token = "-behindthescenes", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "deletedscene", + ExtraType = ExtraType.DeletedScene, RuleType = ExtraRuleType.Suffix, Token = "-deleted", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "featurette", + ExtraType = ExtraType.Clip, RuleType = ExtraRuleType.Suffix, Token = "-featurette", MediaType = MediaType.Video }, new ExtraRule { - ExtraType = "short", + ExtraType = ExtraType.Clip, RuleType = ExtraRuleType.Suffix, Token = "-short", MediaType = MediaType.Video diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index ed1670adf..900b9694c 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -27,7 +27,7 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" /> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index be028c662..fcd4b65c7 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -7,7 +7,7 @@ using System.Text.RegularExpressions; namespace Emby.Naming.Video { /// <summary> - /// http://kodi.wiki/view/Advancedsettings.xml#video + /// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />. /// </summary> public class CleanStringParser { diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs index 989ede206..ea9a6d6c2 100644 --- a/Emby.Naming/Video/ExtraResolver.cs +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -23,7 +23,7 @@ namespace Emby.Naming.Video { return _options.VideoExtraRules .Select(i => GetExtraInfo(path, i)) - .FirstOrDefault(i => !string.IsNullOrEmpty(i.ExtraType)) ?? new ExtraResult(); + .FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult(); } private ExtraResult GetExtraInfo(string path, ExtraRule rule) diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs index 6081a4494..4e991d685 100644 --- a/Emby.Naming/Video/ExtraResult.cs +++ b/Emby.Naming/Video/ExtraResult.cs @@ -1,6 +1,8 @@ #pragma warning disable CS1591 #pragma warning disable SA1600 +using MediaBrowser.Model.Entities; + namespace Emby.Naming.Video { public class ExtraResult @@ -9,7 +11,7 @@ namespace Emby.Naming.Video /// Gets or sets the type of the extra. /// </summary> /// <value>The type of the extra.</value> - public string ExtraType { get; set; } + public ExtraType? ExtraType { get; set; } /// <summary> /// Gets or sets the rule. diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs index cfce79fd0..cfaa84ed6 100644 --- a/Emby.Naming/Video/ExtraRule.cs +++ b/Emby.Naming/Video/ExtraRule.cs @@ -1,7 +1,8 @@ #pragma warning disable CS1591 #pragma warning disable SA1600 -using Emby.Naming.Common; +using MediaBrowser.Model.Entities; +using MediaType = Emby.Naming.Common.MediaType; namespace Emby.Naming.Video { @@ -17,7 +18,7 @@ namespace Emby.Naming.Video /// Gets or sets the type of the extra. /// </summary> /// <value>The type of the extra.</value> - public string ExtraType { get; set; } + public ExtraType ExtraType { get; set; } /// <summary> /// Gets or sets the type of the rule. diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs index bbf399677..95868e89d 100644 --- a/Emby.Naming/Video/StubResolver.cs +++ b/Emby.Naming/Video/StubResolver.cs @@ -14,14 +14,14 @@ namespace Emby.Naming.Video { if (path == null) { - return default(StubResult); + return default; } var extension = Path.GetExtension(path); if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { - return default(StubResult); + return default; } var result = new StubResult() diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index 250a1ec45..90c798da1 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -1,3 +1,5 @@ +using MediaBrowser.Model.Entities; + namespace Emby.Naming.Video { /// <summary> @@ -30,10 +32,10 @@ namespace Emby.Naming.Video public int? Year { get; set; } /// <summary> - /// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc. + /// Gets or sets the type of the extra, e.g. trailer, theme song, behind the scenes, etc. /// </summary> /// <value>The type of the extra.</value> - public string ExtraType { get; set; } + public ExtraType? ExtraType { get; set; } /// <summary> /// Gets or sets the extra rule. diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 5a32846bf..87498000c 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Common; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; namespace Emby.Naming.Video @@ -32,7 +33,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 => string.IsNullOrEmpty(i.ExtraType)) + .Where(i => i.ExtraType == null) .Select(i => new FileSystemMetadata { FullName = i.Path, @@ -79,7 +80,7 @@ namespace Emby.Naming.Video } var standaloneMedia = remainingFiles - .Where(i => string.IsNullOrEmpty(i.ExtraType)) + .Where(i => i.ExtraType == null) .ToList(); foreach (var media in standaloneMedia) @@ -148,7 +149,7 @@ namespace Emby.Naming.Video if (list.Count == 1) { var trailers = remainingFiles - .Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)) + .Where(i => i.ExtraType == ExtraType.Trailer) .ToList(); list[0].Extras.AddRange(trailers); @@ -229,7 +230,7 @@ namespace Emby.Naming.Video } return remainingFiles - .Where(i => !string.IsNullOrEmpty(i.ExtraType)) + .Where(i => i.ExtraType == null) .Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) .ToList(); } diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 5a93e1eaf..41b79697c 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -94,7 +94,7 @@ namespace Emby.Naming.Video { var cleanDateTimeResult = CleanDateTime(name); - if (string.IsNullOrEmpty(extraResult.ExtraType)) + if (extraResult.ExtraType == null) { name = CleanString(cleanDateTimeResult.Name).Name; } diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index 23b7a819b..29ed3c5f7 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -26,9 +26,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> - <PackageReference Include="SerilogAnalyzer" Version="0.15.0" /> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index b622a3167..ac8af66a2 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs index a30e93912..b03c4d182 100644 --- a/Emby.Server.Implementations/Activity/ActivityManager.cs +++ b/Emby.Server.Implementations/Activity/ActivityManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Linq; diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 7be72319e..633343bb6 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a179c1b15..0bb1d832f 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Concurrent; @@ -177,11 +178,7 @@ namespace Emby.Server.Implementations /// Gets the plugins. /// </summary> /// <value>The plugins.</value> - public IPlugin[] Plugins - { - get => _plugins; - protected set => _plugins = value; - } + public IReadOnlyList<IPlugin> Plugins => _plugins; /// <summary> /// Gets or sets the logger factory. @@ -1056,7 +1053,7 @@ namespace Emby.Server.Implementations } ConfigurationManager.AddParts(GetExports<IConfigurationFactory>()); - Plugins = GetExports<IPlugin>() + _plugins = GetExports<IPlugin>() .Select(LoadPlugin) .Where(i => i != null) .ToArray(); @@ -1705,9 +1702,9 @@ namespace Emby.Server.Implementations /// <param name="plugin">The plugin.</param> public void RemovePlugin(IPlugin plugin) { - var list = Plugins.ToList(); + var list = _plugins.ToList(); list.Remove(plugin); - Plugins = list.ToArray(); + _plugins = list.ToArray(); } /// <summary> diff --git a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs index 93000ae12..15aee63a0 100644 --- a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs +++ b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System.Collections.Generic; using MediaBrowser.Common.Configuration; diff --git a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs index 6016fed07..aae416b37 100644 --- a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs +++ b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs index 62aeb9bcb..fe64f1b15 100644 --- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs +++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 6e1baddfe..de2e123af 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Concurrent; diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index 2712fc8c5..36e0e5e26 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Linq; diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index 5774c0415..039e2c138 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index 1fa556ec9..8006b8694 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 1d7c11989..efdef8481 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 776074b72..de83b023d 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Cryptography /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> protected virtual void Dispose(bool disposing) { if (_disposed) diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 0654132f4..b7f643819 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 2a8f2d6b3..8a5387e9b 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Threading; diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 5c094ddd2..2c2f19cd3 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index d474f1c6b..8087419ce 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index c87793072..55c24ccc0 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 22955850a..f6c37e4e5 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index a042320c9..c82c93ffc 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index f0d43e665..ff75efa59 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Globalization; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 2393f1f45..ef7317050 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index bfa49ac5f..f8b754151 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Diagnostics; diff --git a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs b/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs index 02ad3c1a8..219f73c78 100644 --- a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs +++ b/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using MediaBrowser.Model.Diagnostics; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 3d622b3fc..fcf0360c7 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 7ae6f38a1..03cbe00b7 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,9 +29,9 @@ <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" /> - <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" /> - <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" /> - <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" /> + <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" /> + <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.0" /> <PackageReference Include="Mono.Nat" Version="2.0.0" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" /> <PackageReference Include="sharpcompress" Version="0.24.0" /> @@ -51,10 +51,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" /> - <PackageReference Include="SerilogAnalyzer" Version="0.15.0" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" /> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> diff --git a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs index d69b0909d..a6eb1152f 100644 --- a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Linq; diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index e290c62e1..4e4ef3be0 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 5f938e59a..f85d52dbc 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index dbb3503c4..e0aa18e89 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Linq; diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index e431da148..3e22080fc 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index c1c8c3eb3..1795651fd 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 2aefc9fe5..b0126f7fa 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -218,7 +219,6 @@ namespace Emby.Server.Implementations.HttpServer case FileNotFoundException _: case ResourceNotFoundException _: return 404; case MethodNotAllowedException _: return 405; - case RemoteServiceUnavailableException _: return 502; default: return 500; } } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index a62b4e7af..cefcaa835 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 501593725..1c3496e5d 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Threading; diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 8b9028f6b..7cb113a58 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 58421aaf1..03b5b748d 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Linq; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 129faeaab..e8884bca0 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 166952c64..a6a0f5b03 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs index 3150f3367..5be144452 100644 --- a/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs +++ b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 namespace Emby.Server.Implementations.IO { diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 4b5b11f01..cf92ddbcd 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index b1fb8cc63..7777efc3b 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Concurrent; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 9568f62df..e27081c45 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs index e6696b8c4..574b63ae6 100644 --- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs +++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.IO; diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index 40b397edc..c99018e40 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Buffers; diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index fd50f156a..acf3a3b23 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 94f60ea62..ab036eca7 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -70,9 +70,9 @@ namespace Emby.Server.Implementations.Library byte[] calculatedHash = _cryptographyProvider.ComputeHash( readyHash.Id, passwordbytes, - readyHash.Salt); + readyHash.Salt.ToArray()); - if (calculatedHash.SequenceEqual(readyHash.Hash)) + if (readyHash.Hash.SequenceEqual(calculatedHash)) { success = true; } @@ -148,17 +148,18 @@ namespace Emby.Server.Implementations.Library // TODO: make use of iterations parameter? PasswordHash passwordHash = PasswordHash.Parse(user.Password); + var salt = passwordHash.Salt.ToArray(); return new PasswordHash( passwordHash.Id, _cryptographyProvider.ComputeHash( passwordHash.Id, Encoding.UTF8.GetBytes(str), - passwordHash.Salt), - passwordHash.Salt, + salt), + salt, passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); } - public byte[] GetHashed(User user, string str) + public ReadOnlySpan<byte> GetHashed(User user, string str) { if (string.IsNullOrEmpty(user.Password)) { @@ -170,7 +171,7 @@ namespace Emby.Server.Implementations.Library return _cryptographyProvider.ComputeHash( passwordHash.Id, Encoding.UTF8.GetBytes(str), - passwordHash.Salt); + passwordHash.Salt.ToArray()); } } } diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 9a7186898..3eb64c29c 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Globalization; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index cee51479e..ae3cdece9 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Concurrent; @@ -2558,7 +2559,7 @@ namespace Emby.Server.Implementations.Library if (currentVideo != null) { - files.AddRange(currentVideo.Extras.Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path))); + files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path))); } var resolvers = new IItemResolver[] @@ -2608,7 +2609,7 @@ namespace Emby.Server.Implementations.Library if (currentVideo != null) { - files.AddRange(currentVideo.Extras.Where(i => !string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path))); + files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path))); } return ResolvePaths(files, directoryService, null, new LibraryOptions(), null) @@ -2712,7 +2713,7 @@ namespace Emby.Server.Implementations.Library if (!string.Equals(newPath, path, StringComparison.Ordinal)) { - if (to.IndexOf('/') != -1) + if (to.IndexOf('/', StringComparison.Ordinal) != -1) { newPath = newPath.Replace('\\', '/'); } @@ -2733,30 +2734,7 @@ namespace Emby.Server.Implementations.Library var result = resolver.GetExtraInfo(item.Path); - if (string.Equals(result.ExtraType, "deletedscene", StringComparison.OrdinalIgnoreCase)) - { - item.ExtraType = ExtraType.DeletedScene; - } - else if (string.Equals(result.ExtraType, "behindthescenes", StringComparison.OrdinalIgnoreCase)) - { - item.ExtraType = ExtraType.BehindTheScenes; - } - else if (string.Equals(result.ExtraType, "interview", StringComparison.OrdinalIgnoreCase)) - { - item.ExtraType = ExtraType.Interview; - } - else if (string.Equals(result.ExtraType, "scene", StringComparison.OrdinalIgnoreCase)) - { - item.ExtraType = ExtraType.Scene; - } - else if (string.Equals(result.ExtraType, "sample", StringComparison.OrdinalIgnoreCase)) - { - item.ExtraType = ExtraType.Sample; - } - else - { - item.ExtraType = ExtraType.Clip; - } + item.ExtraType = result.ExtraType; } public List<PersonInfo> GetPeople(InternalPeopleQuery query) diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index ed7d8aa40..f28f4a538 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 70d5bd9f4..e310065b2 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 6b9f4d052..1652ad974 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 1ec578371..29af6670b 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 9d4bd9e59..7e3b27a12 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index c4bb861b8..43302bb3f 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 0b93ebeb8..1e2e0704c 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index a71ae8250..e1eb23652 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index a68562fc2..5e672f221 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 1030ed39d..eca60b133 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 7cc9eabc8..e39d85bc9 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs index 62268fce9..6404d6476 100644 --- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 11d6c737a..76ae14720 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 071681b08..f1fb35d9a 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Concurrent; diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 1b9c317d8..656eeb145 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Concurrent; @@ -485,7 +486,7 @@ namespace Emby.Server.Implementations.Library var hash = _cryptoProvider.ComputeHash( passwordHash.Id, Encoding.UTF8.GetBytes(password), - passwordHash.Salt); + passwordHash.Salt.ToArray()); success = passwordHash.Hash.SequenceEqual(hash); } diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 322819b05..935deb71c 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index e1dce82ff..6b25e3df0 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -5,7 +5,7 @@ "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès", "Books": "Livres", - "CameraImageUploadedFrom": "Une nouvelle image de caméra a été chargée depuis {0}", + "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}", "Channels": "Chaînes", "ChapterNameValue": "Chapitre {0}", "Collections": "Collections", diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index c10fbe58f..b91c98ca0 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -1,23 +1,23 @@ { "Albums": "Album-album", "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", + "Application": "Aplikasi", "Artists": "Artis-artis", "AuthenticationSucceededWithUserName": "{0} successfully authenticated", "Books": "Buku-buku", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", + "Channels": "Saluran", "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", + "Collections": "Koleksi", "DeviceOfflineWithName": "{0} has disconnected", "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", + "FailedLoginAttemptWithUserName": "Cubaan log masuk gagal dari {0}", "Favorites": "Favorites", "Folders": "Folders", - "Genres": "Genres", + "Genres": "Genre-genre", "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continue Watching", + "HeaderCameraUploads": "Muatnaik Kamera", + "HeaderContinueWatching": "Terus Menonton", "HeaderFavoriteAlbums": "Favorite Albums", "HeaderFavoriteArtists": "Favorite Artists", "HeaderFavoriteEpisodes": "Favorite Episodes", @@ -39,8 +39,8 @@ "MessageServerConfigurationUpdated": "Server configuration has been updated", "MixedContent": "Mixed content", "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", + "Music": "Muzik", + "MusicVideos": "Video muzik", "NameInstallFailed": "{0} installation failed", "NameSeasonNumber": "Season {0}", "NameSeasonUnknown": "Season Unknown", @@ -68,8 +68,8 @@ "PluginUninstalledWithName": "{0} was uninstalled", "PluginUpdatedWithName": "{0} was updated", "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", + "ScheduledTaskFailedWithName": "{0} gagal", + "ScheduledTaskStartedWithName": "{0} bermula", "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "Shows": "Series", "Songs": "Songs", @@ -78,7 +78,7 @@ "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", "Sync": "Sync", - "System": "System", + "System": "Sistem", "TvShows": "TV Shows", "User": "User", "UserCreatedWithName": "User {0} has been created", @@ -92,6 +92,6 @@ "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", - "ValueSpecialEpisodeName": "Special - {0}", - "VersionNumber": "Version {0}" + "ValueSpecialEpisodeName": "Khas - {0}", + "VersionNumber": "Versi {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -0,0 +1 @@ +{} diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 73ffaa53d..38cdb0998 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -19,7 +19,7 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" /> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index a41112191..166e22909 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -27,10 +27,10 @@ <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" /> - <PackageReference Include="SerilogAnalyzer" Version="0.15.0" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" /> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> @@ -39,8 +39,8 @@ <ItemGroup> <PackageReference Include="CommandLineParser" Version="2.6.0" /> - <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.0.1" /> - <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.1" /> + <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.0" /> + <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.0" /> <PackageReference Include="Serilog.AspNetCore" Version="3.2.0" /> <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" /> diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 6d5a5b783..4b9bb8010 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -1,6 +1,13 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1402 +#pragma warning disable SA1600 +#pragma warning disable SA1649 + using System; +using System.Buffers; using System.Collections.Generic; using System.Globalization; +using System.Text.Json; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -54,7 +61,7 @@ namespace MediaBrowser.Api.Playback public class GetBitrateTestBytes { [ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")] - public long Size { get; set; } + public int Size { get; set; } public GetBitrateTestBytes() { @@ -72,7 +79,6 @@ namespace MediaBrowser.Api.Playback private readonly INetworkManager _networkManager; private readonly IMediaEncoder _mediaEncoder; private readonly IUserManager _userManager; - private readonly IJsonSerializer _json; private readonly IAuthorizationContext _authContext; public MediaInfoService( @@ -85,7 +91,6 @@ namespace MediaBrowser.Api.Playback INetworkManager networkManager, IMediaEncoder mediaEncoder, IUserManager userManager, - IJsonSerializer json, IAuthorizationContext authContext) : base(logger, serverConfigurationManager, httpResultFactory) { @@ -95,20 +100,35 @@ namespace MediaBrowser.Api.Playback _networkManager = networkManager; _mediaEncoder = mediaEncoder; _userManager = userManager; - _json = json; _authContext = authContext; } public object Get(GetBitrateTestBytes request) { - var bytes = new byte[request.Size]; + const int MaxSize = 10_000_000; + + var size = request.Size; + + if (size <= 0) + { + throw new ArgumentException($"The requested size ({size}) is equal to or smaller than 0.", nameof(request)); + } - for (var i = 0; i < bytes.Length; i++) + if (size > MaxSize) { - bytes[i] = 0; + throw new ArgumentException($"The requested size ({size}) is larger than the max allowed value ({MaxSize}).", nameof(request)); } - return ResultFactory.GetResult(null, bytes, "application/octet-stream"); + byte[] buffer = ArrayPool<byte>.Shared.Rent(size); + try + { + new Random().NextBytes(buffer); + return ResultFactory.GetResult(null, buffer, "application/octet-stream"); + } + finally + { + ArrayPool<byte>.Shared.Return(buffer); + } } public async Task<object> Get(GetPlaybackInfo request) @@ -166,8 +186,7 @@ namespace MediaBrowser.Api.Playback public void Post(CloseMediaSource request) { - var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId); - Task.WaitAll(task); + _mediaSourceManager.CloseLiveStream(request.LiveStreamId).GetAwaiter().GetResult(); } public async Task<PlaybackInfoResponse> GetPlaybackInfo(GetPostedPlaybackInfo request) @@ -176,7 +195,7 @@ namespace MediaBrowser.Api.Playback var profile = request.DeviceProfile; - //Logger.LogInformation("GetPostedPlaybackInfo profile: {profile}", _json.SerializeToString(profile)); + Logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile); if (profile == null) { @@ -215,9 +234,7 @@ namespace MediaBrowser.Api.Playback StartTimeTicks = request.StartTimeTicks, SubtitleStreamIndex = request.SubtitleStreamIndex, UserId = request.UserId, - OpenToken = mediaSource.OpenToken, - //EnableMediaProbe = request.EnableMediaProbe - + OpenToken = mediaSource.OpenToken }).ConfigureAwait(false); info.MediaSources = new MediaSourceInfo[] { openStreamResult.MediaSource }; @@ -251,9 +268,8 @@ namespace MediaBrowser.Api.Playback { // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it // Should we move this directly into MediaSourceManager? - - var json = _json.SerializeToString(obj); - return _json.DeserializeFromString<T>(json); + var json = JsonSerializer.SerializeToUtf8Bytes(obj); + return JsonSerializer.Deserialize<T>(json); } private async Task<PlaybackInfoResponse> GetPlaybackInfo(Guid id, Guid userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null) @@ -294,7 +310,7 @@ namespace MediaBrowser.Api.Playback result.MediaSources = new MediaSourceInfo[] { mediaSource }; } - if (result.MediaSources.Length == 0) + if (result.MediaSources.Count == 0) { if (!result.ErrorCode.HasValue) { @@ -311,7 +327,8 @@ namespace MediaBrowser.Api.Playback return result; } - private void SetDeviceSpecificData(Guid itemId, + private void SetDeviceSpecificData( + Guid itemId, PlaybackInfoResponse result, DeviceProfile profile, AuthorizationInfo auth, @@ -339,7 +356,8 @@ namespace MediaBrowser.Api.Playback SortMediaSources(result, maxBitrate); } - private void SetDeviceSpecificData(BaseItem item, + private void SetDeviceSpecificData( + BaseItem item, MediaSourceInfo mediaSource, DeviceProfile profile, AuthorizationInfo auth, @@ -383,10 +401,12 @@ namespace MediaBrowser.Api.Playback { mediaSource.SupportsDirectPlay = false; } + if (!enableDirectStream) { mediaSource.SupportsDirectStream = false; } + if (!enableTranscoding) { mediaSource.SupportsTranscoding = false; @@ -434,9 +454,9 @@ namespace MediaBrowser.Api.Playback } // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) + ? streamBuilder.BuildAudioItem(options) + : streamBuilder.BuildVideoItem(options); if (streamInfo == null || !streamInfo.IsDirectStream) { @@ -473,9 +493,9 @@ namespace MediaBrowser.Api.Playback } // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) + ? streamBuilder.BuildAudioItem(options) + : streamBuilder.BuildVideoItem(options); if (streamInfo == null || !streamInfo.IsDirectStream) { @@ -493,9 +513,9 @@ namespace MediaBrowser.Api.Playback options.MaxBitrate = GetMaxBitrate(maxBitrate, user); // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) + ? streamBuilder.BuildAudioItem(options) + : streamBuilder.BuildVideoItem(options); if (streamInfo != null) { @@ -510,10 +530,12 @@ namespace MediaBrowser.Api.Playback { mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; } + if (!allowAudioStreamCopy) { mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; } + mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; } @@ -609,14 +631,11 @@ namespace MediaBrowser.Api.Playback }).ThenBy(i => { - switch (i.Protocol) + return i.Protocol switch { - case MediaProtocol.File: - return 0; - default: - return 1; - } - + MediaProtocol.File => 0, + _ => 1, + }; }).ThenBy(i => { if (maxBitrate.HasValue) diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index 9cba9df13..bcac5093e 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -74,7 +74,6 @@ namespace MediaBrowser.Api.Playback [Authenticated] public class UniversalAudioService : BaseApiService { - private readonly ILoggerFactory _loggerFactory; private readonly EncodingHelper _encodingHelper; public UniversalAudioService( @@ -243,7 +242,6 @@ namespace MediaBrowser.Api.Playback NetworkManager, MediaEncoder, UserManager, - JsonSerializer, AuthorizationContext) { Request = Request diff --git a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs index 344aecf53..828415c18 100644 --- a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs +++ b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; diff --git a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs index 4c4060096..9b4ed772d 100644 --- a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs +++ b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs index caf2edd83..7773596af 100644 --- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs +++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index 19b8be47a..3477c1c04 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -15,24 +16,24 @@ namespace MediaBrowser.Common.Cryptography public class PasswordHash { private readonly Dictionary<string, string> _parameters; + private readonly byte[] _salt; + private readonly byte[] _hash; public PasswordHash(string id, byte[] hash) : this(id, hash, Array.Empty<byte>()) { - } public PasswordHash(string id, byte[] hash, byte[] salt) : this(id, hash, salt, new Dictionary<string, string>()) { - } public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters) { Id = id; - Hash = hash; - Salt = salt; + _hash = hash; + _salt = salt; _parameters = parameters; } @@ -45,25 +46,24 @@ namespace MediaBrowser.Common.Cryptography /// <summary> /// Gets the additional parameters used by the hash function. /// </summary> - /// <value></value> public IReadOnlyDictionary<string, string> Parameters => _parameters; /// <summary> /// Gets the salt used for hashing the password. /// </summary> /// <value>Returns the salt used for hashing the password.</value> - public byte[] Salt { get; } + public ReadOnlySpan<byte> Salt => _salt; /// <summary> /// Gets the hashed password. /// </summary> /// <value>Return the hashed password.</value> - public byte[] Hash { get; } + public ReadOnlySpan<byte> Hash => _hash; public static PasswordHash Parse(string hashString) { - string[] splitted = hashString.Split('$'); // The string should at least contain the hash function and the hash itself + string[] splitted = hashString.Split('$'); if (splitted.Length < 3) { throw new ArgumentException("String doesn't contain enough segments", nameof(hashString)); @@ -77,7 +77,7 @@ namespace MediaBrowser.Common.Cryptography // Optional parameters Dictionary<string, string> parameters = new Dictionary<string, string>(); - if (splitted[index].IndexOf('=') != -1) + if (splitted[index].IndexOf('=', StringComparison.Ordinal) != -1) { foreach (string paramset in splitted[index++].Split(',')) { @@ -98,6 +98,7 @@ namespace MediaBrowser.Common.Cryptography byte[] hash; byte[] salt; + // Check if the string also contains a salt if (splitted.Length - index == 2) { @@ -141,14 +142,14 @@ namespace MediaBrowser.Common.Cryptography .Append(Id); SerializeParameters(str); - if (Salt.Length != 0) + if (_salt.Length != 0) { str.Append('$') - .Append(Hex.Encode(Salt, false)); + .Append(Hex.Encode(_salt, false)); } return str.Append('$') - .Append(Hex.Encode(Hash, false)).ToString(); + .Append(Hex.Encode(_hash, false)).ToString(); } } } diff --git a/MediaBrowser.Common/Events/EventHelper.cs b/MediaBrowser.Common/Events/EventHelper.cs index b67315df6..c9d3226ac 100644 --- a/MediaBrowser.Common/Events/EventHelper.cs +++ b/MediaBrowser.Common/Events/EventHelper.cs @@ -1,15 +1,13 @@ -#pragma warning disable CS1591 - using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace MediaBrowser.Common.Events { - // TODO: @bond Remove /// <summary> - /// Class EventHelper + /// Class EventHelper. /// </summary> + // TODO: @bond Remove public static class EventHelper { /// <summary> @@ -40,7 +38,7 @@ namespace MediaBrowser.Common.Events /// <summary> /// Queues the event. /// </summary> - /// <typeparam name="T"></typeparam> + /// <typeparam name="T">Argument type for the <c>handler</c>.</typeparam> /// <param name="handler">The handler.</param> /// <param name="sender">The sender.</param> /// <param name="args">The args.</param> diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs index 33473c2be..08964420e 100644 --- a/MediaBrowser.Common/Extensions/BaseExtensions.cs +++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs @@ -1,12 +1,12 @@ using System; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; -using System.Security.Cryptography; namespace MediaBrowser.Common.Extensions { /// <summary> - /// Class BaseExtensions + /// Class BaseExtensions. /// </summary> public static class BaseExtensions { @@ -30,10 +30,13 @@ namespace MediaBrowser.Common.Extensions /// <returns><see cref="Guid" />.</returns> public static Guid GetMD5(this string str) { +#pragma warning disable CA5351 using (var provider = MD5.Create()) { return new Guid(provider.ComputeHash(Encoding.Unicode.GetBytes(str))); } + +#pragma warning restore CA5351 } } } diff --git a/MediaBrowser.Common/Extensions/CopyToExtensions.cs b/MediaBrowser.Common/Extensions/CopyToExtensions.cs index 78a73f07e..2ecbc6539 100644 --- a/MediaBrowser.Common/Extensions/CopyToExtensions.cs +++ b/MediaBrowser.Common/Extensions/CopyToExtensions.cs @@ -5,7 +5,7 @@ namespace MediaBrowser.Common.Extensions /// <summary> /// Provides <c>CopyTo</c> extensions methods for <see cref="IReadOnlyList{T}" />. /// </summary> - public static class CollectionExtensions + public static class CopyToExtensions { /// <summary> /// Copies all the elements of the current collection to the specified list @@ -14,7 +14,7 @@ namespace MediaBrowser.Common.Extensions /// <param name="source">The current collection that is the source of the elements.</param> /// <param name="destination">The list that is the destination of the elements copied from the current collection.</param> /// <param name="index">A 32-bit integer that represents the index in <c>destination</c> at which copying begins.</param> - /// <typeparam name="T"></typeparam> + /// <typeparam name="T">The type of the array.</typeparam> public static void CopyTo<T>(this IReadOnlyList<T> source, IList<T> destination, int index = 0) { for (int i = 0; i < source.Count; i++) diff --git a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs new file mode 100644 index 000000000..48e758ee4 --- /dev/null +++ b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs @@ -0,0 +1,26 @@ +using System; + +namespace MediaBrowser.Common.Extensions +{ + /// <summary> + /// Class MethodNotAllowedException. + /// </summary> + public class MethodNotAllowedException : Exception + { + /// <summary> + /// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class. + /// </summary> + public MethodNotAllowedException() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class. + /// </summary> + /// <param name="message">The message.</param> + public MethodNotAllowedException(string message) + : base(message) + { + } + } +} diff --git a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs new file mode 100644 index 000000000..4e5d4e9ca --- /dev/null +++ b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs @@ -0,0 +1,26 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1600 + +using System; + +namespace MediaBrowser.Common.Extensions +{ + public class RateLimitExceededException : Exception + { + /// <summary> + /// Initializes a new instance of the <see cref="RateLimitExceededException" /> class. + /// </summary> + public RateLimitExceededException() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="RateLimitExceededException" /> class. + /// </summary> + /// <param name="message">The message.</param> + public RateLimitExceededException(string message) + : base(message) + { + } + } +} diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs index 9b064a40d..22130c5a1 100644 --- a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs +++ b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs @@ -1,11 +1,9 @@ -#pragma warning disable CS1591 - using System; namespace MediaBrowser.Common.Extensions { /// <summary> - /// Class ResourceNotFoundException + /// Class ResourceNotFoundException. /// </summary> public class ResourceNotFoundException : Exception { @@ -14,7 +12,6 @@ namespace MediaBrowser.Common.Extensions /// </summary> public ResourceNotFoundException() { - } /// <summary> @@ -24,66 +21,6 @@ namespace MediaBrowser.Common.Extensions public ResourceNotFoundException(string message) : base(message) { - - } - } - - /// <summary> - /// Class MethodNotAllowedException - /// </summary> - public class MethodNotAllowedException : Exception - { - /// <summary> - /// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class. - /// </summary> - public MethodNotAllowedException() - { - - } - - /// <summary> - /// Initializes a new instance of the <see cref="MethodNotAllowedException" /> class. - /// </summary> - /// <param name="message">The message.</param> - public MethodNotAllowedException(string message) - : base(message) - { - - } - } - - public class RemoteServiceUnavailableException : Exception - { - public RemoteServiceUnavailableException() - { - - } - - public RemoteServiceUnavailableException(string message) - : base(message) - { - - } - } - - public class RateLimitExceededException : Exception - { - /// <summary> - /// Initializes a new instance of the <see cref="RateLimitExceededException" /> class. - /// </summary> - public RateLimitExceededException() - { - - } - - /// <summary> - /// Initializes a new instance of the <see cref="RateLimitExceededException" /> class. - /// </summary> - /// <param name="message">The message.</param> - public RateLimitExceededException(string message) - : base(message) - { - } } } diff --git a/MediaBrowser.Common/Hex.cs b/MediaBrowser.Common/Hex.cs index b2d9aea3a..559109f74 100644 --- a/MediaBrowser.Common/Hex.cs +++ b/MediaBrowser.Common/Hex.cs @@ -14,11 +14,11 @@ namespace MediaBrowser.Common internal const int LastHexSymbol = 0x66; // 102: f /// <summary> - /// Map from an ASCII char to its hex value shifted, + /// Gets a map from an ASCII char to its hex value shifted, /// e.g. <c>b</c> -> 11. 0xFF means it's not a hex symbol. /// </summary> - /// <value></value> - internal static ReadOnlySpan<byte> HexLookup => new byte[] { + internal static ReadOnlySpan<byte> HexLookup => new byte[] + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -29,10 +29,10 @@ namespace MediaBrowser.Common }; /// <summary> - /// Encodes <c>bytes</c> as a hex string. + /// Encodes each element of the specified bytes as its hexadecimal string representation. /// </summary> - /// <param name="bytes"></param> - /// <param name="lowercase"></param> + /// <param name="bytes">An array of bytes.</param> + /// <param name="lowercase"><c>true</c> to use lowercase hexadecimal characters; otherwise <c>false</c>.</param> /// <returns><c>bytes</c> as a hex string.</returns> public static string Encode(ReadOnlySpan<byte> bytes, bool lowercase = true) { diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 6668e98aa..68a24aaba 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -8,11 +8,16 @@ using Microsoft.Extensions.DependencyInjection; namespace MediaBrowser.Common { /// <summary> - /// An interface to be implemented by the applications hosting a kernel + /// An interface to be implemented by the applications hosting a kernel. /// </summary> public interface IApplicationHost { /// <summary> + /// Occurs when [has pending restart changed]. + /// </summary> + event EventHandler HasPendingRestartChanged; + + /// <summary> /// Gets the name. /// </summary> /// <value>The name.</value> @@ -25,13 +30,13 @@ namespace MediaBrowser.Common string SystemId { get; } /// <summary> - /// Gets or sets a value indicating whether this instance has pending kernel reload. + /// Gets a value indicating whether this instance has pending kernel reload. /// </summary> /// <value><c>true</c> if this instance has pending kernel reload; otherwise, <c>false</c>.</value> bool HasPendingRestart { get; } /// <summary> - /// Gets or sets a value indicating whether this instance is currently shutting down. + /// Gets a value indicating whether this instance is currently shutting down. /// </summary> /// <value><c>true</c> if this instance is shutting down; otherwise, <c>false</c>.</value> bool IsShuttingDown { get; } @@ -43,27 +48,12 @@ namespace MediaBrowser.Common bool CanSelfRestart { get; } /// <summary> - /// Get the version class of the system. + /// Gets the version class of the system. /// </summary> /// <value><see cref="PackageVersionClass.Release" /> or <see cref="PackageVersionClass.Beta" />.</value> PackageVersionClass SystemUpdateLevel { get; } /// <summary> - /// Occurs when [has pending restart changed]. - /// </summary> - event EventHandler HasPendingRestartChanged; - - /// <summary> - /// Notifies the pending restart. - /// </summary> - void NotifyPendingRestart(); - - /// <summary> - /// Restarts this instance. - /// </summary> - void Restart(); - - /// <summary> /// Gets the application version. /// </summary> /// <value>The application version.</value> @@ -88,6 +78,22 @@ namespace MediaBrowser.Common string ApplicationUserAgentAddress { get; } /// <summary> + /// Gets the plugins. + /// </summary> + /// <value>The plugins.</value> + IReadOnlyList<IPlugin> Plugins { get; } + + /// <summary> + /// Notifies the pending restart. + /// </summary> + void NotifyPendingRestart(); + + /// <summary> + /// Restarts this instance. + /// </summary> + void Restart(); + + /// <summary> /// Gets the exports. /// </summary> /// <typeparam name="T">The type.</typeparam> @@ -98,22 +104,17 @@ namespace MediaBrowser.Common /// <summary> /// Resolves this instance. /// </summary> - /// <typeparam name="T"></typeparam> + /// <typeparam name="T">The <c>Type</c>.</typeparam> /// <returns>``0.</returns> T Resolve<T>(); /// <summary> /// Shuts down. /// </summary> + /// <returns>A task.</returns> Task Shutdown(); /// <summary> - /// Gets the plugins. - /// </summary> - /// <value>The plugins.</value> - IPlugin[] Plugins { get; } - - /// <summary> /// Removes the plugin. /// </summary> /// <param name="plugin">The plugin.</param> @@ -122,6 +123,8 @@ namespace MediaBrowser.Common /// <summary> /// Inits this instance. /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <returns>A task.</returns> Task InitAsync(IServiceCollection serviceCollection); /// <summary> diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 889fbfa5a..567fcdda1 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -12,8 +12,8 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.0.0" /> - <PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.0" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.0" /> + <PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" /> </ItemGroup> <ItemGroup> @@ -27,9 +27,16 @@ <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> - <PropertyGroup> - <!-- We need at least C# 7.1 for the "default literal" feature--> - <LangVersion>latest</LangVersion> + <!-- Code analyzers--> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <!-- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> --> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <!-- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> --> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> + <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> </PropertyGroup> <ItemGroup> diff --git a/MediaBrowser.Common/Net/CustomHeaderNames.cs b/MediaBrowser.Common/Net/CustomHeaderNames.cs index 5ca9897eb..8cc48c55f 100644 --- a/MediaBrowser.Common/Net/CustomHeaderNames.cs +++ b/MediaBrowser.Common/Net/CustomHeaderNames.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 namespace MediaBrowser.Common.Net { diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 18c4b181f..8207a45f3 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -8,11 +9,22 @@ using Microsoft.Net.Http.Headers; namespace MediaBrowser.Common.Net { /// <summary> - /// Class HttpRequestOptions + /// Class HttpRequestOptions. /// </summary> public class HttpRequestOptions { /// <summary> + /// Initializes a new instance of the <see cref="HttpRequestOptions"/> class. + /// </summary> + public HttpRequestOptions() + { + RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + CacheMode = CacheMode.None; + DecompressionMethod = CompressionMethod.Deflate; + } + + /// <summary> /// Gets or sets the URL. /// </summary> /// <value>The URL.</value> @@ -71,14 +83,17 @@ namespace MediaBrowser.Common.Net public string RequestContentType { get; set; } public string RequestContent { get; set; } + public byte[] RequestContentBytes { get; set; } public bool BufferContent { get; set; } public bool LogErrorResponseBody { get; set; } + public bool EnableKeepAlive { get; set; } public CacheMode CacheMode { get; set; } + public TimeSpan CacheLength { get; set; } public bool EnableDefaultUserAgent { get; set; } @@ -89,17 +104,6 @@ namespace MediaBrowser.Common.Net return value; } - - /// <summary> - /// Initializes a new instance of the <see cref="HttpRequestOptions"/> class. - /// </summary> - public HttpRequestOptions() - { - RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - CacheMode = CacheMode.None; - DecompressionMethod = CompressionMethod.Deflate; - } } public enum CacheMode diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs index 0de034b0e..d711ad64a 100644 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.IO; using System.Net; @@ -8,10 +6,25 @@ using System.Net.Http.Headers; namespace MediaBrowser.Common.Net { /// <summary> - /// Class HttpResponseInfo + /// Class HttpResponseInfo. /// </summary> public class HttpResponseInfo : IDisposable { +#pragma warning disable CS1591 +#pragma warning disable SA1600 + public HttpResponseInfo() + { + } + + public HttpResponseInfo(HttpResponseHeaders headers, HttpContentHeaders contentHeader) + { + Headers = headers; + ContentHeaders = contentHeader; + } + +#pragma warning restore CS1591 +#pragma warning restore SA1600 + /// <summary> /// Gets or sets the type of the content. /// </summary> @@ -60,21 +73,10 @@ namespace MediaBrowser.Common.Net /// <value>The content headers.</value> public HttpContentHeaders ContentHeaders { get; set; } - public HttpResponseInfo() - { - - } - - public HttpResponseInfo(HttpResponseHeaders headers, HttpContentHeaders contentHeader) - { - Headers = headers; - ContentHeaders = contentHeader; - } - /// <inheritdoc /> public void Dispose() { - // Only IDisposable for backwards compatibility + // backwards compatibility } } } diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index 23ba34173..534e22edd 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -1,12 +1,12 @@ using System; using System.IO; -using System.Threading.Tasks; using System.Net.Http; +using System.Threading.Tasks; namespace MediaBrowser.Common.Net { /// <summary> - /// Interface IHttpClient + /// Interface IHttpClient. /// </summary> public interface IHttpClient { diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 0b99dc910..6bd7dd1d6 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -11,20 +12,20 @@ namespace MediaBrowser.Common.Net { event EventHandler NetworkChanged; + Func<string[]> LocalSubnetsFn { get; set; } + /// <summary> - /// Gets a random port number that is currently available + /// Gets a random port number that is currently available. /// </summary> /// <returns>System.Int32.</returns> int GetRandomUnusedTcpPort(); int GetRandomUnusedUdpPort(); - Func<string[]> LocalSubnetsFn { get; set; } - /// <summary> - /// Returns MAC Address from first Network Card in Computer + /// Returns the MAC Address from first Network Card in Computer. /// </summary> - /// <returns>[string] MAC Address</returns> + /// <returns>The MAC Address.</returns> List<PhysicalAddress> GetMacAddresses(); /// <summary> diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 6ef891d66..b24d10ff1 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable SA1402 using System; using System.IO; @@ -8,10 +8,13 @@ using MediaBrowser.Model.Serialization; namespace MediaBrowser.Common.Plugins { + /// <summary> + /// Provides a common base class for all plugins. + /// </summary> public abstract class BasePlugin : IPlugin, IPluginAssembly { /// <summary> - /// Gets the name of the plugin + /// Gets the name of the plugin. /// </summary> /// <value>The name.</value> public abstract string Name { get; } @@ -29,18 +32,24 @@ namespace MediaBrowser.Common.Plugins public virtual Guid Id { get; private set; } /// <summary> - /// Gets the plugin version + /// Gets the plugin version. /// </summary> /// <value>The version.</value> public Version Version { get; private set; } /// <summary> - /// Gets the path to the assembly file + /// Gets the path to the assembly file. /// </summary> /// <value>The assembly file path.</value> public string AssemblyFilePath { get; private set; } /// <summary> + /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed. + /// </summary> + /// <value>The data folder path.</value> + public string DataFolderPath { get; private set; } + + /// <summary> /// Gets the plugin info. /// </summary> /// <returns>PluginInfo.</returns> @@ -62,9 +71,9 @@ namespace MediaBrowser.Common.Plugins /// </summary> public virtual void OnUninstalling() { - } + /// <inheritdoc /> public void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion) { AssemblyFilePath = assemblyFilePath; @@ -72,26 +81,49 @@ namespace MediaBrowser.Common.Plugins Version = assemblyVersion; } + /// <inheritdoc /> public void SetId(Guid assemblyId) { Id = assemblyId; } - - /// <summary> - /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed - /// </summary> - /// <value>The data folder path.</value> - public string DataFolderPath { get; private set; } } /// <summary> - /// Provides a common base class for all plugins + /// Provides a common base class for all plugins. /// </summary> /// <typeparam name="TConfigurationType">The type of the T configuration type.</typeparam> public abstract class BasePlugin<TConfigurationType> : BasePlugin, IHasPluginConfiguration where TConfigurationType : BasePluginConfiguration { /// <summary> + /// The configuration sync lock. + /// </summary> + private readonly object _configurationSyncLock = new object(); + + /// <summary> + /// The save lock. + /// </summary> + private readonly object _configurationSaveLock = new object(); + + private Action<string> _directoryCreateFn; + + /// <summary> + /// The configuration. + /// </summary> + private TConfigurationType _configuration; + + /// <summary> + /// Initializes a new instance of the <see cref="BasePlugin{TConfigurationType}" /> class. + /// </summary> + /// <param name="applicationPaths">The application paths.</param> + /// <param name="xmlSerializer">The XML serializer.</param> + protected BasePlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + { + ApplicationPaths = applicationPaths; + XmlSerializer = xmlSerializer; + } + + /// <summary> /// Gets the application paths. /// </summary> /// <value>The application paths.</value> @@ -104,34 +136,19 @@ namespace MediaBrowser.Common.Plugins protected IXmlSerializer XmlSerializer { get; private set; } /// <summary> - /// Gets the type of configuration this plugin uses + /// Gets the type of configuration this plugin uses. /// </summary> /// <value>The type of the configuration.</value> public Type ConfigurationType => typeof(TConfigurationType); - private Action<string> _directoryCreateFn; - public void SetStartupInfo(Action<string> directoryCreateFn) - { - // hack alert, until the .net core transition is complete - _directoryCreateFn = directoryCreateFn; - } - /// <summary> - /// Gets the name the assembly file + /// Gets the name the assembly file. /// </summary> /// <value>The name of the assembly file.</value> protected string AssemblyFileName => Path.GetFileName(AssemblyFilePath); /// <summary> - /// The _configuration sync lock - /// </summary> - private readonly object _configurationSyncLock = new object(); - /// <summary> - /// The _configuration - /// </summary> - private TConfigurationType _configuration; - /// <summary> - /// Gets the plugin's configuration + /// Gets or sets the plugin's configuration. /// </summary> /// <value>The configuration.</value> public TConfigurationType Configuration @@ -149,55 +166,54 @@ namespace MediaBrowser.Common.Plugins } } } + return _configuration; } - protected set => _configuration = value; - } - private TConfigurationType LoadConfiguration() - { - var path = ConfigurationFilePath; - - try - { - return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path); - } - catch - { - return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType)); - } + protected set => _configuration = value; } /// <summary> - /// Gets the name of the configuration file. Subclasses should override + /// Gets the name of the configuration file. Subclasses should override. /// </summary> /// <value>The name of the configuration file.</value> public virtual string ConfigurationFileName => Path.ChangeExtension(AssemblyFileName, ".xml"); /// <summary> - /// Gets the full path to the configuration file + /// Gets the full path to the configuration file. /// </summary> /// <value>The configuration file path.</value> public string ConfigurationFilePath => Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName); /// <summary> - /// Initializes a new instance of the <see cref="BasePlugin{TConfigurationType}" /> class. + /// Gets the plugin's configuration. /// </summary> - /// <param name="applicationPaths">The application paths.</param> - /// <param name="xmlSerializer">The XML serializer.</param> - protected BasePlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + /// <value>The configuration.</value> + BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration; + + /// <inheritdoc /> + public void SetStartupInfo(Action<string> directoryCreateFn) { - ApplicationPaths = applicationPaths; - XmlSerializer = xmlSerializer; + // hack alert, until the .net core transition is complete + _directoryCreateFn = directoryCreateFn; } - /// <summary> - /// The _save lock - /// </summary> - private readonly object _configurationSaveLock = new object(); + private TConfigurationType LoadConfiguration() + { + var path = ConfigurationFilePath; + + try + { + return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path); + } + catch + { + return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType)); + } + } /// <summary> - /// Saves the current configuration to the file system + /// Saves the current configuration to the file system. /// </summary> public virtual void SaveConfiguration() { @@ -209,12 +225,7 @@ namespace MediaBrowser.Common.Plugins } } - /// <summary> - /// Completely overwrites the current configuration with a new copy - /// Returns true or false indicating success or failure - /// </summary> - /// <param name="configuration">The configuration.</param> - /// <exception cref="ArgumentNullException">configuration</exception> + /// <inheritdoc /> public virtual void UpdateConfiguration(BasePluginConfiguration configuration) { if (configuration == null) @@ -227,12 +238,7 @@ namespace MediaBrowser.Common.Plugins SaveConfiguration(); } - /// <summary> - /// Gets the plugin's configuration - /// </summary> - /// <value>The configuration.</value> - BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration; - + /// <inheritdoc /> public override PluginInfo GetPluginInfo() { var info = base.GetPluginInfo(); @@ -242,10 +248,4 @@ namespace MediaBrowser.Common.Plugins return info; } } - - public interface IPluginAssembly - { - void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion); - void SetId(Guid assemblyId); - } } diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs index 7bd90c964..001ca8be8 100644 --- a/MediaBrowser.Common/Plugins/IPlugin.cs +++ b/MediaBrowser.Common/Plugins/IPlugin.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using MediaBrowser.Model.Plugins; @@ -6,12 +7,12 @@ using MediaBrowser.Model.Plugins; namespace MediaBrowser.Common.Plugins { /// <summary> - /// Interface IPlugin + /// Interface IPlugin. /// </summary> public interface IPlugin { /// <summary> - /// Gets the name of the plugin + /// Gets the name of the plugin. /// </summary> /// <value>The name.</value> string Name { get; } @@ -29,19 +30,19 @@ namespace MediaBrowser.Common.Plugins Guid Id { get; } /// <summary> - /// Gets the plugin version + /// Gets the plugin version. /// </summary> /// <value>The version.</value> Version Version { get; } /// <summary> - /// Gets the path to the assembly file + /// Gets the path to the assembly file. /// </summary> /// <value>The assembly file path.</value> string AssemblyFilePath { get; } /// <summary> - /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed + /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed. /// </summary> /// <value>The data folder path.</value> string DataFolderPath { get; } @@ -61,24 +62,24 @@ namespace MediaBrowser.Common.Plugins public interface IHasPluginConfiguration { /// <summary> - /// Gets the type of configuration this plugin uses + /// Gets the type of configuration this plugin uses. /// </summary> /// <value>The type of the configuration.</value> Type ConfigurationType { get; } /// <summary> - /// Completely overwrites the current configuration with a new copy - /// Returns true or false indicating success or failure + /// Gets the plugin's configuration. /// </summary> - /// <param name="configuration">The configuration.</param> - /// <exception cref="ArgumentNullException">configuration</exception> - void UpdateConfiguration(BasePluginConfiguration configuration); + /// <value>The configuration.</value> + BasePluginConfiguration Configuration { get; } /// <summary> - /// Gets the plugin's configuration + /// Completely overwrites the current configuration with a new copy + /// Returns true or false indicating success or failure. /// </summary> - /// <value>The configuration.</value> - BasePluginConfiguration Configuration { get; } + /// <param name="configuration">The configuration.</param> + /// <exception cref="ArgumentNullException"><c>configuration</c> is <c>null</c>.</exception> + void UpdateConfiguration(BasePluginConfiguration configuration); void SetStartupInfo(Action<string> directoryCreateFn); } diff --git a/MediaBrowser.Common/Plugins/IPluginAssembly.cs b/MediaBrowser.Common/Plugins/IPluginAssembly.cs new file mode 100644 index 000000000..388ac61ab --- /dev/null +++ b/MediaBrowser.Common/Plugins/IPluginAssembly.cs @@ -0,0 +1,14 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1600 + +using System; + +namespace MediaBrowser.Common.Plugins +{ + public interface IPluginAssembly + { + void SetAttributes(string assemblyFilePath, string dataFolderPath, Version assemblyVersion); + + void SetId(Guid assemblyId); + } +} diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs index af69055aa..92141ba52 100644 --- a/MediaBrowser.Common/Progress/ActionableProgress.cs +++ b/MediaBrowser.Common/Progress/ActionableProgress.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; diff --git a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs index 0445397ad..a6422e2c8 100644 --- a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs +++ b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System.Collections.Generic; using MediaBrowser.Common.Configuration; diff --git a/MediaBrowser.Common/System/OperatingSystem.cs b/MediaBrowser.Common/System/OperatingSystem.cs index 7d38ddb6e..f23af4799 100644 --- a/MediaBrowser.Common/System/OperatingSystem.cs +++ b/MediaBrowser.Common/System/OperatingSystem.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Runtime.InteropServices; diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index e49812f15..a09c1916c 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; using System.Collections.Generic; @@ -103,17 +104,16 @@ namespace MediaBrowser.Common.Updates Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken = default); /// <summary> - /// Uninstalls a plugin + /// Uninstalls a plugin. /// </summary> /// <param name="plugin">The plugin.</param> - /// <exception cref="ArgumentException"></exception> void UninstallPlugin(IPlugin plugin); /// <summary> - /// Cancels the installation + /// Cancels the installation. /// </summary> - /// <param name="id">The id of the package that is being installed</param> - /// <returns>Returns true if the install was cancelled</returns> + /// <param name="id">The id of the package that is being installed.</param> + /// <returns>Returns true if the install was cancelled.</returns> bool CancelInstallation(Guid id); } } diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs index 36e124ddf..8bbb231ce 100644 --- a/MediaBrowser.Common/Updates/InstallationEventArgs.cs +++ b/MediaBrowser.Common/Updates/InstallationEventArgs.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using MediaBrowser.Model.Updates; diff --git a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs index 46f10c84f..c8967f9db 100644 --- a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs +++ b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable SA1600 using System; diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs index 3c65156e3..5248ea4c1 100644 --- a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs +++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs @@ -1,14 +1,19 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1600 + using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; - namespace MediaBrowser.Controller.Authentication { public class AuthenticationResult { public UserDto User { get; set; } + public SessionInfo SessionInfo { get; set; } + public string AccessToken { get; set; } + public string ServerId { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index cba2c9dda..353c675cb 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -19,7 +19,6 @@ using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Library; diff --git a/MediaBrowser.Model/Entities/MediaType.cs b/MediaBrowser.Model/Entities/MediaType.cs index c56c8f8f2..d8b02c9ea 100644 --- a/MediaBrowser.Model/Entities/MediaType.cs +++ b/MediaBrowser.Model/Entities/MediaType.cs @@ -3,20 +3,23 @@ namespace MediaBrowser.Model.Entities /// <summary> /// Class MediaType /// </summary> - public class MediaType + public static class MediaType { /// <summary> /// The video /// </summary> public const string Video = "Video"; + /// <summary> /// The audio /// </summary> public const string Audio = "Audio"; + /// <summary> /// The photo /// </summary> public const string Photo = "Photo"; + /// <summary> /// The book /// </summary> diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs index 38638af42..440818c3e 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs @@ -1,15 +1,20 @@ +using System; +using System.Collections.Generic; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.MediaInfo { + /// <summary> + /// Class PlaybackInfoResponse. + /// </summary> public class PlaybackInfoResponse { /// <summary> /// Gets or sets the media sources. /// </summary> /// <value>The media sources.</value> - public MediaSourceInfo[] MediaSources { get; set; } + public IReadOnlyList<MediaSourceInfo> MediaSources { get; set; } /// <summary> /// Gets or sets the play session identifier. @@ -23,9 +28,12 @@ namespace MediaBrowser.Model.MediaInfo /// <value>The error code.</value> public PlaybackErrorCode? ErrorCode { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="PlaybackInfoResponse" /> class. + /// </summary> public PlaybackInfoResponse() { - MediaSources = new MediaSourceInfo[] { }; + MediaSources = Array.Empty<MediaSourceInfo>(); } } } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 27d8a7cd9..92b7a03fd 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -24,6 +24,8 @@ <Rule Id="SA1413" Action="None" /> <!-- disable warning SA1512: Single-line comments must not be followed by blank line --> <Rule Id="SA1512" Action="None" /> + <!-- disable warning SA1515: Single-line comment should be preceded by blank line --> + <Rule Id="SA1515" Action="None" /> <!-- disable warning SA1633: The file header is missing or not located at the top of the file --> <Rule Id="SA1633" Action="None" /> </Rules> @@ -35,6 +37,8 @@ <Rule Id="CA1032" Action="Info" /> <!-- disable warning CA1062: Validate arguments of public methods --> <Rule Id="CA1062" Action="Info" /> + <!-- disable warning CA1716: Identifiers should not match keywords --> + <Rule Id="CA1716" Action="Info" /> <!-- disable warning CA1720: Identifiers should not contain type names --> <Rule Id="CA1720" Action="Info" /> <!-- disable warning CA1812: internal class that is apparently never instantiated. @@ -43,14 +47,18 @@ <Rule Id="CA1812" Action="Info" /> <!-- disable warning CA1822: Member does not access instance data and can be marked as static --> <Rule Id="CA1822" Action="Info" /> + <!-- disable warning CA2000: Dispose objects before losing scope --> + <Rule Id="CA2000" Action="Info" /> <!-- disable warning CA1054: Change the type of parameter url from string to System.Uri --> <Rule Id="CA1054" Action="None" /> + <!-- disable warning CA1055: URI return values should not be strings --> + <Rule Id="CA1055" Action="None" /> + <!-- disable warning CA1056: URI properties should not be strings --> + <Rule Id="CA1056" Action="None" /> <!-- disable warning CA1303: Do not pass literals as localized parameters --> <Rule Id="CA1303" Action="None" /> <!-- disable warning CA1308: Normalize strings to uppercase --> <Rule Id="CA1308" Action="None" /> - <!-- disable warning CA2000: Dispose objects before losing scope --> - <Rule Id="CA2000" Action="None" /> </Rules> </RuleSet> diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 1671b8d79..e0deeeabb 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>netcoreapp3.0</TargetFramework> + <TargetFramework>netcoreapp3.1</TargetFramework> <IsPackable>false</IsPackable> </PropertyGroup> diff --git a/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs new file mode 100644 index 000000000..eb69d915c --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs @@ -0,0 +1,57 @@ +using Emby.Naming.Audio; +using Emby.Naming.Common; +using Xunit; + +namespace Jellyfin.Naming.Tests.Music +{ + public class MultiDiscAlbumTests + { + [Fact] + public void TestMultiDiscAlbums() + { + Assert.False(IsMultiDiscAlbumFolder(@"blah blah")); + Assert.False(IsMultiDiscAlbumFolder(@"d:/music\weezer/03 Pinkerton")); + Assert.False(IsMultiDiscAlbumFolder(@"d:/music/michael jackson/Bad (2012 Remaster)")); + + Assert.True(IsMultiDiscAlbumFolder(@"cd1")); + Assert.True(IsMultiDiscAlbumFolder(@"disc1")); + Assert.True(IsMultiDiscAlbumFolder(@"disk1")); + + // Add a space + Assert.True(IsMultiDiscAlbumFolder(@"cd 1")); + Assert.True(IsMultiDiscAlbumFolder(@"disc 1")); + Assert.True(IsMultiDiscAlbumFolder(@"disk 1")); + + Assert.True(IsMultiDiscAlbumFolder(@"cd - 1")); + Assert.True(IsMultiDiscAlbumFolder(@"disc- 1")); + Assert.True(IsMultiDiscAlbumFolder(@"disk - 1")); + + Assert.True(IsMultiDiscAlbumFolder(@"Disc 01 (Hugo Wolf · 24 Lieder)")); + Assert.True(IsMultiDiscAlbumFolder(@"Disc 04 (Encores and Folk Songs)")); + Assert.True(IsMultiDiscAlbumFolder(@"Disc04 (Encores and Folk Songs)")); + Assert.True(IsMultiDiscAlbumFolder(@"Disc 04(Encores and Folk Songs)")); + Assert.True(IsMultiDiscAlbumFolder(@"Disc04(Encores and Folk Songs)")); + + Assert.True(IsMultiDiscAlbumFolder(@"D:/Video/MBTestLibrary/VideoTest/music/.38 special/anth/Disc 2")); + } + + [Fact] + public void TestMultiDiscAlbums1() + { + Assert.False(IsMultiDiscAlbumFolder(@"[1985] Oppurtunities (Let's make lots of money) (1985)")); + } + + [Fact] + public void TestMultiDiscAlbums2() + { + Assert.False(IsMultiDiscAlbumFolder(@"Blah 04(Encores and Folk Songs)")); + } + + private bool IsMultiDiscAlbumFolder(string path) + { + var parser = new AlbumParser(new NamingOptions()); + + return parser.ParseMultiPart(path).IsMultiPart; + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs new file mode 100644 index 000000000..e8f14cdc4 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs @@ -0,0 +1,40 @@ +using Emby.Naming.Common; +using Emby.Naming.Subtitles; +using Xunit; + +namespace Jellyfin.Naming.Tests.Subtitles +{ + public class SubtitleParserTests + { + private SubtitleParser GetParser() + { + var options = new NamingOptions(); + + return new SubtitleParser(options); + } + + [Fact] + public void TestSubtitles() + { + Test("The Skin I Live In (2011).srt", null, false, false); + Test("The Skin I Live In (2011).eng.srt", "eng", false, false); + Test("The Skin I Live In (2011).eng.default.srt", "eng", true, false); + Test("The Skin I Live In (2011).eng.forced.srt", "eng", false, true); + Test("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true); + Test("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true); + + Test("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true); + } + + private void Test(string input, string language, bool isDefault, bool isForced) + { + var parser = GetParser(); + + var result = parser.ParseFile(input); + + Assert.Equal(language, result.Language, true); + Assert.Equal(isDefault, result.IsDefault); + Assert.Equal(isForced, result.IsForced); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs new file mode 100644 index 000000000..9abbcc7bf --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs @@ -0,0 +1,61 @@ +using Emby.Naming.Common; +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + public class AbsoluteEpisodeNumberTests + { + [Fact] + public void TestAbsoluteEpisodeNumber1() + { + Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/12.avi")); + } + + [Fact] + public void TestAbsoluteEpisodeNumber2() + { + Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 12.avi")); + } + + [Fact] + public void TestAbsoluteEpisodeNumber3() + { + Assert.Equal(82, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 82.avi")); + } + + [Fact] + public void TestAbsoluteEpisodeNumber4() + { + Assert.Equal(112, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 112.avi")); + } + + [Fact] + public void TestAbsoluteEpisodeNumber5() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/Foo_ep_02.avi")); + } + + [Fact] + public void TestAbsoluteEpisodeNumber6() + { + Assert.Equal(889, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 889.avi")); + } + + [Fact] + public void TestAbsoluteEpisodeNumber7() + { + Assert.Equal(101, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 101.avi")); + } + + private int? GetEpisodeNumberFromFile(string path) + { + var options = new NamingOptions(); + + var result = new EpisodeResolver(options) + .Resolve(path, false, null, null, true); + + return result.EpisodeNumber; + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs new file mode 100644 index 000000000..29daf8cc3 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs @@ -0,0 +1,69 @@ +using Emby.Naming.Common; +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + public class DailyEpisodeTests + { + [Fact] + public void TestDailyEpisode1() + { + Test(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14); + } + + [Fact] + public void TestDailyEpisode2() + { + Test(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14); + } + + // FIXME + // [Fact] + public void TestDailyEpisode3() + { + Test(@"/server/anything_14.11.1996.mp4", "anything", 1996, 11, 14); + } + + // FIXME + // [Fact] + public void TestDailyEpisode4() + { + Test(@"/server/A Daily Show - (2015-01-15) - Episode Name - [720p].mkv", "A Daily Show", 2015, 01, 15); + } + + [Fact] + public void TestDailyEpisode5() + { + Test(@"/server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv", "james.corden", 2017, 04, 20); + } + + [Fact] + public void TestDailyEpisode6() + { + Test(@"/server/ABC News 2018_03_24_19_00_00.mkv", "ABC News", 2018, 03, 24); + } + + // FIXME + // [Fact] + public void TestDailyEpisode7() + { + Test(@"/server/Last Man Standing_KTLADT_2018_05_25_01_28_00.wtv", "Last Man Standing", 2018, 05, 25); + } + + private void Test(string path, string seriesName, int? year, int? month, int? day) + { + var options = new NamingOptions(); + + var result = new EpisodeResolver(options) + .Resolve(path, false); + + Assert.Null(result.SeasonNumber); + Assert.Null(result.EpisodeNumber); + Assert.Equal(year, result.Year); + Assert.Equal(month, result.Month); + Assert.Equal(day, result.Day); + Assert.Equal(seriesName, result.SeriesName, true); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs new file mode 100644 index 000000000..1ae637281 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs @@ -0,0 +1,424 @@ +using Emby.Naming.Common; +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + public class EpisodeNumberTests + { + [Fact] + public void TestEpisodeNumber1() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/S02E03 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber40() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber41() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/01x02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber42() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber43() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber44() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber45() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber46() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber47() + { + Assert.Equal(36, GetEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv")); + } + + [Fact] + public void TestEpisodeNumber50() + { + // This convention is not currently supported, just adding in case we want to look at it in the future + Assert.Equal(1, GetEpisodeNumberFromFile(@"2016/Season s2016e1.mp4")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumber51() + { + // This convention is not currently supported, just adding in case we want to look at it in the future + Assert.Equal(1, GetEpisodeNumberFromFile(@"2016/Season 2016x1.mp4")); + } + + [Fact] + public void TestEpisodeNumber52() + { + Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode - 16.avi")); + } + + [Fact] + public void TestEpisodeNumber53() + { + // This is not supported. Expected to fail, although it would be a good one to add support for. + Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16.avi")); + } + + [Fact] + public void TestEpisodeNumber54() + { + // This is not supported. Expected to fail, although it would be a good one to add support for. + Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16 - Some Title.avi")); + } + + // [Fact] + public void TestEpisodeNumber55() + { + // This is not supported. Expected to fail, although it would be a good one to add support for. + Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Season 3 Episode 16.avi")); + } + + // [Fact] + public void TestEpisodeNumber56() + { + // This is not supported. Expected to fail, although it would be a good one to add support for. + Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Season 3 Episode 16 - Some Title.avi")); + } + + [Fact] + public void TestEpisodeNumber57() + { + Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 Some Title.avi")); + } + + [Fact] + public void TestEpisodeNumber58() + { + Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 - 12 Some Title.avi")); + } + + [Fact] + public void TestEpisodeNumber59() + { + Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 - 12 Angry Men.avi")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumber60() + { + Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 12 Some Title.avi")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumber61() + { + Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 12 Angry Men.avi")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumber62() + { + // This is not supported. Expected to fail, although it would be a good one to add support for. + Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.E03.avi")); + } + + [Fact] + public void TestEpisodeNumber63() + { + Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.S04E03.avi")); + } + + [Fact] + public void TestEpisodeNumber64() + { + Assert.Equal(368, GetEpisodeNumberFromFile(@"Running Man/Running Man S2017E368.mkv")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumber65() + { + // Not supported yet + Assert.Equal(7, GetEpisodeNumberFromFile(@"/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv")); + } + + [Fact] + public void TestEpisodeNumber30() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumber31() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber32() + { + Assert.Equal(9, GetEpisodeNumberFromFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4")); + } + + [Fact] + public void TestEpisodeNumber33() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber34() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber35() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber36() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber37() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber38() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber39() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber20() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber21() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber22() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber23() + { + Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4")); + } + + [Fact] + public void TestEpisodeNumber24() + { + Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); + } + + [Fact] + public void TestEpisodeNumber25() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber26() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber27() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumber28() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber29() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber11() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber12() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber13() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber14() + { + Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); + } + + [Fact] + public void TestEpisodeNumber15() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber16() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber17() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber18() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber19() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber2() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber3() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber4() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber5() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber6() + { + Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); + } + + [Fact] + public void TestEpisodeNumber7() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi")); + } + + [Fact] + public void TestEpisodeNumber8() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah.avi")); + } + + [Fact] + public void TestEpisodeNumber9() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi")); + } + + [Fact] + public void TestEpisodeNumber10() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02.avi")); + } + + [Fact] + public void TestEpisodeNumber48() + { + Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/2. Infestation.avi")); + } + + [Fact] + public void TestEpisodeNumber49() + { + Assert.Equal(7, GetEpisodeNumberFromFile(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi")); + } + + private int? GetEpisodeNumberFromFile(string path) + { + var options = new NamingOptions(); + + var result = new EpisodePathParser(options) + .Parse(path, false); + + return result.EpisodeNumber; + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs new file mode 100644 index 000000000..00aa9ee7c --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs @@ -0,0 +1,127 @@ +using Emby.Naming.Common; +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + public class EpisodeNumberWithoutSeasonTests + { + [Fact] + public void TestEpisodeNumberWithoutSeason1() + { + Assert.Equal(8, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason2() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 - Ep Name.avi")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason3() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.avi")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason4() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02 - Ep Name.avi")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason5() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02-Ep Name.avi")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason6() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.EpName.avi")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason7() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02.avi")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason8() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 Ep Name.avi")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumberWithoutSeason9() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 - Ep Name.avi")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumberWithoutSeason10() + { + Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 Ep Name.avi")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumberWithoutSeason11() + { + Assert.Equal(7, GetEpisodeNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi")); + Assert.Equal(8, GetSeasonNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason12() + { + Assert.Equal(7, GetEpisodeNumberFromFile(@"GJ Club (2013)/GJ Club - 07.mkv")); + } + + // FIXME + // [Fact] + public void TestEpisodeNumberWithoutSeason13() + { + // This is not supported anymore after removing the episode number 365+ hack from EpisodePathParser + Assert.Equal(13, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 13.mkv")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason14() + { + Assert.Equal(3, GetSeasonNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv")); + Assert.Equal(17, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv")); + } + + [Fact] + public void TestEpisodeNumberWithoutSeason15() + { + Assert.Equal(2017, GetSeasonNumberFromFile(@"Running Man/Running Man S2017E368.mkv")); + } + + private int? GetEpisodeNumberFromFile(string path) + { + var options = new NamingOptions(); + + var result = new EpisodeResolver(options) + .Resolve(path, false); + + return result.EpisodeNumber; + } + + private int? GetSeasonNumberFromFile(string path) + { + var options = new NamingOptions(); + + var result = new EpisodeResolver(options) + .Resolve(path, false); + + return result.SeasonNumber; + } + + } +} diff --git a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs index dd1e04215..da6e99310 100644 --- a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs @@ -2,7 +2,7 @@ using Emby.Naming.Common; using Emby.Naming.TV; using Xunit; -namespace Jellyfin.Naming.Tests +namespace Jellyfin.Naming.Tests.TV { public class EpisodePathParserTest { @@ -11,6 +11,10 @@ namespace Jellyfin.Naming.Tests [InlineData("/media/Foo - S04E011", "Foo", 4, 11)] [InlineData("/media/Foo/Foo s01x01", "Foo", 1, 1)] [InlineData("/media/Foo (2019)/Season 4/Foo (2019).S04E03", "Foo (2019)", 4, 3)] + [InlineData("D:\\media\\Foo\\Foo-S01E01", "Foo", 1, 1)] + [InlineData("D:\\media\\Foo - S04E011", "Foo", 4, 11)] + [InlineData("D:\\media\\Foo\\Foo s01x01", "Foo", 1, 1)] + [InlineData("D:\\media\\Foo (2019)\\Season 4\\Foo (2019).S04E03", "Foo (2019)", 4, 3)] public void ParseEpisodesCorrectly(string path, string name, int season, int episode) { NamingOptions o = new NamingOptions(); @@ -21,18 +25,13 @@ namespace Jellyfin.Naming.Tests Assert.Equal(name, res.SeriesName); Assert.Equal(season, res.SeasonNumber); Assert.Equal(episode, res.EpisodeNumber); - - // testing other paths delimeter - var res2 = p.Parse(path.Replace('/', '\\'), false); - Assert.True(res2.Success); - Assert.Equal(name, res2.SeriesName); - Assert.Equal(season, res2.SeasonNumber); - Assert.Equal(episode, res2.EpisodeNumber); } [Theory] [InlineData("/media/Foo/Foo 889", "Foo", 889)] [InlineData("/media/Foo/[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)] + [InlineData("D:\\media\\Foo\\Foo 889", "Foo", 889)] + [InlineData("D:\\media\\Foo\\[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)] public void ParseEpisodeWithoutSeason(string path, string name, int episode) { NamingOptions o = new NamingOptions(); @@ -43,13 +42,6 @@ namespace Jellyfin.Naming.Tests Assert.Equal(name, res.SeriesName); Assert.Null(res.SeasonNumber); Assert.Equal(episode, res.EpisodeNumber); - - // testing other paths delimeter - var res2 = p.Parse(path.Replace('/', '\\'), false, fillExtendedInfo: false); - Assert.True(res2.Success); - Assert.Equal(name, res2.SeriesName); - Assert.Null(res2.SeasonNumber); - Assert.Equal(episode, res2.EpisodeNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs new file mode 100644 index 000000000..c2851ccdb --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs @@ -0,0 +1,56 @@ +using Emby.Naming.Common; +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + public class EpisodeWithoutSeasonTests + { + // FIXME + // [Fact] + public void TestWithoutSeason1() + { + Test(@"/server/anything_ep02.mp4", "anything", null, 2); + } + + // FIXME + // [Fact] + public void TestWithoutSeason2() + { + Test(@"/server/anything_ep_02.mp4", "anything", null, 2); + } + + // FIXME + // [Fact] + public void TestWithoutSeason3() + { + Test(@"/server/anything_part.II.mp4", "anything", null, null); + } + + // FIXME + // [Fact] + public void TestWithoutSeason4() + { + Test(@"/server/anything_pt.II.mp4", "anything", null, null); + } + + // FIXME + // [Fact] + public void TestWithoutSeason5() + { + Test(@"/server/anything_pt_II.mp4", "anything", null, null); + } + + private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) + { + var options = new NamingOptions(); + + var result = new EpisodeResolver(options) + .Resolve(path, false); + + Assert.Equal(seasonNumber, result.SeasonNumber); + Assert.Equal(episodeNumber, result.EpisodeNumber); + Assert.Equal(seriesName, result.SeriesName, true); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs new file mode 100644 index 000000000..b15dd6b74 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs @@ -0,0 +1,105 @@ +using Emby.Naming.Common; +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + public class MultiEpisodeTests + { + [Fact] + public void TestGetEndingEpisodeNumberFromFile() + { + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/4x01 – 20 Hours in America (1).mkv")); + + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/01x02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 04 Ep Name.mp4")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/My show name 02x03 - 04 Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4")); + Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4")); + Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4")); + + + // Four Digits seasons + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); + Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); + Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); + Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); + + // Without season number + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02.avi")); + + Assert.Equal(3, GetEndingEpisodeNumberFromFile(@"Season 1/02-03 - blah.avi")); + Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04 - blah 14 blah.avi")); + Assert.Equal(5, GetEndingEpisodeNumberFromFile(@"Season 1/02-05 - blah-02 a.avi")); + Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04.avi")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv")); + + // With format specification that must not be detected as ending episode number + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-1080p.mkv")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720p.mkv")); + Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720i.mkv")); + Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 1/MOONLIGHTING_s01e01-e04.mkv")); + } + + [Fact] + public void TestGetEndingEpisodeNumberFromFolder() + { + Assert.Equal(4, GetEndingEpisodeNumberFromFolder(@"Season 1/MOONLIGHTING_s01e01-e04")); + } + + private int? GetEndingEpisodeNumberFromFolder(string path) + { + var options = new NamingOptions(); + + var result = new EpisodePathParser(options) + .Parse(path, true); + + return result.EndingEpsiodeNumber; + } + + private int? GetEndingEpisodeNumberFromFile(string path) + { + var options = new NamingOptions(); + + var result = new EpisodePathParser(options) + .Parse(path, false); + + return result.EndingEpsiodeNumber; + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs new file mode 100644 index 000000000..ffa8d3483 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs @@ -0,0 +1,112 @@ +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + public class SeasonFolderTests + { + [Fact] + public void TestGetSeasonNumberFromPath1() + { + Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1")); + } + + [Fact] + public void TestGetSeasonNumberFromPath2() + { + Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 2")); + } + + [Fact] + public void TestGetSeasonNumberFromPath3() + { + Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 02")); + } + + [Fact] + public void TestGetSeasonNumberFromPath4() + { + Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1")); + } + + [Fact] + public void TestGetSeasonNumberFromPath5() + { + Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/S02")); + } + + [Fact] + public void TestGetSeasonNumberFromPath6() + { + Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/2")); + } + + [Fact] + public void TestGetSeasonNumberFromPath7() + { + Assert.Equal(2009, GetSeasonNumberFromPath(@"/Drive/Season 2009")); + } + + [Fact] + public void TestGetSeasonNumberFromPath8() + { + Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season1")); + } + + [Fact] + public void TestGetSeasonNumberFromPath9() + { + Assert.Equal(4, GetSeasonNumberFromPath(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH")); + } + + [Fact] + public void TestGetSeasonNumberFromPath10() + { + Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Season 7 (2016)")); + } + + [Fact] + public void TestGetSeasonNumberFromPath11() + { + Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Staffel 7 (2016)")); + } + + [Fact] + public void TestGetSeasonNumberFromPath12() + { + Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Stagione 7 (2016)")); + } + + [Fact] + public void TestGetSeasonNumberFromPath14() + { + Assert.Null(GetSeasonNumberFromPath(@"/Drive/Season (8)")); + } + + [Fact] + public void TestGetSeasonNumberFromPath13() + { + Assert.Equal(3, GetSeasonNumberFromPath(@"/Drive/3.Staffel")); + } + + [Fact] + public void TestGetSeasonNumberFromPath15() + { + Assert.Null(GetSeasonNumberFromPath(@"/Drive/s06e05")); + } + + [Fact] + public void TestGetSeasonNumberFromPath16() + { + Assert.Null(GetSeasonNumberFromPath(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv")); + } + + private int? GetSeasonNumberFromPath(string path) + { + var result = new SeasonPathParser() + .Parse(path, true, true); + + return result.SeasonNumber; + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs new file mode 100644 index 000000000..ba3c5ecac --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs @@ -0,0 +1,305 @@ +using Emby.Naming.Common; +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + public class SeasonNumberTests + { + private int? GetSeasonNumberFromEpisodeFile(string path) + { + var options = new NamingOptions(); + + var result = new EpisodeResolver(options) + .Resolve(path, false); + + return result.SeasonNumber; + } + + [Fact] + public void TestSeasonNumber1() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"/Show/Season 02/S02E03 blah.avi")); + } + + [Fact] + public void TestSeasonNumber2() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi")); + } + + [Fact] + public void TestSeasonNumber3() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01x02 blah.avi")); + } + + [Fact] + public void TestSeasonNumber4() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01xE02 blah.avi")); + } + + [Fact] + public void TestSeasonNumber5() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/01x02 blah.avi")); + } + + [Fact] + public void TestSeasonNumber6() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E02 blah.avi")); + } + + [Fact] + public void TestSeasonNumber7() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01xE02 blah.avi")); + } + + // FIXME + // [Fact] + public void TestSeasonNumber8() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname 01x02 blah.avi")); + } + + [Fact] + public void TestSeasonNumber9() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi")); + } + + [Fact] + public void TestSeasonNumber10() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01E02 blah.avi")); + } + + [Fact] + public void TestSeasonNumber11() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber12() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber13() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03-04-15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber14() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber15() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03-E15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber16() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber17() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber18() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber19() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber20() + { + Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestSeasonNumber21() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4")); + } + + [Fact] + public void TestSeasonNumber22() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4")); + } + + [Fact] + public void TestSeasonNumber23() + { + Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4")); + } + + [Fact] + public void TestSeasonNumber24() + { + Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"The Simpsons/The Simpsons.S25E09.Steal this episode.mp4")); + } + + [Fact] + public void TestSeasonNumber25() + { + Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season s2016e1.mp4")); + } + + // FIXME + // [Fact] + public void TestSeasonNumber26() + { + // This convention is not currently supported, just adding in case we want to look at it in the future + Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season 2016x1.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber1() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x02 blah.avi")); + } + + [Fact] + public void TestFourDigitSeasonNumber2() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009x02 blah.avi")); + } + + [Fact] + public void TestFourDigitSeasonNumber3() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E02 blah.avi")); + } + + [Fact] + public void TestFourDigitSeasonNumber4() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009xE02 blah.avi")); + } + + // FIXME + // [Fact] + public void TestFourDigitSeasonNumber5() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname 2009x02 blah.avi")); + } + + [Fact] + public void TestFourDigitSeasonNumber6() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009x02 blah.avi")); + } + + [Fact] + public void TestFourDigitSeasonNumber7() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009E02 blah.avi")); + } + + [Fact] + public void TestFourDigitSeasonNumber8() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber9() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber10() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber11() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber12() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber13() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber14() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber15() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber16() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber17() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber18() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber19() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4")); + } + + [Fact] + public void TestFourDigitSeasonNumber20() + { + Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4")); + } + + [Fact] + public void TestNoSeriesFolder() + { + Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Series/1-12 - The Woman.mp4")); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs new file mode 100644 index 000000000..c9323c218 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs @@ -0,0 +1,95 @@ +using Emby.Naming.Common; +using Emby.Naming.TV; +using Xunit; + +namespace Jellyfin.Naming.Tests.TV +{ + public class SimpleEpisodeTests + { + [Fact] + public void TestSimpleEpisodePath1() + { + Test(@"/server/anything_s01e02.mp4", "anything", 1, 2); + } + + [Fact] + public void TestSimpleEpisodePath2() + { + Test(@"/server/anything_s1e2.mp4", "anything", 1, 2); + } + + [Fact] + public void TestSimpleEpisodePath3() + { + Test(@"/server/anything_s01.e02.mp4", "anything", 1, 2); + } + + [Fact] + public void TestSimpleEpisodePath4() + { + Test(@"/server/anything_s01_e02.mp4", "anything", 1, 2); + } + + [Fact] + public void TestSimpleEpisodePath5() + { + Test(@"/server/anything_102.mp4", "anything", 1, 2); + } + + [Fact] + public void TestSimpleEpisodePath6() + { + Test(@"/server/anything_1x02.mp4", "anything", 1, 2); + } + + // FIXME + // [Fact] + public void TestSimpleEpisodePath7() + { + Test(@"/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1); + } + + [Fact] + public void TestSimpleEpisodePath8() + { + Test(@"/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1); + } + + + [Fact] + public void TestSimpleEpisodePath9() + { + Test(@"/server/Temp/S01E02 foo.mp4", string.Empty, 1, 2); + } + + [Fact] + public void TestSimpleEpisodePath10() + { + Test(@"Series/4-12 - The Woman.mp4", string.Empty, 4, 12); + } + + [Fact] + public void TestSimpleEpisodePath11() + { + Test(@"Series/4x12 - The Woman.mp4", string.Empty, 4, 12); + } + + [Fact] + public void TestSimpleEpisodePath12() + { + Test(@"Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32); + } + + private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber) + { + var options = new NamingOptions(); + + var result = new EpisodeResolver(options) + .Resolve(path, false); + + Assert.Equal(seasonNumber, result.SeasonNumber); + Assert.Equal(episodeNumber, result.EpisodeNumber); + Assert.Equal(seriesName, result.SeriesName, true); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs new file mode 100644 index 000000000..b993e241c --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs @@ -0,0 +1,15 @@ +using Emby.Naming.Common; +using Emby.Naming.Video; + +namespace Jellyfin.Naming.Tests.Video +{ + public abstract class BaseVideoTest + { + protected VideoResolver GetParser() + { + var options = new NamingOptions(); + + return new VideoResolver(options); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs new file mode 100644 index 000000000..bba73ad91 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs @@ -0,0 +1,143 @@ +using System.IO; +using Xunit; + +namespace Jellyfin.Naming.Tests.Video +{ + public class CleanDateTimeTests : BaseVideoTest + { + // FIXME + // [Fact] + public void TestCleanDateTime() + { + Test(@"The Wolf of Wall Street (2013).mkv", "The Wolf of Wall Street", 2013); + Test(@"The Wolf of Wall Street 2 (2013).mkv", "The Wolf of Wall Street 2", 2013); + Test(@"The Wolf of Wall Street - 2 (2013).mkv", "The Wolf of Wall Street - 2", 2013); + Test(@"The Wolf of Wall Street 2001 (2013).mkv", "The Wolf of Wall Street 2001", 2013); + + Test(@"300 (2006).mkv", "300", 2006); + Test(@"d:/movies/300 (2006).mkv", "300", 2006); + Test(@"300 2 (2006).mkv", "300 2", 2006); + Test(@"300 - 2 (2006).mkv", "300 - 2", 2006); + Test(@"300 2001 (2006).mkv", "300 2001", 2006); + + Test(@"curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", "curse.of.chucky", 2013); + Test(@"curse.of.chucky.2013.stv.unrated.multi.2160p.bluray.x264-rough", "curse.of.chucky", 2013); + + Test(@"/server/Movies/300 (2007)/300 (2006).bluray.disc", "300", 2006); + } + + // FIXME + // [Fact] + public void TestCleanDateTime1() + { + Test(@"Arrival.2016.2160p.Blu-Ray.HEVC.mkv", "Arrival", 2016); + } + + // FIXME + // [Fact] + public void TestCleanDateTimeWithoutFileExtension() + { + Test(@"The Wolf of Wall Street (2013)", "The Wolf of Wall Street", 2013); + Test(@"The Wolf of Wall Street 2 (2013)", "The Wolf of Wall Street 2", 2013); + Test(@"The Wolf of Wall Street - 2 (2013)", "The Wolf of Wall Street - 2", 2013); + Test(@"The Wolf of Wall Street 2001 (2013)", "The Wolf of Wall Street 2001", 2013); + + Test(@"300 (2006)", "300", 2006); + Test(@"d:/movies/300 (2006)", "300", 2006); + Test(@"300 2 (2006)", "300 2", 2006); + Test(@"300 - 2 (2006)", "300 - 2", 2006); + Test(@"300 2001 (2006)", "300 2001", 2006); + + Test(@"/server/Movies/300 (2007)/300 (2006)", "300", 2006); + Test(@"/server/Movies/300 (2007)/300 (2006).mkv", "300", 2006); + } + + [Fact] + public void TestCleanDateTimeWithoutDate() + { + Test(@"American.Psycho.mkv", "American.Psycho.mkv", null); + Test(@"American Psycho.mkv", "American Psycho.mkv", null); + } + + [Fact] + public void TestCleanDateTimeWithBracketedName() + { + Test(@"[rec].mkv", "[rec].mkv", null); + } + + // FIXME + // [Fact] + public void TestCleanDateTimeWithoutExtension() + { + Test(@"St. Vincent (2014)", "St. Vincent", 2014); + } + + // FIXME + // [Fact] + public void TestCleanDateTimeWithoutDate1() + { + Test("Super movie(2009).mp4", "Super movie", 2009); + } + + // FIXME + // [Fact] + public void TestCleanDateTimeWithoutParenthesis() + { + Test("Drug War 2013.mp4", "Drug War", 2013); + } + + // FIXME + // [Fact] + public void TestCleanDateTimeWithMultipleYears() + { + Test("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997); + } + + // FIXME + // [Fact] + public void TestCleanDateTimeWithYearAndResolution() + { + Test("First Man 2018 1080p.mkv", "First Man", 2018); + } + + // FIXME + // [Fact] + public void TestCleanDateTimeWithYearAndResolution1() + { + Test("First Man (2018) 1080p.mkv", "First Man", 2018); + } + + // FIXME + // [Fact] + public void TestCleanDateTimeWithSceneRelease() + { + Test("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016); + } + + // FIXME + // [Fact] + public void TestYearInBrackets() + { + Test("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018); + } + + private void Test(string input, string expectedName, int? expectedYear) + { + input = Path.GetFileName(input); + + var result = GetParser().CleanDateTime(input); + + Assert.Equal(expectedName, result.Name, true); + Assert.Equal(expectedYear, result.Year); + } + + // FIXME + // [Fact] + public void TestCleanDateAndStringsSequence() + { + // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again + + Test(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs new file mode 100644 index 000000000..cd90ac236 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs @@ -0,0 +1,133 @@ +using System; +using System.Globalization; +using Xunit; + +namespace Jellyfin.Naming.Tests.Video +{ + public class CleanStringTests : BaseVideoTest + { + // FIXME + // [Fact] + public void TestCleanString() + { + Test("Super movie 480p.mp4", "Super movie"); + Test("Super movie 480p 2001.mp4", "Super movie"); + Test("Super movie [480p].mp4", "Super movie"); + Test("480 Super movie [tmdbid=12345].mp4", "480 Super movie"); + } + + // FIXME + // [Fact] + public void TestCleanString1() + { + Test("Super movie(2009).mp4", "Super movie(2009).mp4"); + } + + // FIXME + // [Fact] + public void TestCleanString2() + { + Test("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4"); + } + + // FIXME + // [Fact] + public void TestStringWithoutDate() + { + Test(@"American.Psycho.mkv", "American.Psycho.mkv"); + Test(@"American Psycho.mkv", "American Psycho.mkv"); + } + + // FIXME + // [Fact] + public void TestNameWithBrackets() + { + Test(@"[rec].mkv", "[rec].mkv"); + } + + // FIXME + // [Fact] + public void Test4k() + { + Test("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon"); + } + + // FIXME + // [Fact] + public void TestUltraHd() + { + Test("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon"); + } + + // FIXME + // [Fact] + public void TestUHd() + { + Test("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon"); + } + + // FIXME + // [Fact] + public void TestHDR() + { + Test("Crouching.Tiger.Hidden.Dragon.HDR.mkv", "Crouching.Tiger.Hidden.Dragon"); + } + + // FIXME + // [Fact] + public void TestHDC() + { + Test("Crouching.Tiger.Hidden.Dragon.HDC.mkv", "Crouching.Tiger.Hidden.Dragon"); + } + + // FIXME + // [Fact] + public void TestHDC1() + { + Test("Crouching.Tiger.Hidden.Dragon-HDC.mkv", "Crouching.Tiger.Hidden.Dragon"); + } + + // FIXME + // [Fact] + public void TestBDrip() + { + Test("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon"); + } + + // FIXME + // [Fact] + public void TestBDripHDC() + { + Test("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon"); + } + + // FIXME + // [Fact] + public void TestMulti() + { + Test("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon"); + } + + // FIXME + // [Fact] + public void TestLeadingBraces() + { + // Not actually supported, just reported by a user + Test("[0004] - After The Sunset.el.mkv", "After The Sunset"); + } + + // FIXME + // [Fact] + public void TestTrailingBraces() + { + Test("After The Sunset - [0004].mkv", "After The Sunset"); + } + + private void Test(string input, string expectedName) + { + var result = GetParser().CleanString(input).ToString(); + + Assert.Equal(expectedName, result, true); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs new file mode 100644 index 000000000..1646237a0 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -0,0 +1,77 @@ +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Model.Entities; +using Xunit; + +namespace Jellyfin.Naming.Tests.Video +{ + public class ExtraTests : BaseVideoTest + { + // Requirements + // movie-deleted = ExtraType deletedscene + + // All of the above rules should be configurable through the options objects (ideally, even the ExtraTypes) + + [Fact] + public void TestKodiExtras() + { + var videoOptions = new NamingOptions(); + + Test("trailer.mp4", ExtraType.Trailer, videoOptions); + Test("300-trailer.mp4", ExtraType.Trailer, videoOptions); + + Test("theme.mp3", ExtraType.ThemeSong, videoOptions); + } + + [Fact] + public void TestExpandedExtras() + { + var videoOptions = new NamingOptions(); + + Test("trailer.mp4", ExtraType.Trailer, videoOptions); + Test("trailer.mp3", null, videoOptions); + Test("300-trailer.mp4", ExtraType.Trailer, videoOptions); + + Test("theme.mp3", ExtraType.ThemeSong, videoOptions); + Test("theme.mkv", null, videoOptions); + + Test("300-scene.mp4", ExtraType.Scene, videoOptions); + Test("300-scene2.mp4", ExtraType.Scene, videoOptions); + Test("300-clip.mp4", ExtraType.Clip, videoOptions); + + Test("300-deleted.mp4", ExtraType.DeletedScene, videoOptions); + Test("300-deletedscene.mp4", ExtraType.DeletedScene, videoOptions); + Test("300-interview.mp4", ExtraType.Interview, videoOptions); + Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, videoOptions); + } + + [Fact] + public void TestSample() + { + var videoOptions = new NamingOptions(); + + Test("300-sample.mp4", ExtraType.Sample, videoOptions); + } + + private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions) + { + var parser = GetExtraTypeParser(videoOptions); + + var extraType = parser.GetExtraInfo(input).ExtraType; + + if (expectedType == null) + { + Assert.Null(extraType); + } + else + { + Assert.Equal(expectedType, extraType); + } + } + + private ExtraResolver GetExtraTypeParser(NamingOptions videoOptions) + { + return new ExtraResolver(videoOptions); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs new file mode 100644 index 000000000..ed3112936 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs @@ -0,0 +1,78 @@ +using Emby.Naming.Common; +using Emby.Naming.Video; +using Xunit; + +namespace Jellyfin.Naming.Tests.Video +{ + public class Format3DTests : BaseVideoTest + { + [Fact] + public void TestKodiFormat3D() + { + var options = new NamingOptions(); + + Test("Super movie.3d.mp4", false, null, options); + Test("Super movie.3d.hsbs.mp4", true, "hsbs", options); + Test("Super movie.3d.sbs.mp4", true, "sbs", options); + Test("Super movie.3d.htab.mp4", true, "htab", options); + Test("Super movie.3d.tab.mp4", true, "tab", options); + Test("Super movie 3d hsbs.mp4", true, "hsbs", options); + } + + [Fact] + public void Test3DName() + { + var result = + GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.3d.hsbs.mkv"); + + Assert.Equal("hsbs", result.Format3D); + Assert.Equal("Oblivion", result.Name); + } + + [Fact] + public void TestExpandedFormat3D() + { + // These were introduced for Media Browser 3 + // Kodi conventions are preferred but these still need to be supported + var options = new NamingOptions(); + + Test("Super movie.3d.mp4", false, null, options); + Test("Super movie.3d.hsbs.mp4", true, "hsbs", options); + Test("Super movie.3d.sbs.mp4", true, "sbs", options); + Test("Super movie.3d.htab.mp4", true, "htab", options); + Test("Super movie.3d.tab.mp4", true, "tab", options); + + Test("Super movie.hsbs.mp4", true, "hsbs", options); + Test("Super movie.sbs.mp4", true, "sbs", options); + Test("Super movie.htab.mp4", true, "htab", options); + Test("Super movie.tab.mp4", true, "tab", options); + Test("Super movie.sbs3d.mp4", true, "sbs3d", options); + Test("Super movie.3d.mvc.mp4", true, "mvc", options); + + Test("Super movie [3d].mp4", false, null, options); + Test("Super movie [hsbs].mp4", true, "hsbs", options); + Test("Super movie [fsbs].mp4", true, "fsbs", options); + Test("Super movie [ftab].mp4", true, "ftab", options); + Test("Super movie [htab].mp4", true, "htab", options); + Test("Super movie [sbs3d].mp4", true, "sbs3d", options); + } + + private void Test(string input, bool is3D, string format3D, NamingOptions options) + { + var parser = new Format3DParser(options); + + var result = parser.Parse(input); + + Assert.Equal(is3D, result.Is3D); + + if (format3D == null) + { + Assert.Null(result.Format3D); + } + else + { + Assert.Equal(format3D, result.Format3D, true); + } + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs new file mode 100644 index 000000000..b8674ec49 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -0,0 +1,438 @@ +using System.Linq; +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Model.IO; +using Xunit; + +namespace Jellyfin.Naming.Tests.Video +{ + public class MultiVersionTests + { + // FIXME + // [Fact] + public void TestMultiEdition1() + { + var files = new[] + { + @"/movies/X-Men Days of Future Past/X-Men Days of Future Past - 1080p.mkv", + @"/movies/X-Men Days of Future Past/X-Men Days of Future Past-trailer.mp4", + @"/movies/X-Men Days of Future Past/X-Men Days of Future Past - [hsbs].mkv", + @"/movies/X-Men Days of Future Past/X-Men Days of Future Past [hsbs].mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Single(result[0].Extras); + } + + // FIXME + // [Fact] + public void TestMultiEdition2() + { + var files = new[] + { + @"/movies/X-Men Days of Future Past/X-Men Days of Future Past - apple.mkv", + @"/movies/X-Men Days of Future Past/X-Men Days of Future Past-trailer.mp4", + @"/movies/X-Men Days of Future Past/X-Men Days of Future Past - banana.mkv", + @"/movies/X-Men Days of Future Past/X-Men Days of Future Past [banana].mp4" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Single(result[0].Extras); + Assert.Equal(2, result[0].AlternateVersions.Count); + } + + [Fact] + public void TestMultiEdition3() + { + // This is currently not supported and will fail, but we should try to figure it out + var files = new[] + { + @"/movies/The Phantom of the Opera (1925)/The Phantom of the Opera (1925) - 1925 version.mkv", + @"/movies/The Phantom of the Opera (1925)/The Phantom of the Opera (1925) - 1929 version.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Single(result[0].AlternateVersions); + } + + // FIXME + // [Fact] + public void TestLetterFolders() + { + var files = new[] + { + @"/movies/M/Movie 1.mkv", + @"/movies/M/Movie 2.mkv", + @"/movies/M/Movie 3.mkv", + @"/movies/M/Movie 4.mkv", + @"/movies/M/Movie 5.mkv", + @"/movies/M/Movie 6.mkv", + @"/movies/M/Movie 7.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(7, result.Count); + Assert.Empty(result[0].Extras); + Assert.Empty(result[0].AlternateVersions); + } + + // FIXME + // [Fact] + public void TestMultiVersionLimit() + { + var files = new[] + { + @"/movies/Movie/Movie.mkv", + @"/movies/Movie/Movie-2.mkv", + @"/movies/Movie/Movie-3.mkv", + @"/movies/Movie/Movie-4.mkv", + @"/movies/Movie/Movie-5.mkv", + @"/movies/Movie/Movie-6.mkv", + @"/movies/Movie/Movie-7.mkv", + @"/movies/Movie/Movie-8.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Empty(result[0].Extras); + Assert.Equal(7, result[0].AlternateVersions.Count); + } + + // FIXME + // [Fact] + public void TestMultiVersionLimit2() + { + var files = new[] + { + @"/movies/Mo/Movie 1.mkv", + @"/movies/Mo/Movie 2.mkv", + @"/movies/Mo/Movie 3.mkv", + @"/movies/Mo/Movie 4.mkv", + @"/movies/Mo/Movie 5.mkv", + @"/movies/Mo/Movie 6.mkv", + @"/movies/Mo/Movie 7.mkv", + @"/movies/Mo/Movie 8.mkv", + @"/movies/Mo/Movie 9.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(9, result.Count); + Assert.Empty(result[0].Extras); + Assert.Empty(result[0].AlternateVersions); + } + + // FIXME + // [Fact] + public void TestMultiVersion3() + { + var files = new[] + { + @"/movies/Movie/Movie 1.mkv", + @"/movies/Movie/Movie 2.mkv", + @"/movies/Movie/Movie 3.mkv", + @"/movies/Movie/Movie 4.mkv", + @"/movies/Movie/Movie 5.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(5, result.Count); + Assert.Empty(result[0].Extras); + Assert.Empty(result[0].AlternateVersions); + } + + // FIXME + // [Fact] + public void TestMultiVersion4() + { + // Test for false positive + + var files = new[] + { + @"/movies/Iron Man/Iron Man.mkv", + @"/movies/Iron Man/Iron Man (2008).mkv", + @"/movies/Iron Man/Iron Man (2009).mkv", + @"/movies/Iron Man/Iron Man (2010).mkv", + @"/movies/Iron Man/Iron Man (2011).mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(5, result.Count); + Assert.Empty(result[0].Extras); + Assert.Empty(result[0].AlternateVersions); + } + + // FIXME + // [Fact] + public void TestMultiVersion5() + { + var files = new[] + { + @"/movies/Iron Man/Iron Man.mkv", + @"/movies/Iron Man/Iron Man-720p.mkv", + @"/movies/Iron Man/Iron Man-test.mkv", + @"/movies/Iron Man/Iron Man-bluray.mkv", + @"/movies/Iron Man/Iron Man-3d.mkv", + @"/movies/Iron Man/Iron Man-3d-hsbs.mkv", + @"/movies/Iron Man/Iron Man-3d.hsbs.mkv", + @"/movies/Iron Man/Iron Man[test].mkv", + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Empty(result[0].Extras); + Assert.Equal(7, result[0].AlternateVersions.Count); + Assert.False(result[0].AlternateVersions[2].Is3D); + Assert.True(result[0].AlternateVersions[3].Is3D); + Assert.True(result[0].AlternateVersions[4].Is3D); + } + + // FIXME + // [Fact] + public void TestMultiVersion6() + { + var files = new[] + { + @"/movies/Iron Man/Iron Man.mkv", + @"/movies/Iron Man/Iron Man - 720p.mkv", + @"/movies/Iron Man/Iron Man - test.mkv", + @"/movies/Iron Man/Iron Man - bluray.mkv", + @"/movies/Iron Man/Iron Man - 3d.mkv", + @"/movies/Iron Man/Iron Man - 3d-hsbs.mkv", + @"/movies/Iron Man/Iron Man - 3d.hsbs.mkv", + @"/movies/Iron Man/Iron Man [test].mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Empty(result[0].Extras); + Assert.Equal(7, result[0].AlternateVersions.Count); + Assert.False(result[0].AlternateVersions[3].Is3D); + Assert.True(result[0].AlternateVersions[4].Is3D); + Assert.True(result[0].AlternateVersions[5].Is3D); + } + + // FIXME + // [Fact] + public void TestMultiVersion7() + { + var files = new[] + { + @"/movies/Iron Man/Iron Man - B (2006).mkv", + @"/movies/Iron Man/Iron Man - C (2007).mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(2, result.Count); + } + + // FIXME + // [Fact] + public void TestMultiVersion8() + { + // This is not actually supported yet + + var files = new[] + { + @"/movies/Iron Man/Iron Man.mkv", + @"/movies/Iron Man/Iron Man_720p.mkv", + @"/movies/Iron Man/Iron Man_test.mkv", + @"/movies/Iron Man/Iron Man_bluray.mkv", + @"/movies/Iron Man/Iron Man_3d.mkv", + @"/movies/Iron Man/Iron Man_3d-hsbs.mkv", + @"/movies/Iron Man/Iron Man_3d.hsbs.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Empty(result[0].Extras); + Assert.Equal(6, result[0].AlternateVersions.Count); + Assert.False(result[0].AlternateVersions[2].Is3D); + Assert.True(result[0].AlternateVersions[3].Is3D); + Assert.True(result[0].AlternateVersions[4].Is3D); + } + + // FIXME + // [Fact] + public void TestMultiVersion9() + { + // Test for false positive + + var files = new[] + { + @"/movies/Iron Man/Iron Man (2007).mkv", + @"/movies/Iron Man/Iron Man (2008).mkv", + @"/movies/Iron Man/Iron Man (2009).mkv", + @"/movies/Iron Man/Iron Man (2010).mkv", + @"/movies/Iron Man/Iron Man (2011).mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(5, result.Count); + Assert.Empty(result[0].Extras); + Assert.Empty(result[0].AlternateVersions); + } + + // FIXME + // [Fact] + public void TestMultiVersion10() + { + var files = new[] + { + @"/movies/Blade Runner (1982)/Blade Runner (1982) [Final Cut] [1080p HEVC AAC].mkv", + @"/movies/Blade Runner (1982)/Blade Runner (1982) [EE by ADM] [480p HEVC AAC,AAC,AAC].mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Empty(result[0].Extras); + Assert.Single(result[0].AlternateVersions); + } + + // FIXME + // [Fact] + public void TestMultiVersion11() + { + // Currently not supported but we should probably handle this. + + var files = new[] + { + @"/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016) [1080p] Blu-ray.x264.DTS.mkv", + @"/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016) [2160p] Blu-ray.x265.AAC.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Empty(result[0].Extras); + Assert.Single(result[0].AlternateVersions); + } + + private VideoListResolver GetResolver() + { + var options = new NamingOptions(); + return new VideoListResolver(options); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs new file mode 100644 index 000000000..5faef0e3d --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs @@ -0,0 +1,478 @@ +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Model.IO; +using Xunit; + +namespace Jellyfin.Naming.Tests.Video +{ + public class StackTests : BaseVideoTest + { + [Fact] + public void TestSimpleStack() + { + var files = new[] + { + "Bad Boys (2006) part1.mkv", + "Bad Boys (2006) part2.mkv", + "Bad Boys (2006) part3.mkv", + "Bad Boys (2006) part4.mkv", + "Bad Boys (2006)-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Single(result.Stacks); + TestStackInfo(result.Stacks[0], "Bad Boys (2006)", 4); + } + + [Fact] + public void TestFalsePositives() + { + var files = new[] + { + "Bad Boys (2006).mkv", + "Bad Boys (2007).mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Empty(result.Stacks); + } + + [Fact] + public void TestFalsePositives2() + { + var files = new[] + { + "Bad Boys 2006.mkv", + "Bad Boys 2007.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Empty(result.Stacks); + } + + [Fact] + public void TestFalsePositives3() + { + var files = new[] + { + "300 (2006).mkv", + "300 (2007).mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Empty(result.Stacks); + } + + [Fact] + public void TestFalsePositives4() + { + var files = new[] + { + "300 2006.mkv", + "300 2007.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Empty(result.Stacks); + } + + [Fact] + public void TestFalsePositives5() + { + var files = new[] + { + "Star Trek 1 - The motion picture.mkv", + "Star Trek 2- The wrath of khan.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Empty(result.Stacks); + } + + [Fact] + public void TestFalsePositives6() + { + var files = new[] + { + "Red Riding in the Year of Our Lord 1983 (2009).mkv", + "Red Riding in the Year of Our Lord 1980 (2009).mkv", + "Red Riding in the Year of Our Lord 1974 (2009).mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Empty(result.Stacks); + } + + [Fact] + public void TestStackName() + { + var files = new[] + { + "d:/movies/300 2006 part1.mkv", + "d:/movies/300 2006 part2.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Single(result.Stacks); + TestStackInfo(result.Stacks[0], "300 2006", 2); + } + + [Fact] + public void TestDirtyNames() + { + var files = new[] + { + "Bad Boys (2006).part1.stv.unrated.multi.1080p.bluray.x264-rough.mkv", + "Bad Boys (2006).part2.stv.unrated.multi.1080p.bluray.x264-rough.mkv", + "Bad Boys (2006).part3.stv.unrated.multi.1080p.bluray.x264-rough.mkv", + "Bad Boys (2006).part4.stv.unrated.multi.1080p.bluray.x264-rough.mkv", + "Bad Boys (2006)-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Single(result.Stacks); + TestStackInfo(result.Stacks[0], "Bad Boys (2006).stv.unrated.multi.1080p.bluray.x264-rough", 4); + } + + [Fact] + public void TestNumberedFiles() + { + var files = new[] + { + "Bad Boys (2006).mkv", + "Bad Boys (2006) 1.mkv", + "Bad Boys (2006) 2.mkv", + "Bad Boys (2006) 3.mkv", + "Bad Boys (2006)-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Empty(result.Stacks); + } + + [Fact] + public void TestSimpleStackWithNumericName() + { + var files = new[] + { + "300 (2006) part1.mkv", + "300 (2006) part2.mkv", + "300 (2006) part3.mkv", + "300 (2006) part4.mkv", + "300 (2006)-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Single(result.Stacks); + TestStackInfo(result.Stacks[0], "300 (2006)", 4); + } + + [Fact] + public void TestMixedExpressionsNotAllowed() + { + var files = new[] + { + "Bad Boys (2006) part1.mkv", + "Bad Boys (2006) part2.mkv", + "Bad Boys (2006) part3.mkv", + "Bad Boys (2006) parta.mkv", + "Bad Boys (2006)-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Single(result.Stacks); + TestStackInfo(result.Stacks[0], "Bad Boys (2006)", 3); + } + + [Fact] + public void TestDualStacks() + { + var files = new[] + { + "Bad Boys (2006) part1.mkv", + "Bad Boys (2006) part2.mkv", + "Bad Boys (2006) part3.mkv", + "Bad Boys (2006) part4.mkv", + "Bad Boys (2006)-trailer.mkv", + "300 (2006) part1.mkv", + "300 (2006) part2.mkv", + "300 (2006) part3.mkv", + "300 (2006)-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Equal(2, result.Stacks.Count); + TestStackInfo(result.Stacks[1], "Bad Boys (2006)", 4); + TestStackInfo(result.Stacks[0], "300 (2006)", 3); + } + + [Fact] + public void TestDirectories() + { + var files = new[] + { + "blah blah - cd 1", + "blah blah - cd 2" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveDirectories(files); + + Assert.Single(result.Stacks); + TestStackInfo(result.Stacks[0], "blah blah", 2); + } + + [Fact] + public void TestFalsePositive() + { + var files = new[] + { + "300a.mkv", + "300b.mkv", + "300c.mkv", + "300-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Single(result.Stacks); + + TestStackInfo(result.Stacks[0], "300", 3); + } + + [Fact] + public void TestFailSequence() + { + var files = new[] + { + "300 part1.mkv", + "300 part2.mkv", + "Avatar", + "Avengers part1.mkv", + "Avengers part2.mkv", + "Avengers part3.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Equal(2, result.Stacks.Count); + + TestStackInfo(result.Stacks[0], "300", 2); + TestStackInfo(result.Stacks[1], "Avengers", 3); + } + + [Fact] + public void TestMixedExpressions() + { + var files = new[] + { + "Bad Boys (2006) part1.mkv", + "Bad Boys (2006) part2.mkv", + "Bad Boys (2006) part3.mkv", + "Bad Boys (2006) part4.mkv", + "Bad Boys (2006)-trailer.mkv", + "300 (2006) parta.mkv", + "300 (2006) partb.mkv", + "300 (2006) partc.mkv", + "300 (2006) partd.mkv", + "300 (2006)-trailer.mkv", + "300a.mkv", + "300b.mkv", + "300c.mkv", + "300-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Equal(3, result.Stacks.Count); + + TestStackInfo(result.Stacks[0], "300 (2006)", 4); + TestStackInfo(result.Stacks[1], "300", 3); + TestStackInfo(result.Stacks[2], "Bad Boys (2006)", 4); + } + + [Fact] + public void TestAlphaLimitOfFour() + { + var files = new[] + { + "300 (2006) parta.mkv", + "300 (2006) partb.mkv", + "300 (2006) partc.mkv", + "300 (2006) partd.mkv", + "300 (2006) parte.mkv", + "300 (2006) partf.mkv", + "300 (2006) partg.mkv", + "300 (2006)-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Single(result.Stacks); + + TestStackInfo(result.Stacks[0], "300 (2006)", 4); + } + + [Fact] + public void TestMixed() + { + var files = new[] + { + new FileSystemMetadata{FullName = "Bad Boys (2006) part1.mkv", IsDirectory = false}, + new FileSystemMetadata{FullName = "Bad Boys (2006) part2.mkv", IsDirectory = false}, + new FileSystemMetadata{FullName = "300 (2006) part2", IsDirectory = true}, + new FileSystemMetadata{FullName = "300 (2006) part3", IsDirectory = true}, + new FileSystemMetadata{FullName = "300 (2006) part1", IsDirectory = true} + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files); + + Assert.Equal(2, result.Stacks.Count); + TestStackInfo(result.Stacks[0], "300 (2006)", 3); + TestStackInfo(result.Stacks[1], "Bad Boys (2006)", 2); + } + + [Fact] + public void TestDirectories2() + { + //TestDirectory(@"blah blah", false, @"blah blah"); + //TestDirectory(@"d:/music/weezer/03 Pinkerton", false, "03 Pinkerton"); + //TestDirectory(@"d:/music/michael jackson/Bad (2012 Remaster)", false, "Bad (2012 Remaster)"); + + //TestDirectory(@"blah blah - cd1", true, "blah blah"); + //TestDirectory(@"blah blah - disc1", true, "blah blah"); + //TestDirectory(@"blah blah - disk1", true, "blah blah"); + //TestDirectory(@"blah blah - pt1", true, "blah blah"); + //TestDirectory(@"blah blah - part1", true, "blah blah"); + //TestDirectory(@"blah blah - dvd1", true, "blah blah"); + + //// Add a space + //TestDirectory(@"blah blah - cd 1", true, "blah blah"); + //TestDirectory(@"blah blah - disc 1", true, "blah blah"); + //TestDirectory(@"blah blah - disk 1", true, "blah blah"); + //TestDirectory(@"blah blah - pt 1", true, "blah blah"); + //TestDirectory(@"blah blah - part 1", true, "blah blah"); + //TestDirectory(@"blah blah - dvd 1", true, "blah blah"); + + //// Not case sensitive + //TestDirectory(@"blah blah - Disc1", true, "blah blah"); + } + + [Fact] + public void TestNamesWithoutParts() + { + // No stacking here because there is no part/disc/etc + var files = new[] + { + "Harry Potter and the Deathly Hallows.mkv", + "Harry Potter and the Deathly Hallows 1.mkv", + "Harry Potter and the Deathly Hallows 2.mkv", + "Harry Potter and the Deathly Hallows 3.mkv", + "Harry Potter and the Deathly Hallows 4.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Empty(result.Stacks); + } + + [Fact] + public void TestNumbersAppearingBeforePartNumber() + { + // No stacking here because there is no part/disc/etc + var files = new[] + { + "Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part1.mkv", + "Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part2.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveFiles(files); + + Assert.Single(result.Stacks); + Assert.Equal(2, result.Stacks[0].Files.Count); + } + + [Fact] + public void TestMultiDiscs() + { + // No stacking here because there is no part/disc/etc + var files = new[] + { + @"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 01)", + @"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 02)" + }; + + var resolver = GetResolver(); + + var result = resolver.ResolveDirectories(files); + + Assert.Single(result.Stacks); + Assert.Equal(2, result.Stacks[0].Files.Count); + } + + private void TestStackInfo(FileStack stack, string name, int fileCount) + { + Assert.Equal(fileCount, stack.Files.Count); + Assert.Equal(name, stack.Name); + } + + private StackResolver GetResolver() + { + return new StackResolver(new NamingOptions()); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs new file mode 100644 index 000000000..96fa8c5a5 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Globalization; +using Emby.Naming.Common; +using Emby.Naming.Video; +using Xunit; + +namespace Jellyfin.Naming.Tests.Video +{ + public class StubTests : BaseVideoTest + { + [Fact] + public void TestStubs() + { + Test("video.mkv", false, null); + Test("video.disc", true, null); + Test("video.dvd.disc", true, "dvd"); + Test("video.hddvd.disc", true, "hddvd"); + Test("video.bluray.disc", true, "bluray"); + Test("video.brrip.disc", true, "bluray"); + Test("video.bd25.disc", true, "bluray"); + Test("video.bd50.disc", true, "bluray"); + Test("video.vhs.disc", true, "vhs"); + Test("video.hdtv.disc", true, "tv"); + Test("video.pdtv.disc", true, "tv"); + Test("video.dsr.disc", true, "tv"); + } + + [Fact] + public void TestStubName() + { + var result = + GetParser().ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.dvd.disc"); + + Assert.Equal("Oblivion", result.Name); + } + + private void Test(string path, bool isStub, string stubType) + { + var options = new NamingOptions(); + + var resultStubType = StubResolver.ResolveFile(path, options); + + Assert.Equal(isStub, resultStubType.IsStub); + + if (stubType == null) + { + Assert.Null(resultStubType.StubType); + } + else + { + Assert.Equal(stubType, resultStubType.StubType, true); + } + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs new file mode 100644 index 000000000..ef8a17898 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -0,0 +1,457 @@ +using System.Linq; +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Model.IO; +using Xunit; + +namespace Jellyfin.Naming.Tests.Video +{ + public class VideoListResolverTests + { + // FIXME + // [Fact] + public void TestStackAndExtras() + { + // No stacking here because there is no part/disc/etc + var files = new[] + { + "Harry Potter and the Deathly Hallows-trailer.mkv", + "Harry Potter and the Deathly Hallows.trailer.mkv", + "Harry Potter and the Deathly Hallows part1.mkv", + "Harry Potter and the Deathly Hallows part2.mkv", + "Harry Potter and the Deathly Hallows part3.mkv", + "Harry Potter and the Deathly Hallows part4.mkv", + "Batman-deleted.mkv", + "Batman-sample.mkv", + "Batman-trailer.mkv", + "Batman part1.mkv", + "Batman part2.mkv", + "Batman part3.mkv", + "Avengers.mkv", + "Avengers-trailer.mkv", + + // Despite having a keyword in the name that will return an ExtraType, there's no original video to match it to + // So this is just a standalone video + "trailer.mkv", + + // Same as above + "WillyWonka-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(5, result.Count); + + Assert.Equal(3, result[1].Files.Count); + Assert.Equal(3, result[1].Extras.Count); + Assert.Equal("Batman", result[1].Name); + + Assert.Equal(4, result[2].Files.Count); + Assert.Equal(2, result[2].Extras.Count); + Assert.Equal("Harry Potter and the Deathly Hallows", result[2].Name); + } + + [Fact] + public void TestWithMetadata() + { + var files = new[] + { + "300.mkv", + "300.nfo" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestWithExtra() + { + var files = new[] + { + "300.mkv", + "300 trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestVariationWithFolderName() + { + var files = new[] + { + "X-Men Days of Future Past - 1080p.mkv", + "X-Men Days of Future Past-trailer.mp4" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestTrailer2() + { + var files = new[] + { + "X-Men Days of Future Past - 1080p.mkv", + "X-Men Days of Future Past-trailer.mp4", + "X-Men Days of Future Past-trailer2.mp4" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestDifferentNames() + { + var files = new[] + { + "Looper (2012)-trailer.mkv", + "Looper.2012.bluray.720p.x264.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestSeparateFiles() + { + // These should be considered separate, unrelated videos + var files = new[] + { + "My video 1.mkv", + "My video 2.mkv", + "My video 3.mkv", + "My video 4.mkv", + "My video 5.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(5, result.Count); + } + + [Fact] + public void TestMultiDisc() + { + var files = new[] + { + @"M:/Movies (DVD)/Movies (Musical)/Sound of Music (1965)/Sound of Music Disc 1", + @"M:/Movies (DVD)/Movies (Musical)/Sound of Music (1965)/Sound of Music Disc 2" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = true, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestPoundSign() + { + // These should be considered separate, unrelated videos + var files = new[] + { + @"My movie #1.mp4", + @"My movie #2.mp4" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = true, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(2, result.Count); + } + + [Fact] + public void TestStackedWithTrailer() + { + var files = new[] + { + @"No (2012) part1.mp4", + @"No (2012) part2.mp4", + @"No (2012) part1-trailer.mp4" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestStackedWithTrailer2() + { + var files = new[] + { + @"No (2012) part1.mp4", + @"No (2012) part2.mp4", + @"No (2012)-trailer.mp4" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestExtrasByFolderName() + { + var files = new[] + { + @"/Movies/Top Gun (1984)/movie.mp4", + @"/Movies/Top Gun (1984)/Top Gun (1984)-trailer.mp4", + @"/Movies/Top Gun (1984)/Top Gun (1984)-trailer2.mp4", + @"trailer.mp4" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestDoubleTags() + { + var files = new[] + { + @"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Counterfeit Racks (2011) Disc 1 cd1.avi", + @"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Counterfeit Racks (2011) Disc 1 cd2.avi", + @"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Disc 2 cd1.avi", + @"/MCFAMILY-PC/Private3$/Heterosexual/Breast In Class 2 Counterfeit Racks (2011)/Breast In Class 2 Disc 2 cd2.avi" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(2, result.Count); + } + + [Fact] + public void TestArgumentOutOfRangeException() + { + var files = new[] + { + @"/nas-markrobbo78/Videos/INDEX HTPC/Movies/Watched/3 - ACTION/Argo (2012)/movie.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestColony() + { + var files = new[] + { + @"The Colony.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestFourSisters() + { + var files = new[] + { + @"Four Sisters and a Wedding - A.avi", + @"Four Sisters and a Wedding - B.avi" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestMovieTrailer() + { + var files = new[] + { + @"/Server/Despicable Me/Despicable Me (2010).mkv", + @"/Server/Despicable Me/movie-trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + [Fact] + public void TestTrailerFalsePositives() + { + var files = new[] + { + @"/Server/Despicable Me/Skyscraper (2018) - Big Game Spot.mkv", + @"/Server/Despicable Me/Skyscraper (2018) - Trailer.mkv", + @"/Server/Despicable Me/Baywatch (2017) - Big Game Spot.mkv", + @"/Server/Despicable Me/Baywatch (2017) - Trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Equal(4, result.Count); + } + + [Fact] + public void TestSubfolders() + { + var files = new[] + { + @"/Movies/Despicable Me/Despicable Me.mkv", + @"/Movies/Despicable Me/trailers/trailer.mkv" + }; + + var resolver = GetResolver(); + + var result = resolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + + }).ToList()).ToList(); + + Assert.Single(result); + } + + private VideoListResolver GetResolver() + { + var options = new NamingOptions(); + return new VideoListResolver(options); + } + } +} diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs new file mode 100644 index 000000000..5a3ce8886 --- /dev/null +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -0,0 +1,275 @@ +using MediaBrowser.Model.Entities; +using Xunit; + +namespace Jellyfin.Naming.Tests.Video +{ + public class VideoResolverTests : BaseVideoTest + { + // FIXME + // [Fact] + public void TestSimpleFile() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).mkv"); + + Assert.Equal(2006, result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Equal("Brave", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void TestSimpleFile2() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv"); + + Assert.Equal(1995, result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Equal("Bad Boys", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void TestSimpleFileWithNumericName() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).mkv"); + + Assert.Equal(2006, result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Equal("300", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void TestExtra() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv"); + + Assert.Equal(2006, result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Equal(ExtraType.Trailer, result.ExtraType); + Assert.Equal("Brave (2006)-trailer", result.Name); + } + + // FIXME + // [Fact] + public void TestExtraWithNumericName() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.mkv"); + + Assert.Equal(2006, result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Equal("300 (2006)-trailer", result.Name); + Assert.Equal(ExtraType.Trailer, result.ExtraType); + } + + // FIXME + // [Fact] + public void TestStubFileWithNumericName() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).bluray.disc"); + + Assert.Equal(2006, result.Year); + Assert.True(result.IsStub); + Assert.Equal("bluray", result.StubType); + Assert.False(result.Is3D); + Assert.Equal("300", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void TestStubFile() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/Brave (2007)/Brave (2006).bluray.disc"); + + Assert.Equal(2006, result.Year); + Assert.True(result.IsStub); + Assert.Equal("bluray", result.StubType); + Assert.False(result.Is3D); + Assert.Equal("Brave", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void TestExtraStubWithNumericNameNotSupported() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc"); + + Assert.Equal(2006, result.Year); + Assert.True(result.IsStub); + Assert.Equal("bluray", result.StubType); + Assert.False(result.Is3D); + Assert.Equal("300", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void TestExtraStubNotSupported() + { + // Using a stub for an extra is currently not supported + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc"); + + Assert.Equal(2006, result.Year); + Assert.True(result.IsStub); + Assert.Equal("bluray", result.StubType); + Assert.False(result.Is3D); + Assert.Equal("brave", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void Test3DFileWithNumericName() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv"); + + Assert.Equal(2006, result.Year); + Assert.False(result.IsStub); + Assert.True(result.Is3D); + Assert.Equal("sbs", result.Format3D); + Assert.Equal("300", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void TestBad3DFileWithNumericName() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv"); + + Assert.Equal(2006, result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Equal("300", result.Name); + Assert.Null(result.ExtraType); + Assert.Null(result.Format3D); + } + + // FIXME + // [Fact] + public void Test3DFile() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv"); + + Assert.Equal(2006, result.Year); + Assert.False(result.IsStub); + Assert.True(result.Is3D); + Assert.Equal("sbs", result.Format3D); + Assert.Equal("brave", result.Name); + Assert.Null(result.ExtraType); + } + + [Fact] + public void TestNameWithoutDate() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/American Psycho/American.Psycho.mkv"); + + Assert.Null(result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Null(result.Format3D); + Assert.Equal("American.Psycho", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void TestCleanDateAndStringsSequence() + { + var parser = GetParser(); + + // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again + var result = + parser.ResolveFile(@"/server/Movies/3.Days.to.Kill/3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv"); + + Assert.Equal(2014, result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Null(result.Format3D); + Assert.Equal("3.Days.to.Kill", result.Name); + Assert.Null(result.ExtraType); + } + + // FIXME + // [Fact] + public void TestCleanDateAndStringsSequence1() + { + var parser = GetParser(); + + // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again + var result = + parser.ResolveFile(@"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv"); + + Assert.Equal(2005, result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Null(result.Format3D); + Assert.Equal("3 days to kill", result.Name); + Assert.Null(result.ExtraType); + } + + [Fact] + public void TestFolderNameWithExtension() + { + var parser = GetParser(); + + var result = + parser.ResolveFile(@"/server/Movies/7 Psychos.mkv/7 Psychos.mkv"); + + Assert.Null(result.Year); + Assert.False(result.IsStub); + Assert.False(result.Is3D); + Assert.Equal("7 Psychos", result.Name); + Assert.Null(result.ExtraType); + } + } +} |
