diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17893a1c..2a167bb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
existing CFs that Recyclarr never created in the first place. The default is `true`.
- New `quality_profiles` section supported for specifying information about quality profiles. For
now, this section doesn't do much, but paves the way for quality profile syncing.
+- New CLI command: `config list` which lists information about current or available configuration
+ files.
+- New `--templates` argument added to `config list` which will list available configuration YAML
+ templates that can be used in the Trash repo.
### Changed
diff --git a/src/.idea/.idea.Recyclarr/.idea/runConfigurations/config_list_templates.xml b/src/.idea/.idea.Recyclarr/.idea/runConfigurations/config_list_templates.xml
new file mode 100644
index 00000000..c696e2c1
--- /dev/null
+++ b/src/.idea/.idea.Recyclarr/.idea/runConfigurations/config_list_templates.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Recyclarr.Cli.TestLibrary/Data/metadata.json b/src/Recyclarr.Cli.TestLibrary/Data/metadata.json
index 326a8299..7338b37a 100644
--- a/src/Recyclarr.Cli.TestLibrary/Data/metadata.json
+++ b/src/Recyclarr.Cli.TestLibrary/Data/metadata.json
@@ -2,12 +2,17 @@
"json_paths": {
"radarr": {
"custom_formats": ["docs/json/radarr/cf"],
- "qualities": ["docs/json/radarr/quality-size"]
+ "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"]
+ "qualities": ["docs/json/sonarr/quality-size"],
+ "naming": ["docs/json/sonarr/naming"]
}
+ },
+ "recyclarr": {
+ "templates": "docs/recyclarr-configs"
}
}
diff --git a/src/Recyclarr.Cli.TestLibrary/IntegrationFixture.cs b/src/Recyclarr.Cli.TestLibrary/IntegrationFixture.cs
index 26ee8912..889be4ba 100644
--- a/src/Recyclarr.Cli.TestLibrary/IntegrationFixture.cs
+++ b/src/Recyclarr.Cli.TestLibrary/IntegrationFixture.cs
@@ -4,7 +4,7 @@ using Autofac;
using Autofac.Features.ResolveAnything;
using Recyclarr.Common;
using Recyclarr.Common.TestLibrary;
-using Recyclarr.TestLibrary;
+using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashLib;
using Recyclarr.TrashLib.ApiServices.System;
using Recyclarr.TrashLib.Repo.VersionControl;
diff --git a/src/Recyclarr.Cli/Console/CliSetup.cs b/src/Recyclarr.Cli/Console/CliSetup.cs
index e2c69979..4e27b633 100644
--- a/src/Recyclarr.Cli/Console/CliSetup.cs
+++ b/src/Recyclarr.Cli/Console/CliSetup.cs
@@ -26,6 +26,7 @@ public static class CliSetup
{
config.SetDescription("Operations for configuration files");
config.AddCommand("create");
+ config.AddCommand("list");
});
// LEGACY / DEPRECATED SUBCOMMANDS
diff --git a/src/Recyclarr.Cli/Console/Commands/ConfigListCommand.cs b/src/Recyclarr.Cli/Console/Commands/ConfigListCommand.cs
new file mode 100644
index 00000000..3468ae02
--- /dev/null
+++ b/src/Recyclarr.Cli/Console/Commands/ConfigListCommand.cs
@@ -0,0 +1,55 @@
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using JetBrains.Annotations;
+using Recyclarr.Cli.Console.Helpers;
+using Recyclarr.TrashLib.Config.Listers;
+using Recyclarr.TrashLib.ExceptionTypes;
+using Recyclarr.TrashLib.Processors;
+using Recyclarr.TrashLib.Repo;
+using Spectre.Console.Cli;
+
+namespace Recyclarr.Cli.Console.Commands;
+
+[UsedImplicitly]
+[Description("List configuration files in various ways.")]
+public class ConfigListCommand : AsyncCommand
+{
+ private readonly ILogger _log;
+ private readonly ConfigListProcessor _processor;
+ private readonly IRepoUpdater _repoUpdater;
+
+ [SuppressMessage("Design", "CA1034:Nested types should not be visible")]
+ public class CliSettings : BaseCommandSettings
+ {
+ [CommandArgument(0, "[ListCategory]")]
+ [EnumDescription("The type of configuration information to list.")]
+ public ConfigListCategory ListCategory { get; [UsedImplicitly] init; } = ConfigListCategory.Local;
+ }
+
+ public ConfigListCommand(ILogger log, ConfigListProcessor processor, IRepoUpdater repoUpdater)
+ {
+ _log = log;
+ _processor = processor;
+ _repoUpdater = repoUpdater;
+ }
+
+ public override async Task ExecuteAsync(CommandContext context, CliSettings settings)
+ {
+ await _repoUpdater.UpdateRepo();
+
+ try
+ {
+ _processor.Process(settings.ListCategory);
+ }
+ catch (FileExistsException e)
+ {
+ _log.Error(
+ "The file {ConfigFile} already exists. Please choose another path or " +
+ "delete/move the existing file and run this command again", e.AttemptedPath);
+
+ return 1;
+ }
+
+ return 0;
+ }
+}
diff --git a/src/Recyclarr.Common.TestLibrary/CommonMockFileSystemExtensions.cs b/src/Recyclarr.Common.TestLibrary/CommonMockFileSystemExtensions.cs
index 30214756..e7a9870e 100644
--- a/src/Recyclarr.Common.TestLibrary/CommonMockFileSystemExtensions.cs
+++ b/src/Recyclarr.Common.TestLibrary/CommonMockFileSystemExtensions.cs
@@ -23,11 +23,29 @@ public static class CommonMockFileSystemExtensions
fs.AddFileFromEmbeddedResource(path, typeInAssembly, $"{resourceSubPath}.{path.Name}");
}
+ public static void AddSameFileFromEmbeddedResource(
+ this MockFileSystem fs,
+ string path,
+ Type typeInAssembly,
+ string resourceSubPath = "Data")
+ {
+ fs.AddFileFromEmbeddedResource(fs.FileInfo.New(path), typeInAssembly, resourceSubPath);
+ }
+
public static void AddFileFromEmbeddedResource(
this MockFileSystem fs,
IFileInfo path,
Type typeInAssembly,
string embeddedResourcePath)
+ {
+ fs.AddFileFromEmbeddedResource(path.FullName, typeInAssembly, embeddedResourcePath);
+ }
+
+ public static void AddFileFromEmbeddedResource(
+ this MockFileSystem fs,
+ string path,
+ Type typeInAssembly,
+ string embeddedResourcePath)
{
var resourcePath = $"{typeInAssembly.Namespace}.{embeddedResourcePath}";
fs.AddFileFromEmbeddedResource(path, typeInAssembly.Assembly, resourcePath);
diff --git a/src/Recyclarr.TestLibrary/AutofacTestExtensions.cs b/src/Recyclarr.TestLibrary/Autofac/AutofacTestExtensions.cs
similarity index 91%
rename from src/Recyclarr.TestLibrary/AutofacTestExtensions.cs
rename to src/Recyclarr.TestLibrary/Autofac/AutofacTestExtensions.cs
index 9e6ed383..63cf7d10 100644
--- a/src/Recyclarr.TestLibrary/AutofacTestExtensions.cs
+++ b/src/Recyclarr.TestLibrary/Autofac/AutofacTestExtensions.cs
@@ -1,6 +1,6 @@
using Autofac;
-namespace Recyclarr.TestLibrary;
+namespace Recyclarr.TestLibrary.Autofac;
public static class AutofacTestExtensions
{
diff --git a/src/Recyclarr.TestLibrary/Autofac/StubAutofacIndex.cs b/src/Recyclarr.TestLibrary/Autofac/StubAutofacIndex.cs
new file mode 100644
index 00000000..d3253376
--- /dev/null
+++ b/src/Recyclarr.TestLibrary/Autofac/StubAutofacIndex.cs
@@ -0,0 +1,22 @@
+using System.Diagnostics.CodeAnalysis;
+using Autofac.Features.Indexed;
+
+namespace Recyclarr.TestLibrary.Autofac;
+
+public class StubAutofacIndex : IIndex
+ where TKey : notnull
+{
+ private readonly Dictionary _values = new();
+
+ public void Add(TKey key, TValue value)
+ {
+ _values.Add(key, value);
+ }
+
+ public bool TryGetValue(TKey key, [UnscopedRef] out TValue value)
+ {
+ return _values.TryGetValue(key, out value!);
+ }
+
+ public TValue this[TKey key] => _values[key];
+}
diff --git a/src/Recyclarr.TrashLib.Tests/Config/ConfigAutofacModuleTest.cs b/src/Recyclarr.TrashLib.Tests/Config/ConfigAutofacModuleTest.cs
new file mode 100644
index 00000000..e86f6c7a
--- /dev/null
+++ b/src/Recyclarr.TrashLib.Tests/Config/ConfigAutofacModuleTest.cs
@@ -0,0 +1,23 @@
+using Autofac.Features.Indexed;
+using Recyclarr.Cli.TestLibrary;
+using Recyclarr.TrashLib.Config.Listers;
+
+namespace Recyclarr.TrashLib.Tests.Config;
+
+[TestFixture]
+[Parallelizable(ParallelScope.All)]
+public class ConfigAutofacModuleTest : IntegrationFixture
+{
+ private static IEnumerable AllConfigListCategories()
+ {
+ return Enum.GetValues();
+ }
+
+ [TestCaseSource(nameof(AllConfigListCategories))]
+ public void All_list_category_types_registered(ConfigListCategory category)
+ {
+ var sut = Resolve>();
+ var result = sut.TryGetValue(category, out _);
+ result.Should().BeTrue();
+ }
+}
diff --git a/src/Recyclarr.TrashLib.Tests/Config/Listers/ConfigTemplateListerTest.cs b/src/Recyclarr.TrashLib.Tests/Config/Listers/ConfigTemplateListerTest.cs
new file mode 100644
index 00000000..2c47d4d2
--- /dev/null
+++ b/src/Recyclarr.TrashLib.Tests/Config/Listers/ConfigTemplateListerTest.cs
@@ -0,0 +1,33 @@
+using System.IO.Abstractions;
+using Recyclarr.Cli.TestLibrary;
+using Recyclarr.TrashLib.Config;
+using Recyclarr.TrashLib.Config.Listers;
+using Recyclarr.TrashLib.Config.Services;
+using Spectre.Console.Testing;
+
+namespace Recyclarr.TrashLib.Tests.Config.Listers;
+
+[TestFixture]
+[Parallelizable(ParallelScope.All)]
+public class ConfigTemplateListerTest : IntegrationFixture
+{
+ [Test, AutoMockData]
+ public void Hidden_templates_are_not_rendered(
+ IFileInfo stubFile,
+ [Frozen(Matching.ImplementedInterfaces)] TestConsole console,
+ [Frozen] IConfigTemplateGuideService guideService,
+ ConfigTemplateLister sut)
+ {
+ guideService.TemplateData.Returns(new[]
+ {
+ new TemplatePath(SupportedServices.Radarr, "r1", stubFile, false),
+ new TemplatePath(SupportedServices.Radarr, "r2", stubFile, false),
+ new TemplatePath(SupportedServices.Sonarr, "s1", stubFile, false),
+ new TemplatePath(SupportedServices.Sonarr, "s2", stubFile, true),
+ });
+
+ sut.List();
+
+ console.Output.Should().NotContain("s2");
+ }
+}
diff --git a/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderTest.cs b/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderTest.cs
index a69030f2..bb1a0901 100644
--- a/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderTest.cs
+++ b/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderTest.cs
@@ -6,7 +6,7 @@ using FluentValidation;
using Recyclarr.Cli.TestLibrary;
using Recyclarr.Common;
using Recyclarr.Common.Extensions;
-using Recyclarr.TestLibrary;
+using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashLib.Config.Parsing;
using Recyclarr.TrashLib.Config.Services.Sonarr;
using Recyclarr.TrashLib.Config.Yaml;
diff --git a/src/Recyclarr.TrashLib.Tests/Config/Services/ConfigTemplateGuideServiceTest.cs b/src/Recyclarr.TrashLib.Tests/Config/Services/ConfigTemplateGuideServiceTest.cs
new file mode 100644
index 00000000..c5a84412
--- /dev/null
+++ b/src/Recyclarr.TrashLib.Tests/Config/Services/ConfigTemplateGuideServiceTest.cs
@@ -0,0 +1,52 @@
+using System.IO.Abstractions;
+using Recyclarr.Cli.TestLibrary;
+using Recyclarr.Common.Extensions;
+using Recyclarr.Common.TestLibrary;
+using Recyclarr.TestLibrary;
+using Recyclarr.TrashLib.Config;
+using Recyclarr.TrashLib.Config.Services;
+
+namespace Recyclarr.TrashLib.Tests.Config.Services;
+
+[TestFixture]
+[Parallelizable(ParallelScope.All)]
+public class ConfigTemplateGuideServiceTest : IntegrationFixture
+{
+ [Test, AutoMockData]
+ public void Throw_when_templates_dir_does_not_exist(
+ ConfigTemplateGuideService sut)
+ {
+ var act = () => _ = sut.TemplateData;
+
+ act.Should().Throw().WithMessage("Path*templates*");
+ }
+
+ [Test]
+ public void Normal_behavior()
+ {
+ var templateDir = Paths.RepoDirectory.SubDir("docs/recyclarr-configs");
+ Fs.AddSameFileFromEmbeddedResource(templateDir.File("templates.json"), typeof(ConfigTemplateGuideServiceTest));
+
+ TemplatePath MakeTemplatePath(SupportedServices service, string id, string path)
+ {
+ var fsPath = templateDir.File(path);
+ Fs.AddEmptyFile(fsPath);
+ fsPath.Refresh();
+ return new TemplatePath(service, id, fsPath, false);
+ }
+
+ var expectedPaths = new[]
+ {
+ MakeTemplatePath(SupportedServices.Radarr, "hd-bluray-web", "radarr/hd-bluray-web.yml"),
+ MakeTemplatePath(SupportedServices.Radarr, "uhd-bluray-web", "radarr/uhd-bluray-web.yml"),
+ MakeTemplatePath(SupportedServices.Sonarr, "web-1080p-v4", "sonarr/web-1080p-v4.yml")
+ };
+
+ var sut = Resolve();
+
+ var data = sut.TemplateData;
+ data.Should().BeEquivalentTo(expectedPaths, o => o.Excluding(x => x.TemplateFile));
+ data.Select(x => x.TemplateFile.FullName)
+ .Should().BeEquivalentTo(expectedPaths.Select(x => x.TemplateFile.FullName));
+ }
+}
diff --git a/src/Recyclarr.TrashLib.Tests/Config/Services/Data/templates.json b/src/Recyclarr.TrashLib.Tests/Config/Services/Data/templates.json
new file mode 100644
index 00000000..e143a8ed
--- /dev/null
+++ b/src/Recyclarr.TrashLib.Tests/Config/Services/Data/templates.json
@@ -0,0 +1,18 @@
+{
+ "radarr": [
+ {
+ "template": "radarr/hd-bluray-web.yml",
+ "id": "hd-bluray-web"
+ },
+ {
+ "template": "radarr/uhd-bluray-web.yml",
+ "id": "uhd-bluray-web"
+ }
+ ],
+ "sonarr": [
+ {
+ "template": "sonarr/web-1080p-v4.yml",
+ "id": "web-1080p-v4"
+ }
+ ]
+}
diff --git a/src/Recyclarr.TrashLib.Tests/Processors/ConfigListProcessorTest.cs b/src/Recyclarr.TrashLib.Tests/Processors/ConfigListProcessorTest.cs
new file mode 100644
index 00000000..9d887b33
--- /dev/null
+++ b/src/Recyclarr.TrashLib.Tests/Processors/ConfigListProcessorTest.cs
@@ -0,0 +1,25 @@
+using Recyclarr.TestLibrary.Autofac;
+using Recyclarr.TrashLib.Config.Listers;
+using Recyclarr.TrashLib.Processors;
+
+namespace Recyclarr.TrashLib.Tests.Processors;
+
+[TestFixture]
+[Parallelizable(ParallelScope.All)]
+public class ConfigListProcessorTest
+{
+ [Test]
+ [InlineAutoMockData(ConfigListCategory.Templates)]
+ public void List_templates_invokes_correct_lister(
+ ConfigListCategory category,
+ [Frozen(Matching.ImplementedInterfaces)] StubAutofacIndex configListers,
+ IConfigLister lister,
+ ConfigListProcessor sut)
+ {
+ configListers.Add(category, lister);
+
+ sut.Process(category);
+
+ lister.Received().List();
+ }
+}
diff --git a/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCompatibilityTest.cs b/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCompatibilityTest.cs
index 4caae2bf..c61bd5b5 100644
--- a/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCompatibilityTest.cs
+++ b/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCompatibilityTest.cs
@@ -3,7 +3,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Recyclarr.Cli.TestLibrary;
-using Recyclarr.TestLibrary;
+using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashLib.Compatibility.Sonarr;
using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Pipelines.ReleaseProfile.Api;
diff --git a/src/Recyclarr.TrashLib/Config/ConfigAutofacModule.cs b/src/Recyclarr.TrashLib/Config/ConfigAutofacModule.cs
index ea804719..7e87df54 100644
--- a/src/Recyclarr.TrashLib/Config/ConfigAutofacModule.cs
+++ b/src/Recyclarr.TrashLib/Config/ConfigAutofacModule.cs
@@ -1,8 +1,10 @@
using System.Reflection;
using Autofac;
using FluentValidation;
+using Recyclarr.TrashLib.Config.Listers;
using Recyclarr.TrashLib.Config.Parsing;
using Recyclarr.TrashLib.Config.Secrets;
+using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Config.Settings;
using Recyclarr.TrashLib.Config.Yaml;
using YamlDotNet.Serialization;
@@ -15,7 +17,7 @@ public class ConfigAutofacModule : Module
{
private readonly Assembly[] _assemblies;
- public ConfigAutofacModule(Assembly[] assemblies)
+ public ConfigAutofacModule(params Assembly[] assemblies)
{
_assemblies = assemblies;
}
@@ -39,5 +41,10 @@ public class ConfigAutofacModule : Module
builder.RegisterType().As();
builder.RegisterType();
builder.RegisterType();
+ builder.RegisterType().As();
+
+ // Config Listers
+ builder.RegisterType().Keyed(ConfigListCategory.Templates);
+ builder.RegisterType().Keyed(ConfigListCategory.Local);
}
}
diff --git a/src/Recyclarr.TrashLib/Config/Listers/ConfigListCategory.cs b/src/Recyclarr.TrashLib/Config/Listers/ConfigListCategory.cs
new file mode 100644
index 00000000..6e860461
--- /dev/null
+++ b/src/Recyclarr.TrashLib/Config/Listers/ConfigListCategory.cs
@@ -0,0 +1,7 @@
+namespace Recyclarr.TrashLib.Config.Listers;
+
+public enum ConfigListCategory
+{
+ Local,
+ Templates
+}
diff --git a/src/Recyclarr.TrashLib/Config/Listers/ConfigLocalLister.cs b/src/Recyclarr.TrashLib/Config/Listers/ConfigLocalLister.cs
new file mode 100644
index 00000000..1a1f2817
--- /dev/null
+++ b/src/Recyclarr.TrashLib/Config/Listers/ConfigLocalLister.cs
@@ -0,0 +1,18 @@
+using Spectre.Console;
+
+namespace Recyclarr.TrashLib.Config.Listers;
+
+public class ConfigLocalLister : IConfigLister
+{
+ private readonly IAnsiConsole _console;
+
+ public ConfigLocalLister(IAnsiConsole console)
+ {
+ _console = console;
+ }
+
+ public void List()
+ {
+ _console.Write("Local listing is not supported yet, but coming soon.");
+ }
+}
diff --git a/src/Recyclarr.TrashLib/Config/Listers/ConfigTemplateLister.cs b/src/Recyclarr.TrashLib/Config/Listers/ConfigTemplateLister.cs
new file mode 100644
index 00000000..ab4bb679
--- /dev/null
+++ b/src/Recyclarr.TrashLib/Config/Listers/ConfigTemplateLister.cs
@@ -0,0 +1,52 @@
+using MoreLinq;
+using Recyclarr.TrashLib.Config.Services;
+using Spectre.Console;
+
+namespace Recyclarr.TrashLib.Config.Listers;
+
+public class ConfigTemplateLister : IConfigLister
+{
+ private readonly IAnsiConsole _console;
+ private readonly IConfigTemplateGuideService _guideService;
+
+ public ConfigTemplateLister(IAnsiConsole console, IConfigTemplateGuideService guideService)
+ {
+ _console = console;
+ _guideService = guideService;
+ }
+
+ public void List()
+ {
+ var data = _guideService.TemplateData;
+
+ var table = new Table();
+ var empty = new Markup("");
+
+ var sonarrRowItems = RenderTemplates(table, data, SupportedServices.Sonarr);
+ var radarrRowItems = RenderTemplates(table, data, SupportedServices.Radarr);
+ var items = radarrRowItems
+ .ZipLongest(sonarrRowItems, (l, r) => (l ?? empty, r ?? empty));
+
+ foreach (var (r, s) in items)
+ {
+ table.AddRow(r, s);
+ }
+
+ _console.Write(table);
+ }
+
+ private static IEnumerable RenderTemplates(
+ Table table,
+ IEnumerable templatePaths,
+ SupportedServices service)
+ {
+ var paths = templatePaths
+ .Where(x => x.Service == service && !x.Hidden)
+ .Select(x => Markup.FromInterpolated($"[blue]{x.Id}[/]"))
+ .ToList();
+
+ table.AddColumn(service.ToString());
+
+ return paths;
+ }
+}
diff --git a/src/Recyclarr.TrashLib/Config/Listers/IConfigLister.cs b/src/Recyclarr.TrashLib/Config/Listers/IConfigLister.cs
new file mode 100644
index 00000000..411b193c
--- /dev/null
+++ b/src/Recyclarr.TrashLib/Config/Listers/IConfigLister.cs
@@ -0,0 +1,6 @@
+namespace Recyclarr.TrashLib.Config.Listers;
+
+public interface IConfigLister
+{
+ void List();
+}
diff --git a/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationLoader.cs b/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationLoader.cs
index f1b52433..460158df 100644
--- a/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationLoader.cs
+++ b/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationLoader.cs
@@ -114,7 +114,6 @@ public class ConfigurationLoader : IConfigurationLoader
if (!ParseSingleSection(parser))
{
parser.SkipThisAndNestedEvents();
- continue;
}
}
}
diff --git a/src/Recyclarr.TrashLib/Config/Services/ConfigTemplateGuideService.cs b/src/Recyclarr.TrashLib/Config/Services/ConfigTemplateGuideService.cs
new file mode 100644
index 00000000..3a3e7d4a
--- /dev/null
+++ b/src/Recyclarr.TrashLib/Config/Services/ConfigTemplateGuideService.cs
@@ -0,0 +1,61 @@
+using System.Collections.ObjectModel;
+using System.IO.Abstractions;
+using JetBrains.Annotations;
+using Recyclarr.Common.Extensions;
+using Recyclarr.TrashLib.Repo;
+using Recyclarr.TrashLib.Startup;
+
+namespace Recyclarr.TrashLib.Config.Services;
+
+[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
+public record TemplateEntry(string Id, string Template, bool Hidden = false);
+
+public record TemplatesData
+{
+ public ReadOnlyCollection Radarr { get; [UsedImplicitly] init; } = new(Array.Empty());
+ public ReadOnlyCollection Sonarr { get; [UsedImplicitly] init; } = new(Array.Empty());
+}
+
+public record TemplatePath(SupportedServices Service, string Id, IFileInfo TemplateFile, bool Hidden);
+
+public class ConfigTemplateGuideService : IConfigTemplateGuideService
+{
+ private readonly IRepoMetadataBuilder _metadataBuilder;
+ private readonly IAppPaths _paths;
+ private readonly Lazy> _templateData;
+
+ public ConfigTemplateGuideService(
+ IRepoMetadataBuilder metadataBuilder,
+ IAppPaths paths)
+ {
+ _metadataBuilder = metadataBuilder;
+ _paths = paths;
+ _templateData = new Lazy>(LoadTemplateData);
+ }
+
+ private IReadOnlyCollection LoadTemplateData()
+ {
+ var metadata = _metadataBuilder.GetMetadata();
+
+ var templatesPath = _paths.RepoDirectory.SubDir(metadata.Recyclarr.Templates);
+ if (!templatesPath.Exists)
+ {
+ throw new InvalidDataException(
+ $"Path to recyclarr templates does not exist: {metadata.Recyclarr.Templates}");
+ }
+
+ var templates = TrashRepoJsonParser.Deserialize(templatesPath.File("templates.json"));
+
+ TemplatePath NewTemplatePath(TemplateEntry entry, SupportedServices service)
+ {
+ return new TemplatePath(service, entry.Id, templatesPath.File(entry.Template), entry.Hidden);
+ }
+
+ return templates.Radarr
+ .Select(x => NewTemplatePath(x, SupportedServices.Radarr))
+ .Concat(templates.Sonarr.Select(x => NewTemplatePath(x, SupportedServices.Sonarr)))
+ .ToList();
+ }
+
+ public IReadOnlyCollection TemplateData => _templateData.Value;
+}
diff --git a/src/Recyclarr.TrashLib/Config/Services/IConfigTemplateGuideService.cs b/src/Recyclarr.TrashLib/Config/Services/IConfigTemplateGuideService.cs
new file mode 100644
index 00000000..a6e12cd5
--- /dev/null
+++ b/src/Recyclarr.TrashLib/Config/Services/IConfigTemplateGuideService.cs
@@ -0,0 +1,6 @@
+namespace Recyclarr.TrashLib.Config.Services;
+
+public interface IConfigTemplateGuideService
+{
+ IReadOnlyCollection TemplateData { get; }
+}
diff --git a/src/Recyclarr.TrashLib/Processors/ConfigListProcessor.cs b/src/Recyclarr.TrashLib/Processors/ConfigListProcessor.cs
new file mode 100644
index 00000000..3dc8a14c
--- /dev/null
+++ b/src/Recyclarr.TrashLib/Processors/ConfigListProcessor.cs
@@ -0,0 +1,27 @@
+using Autofac.Features.Indexed;
+using Recyclarr.TrashLib.Config.Listers;
+
+namespace Recyclarr.TrashLib.Processors;
+
+public class ConfigListProcessor
+{
+ private readonly ILogger _log;
+ private readonly IIndex _configListers;
+
+ public ConfigListProcessor(ILogger log, IIndex configListers)
+ {
+ _log = log;
+ _configListers = configListers;
+ }
+
+ public void Process(ConfigListCategory listCategory)
+ {
+ _log.Debug("Listing configuration for category {Category}", listCategory);
+ if (!_configListers.TryGetValue(listCategory, out var lister))
+ {
+ throw new ArgumentOutOfRangeException(nameof(listCategory), listCategory, "Unknown list category");
+ }
+
+ lister.List();
+ }
+}
diff --git a/src/Recyclarr.TrashLib/Processors/ServiceProcessorsAutofacModule.cs b/src/Recyclarr.TrashLib/Processors/ServiceProcessorsAutofacModule.cs
index 106a6fb9..3cb19aa0 100644
--- a/src/Recyclarr.TrashLib/Processors/ServiceProcessorsAutofacModule.cs
+++ b/src/Recyclarr.TrashLib/Processors/ServiceProcessorsAutofacModule.cs
@@ -10,5 +10,6 @@ public class ServiceProcessorsAutofacModule : Module
builder.RegisterType().As();
builder.RegisterType().As();
builder.RegisterType();
+ builder.RegisterType();
}
}
diff --git a/src/Recyclarr.TrashLib/Repo/IRepoMetadataParser.cs b/src/Recyclarr.TrashLib/Repo/IRepoMetadataParser.cs
deleted file mode 100644
index 644732ff..00000000
--- a/src/Recyclarr.TrashLib/Repo/IRepoMetadataParser.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Recyclarr.TrashLib.Repo;
-
-public interface IRepoMetadataParser
-{
- RepoMetadata Deserialize();
-}
diff --git a/src/Recyclarr.TrashLib/Repo/RepoAutofacModule.cs b/src/Recyclarr.TrashLib/Repo/RepoAutofacModule.cs
index b2ae5156..151eb193 100644
--- a/src/Recyclarr.TrashLib/Repo/RepoAutofacModule.cs
+++ b/src/Recyclarr.TrashLib/Repo/RepoAutofacModule.cs
@@ -8,7 +8,6 @@ public class RepoAutofacModule : Module
{
base.Load(builder);
builder.RegisterType().As();
- builder.RegisterType().As();
builder.RegisterType().As().InstancePerLifetimeScope();
}
}
diff --git a/src/Recyclarr.TrashLib/Repo/RepoMetadata.cs b/src/Recyclarr.TrashLib/Repo/RepoMetadata.cs
index f44e23ac..f8ee7e7c 100644
--- a/src/Recyclarr.TrashLib/Repo/RepoMetadata.cs
+++ b/src/Recyclarr.TrashLib/Repo/RepoMetadata.cs
@@ -19,7 +19,13 @@ public record JsonPaths
public SonarrMetadata Sonarr { get; init; } = new();
}
+public record RecyclarrMetadata
+{
+ public string Templates { get; init; } = "";
+}
+
public record RepoMetadata
{
public JsonPaths JsonPaths { get; init; } = new();
+ public RecyclarrMetadata Recyclarr { get; init; } = new();
}
diff --git a/src/Recyclarr.TrashLib/Repo/RepoMetadataBuilder.cs b/src/Recyclarr.TrashLib/Repo/RepoMetadataBuilder.cs
index 3c6c48bc..d7da876e 100644
--- a/src/Recyclarr.TrashLib/Repo/RepoMetadataBuilder.cs
+++ b/src/Recyclarr.TrashLib/Repo/RepoMetadataBuilder.cs
@@ -8,12 +8,11 @@ public class RepoMetadataBuilder : IRepoMetadataBuilder
private readonly IAppPaths _paths;
private readonly Lazy _metadata;
- public RepoMetadataBuilder(
- IRepoMetadataParser parser,
- IAppPaths paths)
+ public RepoMetadataBuilder(IAppPaths paths)
{
_paths = paths;
- _metadata = new Lazy(parser.Deserialize);
+ _metadata = new Lazy(()
+ => TrashRepoJsonParser.Deserialize(_paths.RepoDirectory.File("metadata.json")));
}
public IReadOnlyList ToDirectoryInfoList(IEnumerable listOfDirectories)
diff --git a/src/Recyclarr.TrashLib/Repo/RepoMetadataParser.cs b/src/Recyclarr.TrashLib/Repo/RepoMetadataParser.cs
deleted file mode 100644
index 2fe062bb..00000000
--- a/src/Recyclarr.TrashLib/Repo/RepoMetadataParser.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.IO.Abstractions;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Serialization;
-using Recyclarr.TrashLib.Startup;
-
-namespace Recyclarr.TrashLib.Repo;
-
-public class RepoMetadataParser : IRepoMetadataParser
-{
- private readonly IAppPaths _paths;
-
- public RepoMetadataParser(IAppPaths paths)
- {
- _paths = paths;
- }
-
- public RepoMetadata Deserialize()
- {
- var serializer = JsonSerializer.Create(new JsonSerializerSettings
- {
- ContractResolver = new DefaultContractResolver
- {
- NamingStrategy = new SnakeCaseNamingStrategy()
- }
- });
-
- var metadataFile = _paths.RepoDirectory.File("metadata.json");
- using var stream = new JsonTextReader(metadataFile.OpenText());
-
- var metadata = serializer.Deserialize(stream);
- if (metadata is null)
- {
- throw new InvalidDataException("Unable to deserialize metadata.json");
- }
-
- return metadata;
- }
-}
diff --git a/src/Recyclarr.TrashLib/Repo/TrashRepoJsonParser.cs b/src/Recyclarr.TrashLib/Repo/TrashRepoJsonParser.cs
new file mode 100644
index 00000000..b753cfe0
--- /dev/null
+++ b/src/Recyclarr.TrashLib/Repo/TrashRepoJsonParser.cs
@@ -0,0 +1,29 @@
+using System.IO.Abstractions;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Recyclarr.TrashLib.Repo;
+
+public static class TrashRepoJsonParser
+{
+ public static T Deserialize(IFileInfo jsonFile)
+ {
+ var serializer = JsonSerializer.Create(new JsonSerializerSettings
+ {
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new SnakeCaseNamingStrategy()
+ }
+ });
+
+ using var stream = new JsonTextReader(jsonFile.OpenText());
+
+ var obj = serializer.Deserialize(stream);
+ if (obj is null)
+ {
+ throw new InvalidDataException($"Unable to deserialize {jsonFile}");
+ }
+
+ return obj;
+ }
+}
diff --git a/src/Recyclarr.sln.DotSettings b/src/Recyclarr.sln.DotSettings
index ca2044fd..13975a2f 100644
--- a/src/Recyclarr.sln.DotSettings
+++ b/src/Recyclarr.sln.DotSettings
@@ -112,6 +112,7 @@
</Language>
</profile></RIDER_SETTINGS><CSharpFormatDocComments>True</CSharpFormatDocComments><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSMakeFieldReadonly>True</CSMakeFieldReadonly></Profile>
Recyclarr Cleanup
+ True
True
True
True