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]