diff --git a/CHANGELOG.md b/CHANGELOG.md index 30551533..db0eaefd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ changes you may need to make. - API Key is now sent via the `X-Api-Key` header instead of the `apikey` query parameter. This lessens the need to redact information in the console. -- **BREAKING**: `replace_existing_custom_formats` now defaults to `true` +- **BREAKING**: `replace_existing_custom_formats` now defaults to `true`. +- **BREAKING**: Restructured repository settings. ### Removed diff --git a/schemas/settings-schema.json b/schemas/settings-schema.json index 2faaea06..53122e0c 100644 --- a/schemas/settings-schema.json +++ b/schemas/settings-schema.json @@ -6,31 +6,18 @@ "description": "Optional settings to control the behavior of Recyclarr", "additionalProperties": false, "properties": { - "repository": { + "repositories": { "type": "object", "additionalProperties": false, - "title": "Settings for the git repo", - "description": "", + "title": "Settings for any local git clones that Recyclarr manages", "properties": { - "clone_url": { - "type": "string", - "format": "uri", - "title": "Clone URL to the trash guides git repository", - "description": "A URL compatible with `git clone` that is used to clone the Trash Guides repository. This setting exists for enthusiasts that may want to instead have Recyclarr pull data from a fork instead of the official repository." + "trash_guide": { + "$ref": "#/$defs/repository", + "title": "Settings for the Trash Guides GitHub repository" }, - "branch": { - "type": "string", - "title": "The name of a branch to check out in the repository" - }, - "sha1": { - "type": "string", - "title": "A SHA1 (commit hash) in Git to use", - "description": "If specified, it overrides the `branch` setting. This SHA1 is passed to `git reset --hard` to force your local clone to this specific revision in the repository. If not specified, only the `branch` controls what revision is used in the repo." - }, - "git_path": { - "type": "string", - "title": "Provide an explicit path to your git executable", - "description": "Note that this is a path to the actual executable itself and not a directory path. If this setting is not specified, Recyclarr will attempt to find git via your PATH environment variable." + "config_templates": { + "$ref": "#/$defs/repository", + "title": "Settings for the Recyclarr Config Templates GitHub repository" } } }, @@ -54,5 +41,33 @@ } } } + }, + "$defs": { + "repository": { + "type": "object", + "additionalProperties": false, + "properties": { + "clone_url": { + "type": "string", + "format": "uri", + "title": "Clone URL to the trash guides git repository", + "description": "A URL compatible with `git clone` that is used to clone the Trash Guides repository. This setting exists for enthusiasts that may want to instead have Recyclarr pull data from a fork instead of the official repository." + }, + "branch": { + "type": "string", + "title": "The name of a branch to check out in the repository" + }, + "sha1": { + "type": "string", + "title": "A SHA1 (commit hash) in Git to use", + "description": "If specified, it overrides the `branch` setting. This SHA1 is passed to `git reset --hard` to force your local clone to this specific revision in the repository. If not specified, only the `branch` controls what revision is used in the repo." + }, + "git_path": { + "type": "string", + "title": "Provide an explicit path to your git executable", + "description": "Note that this is a path to the actual executable itself and not a directory path. If this setting is not specified, Recyclarr will attempt to find git via your PATH environment variable." + } + } + } } } diff --git a/src/Recyclarr.Cli.Tests/ServiceCompatibilityIntegrationTest.cs b/src/Recyclarr.Cli.Tests/ServiceCompatibilityIntegrationTest.cs index 55be8230..102bb99c 100644 --- a/src/Recyclarr.Cli.Tests/ServiceCompatibilityIntegrationTest.cs +++ b/src/Recyclarr.Cli.Tests/ServiceCompatibilityIntegrationTest.cs @@ -15,14 +15,15 @@ public class ServiceCompatibilityIntegrationTest : CliIntegrationFixture // For this test, it doesn't really matter if the YAML data matches what SettingsValue expects. // This test only ensures that the data deserialized is from the actual correct file. const string yamlData = @" -repository: - clone_url: http://the_url.com +repositories: + trash_guide: + clone_url: http://the_url.com "; Fs.AddFile(Paths.SettingsPath.FullName, new MockFileData(yamlData)); var settings = sut.Settings; - settings.Repository.CloneUrl.Should().Be("http://the_url.com"); + settings.Repositories.TrashGuide.CloneUrl.Should().Be("http://the_url.com"); } } diff --git a/src/Recyclarr.Cli/Console/Commands/ListCustomFormatsCommand.cs b/src/Recyclarr.Cli/Console/Commands/ListCustomFormatsCommand.cs index 3551d7ae..5edf02a6 100644 --- a/src/Recyclarr.Cli/Console/Commands/ListCustomFormatsCommand.cs +++ b/src/Recyclarr.Cli/Console/Commands/ListCustomFormatsCommand.cs @@ -5,6 +5,7 @@ using Recyclarr.Cli.Console.Helpers; using Recyclarr.Cli.Pipelines.CustomFormat.Guide; using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Repo; +using Recyclarr.TrashLib.Settings; using Spectre.Console.Cli; #pragma warning disable CS8765 @@ -17,6 +18,7 @@ internal class ListCustomFormatsCommand : AsyncCommand ExecuteAsync(CommandContext context, CliSettings settings) { - await _repoUpdater.UpdateRepo(); + await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide); _lister.ListCustomFormats(settings.Service); return 0; } diff --git a/src/Recyclarr.Cli/Console/Commands/ListQualitiesCommand.cs b/src/Recyclarr.Cli/Console/Commands/ListQualitiesCommand.cs index cd40c4ea..0827c4b1 100644 --- a/src/Recyclarr.Cli/Console/Commands/ListQualitiesCommand.cs +++ b/src/Recyclarr.Cli/Console/Commands/ListQualitiesCommand.cs @@ -5,6 +5,7 @@ using Recyclarr.Cli.Console.Helpers; using Recyclarr.Cli.Pipelines.QualitySize.Guide; using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Repo; +using Recyclarr.TrashLib.Settings; using Spectre.Console.Cli; namespace Recyclarr.Cli.Console.Commands; @@ -16,6 +17,7 @@ internal class ListQualitiesCommand : AsyncCommand ExecuteAsync(CommandContext context, CliSettings settings) { - await _repoUpdater.UpdateRepo(); + await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide); _lister.ListQualities(settings.Service); return 0; } diff --git a/src/Recyclarr.Cli/Console/Commands/ListReleaseProfilesCommand.cs b/src/Recyclarr.Cli/Console/Commands/ListReleaseProfilesCommand.cs index df8207f2..00b664d4 100644 --- a/src/Recyclarr.Cli/Console/Commands/ListReleaseProfilesCommand.cs +++ b/src/Recyclarr.Cli/Console/Commands/ListReleaseProfilesCommand.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using Recyclarr.Cli.Pipelines.ReleaseProfile.Guide; using Recyclarr.TrashLib.Repo; +using Recyclarr.TrashLib.Settings; using Spectre.Console.Cli; #pragma warning disable CS8765 @@ -16,6 +17,7 @@ internal class ListReleaseProfilesCommand : AsyncCommand ExecuteAsync(CommandContext context, CliSettings settings) { try { - await _repoUpdater.UpdateRepo(); + await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide); if (settings.ListTerms is not null) { diff --git a/src/Recyclarr.Cli/Console/Commands/SyncCommand.cs b/src/Recyclarr.Cli/Console/Commands/SyncCommand.cs index 3f3a2ac2..c1aa6226 100644 --- a/src/Recyclarr.Cli/Console/Commands/SyncCommand.cs +++ b/src/Recyclarr.Cli/Console/Commands/SyncCommand.cs @@ -8,6 +8,7 @@ using Recyclarr.Cli.Migration; using Recyclarr.Cli.Processors; using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Repo; +using Recyclarr.TrashLib.Settings; using Spectre.Console.Cli; namespace Recyclarr.Cli.Console.Commands; @@ -19,6 +20,7 @@ public class SyncCommand : AsyncCommand private readonly IMigrationExecutor _migration; private readonly IRepoUpdater _repoUpdater; private readonly ISyncProcessor _syncProcessor; + private readonly ISettingsProvider _settings; [UsedImplicitly] [SuppressMessage("Design", "CA1034:Nested types should not be visible")] @@ -53,11 +55,13 @@ public class SyncCommand : AsyncCommand public SyncCommand( IMigrationExecutor migration, IRepoUpdater repoUpdater, - ISyncProcessor syncProcessor) + ISyncProcessor syncProcessor, + ISettingsProvider settings) { _migration = migration; _repoUpdater = repoUpdater; _syncProcessor = syncProcessor; + _settings = settings; } [SuppressMessage("Design", "CA1031:Do not catch general exception types")] @@ -65,7 +69,7 @@ public class SyncCommand : AsyncCommand { // Will throw if migration is required, otherwise just a warning is issued. _migration.CheckNeededMigrations(); - await _repoUpdater.UpdateRepo(); + await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide); return (int) await _syncProcessor.ProcessConfigs(settings); } diff --git a/src/Recyclarr.TrashLib.Tests/Repo/VersionControl/GitPathTest.cs b/src/Recyclarr.TrashLib.Tests/Repo/VersionControl/GitPathTest.cs index b651b86d..3ee4fea8 100644 --- a/src/Recyclarr.TrashLib.Tests/Repo/VersionControl/GitPathTest.cs +++ b/src/Recyclarr.TrashLib.Tests/Repo/VersionControl/GitPathTest.cs @@ -12,7 +12,10 @@ public class GitPathTest [Frozen] ISettingsProvider settings, GitPath sut) { - settings.Settings.Returns(new SettingsValues {Repository = new TrashRepository {GitPath = null}}); + settings.Settings.Returns(new SettingsValues + { + GitPath = null + }); var result = sut.Path; @@ -25,7 +28,10 @@ public class GitPathTest GitPath sut) { var expectedPath = "/usr/local/bin/git"; - settings.Settings.Returns(new SettingsValues {Repository = new TrashRepository {GitPath = expectedPath}}); + settings.Settings.Returns(new SettingsValues + { + GitPath = expectedPath + }); var result = sut.Path; diff --git a/src/Recyclarr.TrashLib/Config/Listers/ConfigTemplateLister.cs b/src/Recyclarr.TrashLib/Config/Listers/ConfigTemplateLister.cs index ecd7bb96..edac7e97 100644 --- a/src/Recyclarr.TrashLib/Config/Listers/ConfigTemplateLister.cs +++ b/src/Recyclarr.TrashLib/Config/Listers/ConfigTemplateLister.cs @@ -1,5 +1,6 @@ using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Repo; +using Recyclarr.TrashLib.Settings; using Spectre.Console; namespace Recyclarr.TrashLib.Config.Listers; @@ -9,20 +10,23 @@ public class ConfigTemplateLister : IConfigLister private readonly IAnsiConsole _console; private readonly IConfigTemplateGuideService _guideService; private readonly IRepoUpdater _repoUpdater; + private readonly ISettingsProvider _settings; public ConfigTemplateLister( IAnsiConsole console, IConfigTemplateGuideService guideService, - IRepoUpdater repoUpdater) + IRepoUpdater repoUpdater, + ISettingsProvider settings) { _console = console; _guideService = guideService; _repoUpdater = repoUpdater; + _settings = settings; } public async Task List() { - await _repoUpdater.UpdateRepo(); + await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide); var data = _guideService.TemplateData; diff --git a/src/Recyclarr.TrashLib/Config/Parsing/ErrorHandling/ContextualMessages.cs b/src/Recyclarr.TrashLib/Config/Parsing/ErrorHandling/ContextualMessages.cs index 0ade317d..f7bdcafe 100644 --- a/src/Recyclarr.TrashLib/Config/Parsing/ErrorHandling/ContextualMessages.cs +++ b/src/Recyclarr.TrashLib/Config/Parsing/ErrorHandling/ContextualMessages.cs @@ -1,3 +1,4 @@ +using Recyclarr.TrashLib.Settings; using YamlDotNet.Core; namespace Recyclarr.TrashLib.Config.Parsing.ErrorHandling; @@ -16,6 +17,16 @@ public static class ContextualMessages "See: https://recyclarr.dev/wiki/upgrade-guide/v5.0/#reset-unmatched-scores"; } + if (e.Message.Contains( + "Property 'repository' not found on type " + + $"'{typeof(SettingsValues).FullName}'")) + { + return + "Usage of 'repository' setting is no " + + "longer supported. Use 'trash_guide' under 'repositories' instead." + + "See: https://recyclarr.dev/wiki/upgrade-guide/v5.0/#settings-repository-changes"; + } + return null; } } diff --git a/src/Recyclarr.TrashLib/Repo/IRepoUpdater.cs b/src/Recyclarr.TrashLib/Repo/IRepoUpdater.cs index 57ac6f6c..0096bbbf 100644 --- a/src/Recyclarr.TrashLib/Repo/IRepoUpdater.cs +++ b/src/Recyclarr.TrashLib/Repo/IRepoUpdater.cs @@ -1,9 +1,10 @@ using System.IO.Abstractions; +using Recyclarr.TrashLib.Settings; namespace Recyclarr.TrashLib.Repo; public interface IRepoUpdater { IDirectoryInfo RepoPath { get; } - Task UpdateRepo(); + Task UpdateRepo(IRepositorySettings repoSettings); } diff --git a/src/Recyclarr.TrashLib/Repo/RepoUpdater.cs b/src/Recyclarr.TrashLib/Repo/RepoUpdater.cs index f51f02a9..6036548b 100644 --- a/src/Recyclarr.TrashLib/Repo/RepoUpdater.cs +++ b/src/Recyclarr.TrashLib/Repo/RepoUpdater.cs @@ -12,44 +12,40 @@ public class RepoUpdater : IRepoUpdater private readonly IAppPaths _paths; private readonly IGitRepositoryFactory _repositoryFactory; private readonly IFileUtilities _fileUtils; - private readonly ISettingsProvider _settingsProvider; public RepoUpdater( ILogger log, IAppPaths paths, IGitRepositoryFactory repositoryFactory, - IFileUtilities fileUtils, - ISettingsProvider settingsProvider) + IFileUtilities fileUtils) { _log = log; _paths = paths; _repositoryFactory = repositoryFactory; _fileUtils = fileUtils; - _settingsProvider = settingsProvider; } public IDirectoryInfo RepoPath => _paths.RepoDirectory; - public async Task UpdateRepo() + public async Task UpdateRepo(IRepositorySettings repoSettings) { // Retry only once if there's a failure. This gives us an opportunity to delete the git repository and start // fresh. try { - await CheckoutAndUpdateRepo(); + await CheckoutAndUpdateRepo(repoSettings); } catch (GitCmdException e) { _log.Debug(e, "Non-zero exit code {ExitCode} while executing Git command: {Error}", e.ExitCode, e.Error); _log.Warning("Deleting local git repo and retrying git operation due to error..."); _fileUtils.DeleteReadOnlyDirectory(RepoPath.FullName); - await CheckoutAndUpdateRepo(); + await CheckoutAndUpdateRepo(repoSettings); } } - private async Task CheckoutAndUpdateRepo() + private async Task CheckoutAndUpdateRepo(IRepositorySettings repoSettings) { - var repoSettings = _settingsProvider.Settings.Repository; var cloneUrl = repoSettings.CloneUrl; var branch = repoSettings.Branch; diff --git a/src/Recyclarr.TrashLib/Repo/VersionControl/GitPath.cs b/src/Recyclarr.TrashLib/Repo/VersionControl/GitPath.cs index a28764c5..95971194 100644 --- a/src/Recyclarr.TrashLib/Repo/VersionControl/GitPath.cs +++ b/src/Recyclarr.TrashLib/Repo/VersionControl/GitPath.cs @@ -12,5 +12,5 @@ public class GitPath : IGitPath } public static string Default => "git"; - public string Path => _settings.Settings.Repository.GitPath ?? Default; + public string Path => _settings.Settings.GitPath ?? Default; } diff --git a/src/Recyclarr.TrashLib/Settings/IRepositorySettings.cs b/src/Recyclarr.TrashLib/Settings/IRepositorySettings.cs new file mode 100644 index 00000000..4115f885 --- /dev/null +++ b/src/Recyclarr.TrashLib/Settings/IRepositorySettings.cs @@ -0,0 +1,8 @@ +namespace Recyclarr.TrashLib.Settings; + +public interface IRepositorySettings +{ + Uri CloneUrl { get; } + string Branch { get; } + string? Sha1 { get; } +} diff --git a/src/Recyclarr.TrashLib/Settings/SettingsValues.cs b/src/Recyclarr.TrashLib/Settings/SettingsValues.cs index 76a61aa7..2a8a9c3b 100644 --- a/src/Recyclarr.TrashLib/Settings/SettingsValues.cs +++ b/src/Recyclarr.TrashLib/Settings/SettingsValues.cs @@ -2,7 +2,7 @@ using JetBrains.Annotations; namespace Recyclarr.TrashLib.Settings; -public record TrashRepository +public record TrashRepository : IRepositorySettings { public Uri CloneUrl { get; [UsedImplicitly] init; } = new("https://github.com/TRaSH-/Guides.git"); public string Branch { get; [UsedImplicitly] init; } = "master"; @@ -15,9 +15,15 @@ public record LogJanitorSettings public int MaxFiles { get; [UsedImplicitly] init; } = 20; } +public record Repositories +{ + public TrashRepository TrashGuide { get; [UsedImplicitly] init; } = new(); +} + public record SettingsValues { - public TrashRepository Repository { get; [UsedImplicitly] init; } = new(); + public Repositories Repositories { get; [UsedImplicitly] init; } = new(); public bool EnableSslCertificateValidation { get; [UsedImplicitly] init; } = true; public LogJanitorSettings LogJanitor { get; [UsedImplicitly] init; } = new(); + public string? GitPath { get; [UsedImplicitly] init; } }