feat: Move config templates to new repository

pull/201/head
Robert Dailey 1 year ago
parent 7edcd77f98
commit 3823b0ec43

@ -19,6 +19,8 @@ changes you may need to make.
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. - **BREAKING**: Restructured repository settings.
- Configuration templates repository moved to `recyclarr/config-templates` on GitHub. Corresponding
settings for this repo as well (see the Settings YAML Reference on the wiki for more details).
### Removed ### Removed

@ -1,6 +1,7 @@
using System.IO.Abstractions; using System.IO.Abstractions;
using Recyclarr.Cli.Console.Setup; using Recyclarr.Cli.Console.Setup;
using Recyclarr.Cli.TestLibrary; using Recyclarr.Cli.TestLibrary;
using Recyclarr.Common.TestLibrary;
using Recyclarr.TrashLib.Settings; using Recyclarr.TrashLib.Settings;
namespace Recyclarr.Cli.Tests; namespace Recyclarr.Cli.Tests;
@ -74,11 +75,11 @@ log_janitor:
var expectedDirs = new[] var expectedDirs = new[]
{ {
Paths.CacheDirectory.FullName,
Paths.LogDirectory.FullName, Paths.LogDirectory.FullName,
Paths.RepoDirectory.FullName, Paths.ConfigsDirectory.FullName
Paths.CacheDirectory.FullName
}; };
expectedDirs.Should().IntersectWith(Fs.AllDirectories); Fs.LeafDirectories().Should().BeEquivalentTo(expectedDirs);
} }
} }

@ -3,7 +3,10 @@ using System.Diagnostics.CodeAnalysis;
using Autofac; using Autofac;
using Autofac.Core; using Autofac.Core;
using NUnit.Framework.Internal; using NUnit.Framework.Internal;
using Recyclarr.Cli.TestLibrary; using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashLib.Startup;
using Serilog.Core;
using Spectre.Console;
namespace Recyclarr.Cli.Tests; namespace Recyclarr.Cli.Tests;
@ -14,17 +17,26 @@ public class CompositionRootTest
// Warning CA1812 : CompositionRootTest.ConcreteTypeEnumerator is an internal class that is apparently never // Warning CA1812 : CompositionRootTest.ConcreteTypeEnumerator is an internal class that is apparently never
// instantiated. // instantiated.
[SuppressMessage("Performance", "CA1812", Justification = "Created via reflection by TestCaseSource attribute")] [SuppressMessage("Performance", "CA1812", Justification = "Created via reflection by TestCaseSource attribute")]
private sealed class ConcreteTypeEnumerator : CliIntegrationFixture, IEnumerable private sealed class ConcreteTypeEnumerator : IEnumerable
{ {
public IEnumerator GetEnumerator() public IEnumerator GetEnumerator()
{ {
return Container.ComponentRegistry.Registrations var builder = new ContainerBuilder();
CompositionRoot.Setup(builder);
CompositionRoot.RegisterExternal(builder, new LoggingLevelSwitch(), new AppDataPathProvider());
// These are things that Spectre.Console normally registers for us, so they won't explicitly be
// in the CompositionRoot. Register mocks/stubs here.
builder.RegisterMockFor<IAnsiConsole>();
var container = builder.Build();
return container.ComponentRegistry.Registrations
.SelectMany(x => x.Services) .SelectMany(x => x.Services)
.OfType<TypedService>() .OfType<TypedService>()
.Select(x => x.ServiceType) .Select(x => x.ServiceType)
.Distinct() .Distinct()
.Where(x => x.FullName == null || !x.FullName.StartsWith("Autofac.")) .Where(x => x.FullName == null || !x.FullName.StartsWith("Autofac."))
.Select(x => new TestCaseParameters(new object[] {Container, x}) {TestName = x.FullName}) .Select(x => new TestCaseParameters(new object[] {container, x}) {TestName = x.FullName})
.GetEnumerator(); .GetEnumerator();
} }
} }

@ -1,4 +1,5 @@
using System.IO.Abstractions; using System.IO.Abstractions;
using System.IO.Abstractions.Extensions;
using Recyclarr.Cli.Pipelines.CustomFormat.Guide; using Recyclarr.Cli.Pipelines.CustomFormat.Guide;
using Recyclarr.TestLibrary.AutoFixture; using Recyclarr.TestLibrary.AutoFixture;
using Recyclarr.TrashLib.Startup; using Recyclarr.TrashLib.Startup;
@ -50,7 +51,7 @@ public class CustomFormatCategoryParserTest
------ ------
"; ";
var file = paths.RepoDirectory var file = fs.CurrentDirectory()
.SubDirectory("docs") .SubDirectory("docs")
.SubDirectory("Radarr") .SubDirectory("Radarr")
.File("Radarr-collection-of-custom-formats.md"); .File("Radarr-collection-of-custom-formats.md");

@ -4,6 +4,7 @@ using Recyclarr.Cli.TestLibrary;
using Recyclarr.Common.Extensions; using Recyclarr.Common.Extensions;
using Recyclarr.Common.TestLibrary; using Recyclarr.Common.TestLibrary;
using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo;
namespace Recyclarr.Cli.Tests.Pipelines.QualitySize.Guide; namespace Recyclarr.Cli.Tests.Pipelines.QualitySize.Guide;
@ -15,8 +16,23 @@ public class QualityGuideServiceTest : CliIntegrationFixture
[TestCase(SupportedServices.Radarr, "radarr")] [TestCase(SupportedServices.Radarr, "radarr")]
public void Get_data_for_service(SupportedServices service, string serviceDir) public void Get_data_for_service(SupportedServices service, string serviceDir)
{ {
var repo = Resolve<ITrashGuidesRepo>();
const string metadataJson = @"
{
'json_paths': {
'radarr': {
'qualities': ['docs/json/radarr/quality-size']
},
'sonarr': {
'qualities': ['docs/json/sonarr/quality-size']
}
}
}";
Fs.AddFile(repo.Path.File("metadata.json"), new MockFileData(metadataJson));
Fs.AddFileFromEmbeddedResource( Fs.AddFileFromEmbeddedResource(
Paths.RepoDirectory.SubDir("docs", "json", serviceDir, "quality-size").File("metadata.json"), repo.Path.SubDir("docs", "json", serviceDir, "quality-size").File("some-quality-size.json"),
GetType(), GetType(),
"Data.quality_size.json"); "Data.quality_size.json");

@ -10,20 +10,20 @@ public class ServiceCompatibilityIntegrationTest : CliIntegrationFixture
[Test] [Test]
public void Load_settings_yml_correctly_when_file_exists() public void Load_settings_yml_correctly_when_file_exists()
{ {
var sut = Resolve<ISettingsProvider>(); var sut = Resolve<SettingsProvider>();
// 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 = @"
repositories: repositories:
trash_guide: trash_guides:
clone_url: http://the_url.com clone_url: http://the_url.com
"; ";
Fs.AddFile(Paths.SettingsPath.FullName, new MockFileData(yamlData)); Fs.AddFile(Paths.SettingsPath, new MockFileData(yamlData));
Paths.SettingsPath.Refresh();
var settings = sut.Settings; var settings = sut.Settings;
settings.Repositories.TrashGuide.CloneUrl.Should().Be("http://the_url.com"); settings.Repositories.TrashGuides.CloneUrl.Should().Be("http://the_url.com");
} }
} }

@ -19,6 +19,8 @@ using Recyclarr.Cli.Processors;
using Recyclarr.Common; using Recyclarr.Common;
using Recyclarr.TrashLib; using Recyclarr.TrashLib;
using Recyclarr.TrashLib.Interfaces; using Recyclarr.TrashLib.Interfaces;
using Recyclarr.TrashLib.Startup;
using Serilog.Core;
using Spectre.Console.Cli; using Spectre.Console.Cli;
namespace Recyclarr.Cli; namespace Recyclarr.Cli;
@ -87,4 +89,13 @@ public static class CompositionRoot
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.AssignableTo<CommandSettings>(); .AssignableTo<CommandSettings>();
} }
public static void RegisterExternal(
ContainerBuilder builder,
LoggingLevelSwitch logLevelSwitch,
AppDataPathProvider appDataPathProvider)
{
builder.RegisterInstance(logLevelSwitch);
builder.RegisterInstance(appDataPathProvider);
}
} }

@ -5,7 +5,6 @@ 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,8 +16,7 @@ namespace Recyclarr.Cli.Console.Commands;
internal class ListCustomFormatsCommand : AsyncCommand<ListCustomFormatsCommand.CliSettings> internal class ListCustomFormatsCommand : AsyncCommand<ListCustomFormatsCommand.CliSettings>
{ {
private readonly CustomFormatDataLister _lister; private readonly CustomFormatDataLister _lister;
private readonly IRepoUpdater _repoUpdater; private readonly ITrashGuidesRepo _repo;
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,19 +28,15 @@ internal class ListCustomFormatsCommand : AsyncCommand<ListCustomFormatsCommand.
public SupportedServices Service { get; init; } public SupportedServices Service { get; init; }
} }
public ListCustomFormatsCommand( public ListCustomFormatsCommand(CustomFormatDataLister lister, ITrashGuidesRepo repo)
CustomFormatDataLister lister,
IRepoUpdater repoUpdater,
ISettingsProvider settings)
{ {
_lister = lister; _lister = lister;
_repoUpdater = repoUpdater; _repo = repo;
_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(_settings.Settings.Repositories.TrashGuide); await _repo.Update();
_lister.ListCustomFormats(settings.Service); _lister.ListCustomFormats(settings.Service);
return 0; return 0;
} }

@ -5,7 +5,6 @@ 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,8 +15,7 @@ namespace Recyclarr.Cli.Console.Commands;
internal class ListQualitiesCommand : AsyncCommand<ListQualitiesCommand.CliSettings> internal class ListQualitiesCommand : AsyncCommand<ListQualitiesCommand.CliSettings>
{ {
private readonly QualitySizeDataLister _lister; private readonly QualitySizeDataLister _lister;
private readonly IRepoUpdater _repoUpdater; private readonly ITrashGuidesRepo _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")]
@ -29,16 +27,15 @@ internal class ListQualitiesCommand : AsyncCommand<ListQualitiesCommand.CliSetti
public SupportedServices Service { get; init; } public SupportedServices Service { get; init; }
} }
public ListQualitiesCommand(QualitySizeDataLister lister, IRepoUpdater repoUpdater, ISettingsProvider settings) public ListQualitiesCommand(QualitySizeDataLister lister, ITrashGuidesRepo repo)
{ {
_lister = lister; _lister = lister;
_repoUpdater = repoUpdater; _repoUpdater = repo;
_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(_settings.Settings.Repositories.TrashGuide); await _repoUpdater.Update();
_lister.ListQualities(settings.Service); _lister.ListQualities(settings.Service);
return 0; return 0;
} }

@ -3,7 +3,6 @@ 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,8 +15,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 ITrashGuidesRepo _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")]
@ -31,23 +29,18 @@ internal class ListReleaseProfilesCommand : AsyncCommand<ListReleaseProfilesComm
public string? ListTerms { get; init; } public string? ListTerms { get; init; }
} }
public ListReleaseProfilesCommand( public ListReleaseProfilesCommand(ILogger log, ReleaseProfileDataLister lister, ITrashGuidesRepo repo)
ILogger log,
ReleaseProfileDataLister lister,
IRepoUpdater repoUpdater,
ISettingsProvider settings)
{ {
_log = log; _log = log;
_lister = lister; _lister = lister;
_repoUpdater = repoUpdater; _repoUpdater = repo;
_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(_settings.Settings.Repositories.TrashGuide); await _repoUpdater.Update();
if (settings.ListTerms is not null) if (settings.ListTerms is not null)
{ {

@ -8,7 +8,6 @@ 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;
@ -18,9 +17,8 @@ namespace Recyclarr.Cli.Console.Commands;
public class SyncCommand : AsyncCommand<SyncCommand.CliSettings> public class SyncCommand : AsyncCommand<SyncCommand.CliSettings>
{ {
private readonly IMigrationExecutor _migration; private readonly IMigrationExecutor _migration;
private readonly IRepoUpdater _repoUpdater; private readonly ITrashGuidesRepo _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")]
@ -52,16 +50,11 @@ public class SyncCommand : AsyncCommand<SyncCommand.CliSettings>
public IReadOnlyCollection<string> Instances => InstancesOption; public IReadOnlyCollection<string> Instances => InstancesOption;
} }
public SyncCommand( public SyncCommand(IMigrationExecutor migration, ITrashGuidesRepo repo, ISyncProcessor syncProcessor)
IMigrationExecutor migration,
IRepoUpdater repoUpdater,
ISyncProcessor syncProcessor,
ISettingsProvider settings)
{ {
_migration = migration; _migration = migration;
_repoUpdater = repoUpdater; _repoUpdater = repo;
_syncProcessor = syncProcessor; _syncProcessor = syncProcessor;
_settings = settings;
} }
[SuppressMessage("Design", "CA1031:Do not catch general exception types")] [SuppressMessage("Design", "CA1031:Do not catch general exception types")]
@ -69,7 +62,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(_settings.Settings.Repositories.TrashGuide); await _repoUpdater.Update();
return (int) await _syncProcessor.ProcessConfigs(settings); return (int) await _syncProcessor.ProcessConfigs(settings);
} }

@ -1,5 +1,6 @@
using Autofac.Features.Indexed; using Autofac.Features.Indexed;
using Recyclarr.TrashLib.Config.Listers; using Recyclarr.TrashLib.Config.Listers;
using Recyclarr.TrashLib.Repo;
namespace Recyclarr.Cli.Processors; namespace Recyclarr.Cli.Processors;
@ -7,15 +8,25 @@ public class ConfigListProcessor
{ {
private readonly ILogger _log; private readonly ILogger _log;
private readonly IIndex<ConfigListCategory, IConfigLister> _configListers; private readonly IIndex<ConfigListCategory, IConfigLister> _configListers;
private readonly IConfigTemplatesRepo _repo;
public ConfigListProcessor(ILogger log, IIndex<ConfigListCategory, IConfigLister> configListers) public ConfigListProcessor(
ILogger log,
IIndex<ConfigListCategory, IConfigLister> configListers,
IConfigTemplatesRepo repo)
{ {
_log = log; _log = log;
_configListers = configListers; _configListers = configListers;
_repo = repo;
} }
public async Task Process(ConfigListCategory listCategory) public async Task Process(ConfigListCategory listCategory)
{ {
if (listCategory == ConfigListCategory.Templates)
{
await _repo.Update();
}
_log.Debug("Listing configuration for category {Category}", listCategory); _log.Debug("Listing configuration for category {Category}", listCategory);
if (!_configListers.TryGetValue(listCategory, out var lister)) if (!_configListers.TryGetValue(listCategory, out var lister))
{ {

@ -24,10 +24,8 @@ internal static partial class Program
CompositionRoot.Setup(builder); CompositionRoot.Setup(builder);
var logLevelSwitch = new LoggingLevelSwitch(); var logLevelSwitch = new LoggingLevelSwitch();
builder.RegisterInstance(logLevelSwitch);
var appDataPathProvider = new AppDataPathProvider(); var appDataPathProvider = new AppDataPathProvider();
builder.RegisterInstance(appDataPathProvider); CompositionRoot.RegisterExternal(builder, logLevelSwitch, appDataPathProvider);
var app = new CommandApp(new AutofacTypeRegistrar(builder, s => _scope = s)); var app = new CommandApp(new AutofacTypeRegistrar(builder, s => _scope = s));
app.Configure(config => app.Configure(config =>

@ -1,4 +1,5 @@
using Autofac; using Autofac;
using Autofac.Builder;
namespace Recyclarr.Common.Extensions; namespace Recyclarr.Common.Extensions;
@ -9,4 +10,16 @@ public static class AutofacExtensions
var type = genericType.MakeGenericType(genericArgs); var type = genericType.MakeGenericType(genericArgs);
return scope.Resolve(type); return scope.Resolve(type);
} }
public static IRegistrationBuilder<TLimit, TReflectionActivatorData, TStyle>
WithTypeParameter<TLimit, TReflectionActivatorData, TStyle>(
this IRegistrationBuilder<TLimit, TReflectionActivatorData, TStyle> builder,
Type paramType,
Func<IComponentContext, object> resolver)
where TReflectionActivatorData : ReflectionActivatorData
{
return builder.WithParameter(
(info, _) => info.ParameterType == paramType,
(_, context) => resolver(context));
}
} }

@ -1,18 +0,0 @@
{
"json_paths": {
"radarr": {
"custom_formats": ["docs/json/radarr/cf"],
"qualities": ["docs/json/radarr/quality-size"],
"naming": ["docs/json/radarr/naming"]
},
"sonarr": {
"release_profiles": ["docs/json/sonarr/rp"],
"custom_formats": ["docs/json/sonarr/cf"],
"qualities": ["docs/json/sonarr/quality-size"],
"naming": ["docs/json/sonarr/naming"]
}
},
"recyclarr": {
"templates": "docs/recyclarr-configs"
}
}

@ -5,7 +5,6 @@ using Autofac.Features.ResolveAnything;
using AutoMapper.Contrib.Autofac.DependencyInjection; using AutoMapper.Contrib.Autofac.DependencyInjection;
using AutoMapper.EquivalencyExpression; using AutoMapper.EquivalencyExpression;
using Recyclarr.Common; using Recyclarr.Common;
using Recyclarr.Common.TestLibrary;
using Recyclarr.TestLibrary.Autofac; using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashLib.ApiServices.System; using Recyclarr.TrashLib.ApiServices.System;
using Recyclarr.TrashLib.Repo.VersionControl; using Recyclarr.TrashLib.Repo.VersionControl;
@ -28,8 +27,6 @@ public abstract class TrashLibIntegrationFixture : IDisposable
Paths = new AppPaths(Fs.CurrentDirectory().SubDirectory("test").SubDirectory("recyclarr")); Paths = new AppPaths(Fs.CurrentDirectory().SubDirectory("test").SubDirectory("recyclarr"));
Logger = CreateLogger(); Logger = CreateLogger();
SetupMetadataJson();
_container = new Lazy<IContainer>(() => _container = new Lazy<IContainer>(() =>
{ {
var builder = new ContainerBuilder(); var builder = new ContainerBuilder();
@ -77,12 +74,6 @@ public abstract class TrashLibIntegrationFixture : IDisposable
.CreateLogger(); .CreateLogger();
} }
private void SetupMetadataJson()
{
var metadataFile = Paths.RepoDirectory.File("metadata.json");
Fs.AddFileFromEmbeddedResource(metadataFile, typeof(TrashLibIntegrationFixture), "Data.metadata.json");
}
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
private readonly Lazy<IContainer> _container; private readonly Lazy<IContainer> _container;

@ -18,7 +18,7 @@ public class ConfigTemplateListerTest : TrashLibIntegrationFixture
[Frozen] IConfigTemplateGuideService guideService, [Frozen] IConfigTemplateGuideService guideService,
ConfigTemplateLister sut) ConfigTemplateLister sut)
{ {
guideService.TemplateData.Returns(new[] guideService.LoadTemplateData().Returns(new[]
{ {
new TemplatePath(SupportedServices.Radarr, "r1", stubFile, false), new TemplatePath(SupportedServices.Radarr, "r1", stubFile, false),
new TemplatePath(SupportedServices.Radarr, "r2", stubFile, false), new TemplatePath(SupportedServices.Radarr, "r2", stubFile, false),

@ -1,8 +1,8 @@
using System.IO.Abstractions; using System.IO.Abstractions;
using Recyclarr.Common.Extensions;
using Recyclarr.Common.TestLibrary; using Recyclarr.Common.TestLibrary;
using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.TestLibrary; using Recyclarr.TrashLib.TestLibrary;
namespace Recyclarr.TrashLib.Tests.Config.Services; namespace Recyclarr.TrashLib.Tests.Config.Services;
@ -15,15 +15,16 @@ public class ConfigTemplateGuideServiceTest : TrashLibIntegrationFixture
public void Throw_when_templates_dir_does_not_exist( public void Throw_when_templates_dir_does_not_exist(
ConfigTemplateGuideService sut) ConfigTemplateGuideService sut)
{ {
var act = () => _ = sut.TemplateData; var act = () => _ = sut.LoadTemplateData();
act.Should().Throw<InvalidDataException>().WithMessage("Path*templates*"); act.Should().Throw<InvalidDataException>().WithMessage("Recyclarr*templates*");
} }
[Test] [Test]
public void Normal_behavior() public void Normal_behavior()
{ {
var templateDir = Paths.RepoDirectory.SubDir("docs/recyclarr-configs"); var repo = Resolve<IConfigTemplatesRepo>();
var templateDir = repo.Path;
Fs.AddSameFileFromEmbeddedResource(templateDir.File("templates.json"), typeof(ConfigTemplateGuideServiceTest)); Fs.AddSameFileFromEmbeddedResource(templateDir.File("templates.json"), typeof(ConfigTemplateGuideServiceTest));
TemplatePath MakeTemplatePath(SupportedServices service, string id, string path) TemplatePath MakeTemplatePath(SupportedServices service, string id, string path)
@ -43,7 +44,7 @@ public class ConfigTemplateGuideServiceTest : TrashLibIntegrationFixture
var sut = Resolve<ConfigTemplateGuideService>(); var sut = Resolve<ConfigTemplateGuideService>();
var data = sut.TemplateData; var data = sut.LoadTemplateData();
data.Should().BeEquivalentTo(expectedPaths, o => o.Excluding(x => x.TemplateFile)); data.Should().BeEquivalentTo(expectedPaths, o => o.Excluding(x => x.TemplateFile));
data.Select(x => x.TemplateFile.FullName) data.Select(x => x.TemplateFile.FullName)
.Should().BeEquivalentTo(expectedPaths.Select(x => x.TemplateFile.FullName)); .Should().BeEquivalentTo(expectedPaths.Select(x => x.TemplateFile.FullName));

@ -20,7 +20,7 @@ public class AppPaths : IAppPaths
public IFileInfo SettingsPath => AppDataDirectory.File("settings.yml"); public IFileInfo SettingsPath => AppDataDirectory.File("settings.yml");
public IFileInfo SecretsPath => AppDataDirectory.File("secrets.yml"); public IFileInfo SecretsPath => AppDataDirectory.File("secrets.yml");
public IDirectoryInfo LogDirectory => AppDataDirectory.SubDir("logs", "cli"); public IDirectoryInfo LogDirectory => AppDataDirectory.SubDir("logs", "cli");
public IDirectoryInfo RepoDirectory => AppDataDirectory.SubDir("repo"); public IDirectoryInfo ReposDirectory => AppDataDirectory.SubDir("repositories");
public IDirectoryInfo CacheDirectory => AppDataDirectory.SubDir("cache"); public IDirectoryInfo CacheDirectory => AppDataDirectory.SubDir("cache");
public IDirectoryInfo ConfigsDirectory => AppDataDirectory.SubDir("configs"); public IDirectoryInfo ConfigsDirectory => AppDataDirectory.SubDir("configs");
} }

@ -1,6 +1,4 @@
using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Config.Services;
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,26 +7,18 @@ 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 ISettingsProvider _settings;
public ConfigTemplateLister( public ConfigTemplateLister(
IAnsiConsole console, IAnsiConsole console,
IConfigTemplateGuideService guideService, IConfigTemplateGuideService guideService)
IRepoUpdater repoUpdater,
ISettingsProvider settings)
{ {
_console = console; _console = console;
_guideService = guideService; _guideService = guideService;
_repoUpdater = repoUpdater;
_settings = settings;
} }
public async Task List() public Task List()
{ {
await _repoUpdater.UpdateRepo(_settings.Settings.Repositories.ConfigTemplates); var data = _guideService.LoadTemplateData();
var data = _guideService.TemplateData;
var table = new Table(); var table = new Table();
var empty = new Markup(""); var empty = new Markup("");
@ -44,6 +34,7 @@ public class ConfigTemplateLister : IConfigLister
} }
_console.Write(table); _console.Write(table);
return Task.CompletedTask;
} }
private static IEnumerable<Markup> RenderTemplates( private static IEnumerable<Markup> RenderTemplates(

@ -23,7 +23,7 @@ public static class ContextualMessages
{ {
return return
"Usage of 'repository' setting is no " + "Usage of 'repository' setting is no " +
"longer supported. Use 'trash_guide' under 'repositories' instead." + "longer supported. Use 'trash_guides' under 'repositories' instead." +
"See: https://recyclarr.dev/wiki/upgrade-guide/v5.0/#settings-repository-changes"; "See: https://recyclarr.dev/wiki/upgrade-guide/v5.0/#settings-repository-changes";
} }

@ -1,9 +1,7 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO.Abstractions; using System.IO.Abstractions;
using JetBrains.Annotations; using JetBrains.Annotations;
using Recyclarr.Common.Extensions;
using Recyclarr.TrashLib.Repo; using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Startup;
namespace Recyclarr.TrashLib.Config.Services; namespace Recyclarr.TrashLib.Config.Services;
@ -20,35 +18,27 @@ public record TemplatePath(SupportedServices Service, string Id, IFileInfo Templ
public class ConfigTemplateGuideService : IConfigTemplateGuideService public class ConfigTemplateGuideService : IConfigTemplateGuideService
{ {
private readonly IRepoMetadataBuilder _metadataBuilder; private readonly IConfigTemplatesRepo _repo;
private readonly IAppPaths _paths;
private readonly Lazy<IReadOnlyCollection<TemplatePath>> _templateData;
public ConfigTemplateGuideService( public ConfigTemplateGuideService(IConfigTemplatesRepo repo)
IRepoMetadataBuilder metadataBuilder,
IAppPaths paths)
{ {
_metadataBuilder = metadataBuilder; _repo = repo;
_paths = paths;
_templateData = new Lazy<IReadOnlyCollection<TemplatePath>>(LoadTemplateData);
} }
private IReadOnlyCollection<TemplatePath> LoadTemplateData() public IReadOnlyCollection<TemplatePath> LoadTemplateData()
{ {
var metadata = _metadataBuilder.GetMetadata(); var templatesPath = _repo.Path.File("templates.json");
var templatesPath = _paths.RepoDirectory.SubDir(metadata.Recyclarr.Templates);
if (!templatesPath.Exists) if (!templatesPath.Exists)
{ {
throw new InvalidDataException( throw new InvalidDataException(
$"Path to recyclarr templates does not exist: {metadata.Recyclarr.Templates}"); $"Recyclarr templates.json does not exist: {templatesPath}");
} }
var templates = TrashRepoJsonParser.Deserialize<TemplatesData>(templatesPath.File("templates.json")); var templates = TrashRepoJsonParser.Deserialize<TemplatesData>(templatesPath);
TemplatePath NewTemplatePath(TemplateEntry entry, SupportedServices service) TemplatePath NewTemplatePath(TemplateEntry entry, SupportedServices service)
{ {
return new TemplatePath(service, entry.Id, templatesPath.File(entry.Template), entry.Hidden); return new TemplatePath(service, entry.Id, _repo.Path.File(entry.Template), entry.Hidden);
} }
return templates.Radarr return templates.Radarr
@ -56,6 +46,4 @@ public class ConfigTemplateGuideService : IConfigTemplateGuideService
.Concat(templates.Sonarr.Select(x => NewTemplatePath(x, SupportedServices.Sonarr))) .Concat(templates.Sonarr.Select(x => NewTemplatePath(x, SupportedServices.Sonarr)))
.ToList(); .ToList();
} }
public IReadOnlyCollection<TemplatePath> TemplateData => _templateData.Value;
} }

@ -2,5 +2,5 @@ namespace Recyclarr.TrashLib.Config.Services;
public interface IConfigTemplateGuideService public interface IConfigTemplateGuideService
{ {
IReadOnlyCollection<TemplatePath> TemplateData { get; } IReadOnlyCollection<TemplatePath> LoadTemplateData();
} }

@ -0,0 +1,26 @@
using System.IO.Abstractions;
using Recyclarr.Common.Extensions;
using Recyclarr.TrashLib.Settings;
using Recyclarr.TrashLib.Startup;
namespace Recyclarr.TrashLib.Repo;
public class ConfigTemplatesRepo : IConfigTemplatesRepo
{
private readonly IRepoUpdater _repoUpdater;
private readonly ISettingsProvider _settings;
public ConfigTemplatesRepo(IRepoUpdater repoUpdater, IAppPaths paths, ISettingsProvider settings)
{
_repoUpdater = repoUpdater;
_settings = settings;
Path = paths.ReposDirectory.SubDir("config-templates");
}
public IDirectoryInfo Path { get; }
public Task Update()
{
return _repoUpdater.UpdateRepo(Path, _settings.Settings.Repositories.ConfigTemplates);
}
}

@ -0,0 +1,9 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Repo;
public interface IConfigTemplatesRepo
{
IDirectoryInfo Path { get; }
Task Update();
}

@ -5,6 +5,5 @@ namespace Recyclarr.TrashLib.Repo;
public interface IRepoUpdater public interface IRepoUpdater
{ {
IDirectoryInfo RepoPath { get; } Task UpdateRepo(IDirectoryInfo repoPath, IRepositorySettings repoSettings);
Task UpdateRepo(IRepositorySettings repoSettings);
} }

@ -0,0 +1,9 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Repo;
public interface ITrashGuidesRepo
{
IDirectoryInfo Path { get; }
Task Update();
}

@ -7,6 +7,9 @@ public class RepoAutofacModule : Module
protected override void Load(ContainerBuilder builder) protected override void Load(ContainerBuilder builder)
{ {
base.Load(builder); base.Load(builder);
builder.RegisterType<ConfigTemplatesRepo>().As<IConfigTemplatesRepo>();
builder.RegisterType<TrashGuidesRepo>().As<ITrashGuidesRepo>();
builder.RegisterType<RepoUpdater>().As<IRepoUpdater>(); builder.RegisterType<RepoUpdater>().As<IRepoUpdater>();
builder.RegisterType<RepoMetadataBuilder>().As<IRepoMetadataBuilder>().InstancePerLifetimeScope(); builder.RegisterType<RepoMetadataBuilder>().As<IRepoMetadataBuilder>().InstancePerLifetimeScope();
} }

@ -19,13 +19,7 @@ public record JsonPaths
public SonarrMetadata Sonarr { get; init; } = new(); public SonarrMetadata Sonarr { get; init; } = new();
} }
public record RecyclarrMetadata
{
public string Templates { get; init; } = "";
}
public record RepoMetadata public record RepoMetadata
{ {
public JsonPaths JsonPaths { get; init; } = new(); public JsonPaths JsonPaths { get; init; } = new();
public RecyclarrMetadata Recyclarr { get; init; } = new();
} }

@ -1,26 +1,25 @@
using System.IO.Abstractions; using System.IO.Abstractions;
using Recyclarr.TrashLib.Startup;
namespace Recyclarr.TrashLib.Repo; namespace Recyclarr.TrashLib.Repo;
public class RepoMetadataBuilder : IRepoMetadataBuilder public class RepoMetadataBuilder : IRepoMetadataBuilder
{ {
private readonly IAppPaths _paths;
private readonly Lazy<RepoMetadata> _metadata; private readonly Lazy<RepoMetadata> _metadata;
private readonly IDirectoryInfo _repoPath;
public RepoMetadataBuilder(IAppPaths paths) public RepoMetadataBuilder(ITrashGuidesRepo repo)
{ {
_paths = paths; _repoPath = repo.Path;
_metadata = new Lazy<RepoMetadata>(() _metadata = new Lazy<RepoMetadata>(
=> TrashRepoJsonParser.Deserialize<RepoMetadata>(_paths.RepoDirectory.File("metadata.json"))); () => TrashRepoJsonParser.Deserialize<RepoMetadata>(_repoPath.File("metadata.json")));
} }
public IReadOnlyList<IDirectoryInfo> ToDirectoryInfoList(IEnumerable<string> listOfDirectories) public IReadOnlyList<IDirectoryInfo> ToDirectoryInfoList(IEnumerable<string> listOfDirectories)
{ {
return listOfDirectories.Select(x => _paths.RepoDirectory.SubDirectory(x)).ToList(); return listOfDirectories.Select(x => _repoPath.SubDirectory(x)).ToList();
} }
public IDirectoryInfo DocsDirectory => _paths.RepoDirectory.SubDirectory("docs"); public IDirectoryInfo DocsDirectory => _repoPath.SubDirectory("docs");
public RepoMetadata GetMetadata() public RepoMetadata GetMetadata()
{ {

@ -2,49 +2,43 @@ using System.IO.Abstractions;
using Recyclarr.Common; using Recyclarr.Common;
using Recyclarr.TrashLib.Repo.VersionControl; using Recyclarr.TrashLib.Repo.VersionControl;
using Recyclarr.TrashLib.Settings; using Recyclarr.TrashLib.Settings;
using Recyclarr.TrashLib.Startup;
namespace Recyclarr.TrashLib.Repo; namespace Recyclarr.TrashLib.Repo;
public class RepoUpdater : IRepoUpdater public class RepoUpdater : IRepoUpdater
{ {
private readonly ILogger _log; private readonly ILogger _log;
private readonly IAppPaths _paths;
private readonly IGitRepositoryFactory _repositoryFactory; private readonly IGitRepositoryFactory _repositoryFactory;
private readonly IFileUtilities _fileUtils; private readonly IFileUtilities _fileUtils;
public RepoUpdater( public RepoUpdater(
ILogger log, ILogger log,
IAppPaths paths,
IGitRepositoryFactory repositoryFactory, IGitRepositoryFactory repositoryFactory,
IFileUtilities fileUtils) IFileUtilities fileUtils)
{ {
_log = log; _log = log;
_paths = paths;
_repositoryFactory = repositoryFactory; _repositoryFactory = repositoryFactory;
_fileUtils = fileUtils; _fileUtils = fileUtils;
} }
public IDirectoryInfo RepoPath => _paths.RepoDirectory; public async Task UpdateRepo(IDirectoryInfo repoPath, IRepositorySettings repoSettings)
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(repoSettings); await CheckoutAndUpdateRepo(repoPath, 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 '{Repodir}' and retrying git operation due to error", repoPath.Name);
_fileUtils.DeleteReadOnlyDirectory(RepoPath.FullName); _fileUtils.DeleteReadOnlyDirectory(repoPath.FullName);
await CheckoutAndUpdateRepo(repoSettings); await CheckoutAndUpdateRepo(repoPath, repoSettings);
} }
} }
private async Task CheckoutAndUpdateRepo(IRepositorySettings repoSettings) private async Task CheckoutAndUpdateRepo(IDirectoryInfo repoPath, IRepositorySettings repoSettings)
{ {
var cloneUrl = repoSettings.CloneUrl; var cloneUrl = repoSettings.CloneUrl;
var branch = repoSettings.Branch; var branch = repoSettings.Branch;
@ -55,7 +49,7 @@ public class RepoUpdater : IRepoUpdater
_log.Warning("Using explicit SHA1 for local repository: {Sha1}", repoSettings.Sha1); _log.Warning("Using explicit SHA1 for local repository: {Sha1}", repoSettings.Sha1);
} }
using var repo = await _repositoryFactory.CreateAndCloneIfNeeded(cloneUrl, RepoPath.FullName, branch); using var repo = await _repositoryFactory.CreateAndCloneIfNeeded(cloneUrl, repoPath, branch);
await repo.ForceCheckout(branch); await repo.ForceCheckout(branch);
await repo.Fetch(); await repo.Fetch();
await repo.ResetHard(repoSettings.Sha1 ?? $"origin/{branch}"); await repo.ResetHard(repoSettings.Sha1 ?? $"origin/{branch}");

@ -0,0 +1,26 @@
using System.IO.Abstractions;
using Recyclarr.Common.Extensions;
using Recyclarr.TrashLib.Settings;
using Recyclarr.TrashLib.Startup;
namespace Recyclarr.TrashLib.Repo;
public class TrashGuidesRepo : ITrashGuidesRepo
{
private readonly IRepoUpdater _repoUpdater;
private readonly ISettingsProvider _settings;
public TrashGuidesRepo(IRepoUpdater repoUpdater, IAppPaths paths, ISettingsProvider settings)
{
_repoUpdater = repoUpdater;
_settings = settings;
Path = paths.ReposDirectory.SubDir("trash-guides");
}
public IDirectoryInfo Path { get; }
public Task Update()
{
return _repoUpdater.UpdateRepo(Path, _settings.Settings.Repositories.TrashGuides);
}
}

@ -1,21 +1,20 @@
using System.IO.Abstractions; using System.IO.Abstractions;
using System.Text; using System.Text;
using CliWrap; using CliWrap;
using Recyclarr.TrashLib.Startup;
namespace Recyclarr.TrashLib.Repo.VersionControl; namespace Recyclarr.TrashLib.Repo.VersionControl;
public sealed class GitRepository : IGitRepository public sealed class GitRepository : IGitRepository
{ {
private readonly ILogger _log; private readonly ILogger _log;
private readonly IAppPaths _paths;
private readonly IGitPath _gitPath; private readonly IGitPath _gitPath;
private readonly IDirectoryInfo _workDir;
public GitRepository(ILogger log, IAppPaths paths, IGitPath gitPath) public GitRepository(ILogger log, IGitPath gitPath, IDirectoryInfo workDir)
{ {
_log = log; _log = log;
_paths = paths;
_gitPath = gitPath; _gitPath = gitPath;
_workDir = workDir;
} }
private Task RunGitCmd(params string[] args) private Task RunGitCmd(params string[] args)
@ -30,18 +29,15 @@ public sealed class GitRepository : IGitRepository
var output = new StringBuilder(); var output = new StringBuilder();
var error = new StringBuilder(); var error = new StringBuilder();
_log.Debug("Using working directory: {Dir}", _workDir.FullName);
_workDir.Create();
var cli = Cli.Wrap(_gitPath.Path) var cli = Cli.Wrap(_gitPath.Path)
.WithArguments(args) .WithArguments(args)
.WithValidation(CommandResultValidation.None) .WithValidation(CommandResultValidation.None)
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(output)) .WithStandardOutputPipe(PipeTarget.ToStringBuilder(output))
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(error)); .WithStandardErrorPipe(PipeTarget.ToStringBuilder(error))
.WithWorkingDirectory(_workDir.FullName);
if (_paths.RepoDirectory.Exists)
{
var workDir = _paths.RepoDirectory.FullName;
_log.Debug("Using working directory: {Dir}", workDir);
cli = cli.WithWorkingDirectory(workDir);
}
var result = await cli.ExecuteAsync(); var result = await cli.ExecuteAsync();
@ -53,8 +49,6 @@ public sealed class GitRepository : IGitRepository
} }
} }
public IDirectoryInfo Path => _paths.RepoDirectory;
public void Dispose() public void Dispose()
{ {
// Nothing to do here // Nothing to do here
@ -93,7 +87,7 @@ public sealed class GitRepository : IGitRepository
args.AddRange(new[] {"-b", branch}); args.AddRange(new[] {"-b", branch});
} }
args.AddRange(new[] {cloneUrl.ToString(), _paths.RepoDirectory.FullName}); args.AddRange(new[] {cloneUrl.ToString(), "."});
await RunGitCmd(args); await RunGitCmd(args);
} }
} }

@ -1,25 +1,25 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Repo.VersionControl; namespace Recyclarr.TrashLib.Repo.VersionControl;
public class GitRepositoryFactory : IGitRepositoryFactory public class GitRepositoryFactory : IGitRepositoryFactory
{ {
private readonly Func<string, IGitRepository> _repoFactory;
private readonly ILogger _log; private readonly ILogger _log;
private readonly IGitPath _gitPath;
public GitRepositoryFactory( public GitRepositoryFactory(ILogger log, IGitPath gitPath)
Func<string, IGitRepository> repoFactory,
ILogger log)
{ {
_repoFactory = repoFactory;
_log = log; _log = log;
_gitPath = gitPath;
} }
public async Task<IGitRepository> CreateAndCloneIfNeeded(Uri repoUrl, string repoPath, string branch) public async Task<IGitRepository> CreateAndCloneIfNeeded(Uri repoUrl, IDirectoryInfo repoPath, string branch)
{ {
var repo = _repoFactory(repoPath); var repo = new GitRepository(_log, _gitPath, repoPath);
if (!repo.Path.Exists) if (!repoPath.Exists)
{ {
_log.Information("Cloning trash repository..."); _log.Information("Cloning '{RepoName}' repository...", repoPath.Name);
await repo.Clone(repoUrl, branch); await repo.Clone(repoUrl, branch);
} }
else else

@ -1,5 +1,3 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Repo.VersionControl; namespace Recyclarr.TrashLib.Repo.VersionControl;
public interface IGitRepository : IDisposable public interface IGitRepository : IDisposable
@ -8,7 +6,6 @@ public interface IGitRepository : IDisposable
Task Fetch(string remote = "origin"); Task Fetch(string remote = "origin");
Task ResetHard(string toBranchOrSha1); Task ResetHard(string toBranchOrSha1);
Task SetRemote(string name, Uri newUrl); Task SetRemote(string name, Uri newUrl);
IDirectoryInfo Path { get; }
Task Clone(Uri cloneUrl, string? branch = null); Task Clone(Uri cloneUrl, string? branch = null);
Task Status(); Task Status();
} }

@ -1,6 +1,8 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Repo.VersionControl; namespace Recyclarr.TrashLib.Repo.VersionControl;
public interface IGitRepositoryFactory public interface IGitRepositoryFactory
{ {
Task<IGitRepository> CreateAndCloneIfNeeded(Uri repoUrl, string repoPath, string branch); Task<IGitRepository> CreateAndCloneIfNeeded(Uri repoUrl, IDirectoryInfo repoPath, string branch);
} }

@ -6,7 +6,6 @@ public class VersionControlAutofacModule : Module
{ {
protected override void Load(ContainerBuilder builder) protected override void Load(ContainerBuilder builder)
{ {
builder.RegisterType<GitRepository>().As<IGitRepository>();
builder.RegisterType<GitRepositoryFactory>().As<IGitRepositoryFactory>(); builder.RegisterType<GitRepositoryFactory>().As<IGitRepositoryFactory>();
builder.RegisterType<GitPath>().As<IGitPath>(); builder.RegisterType<GitPath>().As<IGitPath>();
base.Load(builder); base.Load(builder);

@ -1,6 +1,8 @@
using Recyclarr.Common.Extensions; using Recyclarr.Common.Extensions;
using Recyclarr.TrashLib.Config.Parsing.ErrorHandling;
using Recyclarr.TrashLib.Config.Yaml; using Recyclarr.TrashLib.Config.Yaml;
using Recyclarr.TrashLib.Startup; using Recyclarr.TrashLib.Startup;
using YamlDotNet.Core;
namespace Recyclarr.TrashLib.Settings; namespace Recyclarr.TrashLib.Settings;
@ -8,11 +10,13 @@ public class SettingsProvider : ISettingsProvider
{ {
public SettingsValues Settings => _settings.Value; public SettingsValues Settings => _settings.Value;
private readonly ILogger _log;
private readonly IAppPaths _paths; private readonly IAppPaths _paths;
private readonly Lazy<SettingsValues> _settings; private readonly Lazy<SettingsValues> _settings;
public SettingsProvider(IAppPaths paths, IYamlSerializerFactory serializerFactory) public SettingsProvider(ILogger log, IAppPaths paths, IYamlSerializerFactory serializerFactory)
{ {
_log = log;
_paths = paths; _paths = paths;
_settings = new Lazy<SettingsValues>(() => LoadOrCreateSettingsFile(serializerFactory)); _settings = new Lazy<SettingsValues>(() => LoadOrCreateSettingsFile(serializerFactory));
} }
@ -24,9 +28,22 @@ public class SettingsProvider : ISettingsProvider
CreateDefaultSettingsFile(); CreateDefaultSettingsFile();
} }
using var stream = _paths.SettingsPath.OpenText(); try
var deserializer = serializerFactory.CreateDeserializer(); {
return deserializer.Deserialize<SettingsValues?>(stream.ReadToEnd()) ?? new SettingsValues(); using var stream = _paths.SettingsPath.OpenText();
var deserializer = serializerFactory.CreateDeserializer();
return deserializer.Deserialize<SettingsValues?>(stream.ReadToEnd()) ?? new SettingsValues();
}
catch (YamlException e)
{
_log.Debug(e, "Exception while parsing settings file");
var line = e.Start.Line;
var msg = ContextualMessages.GetContextualErrorFromException(e);
_log.Error("Exception while parsing settings.yml at line {Line}: {Msg}", line, msg);
throw;
}
} }
private void CreateDefaultSettingsFile() private void CreateDefaultSettingsFile()

@ -23,7 +23,7 @@ public record LogJanitorSettings
public record Repositories public record Repositories
{ {
public TrashRepository TrashGuide { get; [UsedImplicitly] init; } = new(); public TrashRepository TrashGuides { get; [UsedImplicitly] init; } = new();
public ConfigTemplateRepository ConfigTemplates { get; [UsedImplicitly] init; } = new(); public ConfigTemplateRepository ConfigTemplates { get; [UsedImplicitly] init; } = new();
} }

@ -9,7 +9,7 @@ public interface IAppPaths
IFileInfo SettingsPath { get; } IFileInfo SettingsPath { get; }
IFileInfo SecretsPath { get; } IFileInfo SecretsPath { get; }
IDirectoryInfo LogDirectory { get; } IDirectoryInfo LogDirectory { get; }
IDirectoryInfo RepoDirectory { get; } IDirectoryInfo ReposDirectory { get; }
IDirectoryInfo CacheDirectory { get; } IDirectoryInfo CacheDirectory { get; }
IDirectoryInfo ConfigsDirectory { get; } IDirectoryInfo ConfigsDirectory { get; }
} }

Loading…
Cancel
Save