diff --git a/CHANGELOG.md b/CHANGELOG.md index 441d8e95..659e1aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ changes you may need to make. [breaking7]: https://recyclarr.dev/wiki/upgrade-guide/v7.0/ +### Added + +- YAML: New `includes` subdirectory intended to hold only include templates. Relative paths + specified in the `config` include directive are resolved starting at this new directory. Relative + paths to include templates located under the `configs` directory is now **DEPRECATED**. See the + "File Structure" page on the wiki for more details. + ### Changed - **BREAKING**: The app data directory on OSX has changed. It now lives at `~/Library/Application diff --git a/src/Recyclarr.Cli/Console/Setup/AppPathSetupTask.cs b/src/Recyclarr.Cli/Console/Setup/AppPathSetupTask.cs index 07eeaed5..cdd70bb5 100644 --- a/src/Recyclarr.Cli/Console/Setup/AppPathSetupTask.cs +++ b/src/Recyclarr.Cli/Console/Setup/AppPathSetupTask.cs @@ -13,6 +13,7 @@ public class AppPathSetupTask(ILogger log, IAppPaths paths) : IBaseCommandSetupT paths.CacheDirectory.Create(); paths.LogDirectory.Create(); paths.ConfigsDirectory.Create(); + paths.IncludesDirectory.Create(); } public void OnFinish() diff --git a/src/Recyclarr.Config/Parsing/PostProcessing/ConfigMerging/ConfigIncludeProcessor.cs b/src/Recyclarr.Config/Parsing/PostProcessing/ConfigMerging/ConfigIncludeProcessor.cs index 55df0825..4fecc65e 100644 --- a/src/Recyclarr.Config/Parsing/PostProcessing/ConfigMerging/ConfigIncludeProcessor.cs +++ b/src/Recyclarr.Config/Parsing/PostProcessing/ConfigMerging/ConfigIncludeProcessor.cs @@ -4,13 +4,43 @@ using Recyclarr.TrashGuide; namespace Recyclarr.Config.Parsing.PostProcessing.ConfigMerging; -public class ConfigIncludeProcessor(IFileSystem fs, IAppPaths paths) : IIncludeProcessor +public class ConfigIncludeProcessor(IFileSystem fs, IAppPaths paths, ILogger log) : IIncludeProcessor { public bool CanProcess(IYamlInclude includeDirective) { return includeDirective is ConfigYamlInclude; } + private IFileInfo? ConvertToAbsolute(string path) + { + if (fs.Path.IsPathRooted(path)) + { + log.Debug("Path processed as absolute: {Path}", path); + return fs.FileInfo.New(path); + } + + var fsPath = paths.IncludesDirectory.File(path); + if (fsPath.Exists) + { + log.Debug("Path rooted to the includes directory: {Path}", path); + return fsPath; + } + + fsPath = paths.ConfigsDirectory.File(path); + // ReSharper disable once InvertIf + if (fsPath.Exists) + { + log.Warning( + "DEPRECATED: Include templates inside the `configs` directory are no longer supported. " + + "These files should be relocated to the new sibling `includes` directory instead. " + + "See: https://recyclarr.dev/wiki/upgrade-guide/v8.0/#include-dir"); + + return fsPath; + } + + return null; + } + public IFileInfo GetPathToConfig(IYamlInclude includeDirective, SupportedServices serviceType) { var include = (ConfigYamlInclude) includeDirective; @@ -20,16 +50,10 @@ public class ConfigIncludeProcessor(IFileSystem fs, IAppPaths paths) : IIncludeP throw new YamlIncludeException("`config` property is required."); } - var rooted = fs.Path.IsPathRooted(include.Config); - - var configFile = rooted - ? fs.FileInfo.New(include.Config) - : paths.ConfigsDirectory.File(include.Config); - - if (!configFile.Exists) + var configFile = ConvertToAbsolute(include.Config); + if (configFile?.Exists != true) { - var pathType = rooted ? "Absolute" : "Relative"; - throw new YamlIncludeException($"{pathType} include path does not exist: {configFile.FullName}"); + throw new YamlIncludeException($"Include path could not be resolved: {include.Config}"); } return configFile; diff --git a/src/Recyclarr.Platform/AppPaths.cs b/src/Recyclarr.Platform/AppPaths.cs index f0224a59..deab2605 100644 --- a/src/Recyclarr.Platform/AppPaths.cs +++ b/src/Recyclarr.Platform/AppPaths.cs @@ -11,4 +11,5 @@ public class AppPaths(IDirectoryInfo appDataPath) : IAppPaths public IDirectoryInfo ReposDirectory => AppDataDirectory.SubDirectory("repositories"); public IDirectoryInfo CacheDirectory => AppDataDirectory.SubDirectory("cache"); public IDirectoryInfo ConfigsDirectory => AppDataDirectory.SubDirectory("configs"); + public IDirectoryInfo IncludesDirectory => AppDataDirectory.SubDirectory("includes"); } diff --git a/src/Recyclarr.Platform/IAppPaths.cs b/src/Recyclarr.Platform/IAppPaths.cs index 64f847fe..8f2834bc 100644 --- a/src/Recyclarr.Platform/IAppPaths.cs +++ b/src/Recyclarr.Platform/IAppPaths.cs @@ -9,4 +9,5 @@ public interface IAppPaths IDirectoryInfo ReposDirectory { get; } IDirectoryInfo CacheDirectory { get; } IDirectoryInfo ConfigsDirectory { get; } + IDirectoryInfo IncludesDirectory { get; } } diff --git a/tests/Recyclarr.Cli.IntegrationTests/BaseCommandSetupIntegrationTest.cs b/tests/Recyclarr.Cli.IntegrationTests/BaseCommandSetupIntegrationTest.cs index 5993ab29..418f66b6 100644 --- a/tests/Recyclarr.Cli.IntegrationTests/BaseCommandSetupIntegrationTest.cs +++ b/tests/Recyclarr.Cli.IntegrationTests/BaseCommandSetupIntegrationTest.cs @@ -76,7 +76,8 @@ internal class BaseCommandSetupIntegrationTest : CliIntegrationFixture { Paths.CacheDirectory.FullName, Paths.LogDirectory.FullName, - Paths.ConfigsDirectory.FullName + Paths.ConfigsDirectory.FullName, + Paths.IncludesDirectory.FullName }; Fs.LeafDirectories().Should().BeEquivalentTo(expectedDirs); diff --git a/tests/Recyclarr.Tests/Config/Parsing/PostProcessing/ConfigMerging/ConfigIncludeProcessorTest.cs b/tests/Recyclarr.Tests/Config/Parsing/PostProcessing/ConfigMerging/ConfigIncludeProcessorTest.cs index c31c8116..62935892 100644 --- a/tests/Recyclarr.Tests/Config/Parsing/PostProcessing/ConfigMerging/ConfigIncludeProcessorTest.cs +++ b/tests/Recyclarr.Tests/Config/Parsing/PostProcessing/ConfigMerging/ConfigIncludeProcessorTest.cs @@ -35,7 +35,7 @@ public class ConfigIncludeProcessorTest [Frozen] IAppPaths paths, ConfigIncludeProcessor sut) { - fs.AddEmptyFile(paths.ConfigsDirectory.File("foo/bar/config.yml")); + fs.AddEmptyFile(paths.IncludesDirectory.File("foo/bar/config.yml")); var includeDirective = new ConfigYamlInclude { @@ -44,7 +44,7 @@ public class ConfigIncludeProcessorTest var path = sut.GetPathToConfig(includeDirective, default); - path.FullName.Should().Be(paths.ConfigsDirectory.File("foo/bar/config.yml").FullName); + path.FullName.Should().Be(paths.IncludesDirectory.File("foo/bar/config.yml").FullName); } [Test, AutoMockData] @@ -78,7 +78,7 @@ public class ConfigIncludeProcessorTest var act = () => sut.GetPathToConfig(includeDirective, default); - act.Should().Throw().WithMessage("Relative*not exist*"); + act.Should().Throw(); } [Test, AutoMockData] @@ -95,6 +95,6 @@ public class ConfigIncludeProcessorTest var act = () => sut.GetPathToConfig(includeDirective, default); - act.Should().Throw().WithMessage("Absolute*not exist*"); + act.Should().Throw(); } }