feat!: Restructure repository settings

pull/201/head
Robert Dailey 1 year ago
parent b8ac93a464
commit da35eef1ed

@ -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 - 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. 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 ### Removed

@ -6,31 +6,18 @@
"description": "Optional settings to control the behavior of Recyclarr", "description": "Optional settings to control the behavior of Recyclarr",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"repository": { "repositories": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"title": "Settings for the git repo", "title": "Settings for any local git clones that Recyclarr manages",
"description": "",
"properties": { "properties": {
"clone_url": { "trash_guide": {
"type": "string", "$ref": "#/$defs/repository",
"format": "uri", "title": "Settings for the Trash Guides GitHub repository"
"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": { "config_templates": {
"type": "string", "$ref": "#/$defs/repository",
"title": "The name of a branch to check out in the repository" "title": "Settings for the Recyclarr Config Templates GitHub 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."
} }
} }
}, },
@ -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."
}
}
}
} }
} }

@ -15,14 +15,15 @@ public class ServiceCompatibilityIntegrationTest : CliIntegrationFixture
// For this test, it doesn't really matter if the YAML data matches what SettingsValue expects. // 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. // This test only ensures that the data deserialized is from the actual correct file.
const string yamlData = @" const string yamlData = @"
repository: repositories:
clone_url: http://the_url.com trash_guide:
clone_url: http://the_url.com
"; ";
Fs.AddFile(Paths.SettingsPath.FullName, new MockFileData(yamlData)); Fs.AddFile(Paths.SettingsPath.FullName, new MockFileData(yamlData));
var settings = sut.Settings; var settings = sut.Settings;
settings.Repository.CloneUrl.Should().Be("http://the_url.com"); settings.Repositories.TrashGuide.CloneUrl.Should().Be("http://the_url.com");
} }
} }

@ -5,6 +5,7 @@ using Recyclarr.Cli.Console.Helpers;
using Recyclarr.Cli.Pipelines.CustomFormat.Guide; using Recyclarr.Cli.Pipelines.CustomFormat.Guide;
using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo; using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Settings;
using Spectre.Console.Cli; using Spectre.Console.Cli;
#pragma warning disable CS8765 #pragma warning disable CS8765
@ -17,6 +18,7 @@ internal class ListCustomFormatsCommand : AsyncCommand<ListCustomFormatsCommand.
{ {
private readonly CustomFormatDataLister _lister; private readonly CustomFormatDataLister _lister;
private readonly IRepoUpdater _repoUpdater; private readonly IRepoUpdater _repoUpdater;
private readonly ISettingsProvider _settings;
[UsedImplicitly] [UsedImplicitly]
[SuppressMessage("Design", "CA1034:Nested types should not be visible")] [SuppressMessage("Design", "CA1034:Nested types should not be visible")]
@ -30,15 +32,17 @@ internal class ListCustomFormatsCommand : AsyncCommand<ListCustomFormatsCommand.
public ListCustomFormatsCommand( public ListCustomFormatsCommand(
CustomFormatDataLister lister, CustomFormatDataLister lister,
IRepoUpdater repoUpdater) IRepoUpdater repoUpdater,
ISettingsProvider settings)
{ {
_lister = lister; _lister = lister;
_repoUpdater = repoUpdater; _repoUpdater = repoUpdater;
_settings = settings;
} }
public override async Task<int> ExecuteAsync(CommandContext context, CliSettings settings) public override async Task<int> ExecuteAsync(CommandContext context, CliSettings settings)
{ {
await _repoUpdater.UpdateRepo(); await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide);
_lister.ListCustomFormats(settings.Service); _lister.ListCustomFormats(settings.Service);
return 0; return 0;
} }

@ -5,6 +5,7 @@ using Recyclarr.Cli.Console.Helpers;
using Recyclarr.Cli.Pipelines.QualitySize.Guide; using Recyclarr.Cli.Pipelines.QualitySize.Guide;
using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo; using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Settings;
using Spectre.Console.Cli; using Spectre.Console.Cli;
namespace Recyclarr.Cli.Console.Commands; namespace Recyclarr.Cli.Console.Commands;
@ -16,6 +17,7 @@ internal class ListQualitiesCommand : AsyncCommand<ListQualitiesCommand.CliSetti
{ {
private readonly QualitySizeDataLister _lister; private readonly QualitySizeDataLister _lister;
private readonly IRepoUpdater _repoUpdater; private readonly IRepoUpdater _repoUpdater;
private readonly ISettingsProvider _settings;
[UsedImplicitly] [UsedImplicitly]
[SuppressMessage("Design", "CA1034:Nested types should not be visible")] [SuppressMessage("Design", "CA1034:Nested types should not be visible")]
@ -27,15 +29,16 @@ internal class ListQualitiesCommand : AsyncCommand<ListQualitiesCommand.CliSetti
public SupportedServices Service { get; init; } public SupportedServices Service { get; init; }
} }
public ListQualitiesCommand(QualitySizeDataLister lister, IRepoUpdater repoUpdater) public ListQualitiesCommand(QualitySizeDataLister lister, IRepoUpdater repoUpdater, ISettingsProvider settings)
{ {
_lister = lister; _lister = lister;
_repoUpdater = repoUpdater; _repoUpdater = repoUpdater;
_settings = settings;
} }
public override async Task<int> ExecuteAsync(CommandContext context, CliSettings settings) public override async Task<int> ExecuteAsync(CommandContext context, CliSettings settings)
{ {
await _repoUpdater.UpdateRepo(); await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide);
_lister.ListQualities(settings.Service); _lister.ListQualities(settings.Service);
return 0; return 0;
} }

@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations; using JetBrains.Annotations;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Guide; using Recyclarr.Cli.Pipelines.ReleaseProfile.Guide;
using Recyclarr.TrashLib.Repo; using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Settings;
using Spectre.Console.Cli; using Spectre.Console.Cli;
#pragma warning disable CS8765 #pragma warning disable CS8765
@ -16,6 +17,7 @@ internal class ListReleaseProfilesCommand : AsyncCommand<ListReleaseProfilesComm
private readonly ILogger _log; private readonly ILogger _log;
private readonly ReleaseProfileDataLister _lister; private readonly ReleaseProfileDataLister _lister;
private readonly IRepoUpdater _repoUpdater; private readonly IRepoUpdater _repoUpdater;
private readonly ISettingsProvider _settings;
[UsedImplicitly] [UsedImplicitly]
[SuppressMessage("Design", "CA1034:Nested types should not be visible")] [SuppressMessage("Design", "CA1034:Nested types should not be visible")]
@ -32,18 +34,20 @@ internal class ListReleaseProfilesCommand : AsyncCommand<ListReleaseProfilesComm
public ListReleaseProfilesCommand( public ListReleaseProfilesCommand(
ILogger log, ILogger log,
ReleaseProfileDataLister lister, ReleaseProfileDataLister lister,
IRepoUpdater repoUpdater) IRepoUpdater repoUpdater,
ISettingsProvider settings)
{ {
_log = log; _log = log;
_lister = lister; _lister = lister;
_repoUpdater = repoUpdater; _repoUpdater = repoUpdater;
_settings = settings;
} }
public override async Task<int> ExecuteAsync(CommandContext context, CliSettings settings) public override async Task<int> ExecuteAsync(CommandContext context, CliSettings settings)
{ {
try try
{ {
await _repoUpdater.UpdateRepo(); await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide);
if (settings.ListTerms is not null) if (settings.ListTerms is not null)
{ {

@ -8,6 +8,7 @@ using Recyclarr.Cli.Migration;
using Recyclarr.Cli.Processors; using Recyclarr.Cli.Processors;
using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo; using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Settings;
using Spectre.Console.Cli; using Spectre.Console.Cli;
namespace Recyclarr.Cli.Console.Commands; namespace Recyclarr.Cli.Console.Commands;
@ -19,6 +20,7 @@ public class SyncCommand : AsyncCommand<SyncCommand.CliSettings>
private readonly IMigrationExecutor _migration; private readonly IMigrationExecutor _migration;
private readonly IRepoUpdater _repoUpdater; private readonly IRepoUpdater _repoUpdater;
private readonly ISyncProcessor _syncProcessor; private readonly ISyncProcessor _syncProcessor;
private readonly ISettingsProvider _settings;
[UsedImplicitly] [UsedImplicitly]
[SuppressMessage("Design", "CA1034:Nested types should not be visible")] [SuppressMessage("Design", "CA1034:Nested types should not be visible")]
@ -53,11 +55,13 @@ public class SyncCommand : AsyncCommand<SyncCommand.CliSettings>
public SyncCommand( public SyncCommand(
IMigrationExecutor migration, IMigrationExecutor migration,
IRepoUpdater repoUpdater, IRepoUpdater repoUpdater,
ISyncProcessor syncProcessor) ISyncProcessor syncProcessor,
ISettingsProvider settings)
{ {
_migration = migration; _migration = migration;
_repoUpdater = repoUpdater; _repoUpdater = repoUpdater;
_syncProcessor = syncProcessor; _syncProcessor = syncProcessor;
_settings = settings;
} }
[SuppressMessage("Design", "CA1031:Do not catch general exception types")] [SuppressMessage("Design", "CA1031:Do not catch general exception types")]
@ -65,7 +69,7 @@ public class SyncCommand : AsyncCommand<SyncCommand.CliSettings>
{ {
// Will throw if migration is required, otherwise just a warning is issued. // Will throw if migration is required, otherwise just a warning is issued.
_migration.CheckNeededMigrations(); _migration.CheckNeededMigrations();
await _repoUpdater.UpdateRepo(); await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide);
return (int) await _syncProcessor.ProcessConfigs(settings); return (int) await _syncProcessor.ProcessConfigs(settings);
} }

@ -12,7 +12,10 @@ public class GitPathTest
[Frozen] ISettingsProvider settings, [Frozen] ISettingsProvider settings,
GitPath sut) GitPath sut)
{ {
settings.Settings.Returns(new SettingsValues {Repository = new TrashRepository {GitPath = null}}); settings.Settings.Returns(new SettingsValues
{
GitPath = null
});
var result = sut.Path; var result = sut.Path;
@ -25,7 +28,10 @@ public class GitPathTest
GitPath sut) GitPath sut)
{ {
var expectedPath = "/usr/local/bin/git"; 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; var result = sut.Path;

@ -1,5 +1,6 @@
using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Repo; using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Settings;
using Spectre.Console; using Spectre.Console;
namespace Recyclarr.TrashLib.Config.Listers; namespace Recyclarr.TrashLib.Config.Listers;
@ -9,20 +10,23 @@ public class ConfigTemplateLister : IConfigLister
private readonly IAnsiConsole _console; private readonly IAnsiConsole _console;
private readonly IConfigTemplateGuideService _guideService; private readonly IConfigTemplateGuideService _guideService;
private readonly IRepoUpdater _repoUpdater; private readonly IRepoUpdater _repoUpdater;
private readonly ISettingsProvider _settings;
public ConfigTemplateLister( public ConfigTemplateLister(
IAnsiConsole console, IAnsiConsole console,
IConfigTemplateGuideService guideService, IConfigTemplateGuideService guideService,
IRepoUpdater repoUpdater) IRepoUpdater repoUpdater,
ISettingsProvider settings)
{ {
_console = console; _console = console;
_guideService = guideService; _guideService = guideService;
_repoUpdater = repoUpdater; _repoUpdater = repoUpdater;
_settings = settings;
} }
public async Task List() public async Task List()
{ {
await _repoUpdater.UpdateRepo(); await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.TrashGuide);
var data = _guideService.TemplateData; var data = _guideService.TemplateData;

@ -1,3 +1,4 @@
using Recyclarr.TrashLib.Settings;
using YamlDotNet.Core; using YamlDotNet.Core;
namespace Recyclarr.TrashLib.Config.Parsing.ErrorHandling; 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"; "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; return null;
} }
} }

@ -1,9 +1,10 @@
using System.IO.Abstractions; using System.IO.Abstractions;
using Recyclarr.TrashLib.Settings;
namespace Recyclarr.TrashLib.Repo; namespace Recyclarr.TrashLib.Repo;
public interface IRepoUpdater public interface IRepoUpdater
{ {
IDirectoryInfo RepoPath { get; } IDirectoryInfo RepoPath { get; }
Task UpdateRepo(); Task UpdateRepo(IRepositorySettings repoSettings);
} }

@ -12,44 +12,40 @@ public class RepoUpdater : IRepoUpdater
private readonly IAppPaths _paths; private readonly IAppPaths _paths;
private readonly IGitRepositoryFactory _repositoryFactory; private readonly IGitRepositoryFactory _repositoryFactory;
private readonly IFileUtilities _fileUtils; private readonly IFileUtilities _fileUtils;
private readonly ISettingsProvider _settingsProvider;
public RepoUpdater( public RepoUpdater(
ILogger log, ILogger log,
IAppPaths paths, IAppPaths paths,
IGitRepositoryFactory repositoryFactory, IGitRepositoryFactory repositoryFactory,
IFileUtilities fileUtils, IFileUtilities fileUtils)
ISettingsProvider settingsProvider)
{ {
_log = log; _log = log;
_paths = paths; _paths = paths;
_repositoryFactory = repositoryFactory; _repositoryFactory = repositoryFactory;
_fileUtils = fileUtils; _fileUtils = fileUtils;
_settingsProvider = settingsProvider;
} }
public IDirectoryInfo RepoPath => _paths.RepoDirectory; 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 // Retry only once if there's a failure. This gives us an opportunity to delete the git repository and start
// fresh. // fresh.
try try
{ {
await CheckoutAndUpdateRepo(); await CheckoutAndUpdateRepo(repoSettings);
} }
catch (GitCmdException e) catch (GitCmdException e)
{ {
_log.Debug(e, "Non-zero exit code {ExitCode} while executing Git command: {Error}", e.ExitCode, e.Error); _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..."); _log.Warning("Deleting local git repo and retrying git operation due to error...");
_fileUtils.DeleteReadOnlyDirectory(RepoPath.FullName); _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 cloneUrl = repoSettings.CloneUrl;
var branch = repoSettings.Branch; var branch = repoSettings.Branch;

@ -12,5 +12,5 @@ public class GitPath : IGitPath
} }
public static string Default => "git"; public static string Default => "git";
public string Path => _settings.Settings.Repository.GitPath ?? Default; public string Path => _settings.Settings.GitPath ?? Default;
} }

@ -0,0 +1,8 @@
namespace Recyclarr.TrashLib.Settings;
public interface IRepositorySettings
{
Uri CloneUrl { get; }
string Branch { get; }
string? Sha1 { get; }
}

@ -2,7 +2,7 @@ using JetBrains.Annotations;
namespace Recyclarr.TrashLib.Settings; 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 Uri CloneUrl { get; [UsedImplicitly] init; } = new("https://github.com/TRaSH-/Guides.git");
public string Branch { get; [UsedImplicitly] init; } = "master"; public string Branch { get; [UsedImplicitly] init; } = "master";
@ -15,9 +15,15 @@ public record LogJanitorSettings
public int MaxFiles { get; [UsedImplicitly] init; } = 20; public int MaxFiles { get; [UsedImplicitly] init; } = 20;
} }
public record Repositories
{
public TrashRepository TrashGuide { get; [UsedImplicitly] init; } = new();
}
public record SettingsValues 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 bool EnableSslCertificateValidation { get; [UsedImplicitly] init; } = true;
public LogJanitorSettings LogJanitor { get; [UsedImplicitly] init; } = new(); public LogJanitorSettings LogJanitor { get; [UsedImplicitly] init; } = new();
public string? GitPath { get; [UsedImplicitly] init; }
} }

Loading…
Cancel
Save