diff --git a/CHANGELOG.md b/CHANGELOG.md index cdfcc6b0..81288072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ changes you may need to make. [breaking5]: https://recyclarr.dev/wiki/upgrade-guide/v5.0 +### Added + +- The `*.yaml` extension is now accepted for all YAML files (e.g. `settings.yaml`, `recyclarr.yaml`) + in addition to `*.yml` (which was already supported). + ### Changed - API Key is now sent via the `X-Api-Key` header instead of the `apikey` query parameter. This diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 3b575005..1dc270e6 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -45,6 +45,7 @@ + @@ -63,4 +64,4 @@ - \ No newline at end of file + diff --git a/src/Recyclarr.Cli/Processors/Config/ConfigCreationProcessor.cs b/src/Recyclarr.Cli/Processors/Config/ConfigCreationProcessor.cs index e838bb0e..c84ceac9 100644 --- a/src/Recyclarr.Cli/Processors/Config/ConfigCreationProcessor.cs +++ b/src/Recyclarr.Cli/Processors/Config/ConfigCreationProcessor.cs @@ -26,7 +26,10 @@ public class ConfigCreationProcessor : IConfigCreationProcessor public async Task Process(string? configFilePath) { - var configFile = configFilePath is null ? _paths.ConfigPath : _fs.FileInfo.New(configFilePath); + var configFile = configFilePath is null + ? _paths.AppDataDirectory.File("recyclarr.yml") + : _fs.FileInfo.New(configFilePath); + if (configFile.Exists) { throw new FileExistsException(configFile.FullName); diff --git a/src/Recyclarr.Common/ConflictingYamlFilesException.cs b/src/Recyclarr.Common/ConflictingYamlFilesException.cs new file mode 100644 index 00000000..1ffe7244 --- /dev/null +++ b/src/Recyclarr.Common/ConflictingYamlFilesException.cs @@ -0,0 +1,16 @@ +namespace Recyclarr.Common; + +public class ConflictingYamlFilesException : Exception +{ + public ConflictingYamlFilesException(IEnumerable supportedFiles) + : base(BuildMessage(supportedFiles)) + { + } + + private static string BuildMessage(IEnumerable supportedFiles) + { + return + "Expected only 1 of the following files to exist, but found more than one: " + + string.Join(", ", supportedFiles); + } +} diff --git a/src/Recyclarr.Common/Extensions/FileSystemExtensions.cs b/src/Recyclarr.Common/Extensions/FileSystemExtensions.cs index 162e11f7..38d9eb24 100644 --- a/src/Recyclarr.Common/Extensions/FileSystemExtensions.cs +++ b/src/Recyclarr.Common/Extensions/FileSystemExtensions.cs @@ -73,4 +73,20 @@ public static class FileSystemExtensions return subdirectories.Aggregate(dir, (d, s) => d.FileSystem.DirectoryInfo.New(d.FileSystem.Path.Combine(d.FullName, s))); } + + public static IFileInfo? YamlFile(this IDirectoryInfo dir, string yamlFilenameNoExtension) + { + var supportedFiles = new[] {$"{yamlFilenameNoExtension}.yml", $"{yamlFilenameNoExtension}.yaml"}; + var configs = supportedFiles + .Select(dir.File) + .Where(x => x.Exists) + .ToList(); + + if (configs.Count > 1) + { + throw new ConflictingYamlFilesException(supportedFiles); + } + + return configs.FirstOrDefault(); + } } diff --git a/src/Recyclarr.Common/Recyclarr.Common.csproj b/src/Recyclarr.Common/Recyclarr.Common.csproj index 9638bc00..a1c3cf73 100644 --- a/src/Recyclarr.Common/Recyclarr.Common.csproj +++ b/src/Recyclarr.Common/Recyclarr.Common.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Recyclarr.TrashLib/AppPaths.cs b/src/Recyclarr.TrashLib/AppPaths.cs index b96f2d90..eb7dbb20 100644 --- a/src/Recyclarr.TrashLib/AppPaths.cs +++ b/src/Recyclarr.TrashLib/AppPaths.cs @@ -11,14 +11,9 @@ public class AppPaths : IAppPaths AppDataDirectory = appDataPath; } - public static string DefaultConfigFilename => "recyclarr.yml"; public static string DefaultAppDataDirectoryName => "recyclarr"; public IDirectoryInfo AppDataDirectory { get; } - - public IFileInfo ConfigPath => AppDataDirectory.File(DefaultConfigFilename); - public IFileInfo SettingsPath => AppDataDirectory.File("settings.yml"); - public IFileInfo SecretsPath => AppDataDirectory.File("secrets.yml"); public IDirectoryInfo LogDirectory => AppDataDirectory.SubDir("logs", "cli"); public IDirectoryInfo ReposDirectory => AppDataDirectory.SubDir("repositories"); public IDirectoryInfo CacheDirectory => AppDataDirectory.SubDir("cache"); diff --git a/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationFinder.cs b/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationFinder.cs index ebd9faa5..61ecd659 100644 --- a/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationFinder.cs +++ b/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationFinder.cs @@ -1,4 +1,5 @@ using System.IO.Abstractions; +using Recyclarr.Common.Extensions; using Recyclarr.TrashLib.Config.Parsing.ErrorHandling; using Recyclarr.TrashLib.Startup; @@ -19,12 +20,15 @@ public class ConfigurationFinder : IConfigurationFinder if (_paths.ConfigsDirectory.Exists) { - configs.AddRange(_paths.ConfigsDirectory.EnumerateFiles("*.yml")); + var extensions = new[] {"*.yml", "*.yaml"}; + var files = extensions.SelectMany(x => _paths.ConfigsDirectory.EnumerateFiles(x)); + configs.AddRange(files); } - if (_paths.ConfigPath.Exists) + var configPath = _paths.AppDataDirectory.YamlFile("recyclarr"); + if (configPath is not null) { - configs.Add(_paths.ConfigPath); + configs.Add(configPath); } return configs; diff --git a/src/Recyclarr.TrashLib/Config/Secrets/SecretsProvider.cs b/src/Recyclarr.TrashLib/Config/Secrets/SecretsProvider.cs index c905f9da..2c4324d1 100644 --- a/src/Recyclarr.TrashLib/Config/Secrets/SecretsProvider.cs +++ b/src/Recyclarr.TrashLib/Config/Secrets/SecretsProvider.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using Recyclarr.Common.Extensions; using Recyclarr.TrashLib.Startup; using YamlDotNet.Serialization; @@ -21,9 +22,10 @@ public class SecretsProvider : ISecretsProvider { var result = new Dictionary(); - if (_paths.SecretsPath.Exists) + var yamlPath = _paths.AppDataDirectory.YamlFile("secrets"); + if (yamlPath is not null) { - using var stream = _paths.SecretsPath.OpenText(); + using var stream = yamlPath.OpenText(); var deserializer = new DeserializerBuilder().Build(); var dict = deserializer.Deserialize?>(stream); if (dict is not null) diff --git a/src/Recyclarr.TrashLib/Settings/SettingsProvider.cs b/src/Recyclarr.TrashLib/Settings/SettingsProvider.cs index 3c22263b..0bad1e72 100644 --- a/src/Recyclarr.TrashLib/Settings/SettingsProvider.cs +++ b/src/Recyclarr.TrashLib/Settings/SettingsProvider.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions; using Recyclarr.Common.Extensions; using Recyclarr.TrashLib.Config.Parsing.ErrorHandling; using Recyclarr.TrashLib.Config.Yaml; @@ -23,14 +24,15 @@ public class SettingsProvider : ISettingsProvider private SettingsValues LoadOrCreateSettingsFile(IYamlSerializerFactory serializerFactory) { - if (!_paths.SettingsPath.Exists) + var yamlPath = _paths.AppDataDirectory.YamlFile("settings"); + if (yamlPath is null) { - CreateDefaultSettingsFile(); + yamlPath = CreateDefaultSettingsFile(); } try { - using var stream = _paths.SettingsPath.OpenText(); + using var stream = yamlPath.OpenText(); var deserializer = serializerFactory.CreateDeserializer(); return deserializer.Deserialize(stream.ReadToEnd()) ?? new SettingsValues(); } @@ -46,7 +48,7 @@ public class SettingsProvider : ISettingsProvider } } - private void CreateDefaultSettingsFile() + private IFileInfo CreateDefaultSettingsFile() { const string fileData = "# yaml-language-server: $schema=https://raw.githubusercontent.com/recyclarr/recyclarr/master/schemas/settings-schema.json\n" + @@ -55,8 +57,10 @@ public class SettingsProvider : ISettingsProvider "# For the settings file reference guide, visit the link to the wiki below:\n" + "# https://recyclarr.dev/wiki/yaml/settings-reference/\n"; - _paths.SettingsPath.CreateParentDirectory(); - using var stream = _paths.SettingsPath.CreateText(); + var settingsFile = _paths.AppDataDirectory.File("settings.yml"); + settingsFile.CreateParentDirectory(); + using var stream = settingsFile.CreateText(); stream.Write(fileData); + return settingsFile; } } diff --git a/src/Recyclarr.TrashLib/Startup/IAppPaths.cs b/src/Recyclarr.TrashLib/Startup/IAppPaths.cs index 12d08e3e..f98f153a 100644 --- a/src/Recyclarr.TrashLib/Startup/IAppPaths.cs +++ b/src/Recyclarr.TrashLib/Startup/IAppPaths.cs @@ -5,9 +5,6 @@ namespace Recyclarr.TrashLib.Startup; public interface IAppPaths { IDirectoryInfo AppDataDirectory { get; } - IFileInfo ConfigPath { get; } - IFileInfo SettingsPath { get; } - IFileInfo SecretsPath { get; } IDirectoryInfo LogDirectory { get; } IDirectoryInfo ReposDirectory { get; } IDirectoryInfo CacheDirectory { get; } diff --git a/src/tests/Directory.Build.props b/src/tests/Directory.Build.props index 80c880d1..e7828b1e 100644 --- a/src/tests/Directory.Build.props +++ b/src/tests/Directory.Build.props @@ -13,6 +13,7 @@ + diff --git a/src/tests/Recyclarr.Cli.Tests/BaseCommandSetupIntegrationTest.cs b/src/tests/Recyclarr.Cli.Tests/BaseCommandSetupIntegrationTest.cs index 13bcca13..bcdd96b9 100644 --- a/src/tests/Recyclarr.Cli.Tests/BaseCommandSetupIntegrationTest.cs +++ b/src/tests/Recyclarr.Cli.Tests/BaseCommandSetupIntegrationTest.cs @@ -26,7 +26,7 @@ public class BaseCommandSetupIntegrationTest : CliIntegrationFixture { const int maxFiles = 25; - Fs.AddFile(Paths.SettingsPath.FullName, new MockFileData($@" + Fs.AddFile(Paths.AppDataDirectory.File("settings.yml").FullName, new MockFileData($@" log_janitor: max_files: {maxFiles} ")); diff --git a/src/tests/Recyclarr.Cli.Tests/Pipelines/QualitySize/PipelinePhases/QualitySizeGuidePhaseTest.cs b/src/tests/Recyclarr.Cli.Tests/Pipelines/QualitySize/PipelinePhases/QualitySizeGuidePhaseTest.cs index 8d1a7290..04f82180 100644 --- a/src/tests/Recyclarr.Cli.Tests/Pipelines/QualitySize/PipelinePhases/QualitySizeGuidePhaseTest.cs +++ b/src/tests/Recyclarr.Cli.Tests/Pipelines/QualitySize/PipelinePhases/QualitySizeGuidePhaseTest.cs @@ -63,7 +63,8 @@ public class QualitySizeGuidePhaseTest _ = sut.Execute(config); - config.QualityDefinition?.PreferredRatio.Should().Be(decimal.Parse(expectedPreferred)); + config.QualityDefinition.Should().NotBeNull(); + config.QualityDefinition!.PreferredRatio.Should().Be(decimal.Parse(expectedPreferred)); } [Test, AutoMockData] @@ -91,8 +92,8 @@ public class QualitySizeGuidePhaseTest }); var result = sut.Execute(config); - - result?.Qualities.Should().BeEquivalentTo(new[] + result.Should().NotBeNull(); + result!.Qualities.Should().BeEquivalentTo(new[] { new QualitySizeItem("quality1", 0, 100, 50) }, @@ -127,8 +128,8 @@ public class QualitySizeGuidePhaseTest }); var result = sut.Execute(config); - - result?.Qualities.Should().BeEquivalentTo(new[] + result.Should().NotBeNull(); + result!.Qualities.Should().BeEquivalentTo(new[] { new QualitySizeItem("quality1", 0, 100, 90) }, diff --git a/src/tests/Recyclarr.Cli.Tests/Processors/ConfigCreationProcessorTest.cs b/src/tests/Recyclarr.Cli.Tests/Processors/ConfigCreationProcessorTest.cs index 41cc2bd0..c5b89773 100644 --- a/src/tests/Recyclarr.Cli.Tests/Processors/ConfigCreationProcessorTest.cs +++ b/src/tests/Recyclarr.Cli.Tests/Processors/ConfigCreationProcessorTest.cs @@ -17,7 +17,7 @@ public class ConfigCreationProcessorTest : CliIntegrationFixture await sut.Process(null); - var file = Fs.GetFile(Paths.ConfigPath); + var file = Fs.GetFile(Paths.AppDataDirectory.File("recyclarr.yml")); file.Should().NotBeNull(); file.Contents.Should().NotBeEmpty(); } diff --git a/src/tests/Recyclarr.Cli.Tests/ServiceCompatibilityIntegrationTest.cs b/src/tests/Recyclarr.Cli.Tests/ServiceCompatibilityIntegrationTest.cs index dc6b3fc1..a899b363 100644 --- a/src/tests/Recyclarr.Cli.Tests/ServiceCompatibilityIntegrationTest.cs +++ b/src/tests/Recyclarr.Cli.Tests/ServiceCompatibilityIntegrationTest.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions; using Recyclarr.Cli.TestLibrary; using Recyclarr.TrashLib.Settings; @@ -19,7 +20,7 @@ repositories: clone_url: http://the_url.com "; - Fs.AddFile(Paths.SettingsPath, new MockFileData(yamlData)); + Fs.AddFile(Paths.AppDataDirectory.File("settings.yml"), new MockFileData(yamlData)); var settings = sut.Settings; diff --git a/src/tests/Recyclarr.Common.Tests/Extensions/FileSystemExtensionsTest.cs b/src/tests/Recyclarr.Common.Tests/Extensions/FileSystemExtensionsTest.cs index f99154d0..97e9c5e5 100644 --- a/src/tests/Recyclarr.Common.Tests/Extensions/FileSystemExtensionsTest.cs +++ b/src/tests/Recyclarr.Common.Tests/Extensions/FileSystemExtensionsTest.cs @@ -1,4 +1,5 @@ using System.IO.Abstractions; +using System.IO.Abstractions.Extensions; using System.Text.RegularExpressions; using Recyclarr.Common.Extensions; using Recyclarr.TestLibrary; @@ -107,4 +108,35 @@ public class FileSystemExtensionsTest act.Should().Throw(); } + + [Test] + public void Return_null_when_no_yaml_files_exist() + { + var fs = new MockFileSystem(); + var result = fs.CurrentDirectory().YamlFile("test"); + result.Should().BeNull(); + } + + [TestCase("test.yml")] + [TestCase("test.yaml")] + public void Return_non_null_when_single_yaml_file_exists(string yamlFilename) + { + var fs = new MockFileSystem(); + fs.AddEmptyFile(yamlFilename); + + var result = fs.CurrentDirectory().YamlFile("test"); + result.Should().NotBeNull(); + result!.Name.Should().Be(yamlFilename); + } + + [Test] + public void Throw_when_both_files_exist() + { + var fs = new MockFileSystem(); + fs.AddEmptyFile("test.yml"); + fs.AddEmptyFile("test.yaml"); + + var act = () => fs.CurrentDirectory().YamlFile("test"); + act.Should().Throw(); + } } diff --git a/src/tests/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationFinderTest.cs b/src/tests/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationFinderTest.cs index 7b777089..66b15691 100644 --- a/src/tests/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationFinderTest.cs +++ b/src/tests/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationFinderTest.cs @@ -14,9 +14,9 @@ public class ConfigurationFinderTest { return new[] { - paths.ConfigPath, + paths.AppDataDirectory.File("recyclarr.yml"), paths.ConfigsDirectory.File("b.yml"), - paths.ConfigsDirectory.File("c.yml") + paths.ConfigsDirectory.File("c.yaml") }; } @@ -97,11 +97,12 @@ public class ConfigurationFinderTest [Frozen(Matching.ImplementedInterfaces)] AppPaths paths, ConfigurationFinder sut) { - fs.AddEmptyFile(paths.ConfigPath); + var configFile = paths.AppDataDirectory.File("recyclarr.yml"); + fs.AddEmptyFile(configFile); var result = sut.GetConfigFiles(Array.Empty()); - result.Should().ContainSingle(x => x.FullName == paths.ConfigPath.FullName); + result.Should().ContainSingle(x => x.FullName == configFile.FullName); } [Test, AutoMockData] diff --git a/src/tests/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderSecretsTest.cs b/src/tests/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderSecretsTest.cs index 9dc2a752..025831b4 100644 --- a/src/tests/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderSecretsTest.cs +++ b/src/tests/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderSecretsTest.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions; using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config.Parsing; using Recyclarr.TrashLib.TestLibrary; @@ -30,7 +31,7 @@ api_key: 95283e6b156c42f3af8a9b16173f876b secret_rp: 1234567 "; - Fs.AddFile(Paths.SecretsPath.FullName, new MockFileData(secretsYml)); + Fs.AddFile(Paths.AppDataDirectory.File("secrets.yml").FullName, new MockFileData(secretsYml)); var expected = new[] { new @@ -67,7 +68,7 @@ sonarr: const string secretsYml = "no_api_key: 95283e6b156c42f3af8a9b16173f876b"; - Fs.AddFile(Paths.SecretsPath.FullName, new MockFileData(secretsYml)); + Fs.AddFile(Paths.AppDataDirectory.File("recyclarr.yml").FullName, new MockFileData(secretsYml)); var result = configLoader.Load(() => new StringReader(testYml), SupportedServices.Sonarr); result.Should().BeEmpty(); @@ -120,7 +121,7 @@ sonarr: const string secretsYml = @"bogus_profile: 95283e6b156c42f3af8a9b16173f876b"; - Fs.AddFile(Paths.SecretsPath.FullName, new MockFileData(secretsYml)); + Fs.AddFile(Paths.AppDataDirectory.File("recyclarr.yml").FullName, new MockFileData(secretsYml)); var result = configLoader.Load(() => new StringReader(testYml), SupportedServices.Sonarr); result.Should().BeEmpty(); } diff --git a/src/tests/Recyclarr.TrashLib.Tests/Config/Settings/SettingsPersisterTest.cs b/src/tests/Recyclarr.TrashLib.Tests/Config/Settings/SettingsPersisterTest.cs index a4bd4e3d..8821186a 100644 --- a/src/tests/Recyclarr.TrashLib.Tests/Config/Settings/SettingsPersisterTest.cs +++ b/src/tests/Recyclarr.TrashLib.Tests/Config/Settings/SettingsPersisterTest.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions; using Recyclarr.TrashLib.Config.Yaml; using Recyclarr.TrashLib.Settings; using Recyclarr.TrashLib.Startup; @@ -16,7 +17,7 @@ public class SettingsPersisterTest { _ = sut.Settings; - fileSystem.AllFiles.Should().ContainSingle(paths.SettingsPath.FullName); + fileSystem.AllFiles.Should().ContainSingle(paths.AppDataDirectory.File("settings.yml").FullName); } [Test, AutoMockData]