refactor: Split Sonarr/Radarr orthogonally into service-based classes

Instead of organizing logic using service, such as Sonarr or Radarr,
organize it using function. So now logic is broken up by Custom Format,
Release Profile, and Quality Size.
pull/201/head
Robert Dailey 1 year ago
parent 7dec45a07a
commit bcc65857df

@ -14,10 +14,11 @@ using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Http;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Repo.VersionControl;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.CustomFormat;
using Recyclarr.TrashLib.Services.Processors;
using Recyclarr.TrashLib.Services.QualitySize;
using Recyclarr.TrashLib.Services.Radarr;
using Recyclarr.TrashLib.Services.ReleaseProfile;
using Recyclarr.TrashLib.Services.Sonarr;
using Recyclarr.TrashLib.Services.System;
using Recyclarr.TrashLib.Startup;
@ -38,11 +39,12 @@ public static class CompositionRoot
builder.RegisterModule<SonarrAutofacModule>();
builder.RegisterModule<RadarrAutofacModule>();
builder.RegisterModule<QualitySizeAutofacModule>();
builder.RegisterModule<CustomFormatAutofacModule>();
builder.RegisterModule<ReleaseProfileAutofacModule>();
builder.RegisterModule<VersionControlAutofacModule>();
builder.RegisterModule<MigrationAutofacModule>();
builder.RegisterModule<RepoAutofacModule>();
builder.RegisterModule<CustomFormatAutofacModule>();
builder.RegisterModule<GuideServicesAutofacModule>();
builder.RegisterModule<SystemServiceAutofacModule>();
builder.RegisterModule(new ConfigAutofacModule(assemblies));
builder.RegisterModule<ServiceProcessorsAutofacModule>();

@ -1,11 +1,10 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Autofac.Features.Indexed;
using JetBrains.Annotations;
using Recyclarr.Cli.Console.Helpers;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.CustomFormat.Guide;
using Spectre.Console.Cli;
#pragma warning disable CS8765
@ -16,8 +15,7 @@ namespace Recyclarr.Cli.Console.Commands;
[Description("List custom formats in the guide for a particular service.")]
internal class ListCustomFormatsCommand : AsyncCommand<ListCustomFormatsCommand.CliSettings>
{
private readonly IGuideDataLister _lister;
private readonly IIndex<SupportedServices, IGuideService> _guideService;
private readonly CustomFormatDataLister _lister;
private readonly IRepoUpdater _repoUpdater;
[UsedImplicitly]
@ -31,20 +29,17 @@ internal class ListCustomFormatsCommand : AsyncCommand<ListCustomFormatsCommand.
}
public ListCustomFormatsCommand(
IGuideDataLister lister,
IIndex<SupportedServices, IGuideService> guideService,
CustomFormatDataLister lister,
IRepoUpdater repoUpdater)
{
_lister = lister;
_guideService = guideService;
_repoUpdater = repoUpdater;
}
public override async Task<int> ExecuteAsync(CommandContext context, CliSettings settings)
{
await _repoUpdater.UpdateRepo();
var guideService = _guideService[settings.Service];
_lister.ListCustomFormats(guideService.GetCustomFormatData());
_lister.ListCustomFormats(settings.Service);
return 0;
}
}

@ -1,11 +1,10 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Autofac.Features.Indexed;
using JetBrains.Annotations;
using Recyclarr.Cli.Console.Helpers;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.QualitySize.Guide;
using Spectre.Console.Cli;
namespace Recyclarr.Cli.Console.Commands;
@ -15,8 +14,7 @@ namespace Recyclarr.Cli.Console.Commands;
[Description("List quality definitions in the guide for a particular service.")]
internal class ListQualitiesCommand : AsyncCommand<ListQualitiesCommand.CliSettings>
{
private readonly IGuideDataLister _lister;
private readonly IIndex<SupportedServices, IGuideService> _guideService;
private readonly QualitySizeDataLister _lister;
private readonly IRepoUpdater _repoUpdater;
[UsedImplicitly]
@ -29,21 +27,16 @@ internal class ListQualitiesCommand : AsyncCommand<ListQualitiesCommand.CliSetti
public required SupportedServices Service { get; init; }
}
public ListQualitiesCommand(
IGuideDataLister lister,
IIndex<SupportedServices, IGuideService> guideService,
IRepoUpdater repoUpdater)
public ListQualitiesCommand(QualitySizeDataLister lister, IRepoUpdater repoUpdater)
{
_lister = lister;
_guideService = guideService;
_repoUpdater = repoUpdater;
}
public override async Task<int> ExecuteAsync(CommandContext context, CliSettings settings)
{
await _repoUpdater.UpdateRepo();
var guideService = _guideService[settings.Service];
_lister.ListQualities(guideService.GetQualities());
_lister.ListQualities(settings.Service);
return 0;
}
}

@ -2,7 +2,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Services.Sonarr;
using Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
using Spectre.Console.Cli;
#pragma warning disable CS8765
@ -14,7 +14,7 @@ namespace Recyclarr.Cli.Console.Commands;
internal class ListReleaseProfilesCommand : AsyncCommand<ListReleaseProfilesCommand.CliSettings>
{
private readonly ILogger _log;
private readonly ISonarrGuideDataLister _lister;
private readonly ReleaseProfileDataLister _lister;
private readonly IRepoUpdater _repoUpdater;
[UsedImplicitly]
@ -31,7 +31,7 @@ internal class ListReleaseProfilesCommand : AsyncCommand<ListReleaseProfilesComm
public ListReleaseProfilesCommand(
ILogger log,
ISonarrGuideDataLister lister,
ReleaseProfileDataLister lister,
IRepoUpdater repoUpdater)
{
_log = log;

@ -6,8 +6,9 @@ using Recyclarr.Cli.Console.Helpers;
using Recyclarr.Cli.Migration;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Services.CustomFormat.Guide;
using Recyclarr.TrashLib.Services.Processors;
using Recyclarr.TrashLib.Services.Radarr;
using Recyclarr.TrashLib.Services.QualitySize.Guide;
using Spectre.Console.Cli;
namespace Recyclarr.Cli.Console.Commands;
@ -17,7 +18,8 @@ namespace Recyclarr.Cli.Console.Commands;
internal class RadarrCommand : AsyncCommand<RadarrCommand.CliSettings>
{
private readonly ILogger _log;
private readonly IRadarrGuideDataLister _lister;
private readonly CustomFormatDataLister _cfLister;
private readonly QualitySizeDataLister _qualityLister;
private readonly IMigrationExecutor _migration;
private readonly IRepoUpdater _repoUpdater;
private readonly ISyncProcessor _syncProcessor;
@ -56,13 +58,15 @@ internal class RadarrCommand : AsyncCommand<RadarrCommand.CliSettings>
public RadarrCommand(
ILogger log,
IRadarrGuideDataLister lister,
CustomFormatDataLister cfLister,
QualitySizeDataLister qualityLister,
IMigrationExecutor migration,
IRepoUpdater repoUpdater,
ISyncProcessor syncProcessor)
{
_log = log;
_lister = lister;
_cfLister = cfLister;
_qualityLister = qualityLister;
_migration = migration;
_repoUpdater = repoUpdater;
_syncProcessor = syncProcessor;
@ -77,14 +81,14 @@ internal class RadarrCommand : AsyncCommand<RadarrCommand.CliSettings>
if (settings.ListCustomFormats)
{
_log.Warning("The `radarr` subcommand is DEPRECATED -- Use `list custom-formats radarr` instead!");
_lister.ListCustomFormats();
_cfLister.ListCustomFormats(SupportedServices.Radarr);
return 0;
}
if (settings.ListQualities)
{
_log.Warning("The `radarr` subcommand is DEPRECATED -- Use `list qualities radarr` instead!");
_lister.ListQualities();
_qualityLister.ListQualities(SupportedServices.Radarr);
return 0;
}

@ -6,8 +6,10 @@ using Recyclarr.Cli.Console.Helpers;
using Recyclarr.Cli.Migration;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Services.CustomFormat.Guide;
using Recyclarr.TrashLib.Services.Processors;
using Recyclarr.TrashLib.Services.Sonarr;
using Recyclarr.TrashLib.Services.QualitySize.Guide;
using Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
using Spectre.Console.Cli;
namespace Recyclarr.Cli.Console.Commands;
@ -17,7 +19,9 @@ namespace Recyclarr.Cli.Console.Commands;
internal class SonarrCommand : AsyncCommand<SonarrCommand.CliSettings>
{
private readonly ILogger _log;
private readonly ISonarrGuideDataLister _lister;
private readonly CustomFormatDataLister _cfLister;
private readonly QualitySizeDataLister _qualityLister;
private readonly ReleaseProfileDataLister _rpLister;
private readonly IMigrationExecutor _migration;
private readonly IRepoUpdater _repoUpdater;
private readonly ISyncProcessor _syncProcessor;
@ -70,13 +74,17 @@ internal class SonarrCommand : AsyncCommand<SonarrCommand.CliSettings>
public SonarrCommand(
ILogger log,
ISonarrGuideDataLister lister,
CustomFormatDataLister cfLister,
QualitySizeDataLister qualityLister,
ReleaseProfileDataLister rpLister,
IMigrationExecutor migration,
IRepoUpdater repoUpdater,
ISyncProcessor syncProcessor)
{
_log = log;
_lister = lister;
_cfLister = cfLister;
_qualityLister = qualityLister;
_rpLister = rpLister;
_migration = migration;
_repoUpdater = repoUpdater;
_syncProcessor = syncProcessor;
@ -91,27 +99,27 @@ internal class SonarrCommand : AsyncCommand<SonarrCommand.CliSettings>
if (settings.ListCustomFormats)
{
_log.Warning("The `sonarr` subcommand is DEPRECATED -- Use `list custom-formats sonarr` instead!");
_lister.ListCustomFormats();
_cfLister.ListCustomFormats(SupportedServices.Sonarr);
return 0;
}
if (settings.ListQualities)
{
_log.Warning("The `sonarr` subcommand is DEPRECATED -- Use `list qualities sonarr` instead!");
_lister.ListQualities();
_qualityLister.ListQualities(SupportedServices.Sonarr);
return 0;
}
if (settings.ListReleaseProfiles)
{
_log.Warning("The `sonarr` subcommand is DEPRECATED -- Use `list release-profiles` instead!");
_lister.ListReleaseProfiles();
_rpLister.ListReleaseProfiles();
return 0;
}
if (settings.ListTerms is not null)
{
_lister.ListTerms(settings.ListTerms);
_rpLister.ListTerms(settings.ListTerms);
return 0;
}

@ -1,8 +1,9 @@
using AutoFixture.NUnit3;
using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
using Recyclarr.TestLibrary.AutoFixture;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.CustomFormat.Guide;
using Recyclarr.TrashLib.TestLibrary;
using Spectre.Console.Testing;
@ -15,7 +16,8 @@ public class GuideDataListerTest
[Test, AutoMockData]
public void Custom_formats_appear_in_console_output(
[Frozen(Matching.ImplementedInterfaces)] TestConsole console,
GuideDataLister sut)
[Frozen] ICustomFormatGuideService guide,
CustomFormatDataLister sut)
{
var testData = new[]
{
@ -23,7 +25,9 @@ public class GuideDataListerTest
NewCf.Data("Second", "456")
};
sut.ListCustomFormats(testData);
guide.GetCustomFormatData(default!).ReturnsForAnyArgs(testData);
sut.ListCustomFormats(default!);
console.Output.Should().ContainAll(
testData.SelectMany(x => new[] {x.Name, x.TrashId}));

@ -10,7 +10,6 @@ using Recyclarr.TrashLib.Services.CustomFormat.Guide;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.CustomFormat.Processors;
using Recyclarr.TrashLib.Services.CustomFormat.Processors.GuideSteps;
using Recyclarr.TrashLib.Services.Radarr;
using Recyclarr.TrashLib.TestLibrary;
namespace Recyclarr.TrashLib.Tests.CustomFormat.Processors;
@ -57,11 +56,11 @@ public class GuideProcessorTest
public async Task Guide_processor_behaves_as_expected_with_normal_guide_data()
{
var ctx = new Context();
var guideService = Substitute.For<RadarrGuideService>();
var guideProcessor = new GuideProcessor(new TestGuideProcessorSteps());
var guideService = Substitute.For<ICustomFormatGuideService>();
var guideProcessor = new GuideProcessor(new TestGuideProcessorSteps(), guideService);
// simulate guide data
guideService.GetCustomFormatData().Returns(new[]
guideService.GetCustomFormatData(default!).ReturnsForAnyArgs(new[]
{
ctx.ReadCustomFormat("ImportableCustomFormat1.json"),
ctx.ReadCustomFormat("ImportableCustomFormat2.json"),
@ -102,7 +101,7 @@ public class GuideProcessorTest
}
};
await guideProcessor.BuildGuideDataAsync(config, null, guideService);
await guideProcessor.BuildGuideDataAsync(config, null, default!);
var expectedProcessedCustomFormatData = new List<ProcessedCustomFormatData>
{

@ -9,20 +9,20 @@ using NUnit.Framework;
using Recyclarr.TestLibrary;
using Recyclarr.TestLibrary.AutoFixture;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Services.Sonarr;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.TrashLib.Services.ReleaseProfile;
using Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
namespace Recyclarr.TrashLib.Tests.Sonarr.ReleaseProfile.Guide;
namespace Recyclarr.TrashLib.Tests.Services;
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class LocalRepoSonarrGuideServiceTest
public class GuideServiceTest
{
[Test, AutoMockData]
public void Get_custom_format_json_works(
public void Get_release_profile_json_works(
[Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs,
[Frozen] IRepoPaths repoPaths,
LocalRepoSonarrGuideService sut)
[Frozen] IRepoMetadataBuilder metadataBuilder,
ReleaseProfileGuideService sut)
{
static ReleaseProfileData MakeMockObject(string term)
{
@ -50,7 +50,7 @@ public class LocalRepoSonarrGuideServiceTest
fs.AddFile(baseDir.File("first.json").FullName, MockFileData(mockData1));
fs.AddFile(baseDir.File("second.json").FullName, MockFileData(mockData2));
repoPaths.SonarrReleaseProfilePaths.Returns(new[] {baseDir});
metadataBuilder.ToDirectoryInfoList(default!).ReturnsForAnyArgs(new[] {baseDir});
var results = sut.GetReleaseProfileData();
@ -64,8 +64,8 @@ public class LocalRepoSonarrGuideServiceTest
[Test, AutoMockData]
public void Json_exceptions_do_not_interrupt_parsing_other_files(
[Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs,
[Frozen] IRepoPaths repoPaths,
LocalRepoSonarrGuideService sut)
[Frozen] IRepoMetadataBuilder metadataBuilder,
ReleaseProfileGuideService sut)
{
var rootPath = fs.CurrentDirectory().SubDirectory("files");
rootPath.Create();
@ -84,7 +84,7 @@ public class LocalRepoSonarrGuideServiceTest
fs.AddFile(rootPath.File("0_bad_data.json").FullName, MockData.FromString(badData));
fs.AddFile(rootPath.File("1_good_data.json").FullName, MockData.FromJson(goodData));
repoPaths.SonarrReleaseProfilePaths.Returns(new[] {rootPath});
metadataBuilder.ToDirectoryInfoList(default!).ReturnsForAnyArgs(new[] {rootPath});
var results = sut.GetReleaseProfileData();

@ -3,21 +3,21 @@ using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
using Recyclarr.TestLibrary.AutoFixture;
using Recyclarr.TrashLib.Services.Sonarr;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.TrashLib.Services.ReleaseProfile;
using Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
using Spectre.Console.Testing;
namespace Recyclarr.TrashLib.Tests.Sonarr;
namespace Recyclarr.TrashLib.Tests.Services;
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class SonarrGuideDataListerTest
public class ReleaseProfileDataListerTest
{
[Test, AutoMockData]
public void Release_profiles_appear_in_console_output(
[Frozen] SonarrGuideService guide,
[Frozen(Matching.ImplementedInterfaces)] TestConsole console,
SonarrGuideDataLister sut)
[Frozen] IReleaseProfileGuideService guide,
ReleaseProfileDataLister sut)
{
var testData = new[]
{
@ -35,9 +35,9 @@ public class SonarrGuideDataListerTest
[Test, AutoMockData]
public void Terms_appear_in_console_output(
[Frozen] SonarrGuideService guide,
[Frozen] IReleaseProfileGuideService guide,
[Frozen(Matching.ImplementedInterfaces)] TestConsole console,
SonarrGuideDataLister sut)
ReleaseProfileDataLister sut)
{
var requiredData = new[]
{
@ -57,15 +57,18 @@ public class SonarrGuideDataListerTest
new TermData {Name = "Sixth", TrashId = "666", Term = "term6"}
};
guide.GetUnfilteredProfileById(default!).ReturnsForAnyArgs(new ReleaseProfileData
guide.GetReleaseProfileData().Returns(new[]
{
Name = "Release Profile",
TrashId = "098",
Required = requiredData,
Ignored = ignoredData,
Preferred = new PreferredTermData[]
new ReleaseProfileData
{
new() {Score = 100, Terms = preferredData}
Name = "Release Profile",
TrashId = "098",
Required = requiredData,
Ignored = ignoredData,
Preferred = new PreferredTermData[]
{
new() {Score = 100, Terms = preferredData}
}
}
});
@ -78,7 +81,6 @@ public class SonarrGuideDataListerTest
preferredData.SelectMany(x => new[] {x.Name, x.TrashId})
};
guide.Received().GetUnfilteredProfileById("098");
console.Output.Should().ContainAll(expectedOutput.SelectMany(x => x));
}
}

@ -1,9 +1,9 @@
using FluentAssertions;
using NUnit.Framework;
using Recyclarr.TestLibrary.AutoFixture;
using Recyclarr.TrashLib.Services.ReleaseProfile;
using Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
using Recyclarr.TrashLib.Services.Sonarr.Config;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Tests.Sonarr.ReleaseProfile.Filters;

@ -1,8 +1,8 @@
using FluentAssertions;
using NUnit.Framework;
using Recyclarr.TestLibrary.AutoFixture;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
using Recyclarr.TrashLib.Services.ReleaseProfile;
using Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Tests.Sonarr.ReleaseProfile.Filters;

@ -1,9 +1,9 @@
using FluentAssertions;
using NUnit.Framework;
using Recyclarr.TestLibrary.AutoFixture;
using Recyclarr.TrashLib.Services.ReleaseProfile;
using Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
using Recyclarr.TrashLib.Services.Sonarr.Config;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Tests.Sonarr.ReleaseProfile.Filters;

@ -1,7 +1,7 @@
using FluentAssertions;
using FluentValidation.TestHelper;
using NUnit.Framework;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.TrashLib.Services.ReleaseProfile;
namespace Recyclarr.TrashLib.Tests.Sonarr.ReleaseProfile;

@ -1,6 +1,6 @@
using FluentAssertions;
using NUnit.Framework;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.TrashLib.Services.ReleaseProfile;
namespace Recyclarr.TrashLib.Tests.Sonarr.ReleaseProfile;

@ -7,8 +7,8 @@ using NSubstitute;
using NUnit.Framework;
using Recyclarr.Cli.TestLibrary;
using Recyclarr.TestLibrary;
using Recyclarr.TrashLib.Services.Sonarr.Api;
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects;
using Recyclarr.TrashLib.Services.Sonarr.Capabilities;
namespace Recyclarr.TrashLib.Tests.Sonarr;

@ -0,0 +1,10 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Repo;
public interface IRepoMetadataBuilder
{
RepoMetadata GetMetadata();
IReadOnlyList<IDirectoryInfo> ToDirectoryInfoList(IEnumerable<string> listOfDirectories);
IDirectoryInfo DocsDirectory { get; }
}

@ -1,14 +0,0 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Repo;
public interface IRepoPaths
{
IReadOnlyCollection<IDirectoryInfo> RadarrCustomFormatPaths { get; }
IReadOnlyCollection<IDirectoryInfo> SonarrReleaseProfilePaths { get; }
IReadOnlyCollection<IDirectoryInfo> SonarrQualityPaths { get; }
IReadOnlyCollection<IDirectoryInfo> RadarrQualityPaths { get; }
IReadOnlyCollection<IDirectoryInfo> SonarrCustomFormatPaths { get; }
IFileInfo RadarrCollectionOfCustomFormats { get; }
IFileInfo SonarrCollectionOfCustomFormats { get; }
}

@ -1,7 +0,0 @@
namespace Recyclarr.TrashLib.Repo;
public interface IRepoPathsFactory
{
IRepoPaths Create();
RepoMetadata Metadata { get; }
}

@ -9,6 +9,6 @@ public class RepoAutofacModule : Module
base.Load(builder);
builder.RegisterType<RepoUpdater>().As<IRepoUpdater>();
builder.RegisterType<RepoMetadataParser>().As<IRepoMetadataParser>();
builder.RegisterType<RepoPathsFactory>().As<IRepoPathsFactory>().InstancePerLifetimeScope();
builder.RegisterType<RepoMetadataBuilder>().As<IRepoMetadataBuilder>().InstancePerLifetimeScope();
}
}

@ -1,21 +1,25 @@
namespace Recyclarr.TrashLib.Repo;
public record RadarrMetadata(
IReadOnlyCollection<string> CustomFormats,
IReadOnlyCollection<string> Qualities
);
public record RadarrMetadata
{
public IReadOnlyCollection<string> CustomFormats { get; init; } = Array.Empty<string>();
public IReadOnlyCollection<string> Qualities { get; init; } = Array.Empty<string>();
}
public record SonarrMetadata(
IReadOnlyCollection<string> ReleaseProfiles,
IReadOnlyCollection<string> Qualities,
IReadOnlyCollection<string> CustomFormats
);
public record SonarrMetadata
{
public IReadOnlyCollection<string> ReleaseProfiles { get; init; } = Array.Empty<string>();
public IReadOnlyCollection<string> Qualities { get; init; } = Array.Empty<string>();
public IReadOnlyCollection<string> CustomFormats { get; init; } = Array.Empty<string>();
}
public record JsonPaths(
RadarrMetadata Radarr,
SonarrMetadata Sonarr
);
public record JsonPaths
{
public RadarrMetadata Radarr { get; init; } = new();
public SonarrMetadata Sonarr { get; init; } = new();
}
public record RepoMetadata(
JsonPaths JsonPaths
);
public record RepoMetadata
{
public JsonPaths JsonPaths { get; init; } = new();
}

@ -0,0 +1,30 @@
using System.IO.Abstractions;
using Recyclarr.TrashLib.Startup;
namespace Recyclarr.TrashLib.Repo;
public class RepoMetadataBuilder : IRepoMetadataBuilder
{
private readonly IAppPaths _paths;
private readonly Lazy<RepoMetadata> _metadata;
public RepoMetadataBuilder(
IRepoMetadataParser parser,
IAppPaths paths)
{
_paths = paths;
_metadata = new Lazy<RepoMetadata>(parser.Deserialize);
}
public IReadOnlyList<IDirectoryInfo> ToDirectoryInfoList(IEnumerable<string> listOfDirectories)
{
return listOfDirectories.Select(x => _paths.RepoDirectory.SubDirectory(x)).ToList();
}
public IDirectoryInfo DocsDirectory => _paths.RepoDirectory.SubDirectory("docs");
public RepoMetadata GetMetadata()
{
return _metadata.Value;
}
}

@ -1,13 +0,0 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Repo;
public record RepoPaths(
IReadOnlyCollection<IDirectoryInfo> RadarrCustomFormatPaths,
IReadOnlyCollection<IDirectoryInfo> SonarrReleaseProfilePaths,
IReadOnlyCollection<IDirectoryInfo> RadarrQualityPaths,
IReadOnlyCollection<IDirectoryInfo> SonarrQualityPaths,
IReadOnlyCollection<IDirectoryInfo> SonarrCustomFormatPaths,
IFileInfo RadarrCollectionOfCustomFormats,
IFileInfo SonarrCollectionOfCustomFormats
) : IRepoPaths;

@ -1,40 +0,0 @@
using System.IO.Abstractions;
using Recyclarr.TrashLib.Startup;
namespace Recyclarr.TrashLib.Repo;
public class RepoPathsFactory : IRepoPathsFactory
{
private readonly IAppPaths _paths;
private readonly Lazy<RepoMetadata> _metadata;
public RepoMetadata Metadata => _metadata.Value;
public RepoPathsFactory(IRepoMetadataParser parser, IAppPaths paths)
{
_paths = paths;
_metadata = new Lazy<RepoMetadata>(parser.Deserialize);
}
private List<IDirectoryInfo> ToDirectoryInfoList(IEnumerable<string> listOfDirectories)
{
return listOfDirectories
.Select(x => _paths.RepoDirectory.SubDirectory(x))
.ToList();
}
public IRepoPaths Create()
{
var docs = _paths.RepoDirectory.SubDirectory("docs");
var metadata = _metadata.Value;
return new RepoPaths(
ToDirectoryInfoList(metadata.JsonPaths.Radarr.CustomFormats),
ToDirectoryInfoList(metadata.JsonPaths.Sonarr.ReleaseProfiles),
ToDirectoryInfoList(metadata.JsonPaths.Radarr.Qualities),
ToDirectoryInfoList(metadata.JsonPaths.Sonarr.Qualities),
ToDirectoryInfoList(metadata.JsonPaths.Sonarr.CustomFormats),
docs.SubDirectory("Radarr").File("Radarr-collection-of-custom-formats.md"),
docs.SubDirectory("Sonarr").File("sonarr-collection-of-custom-formats.md")
);
}
}

@ -1,54 +0,0 @@
using MoreLinq;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.QualitySize;
using Spectre.Console;
namespace Recyclarr.TrashLib.Services.Common;
public class GuideDataLister : IGuideDataLister
{
private readonly IAnsiConsole _console;
public GuideDataLister(IAnsiConsole console)
{
_console = console;
}
public void ListCustomFormats(IEnumerable<CustomFormatData> customFormats)
{
_console.WriteLine("\nList of Custom Formats in the TRaSH Guides:");
var categories = customFormats
.OrderBy(x => x.Name)
.ToLookup(x => x.Category)
.OrderBy(x => x.Key);
foreach (var cat in categories)
{
var title = cat.Key is not null ? $"{cat.Key}" : "[No Category]";
_console.WriteLine($"\n # {title}");
foreach (var cf in cat)
{
_console.WriteLine($" - {cf.TrashId} # {cf.Name}");
}
}
_console.WriteLine(
"\nThe above Custom Formats are in YAML format and ready to be copied & pasted " +
"under the `trash_ids:` property.");
}
public void ListQualities(IEnumerable<QualitySizeData> qualityData)
{
_console.WriteLine("\nList of Quality Definition types in the TRaSH Guides:\n");
qualityData
.Select(x => x.Type)
.ForEach(x => _console.WriteLine($" - {x}"));
_console.WriteLine(
"\nThe above quality definition types can be used with the `quality_definition:` property in your " +
"recyclarr.yml file.");
}
}

@ -1,12 +0,0 @@
using Autofac;
namespace Recyclarr.TrashLib.Services.Common;
public class GuideServicesAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<GuideDataLister>().As<IGuideDataLister>();
}
}

@ -1,10 +0,0 @@
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.QualitySize;
namespace Recyclarr.TrashLib.Services.Common;
public interface IGuideDataLister
{
void ListCustomFormats(IEnumerable<CustomFormatData> customFormats);
void ListQualities(IEnumerable<QualitySizeData> qualityData);
}

@ -1,10 +0,0 @@
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.QualitySize;
namespace Recyclarr.TrashLib.Services.Common;
public interface IGuideService
{
ICollection<CustomFormatData> GetCustomFormatData();
ICollection<QualitySizeData> GetQualities();
}

@ -15,6 +15,7 @@ public class CustomFormatAutofacModule : Module
builder.RegisterType<CustomFormatService>().As<ICustomFormatService>();
builder.RegisterType<QualityProfileService>().As<IQualityProfileService>();
builder.RegisterType<CustomFormatUpdater>().As<ICustomFormatUpdater>();
builder.RegisterType<CustomFormatGuideService>().As<ICustomFormatGuideService>();
builder.RegisterType<CachePersister>().As<ICachePersister>();
builder.RegisterType<GuideProcessor>().As<IGuideProcessor>();
builder.RegisterType<CustomFormatLoader>().As<ICustomFormatLoader>();

@ -1,6 +1,6 @@
using Recyclarr.Common.Extensions;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.CustomFormat.Processors;
using Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps;
using Spectre.Console;
@ -29,11 +29,11 @@ internal class CustomFormatUpdater : ICustomFormatUpdater
_console = console;
}
public async Task Process(bool isPreview, IEnumerable<CustomFormatConfig> configs, IGuideService guideService)
public async Task Process(bool isPreview, IEnumerable<CustomFormatConfig> configs, SupportedServices serviceType)
{
_cache.Load();
await _guideProcessor.BuildGuideDataAsync(configs, _cache.CfCache, guideService);
await _guideProcessor.BuildGuideDataAsync(configs, _cache.CfCache, serviceType);
if (!ValidateGuideDataAndCheckShouldProceed())
{

@ -0,0 +1,41 @@
using Recyclarr.TrashLib.Config;
using Spectre.Console;
namespace Recyclarr.TrashLib.Services.CustomFormat.Guide;
public class CustomFormatDataLister
{
private readonly IAnsiConsole _console;
private readonly ICustomFormatGuideService _guide;
public CustomFormatDataLister(IAnsiConsole console, ICustomFormatGuideService guide)
{
_console = console;
_guide = guide;
}
public void ListCustomFormats(SupportedServices serviceType)
{
_console.WriteLine("\nList of Custom Formats in the TRaSH Guides:");
var categories = _guide.GetCustomFormatData(serviceType)
.OrderBy(x => x.Name)
.ToLookup(x => x.Category)
.OrderBy(x => x.Key);
foreach (var cat in categories)
{
var title = cat.Key is not null ? $"{cat.Key}" : "[No Category]";
_console.WriteLine($"\n # {title}");
foreach (var cf in cat)
{
_console.WriteLine($" - {cf.TrashId} # {cf.Name}");
}
}
_console.WriteLine(
"\nThe above Custom Formats are in YAML format and ready to be copied & pasted " +
"under the `trash_ids:` property.");
}
}

@ -0,0 +1,46 @@
using System.IO.Abstractions;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
namespace Recyclarr.TrashLib.Services.CustomFormat.Guide;
public class CustomFormatGuideService : ICustomFormatGuideService
{
private readonly IRepoMetadataBuilder _metadataBuilder;
private readonly ICustomFormatLoader _cfLoader;
public CustomFormatGuideService(
IRepoMetadataBuilder metadataBuilder,
ICustomFormatLoader cfLoader)
{
_metadataBuilder = metadataBuilder;
_cfLoader = cfLoader;
}
private CustomFormatPaths CreatePaths(SupportedServices serviceType)
{
var metadata = _metadataBuilder.GetMetadata();
return serviceType switch
{
SupportedServices.Radarr => new CustomFormatPaths(
_metadataBuilder.ToDirectoryInfoList(metadata.JsonPaths.Radarr.CustomFormats),
_metadataBuilder.DocsDirectory.SubDirectory("Radarr").File("Radarr-collection-of-custom-formats.md")
),
SupportedServices.Sonarr => new CustomFormatPaths(
_metadataBuilder.ToDirectoryInfoList(metadata.JsonPaths.Sonarr.CustomFormats),
_metadataBuilder.DocsDirectory.SubDirectory("Sonarr").File("sonarr-collection-of-custom-formats.md")
),
_ => throw new ArgumentOutOfRangeException(nameof(serviceType), serviceType, null)
};
}
public ICollection<CustomFormatData> GetCustomFormatData(SupportedServices serviceType)
{
var paths = CreatePaths(serviceType);
return _cfLoader.LoadAllCustomFormatsAtPaths(
paths.CustomFormatDirectories,
paths.CollectionOfCustomFormatsMarkdown);
}
}

@ -0,0 +1,8 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Services.CustomFormat.Guide;
internal record CustomFormatPaths(
IReadOnlyList<IDirectoryInfo> CustomFormatDirectories,
IFileInfo CollectionOfCustomFormatsMarkdown
);

@ -0,0 +1,9 @@
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
namespace Recyclarr.TrashLib.Services.CustomFormat.Guide;
public interface ICustomFormatGuideService
{
ICollection<CustomFormatData> GetCustomFormatData(SupportedServices serviceType);
}

@ -1,9 +1,9 @@
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Services.Common;
namespace Recyclarr.TrashLib.Services.CustomFormat;
public interface ICustomFormatUpdater
{
Task Process(bool isPreview, IEnumerable<CustomFormatConfig> configs, IGuideService guideService);
Task Process(bool isPreview, IEnumerable<CustomFormatConfig> configs, SupportedServices serviceType);
}

@ -1,5 +1,6 @@
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.CustomFormat.Guide;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache;
using Recyclarr.TrashLib.Services.CustomFormat.Processors.GuideSteps;
@ -17,10 +18,14 @@ internal class GuideProcessor : IGuideProcessor
{
private IList<CustomFormatData>? _guideCustomFormatJson;
private readonly IGuideProcessorSteps _steps;
private readonly ICustomFormatGuideService _guide;
public GuideProcessor(IGuideProcessorSteps steps)
public GuideProcessor(
IGuideProcessorSteps steps,
ICustomFormatGuideService guide)
{
_steps = steps;
_guide = guide;
}
public IReadOnlyCollection<ProcessedCustomFormatData> ProcessedCustomFormats
@ -44,10 +49,12 @@ internal class GuideProcessor : IGuideProcessor
public IReadOnlyCollection<TrashIdMapping> DeletedCustomFormatsInCache
=> _steps.CustomFormat.DeletedCustomFormatsInCache;
public Task BuildGuideDataAsync(IEnumerable<CustomFormatConfig> config, CustomFormatCache? cache,
IGuideService guideService)
public Task BuildGuideDataAsync(
IEnumerable<CustomFormatConfig> config,
CustomFormatCache? cache,
SupportedServices serviceType)
{
_guideCustomFormatJson ??= guideService.GetCustomFormatData().ToList();
_guideCustomFormatJson ??= _guide.GetCustomFormatData(serviceType).ToList();
var listOfConfigs = config.ToList();

@ -1,5 +1,5 @@
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache;
@ -15,6 +15,8 @@ internal interface IGuideProcessor
IReadOnlyCollection<TrashIdMapping> DeletedCustomFormatsInCache { get; }
IReadOnlyDictionary<string, Dictionary<string, HashSet<int>>> DuplicateScores { get; }
Task BuildGuideDataAsync(IEnumerable<CustomFormatConfig> config, CustomFormatCache? cache,
IGuideService guideService);
Task BuildGuideDataAsync(
IEnumerable<CustomFormatConfig> config,
CustomFormatCache? cache,
SupportedServices serviceType);
}

@ -1,6 +1,6 @@
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Services.CustomFormat;
using Recyclarr.TrashLib.Services.QualitySize;
using Recyclarr.TrashLib.Services.Radarr;
using Recyclarr.TrashLib.Services.Radarr.Config;
namespace Recyclarr.TrashLib.Services.Processors;
@ -10,20 +10,17 @@ public class RadarrProcessor : IServiceProcessor
private readonly ILogger _log;
private readonly ICustomFormatUpdater _cfUpdater;
private readonly IQualitySizeUpdater _qualityUpdater;
private readonly RadarrGuideService _guideService;
private readonly RadarrConfiguration _config;
public RadarrProcessor(
ILogger log,
ICustomFormatUpdater cfUpdater,
IQualitySizeUpdater qualityUpdater,
RadarrGuideService guideService,
RadarrConfiguration config)
{
_log = log;
_cfUpdater = cfUpdater;
_qualityUpdater = qualityUpdater;
_guideService = guideService;
_config = config;
}
@ -33,13 +30,13 @@ public class RadarrProcessor : IServiceProcessor
if (_config.QualityDefinition != null)
{
await _qualityUpdater.Process(settings.Preview, _config.QualityDefinition, _guideService);
await _qualityUpdater.Process(settings.Preview, _config.QualityDefinition, SupportedServices.Radarr);
didWork = true;
}
if (_config.CustomFormats.Count > 0)
{
await _cfUpdater.Process(settings.Preview, _config.CustomFormats, _guideService);
await _cfUpdater.Process(settings.Preview, _config.CustomFormats, SupportedServices.Radarr);
didWork = true;
}

@ -1,9 +1,9 @@
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Services.CustomFormat;
using Recyclarr.TrashLib.Services.QualitySize;
using Recyclarr.TrashLib.Services.Sonarr;
using Recyclarr.TrashLib.Services.ReleaseProfile;
using Recyclarr.TrashLib.Services.Sonarr.Capabilities;
using Recyclarr.TrashLib.Services.Sonarr.Config;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
namespace Recyclarr.TrashLib.Services.Processors;
@ -12,7 +12,6 @@ public class SonarrProcessor : IServiceProcessor
private readonly ILogger _log;
private readonly ICustomFormatUpdater _cfUpdater;
private readonly IQualitySizeUpdater _qualityUpdater;
private readonly SonarrGuideService _guideService;
private readonly IReleaseProfileUpdater _profileUpdater;
private readonly SonarrCapabilityEnforcer _compatibilityEnforcer;
private readonly SonarrConfiguration _config;
@ -21,7 +20,6 @@ public class SonarrProcessor : IServiceProcessor
ILogger log,
ICustomFormatUpdater cfUpdater,
IQualitySizeUpdater qualityUpdater,
SonarrGuideService guideService,
IReleaseProfileUpdater profileUpdater,
SonarrCapabilityEnforcer compatibilityEnforcer,
SonarrConfiguration config)
@ -29,7 +27,6 @@ public class SonarrProcessor : IServiceProcessor
_log = log;
_cfUpdater = cfUpdater;
_qualityUpdater = qualityUpdater;
_guideService = guideService;
_profileUpdater = profileUpdater;
_compatibilityEnforcer = compatibilityEnforcer;
_config = config;
@ -50,13 +47,13 @@ public class SonarrProcessor : IServiceProcessor
if (_config.QualityDefinition != null)
{
await _qualityUpdater.Process(settings.Preview, _config.QualityDefinition, _guideService);
await _qualityUpdater.Process(settings.Preview, _config.QualityDefinition, SupportedServices.Sonarr);
didWork = true;
}
if (_config.CustomFormats.Count > 0)
{
await _cfUpdater.Process(settings.Preview, _config.CustomFormats, _guideService);
await _cfUpdater.Process(settings.Preview, _config.CustomFormats, SupportedServices.Sonarr);
didWork = true;
}

@ -0,0 +1,8 @@
using Recyclarr.TrashLib.Config;
namespace Recyclarr.TrashLib.Services.QualitySize.Guide;
public interface IQualityGuideService
{
IReadOnlyList<QualitySizeData> GetQualitySizeData(SupportedServices serviceType);
}

@ -0,0 +1,39 @@
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Repo;
namespace Recyclarr.TrashLib.Services.QualitySize.Guide;
public class QualityGuideService : IQualityGuideService
{
private readonly IRepoMetadataBuilder _metadataBuilder;
private readonly QualitySizeGuideParser _parser;
public QualityGuideService(
IRepoMetadataBuilder metadataBuilder,
QualitySizeGuideParser parser)
{
_metadataBuilder = metadataBuilder;
_parser = parser;
}
private QualitySizePaths CreatePaths(SupportedServices serviceType)
{
var metadata = _metadataBuilder.GetMetadata();
return serviceType switch
{
SupportedServices.Radarr => new QualitySizePaths(
_metadataBuilder.ToDirectoryInfoList(metadata.JsonPaths.Radarr.Qualities)
),
SupportedServices.Sonarr => new QualitySizePaths(
_metadataBuilder.ToDirectoryInfoList(metadata.JsonPaths.Sonarr.Qualities)
),
_ => throw new ArgumentOutOfRangeException(nameof(serviceType), serviceType, null)
};
}
public IReadOnlyList<QualitySizeData> GetQualitySizeData(SupportedServices serviceType)
{
var paths = CreatePaths(serviceType);
return _parser.GetQualities(paths.QualitySizeDirectories);
}
}

@ -0,0 +1,32 @@
using MoreLinq;
using Recyclarr.TrashLib.Config;
using Spectre.Console;
namespace Recyclarr.TrashLib.Services.QualitySize.Guide;
public class QualitySizeDataLister
{
private readonly IAnsiConsole _console;
private readonly IQualityGuideService _guide;
public QualitySizeDataLister(
IAnsiConsole console,
IQualityGuideService guide)
{
_console = console;
_guide = guide;
}
public void ListQualities(SupportedServices serviceType)
{
_console.WriteLine("\nList of Quality Definition types in the TRaSH Guides:\n");
_guide.GetQualitySizeData(serviceType)
.Select(x => x.Type)
.ForEach(x => _console.WriteLine($" - {x}"));
_console.WriteLine(
"\nThe above quality definition types can be used with the `quality_definition:` property in your " +
"recyclarr.yml file.");
}
}

@ -6,7 +6,7 @@ using Recyclarr.Common.Extensions;
namespace Recyclarr.TrashLib.Services.QualitySize.Guide;
internal class QualitySizeGuideParser<T> where T : class
public class QualitySizeGuideParser
{
private readonly ILogger _log;
@ -15,7 +15,7 @@ internal class QualitySizeGuideParser<T> where T : class
_log = log;
}
public ICollection<T> GetQualities(IEnumerable<IDirectoryInfo> jsonDirectories)
public IReadOnlyList<QualitySizeData> GetQualities(IEnumerable<IDirectoryInfo> jsonDirectories)
{
return JsonUtils.GetJsonFilesInDirectories(jsonDirectories, _log)
.Select(ParseQuality)
@ -23,7 +23,7 @@ internal class QualitySizeGuideParser<T> where T : class
.ToList();
}
private T? ParseQuality(IFileInfo jsonFile)
private QualitySizeData? ParseQuality(IFileInfo jsonFile)
{
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{
@ -34,7 +34,7 @@ internal class QualitySizeGuideParser<T> where T : class
});
using var json = new JsonTextReader(jsonFile.OpenText());
var quality = serializer.Deserialize<T>(json);
var quality = serializer.Deserialize<QualitySizeData>(json);
if (quality is null)
{
_log.Debug("Failed to parse quality definition JSON file: {Filename}", jsonFile.FullName);

@ -0,0 +1,7 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Services.QualitySize.Guide;
internal record QualitySizePaths(
IReadOnlyCollection<IDirectoryInfo> QualitySizeDirectories
);

@ -1,9 +1,9 @@
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Services.Common;
namespace Recyclarr.TrashLib.Services.QualitySize;
public interface IQualitySizeUpdater
{
Task Process(bool isPreview, QualityDefinitionConfig config, IGuideService guideService);
Task Process(bool isPreview, QualityDefinitionConfig config, SupportedServices serviceType);
}

@ -0,0 +1,16 @@
using Autofac;
using Recyclarr.TrashLib.Services.QualitySize.Api;
using Recyclarr.TrashLib.Services.QualitySize.Guide;
namespace Recyclarr.TrashLib.Services.QualitySize;
public class QualitySizeAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<QualityDefinitionService>().As<IQualityDefinitionService>();
builder.RegisterType<QualitySizeUpdater>().As<IQualitySizeUpdater>();
builder.RegisterType<QualityGuideService>().As<IQualityGuideService>();
builder.RegisterType<QualitySizeGuideParser>();
}
}

@ -1,7 +1,8 @@
using Recyclarr.Common.Extensions;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Config.Services;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.QualitySize.Api;
using Recyclarr.TrashLib.Services.QualitySize.Guide;
using Spectre.Console;
namespace Recyclarr.TrashLib.Services.QualitySize;
@ -11,21 +12,24 @@ internal class QualitySizeUpdater : IQualitySizeUpdater
private readonly ILogger _log;
private readonly IQualityDefinitionService _api;
private readonly IAnsiConsole _console;
private readonly IQualityGuideService _guide;
public QualitySizeUpdater(
ILogger logger,
IQualityDefinitionService api,
IAnsiConsole console)
IAnsiConsole console,
IQualityGuideService guide)
{
_log = logger;
_api = api;
_console = console;
_guide = guide;
}
public async Task Process(bool isPreview, QualityDefinitionConfig config, IGuideService guideService)
public async Task Process(bool isPreview, QualityDefinitionConfig config, SupportedServices serviceType)
{
_log.Information("Processing Quality Definition: {QualityDefinition}", config.Type);
var qualityDefinitions = guideService.GetQualities();
var qualityDefinitions = _guide.GetQualitySizeData(serviceType);
var qualityTypeInConfig = config.Type;
var selectedQuality = qualityDefinitions

@ -1,7 +0,0 @@
namespace Recyclarr.TrashLib.Services.Radarr;
public interface IRadarrGuideDataLister
{
void ListCustomFormats();
void ListQualities();
}

@ -1,34 +0,0 @@
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Services.CustomFormat.Guide;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.QualitySize;
using Recyclarr.TrashLib.Services.QualitySize.Guide;
namespace Recyclarr.TrashLib.Services.Radarr;
public class LocalRepoRadarrGuideService : RadarrGuideService
{
private readonly IRepoPathsFactory _pathsFactory;
private readonly ICustomFormatLoader _cfLoader;
private readonly QualitySizeGuideParser<QualitySizeData> _parser;
public LocalRepoRadarrGuideService(IRepoPathsFactory pathsFactory, ILogger log, ICustomFormatLoader cfLoader)
{
_pathsFactory = pathsFactory;
_cfLoader = cfLoader;
_parser = new QualitySizeGuideParser<QualitySizeData>(log);
}
public override ICollection<QualitySizeData> GetQualities()
{
return _parser.GetQualities(_pathsFactory.Create().RadarrQualityPaths);
}
public override ICollection<CustomFormatData> GetCustomFormatData()
{
var paths = _pathsFactory.Create();
return _cfLoader.LoadAllCustomFormatsAtPaths(
paths.RadarrCustomFormatPaths,
paths.RadarrCollectionOfCustomFormats);
}
}

@ -1,8 +1,4 @@
using Autofac;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.QualitySize;
using Recyclarr.TrashLib.Services.QualitySize.Api;
namespace Recyclarr.TrashLib.Services.Radarr;
@ -10,14 +6,6 @@ public class RadarrAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<QualityDefinitionService>().As<IQualityDefinitionService>();
builder.RegisterType<RadarrGuideDataLister>().As<IRadarrGuideDataLister>();
builder.RegisterType<QualitySizeUpdater>().As<IQualitySizeUpdater>();
builder.RegisterType<LocalRepoRadarrGuideService>()
.As<RadarrGuideService>()
.Keyed<IGuideService>(SupportedServices.Radarr);
builder.RegisterType<RadarrCapabilityChecker>().As<IRadarrCapabilityChecker>()
.InstancePerLifetimeScope();
}

@ -1,29 +0,0 @@
using JetBrains.Annotations;
using Recyclarr.TrashLib.Services.Common;
namespace Recyclarr.TrashLib.Services.Radarr;
[UsedImplicitly]
public class RadarrGuideDataLister : IRadarrGuideDataLister
{
private readonly RadarrGuideService _guide;
private readonly IGuideDataLister _guideLister;
public RadarrGuideDataLister(
RadarrGuideService guide,
IGuideDataLister guideLister)
{
_guide = guide;
_guideLister = guideLister;
}
public void ListCustomFormats()
{
_guideLister.ListCustomFormats(_guide.GetCustomFormatData());
}
public void ListQualities()
{
_guideLister.ListQualities(_guide.GetQualities());
}
}

@ -1,11 +0,0 @@
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.QualitySize;
namespace Recyclarr.TrashLib.Services.Radarr;
public abstract class RadarrGuideService : IGuideService
{
public abstract ICollection<CustomFormatData> GetCustomFormatData();
public abstract ICollection<QualitySizeData> GetQualities();
}

@ -1,6 +1,6 @@
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects;
namespace Recyclarr.TrashLib.Services.Sonarr.Api;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Api;
public interface IReleaseProfileApiService
{

@ -1,7 +1,7 @@
using Newtonsoft.Json.Linq;
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects;
namespace Recyclarr.TrashLib.Services.Sonarr.Api;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Api;
public interface ISonarrReleaseProfileCompatibilityHandler
{

@ -1,8 +1,8 @@
using AutoMapper;
using JetBrains.Annotations;
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects;
namespace Recyclarr.TrashLib.Services.Sonarr.Api.Mappings;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Api.Mappings;
[UsedImplicitly]
public class SonarrApiObjectMappingProfile : Profile

@ -1,7 +1,7 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects;
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
public class SonarrPreferredTerm

@ -1,9 +1,9 @@
using Flurl.Http;
using Newtonsoft.Json.Linq;
using Recyclarr.TrashLib.Http;
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects;
namespace Recyclarr.TrashLib.Services.Sonarr.Api;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Api;
public class ReleaseProfileApiService : IReleaseProfileApiService
{

@ -1,4 +1,4 @@
namespace Recyclarr.TrashLib.Services.Sonarr.Api.Schemas;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Api.Schemas;
public static class SonarrReleaseProfileSchema
{

@ -2,11 +2,11 @@ using AutoMapper;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using Recyclarr.TrashLib.ExceptionTypes;
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
using Recyclarr.TrashLib.Services.Sonarr.Api.Schemas;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Schemas;
using Recyclarr.TrashLib.Services.Sonarr.Capabilities;
namespace Recyclarr.TrashLib.Services.Sonarr.Api;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Api;
public class SonarrReleaseProfileCompatibilityHandler : ISonarrReleaseProfileCompatibilityHandler
{

@ -1,6 +1,6 @@
using Recyclarr.TrashLib.Services.Sonarr.Config;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
public interface IReleaseProfileFilter
{

@ -1,6 +1,6 @@
using Recyclarr.TrashLib.Services.Sonarr.Config;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
public interface IReleaseProfileFilterPipeline
{

@ -1,6 +1,6 @@
using Recyclarr.TrashLib.Services.Sonarr.Config;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
public class IncludeExcludeFilter : IReleaseProfileFilter
{

@ -1,7 +1,7 @@
using System.Collections.ObjectModel;
using Recyclarr.TrashLib.Services.Sonarr.Config;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
public class ReleaseProfileDataFilterer
{

@ -1,7 +1,7 @@
using FluentValidation.Results;
using Recyclarr.Common.FluentValidation;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
public class ReleaseProfileDataValidationFilterer
{

@ -1,6 +1,6 @@
using Recyclarr.TrashLib.Services.Sonarr.Config;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
public class ReleaseProfileFilterPipeline : IReleaseProfileFilterPipeline
{

@ -1,6 +1,6 @@
using Recyclarr.TrashLib.Services.Sonarr.Config;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
public class StrictNegativeScoresFilter : IReleaseProfileFilter
{

@ -0,0 +1,6 @@
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
public interface IReleaseProfileGuideService
{
IReadOnlyList<ReleaseProfileData> GetReleaseProfileData();
}

@ -1,36 +1,18 @@
using System.Text;
using JetBrains.Annotations;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.Common.Extensions;
using Spectre.Console;
namespace Recyclarr.TrashLib.Services.Sonarr;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
[UsedImplicitly]
public class SonarrGuideDataLister : ISonarrGuideDataLister
public class ReleaseProfileDataLister
{
private readonly IAnsiConsole _console;
private readonly SonarrGuideService _guide;
private readonly IGuideDataLister _guideLister;
private readonly IReleaseProfileGuideService _guide;
public SonarrGuideDataLister(
IAnsiConsole console,
SonarrGuideService guide,
IGuideDataLister guideLister)
public ReleaseProfileDataLister(IAnsiConsole console, IReleaseProfileGuideService guide)
{
_console = console;
_guide = guide;
_guideLister = guideLister;
}
public void ListCustomFormats()
{
_guideLister.ListCustomFormats(_guide.GetCustomFormatData());
}
public void ListQualities()
{
_guideLister.ListQualities(_guide.GetQualities());
}
public void ListReleaseProfiles()
@ -62,7 +44,9 @@ public class SonarrGuideDataLister : ISonarrGuideDataLister
public void ListTerms(string releaseProfileId)
{
var profile = _guide.GetUnfilteredProfileById(releaseProfileId);
var profile = _guide.GetReleaseProfileData()
.FirstOrDefault(x => x.TrashId.EqualsIgnoreCase(releaseProfileId));
if (profile is null)
{
throw new ArgumentException("No release profile found with that Trash ID");

@ -0,0 +1,59 @@
using System.IO.Abstractions;
using MoreLinq;
using Newtonsoft.Json;
using Recyclarr.Common;
using Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
using Serilog;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
public class ReleaseProfileGuideParser
{
private readonly ILogger _log;
public ReleaseProfileGuideParser(ILogger log)
{
_log = log;
}
private async Task<ReleaseProfileData?> LoadAndParseFile(IFileInfo file, params JsonConverter[] converters)
{
try
{
using var stream = file.OpenText();
var json = await stream.ReadToEndAsync();
return JsonConvert.DeserializeObject<ReleaseProfileData>(json, converters);
}
catch (JsonException e)
{
HandleJsonException(e, file);
}
catch (AggregateException ae) when (ae.InnerException is JsonException e)
{
HandleJsonException(e, file);
}
return null;
}
private void HandleJsonException(JsonException exception, IFileInfo file)
{
_log.Warning(exception,
"Failed to parse Sonarr JSON file (This likely indicates a bug that should be " +
"reported in the TRaSH repo): {File}", file.Name);
}
public IEnumerable<ReleaseProfileData> GetReleaseProfileData(IEnumerable<IDirectoryInfo> paths)
{
var converter = new TermDataConverter();
var tasks = JsonUtils.GetJsonFilesInDirectories(paths, _log)
.Select(x => LoadAndParseFile(x, converter));
var data = Task.WhenAll(tasks).Result
// Make non-nullable type and filter out null values
.Choose(x => x is not null ? (true, x) : default);
var validator = new ReleaseProfileDataValidationFilterer(_log);
return validator.FilterProfiles(data);
}
}

@ -0,0 +1,31 @@
using Recyclarr.TrashLib.Repo;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
public class ReleaseProfileGuideService : IReleaseProfileGuideService
{
private readonly IRepoMetadataBuilder _metadataBuilder;
private readonly ReleaseProfileGuideParser _parser;
public ReleaseProfileGuideService(
IRepoMetadataBuilder metadataBuilder,
ReleaseProfileGuideParser parser)
{
_metadataBuilder = metadataBuilder;
_parser = parser;
}
private ReleaseProfilePaths GetPaths()
{
var metadata = _metadataBuilder.GetMetadata();
return new ReleaseProfilePaths(
_metadataBuilder.ToDirectoryInfoList(metadata.JsonPaths.Sonarr.ReleaseProfiles)
);
}
public IReadOnlyList<ReleaseProfileData> GetReleaseProfileData()
{
var paths = GetPaths();
return _parser.GetReleaseProfileData(paths.ReleaseProfileDirectories).ToList();
}
}

@ -0,0 +1,7 @@
using System.IO.Abstractions;
namespace Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
public record ReleaseProfilePaths(
IReadOnlyList<IDirectoryInfo> ReleaseProfileDirectories
);

@ -1,6 +1,6 @@
using Recyclarr.TrashLib.Services.Sonarr.Config;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
namespace Recyclarr.TrashLib.Services.ReleaseProfile;
public interface IReleaseProfileUpdater
{

@ -0,0 +1,31 @@
using Autofac;
using Autofac.Extras.Ordering;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api;
using Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
using Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
namespace Recyclarr.TrashLib.Services.ReleaseProfile;
public class ReleaseProfileAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<ReleaseProfileApiService>().As<IReleaseProfileApiService>();
builder.RegisterType<ReleaseProfileUpdater>().As<IReleaseProfileUpdater>();
builder.RegisterType<SonarrReleaseProfileCompatibilityHandler>()
.As<ISonarrReleaseProfileCompatibilityHandler>();
builder.RegisterType<ReleaseProfileFilterPipeline>().As<IReleaseProfileFilterPipeline>();
builder.RegisterType<ReleaseProfileGuideParser>();
builder.RegisterType<ReleaseProfileGuideService>().As<IReleaseProfileGuideService>();
// Release Profile Filters (ORDER MATTERS!)
builder.RegisterTypes(
typeof(IncludeExcludeFilter),
typeof(StrictNegativeScoresFilter))
.As<IReleaseProfileFilter>()
.OrderByRegistration();
}
}

@ -1,7 +1,7 @@
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
namespace Recyclarr.TrashLib.Services.ReleaseProfile;
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public record TermData
@ -11,11 +11,6 @@ public record TermData
public string Name { get; init; } = string.Empty;
public string Term { get; init; } = string.Empty;
public override string ToString()
{
return $"[TrashId: {TrashId}] [Name: {Name}] [Term: {Term}]";
}
}
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
@ -29,11 +24,6 @@ public record PreferredTermData
score = Score;
terms = Terms;
}
public override string ToString()
{
return $"[Score: {Score}] [Terms: {Terms.Count}]";
}
}
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
@ -47,14 +37,4 @@ public record ReleaseProfileData
public IReadOnlyCollection<TermData> Required { get; init; } = Array.Empty<TermData>();
public IReadOnlyCollection<TermData> Ignored { get; init; } = Array.Empty<TermData>();
public IReadOnlyCollection<PreferredTermData> Preferred { get; init; } = Array.Empty<PreferredTermData>();
public override string ToString()
{
return $"[TrashId: {TrashId}] " +
$"[Name: {Name}] " +
$"[IncludePreferred: {IncludePreferredWhenRenaming}] " +
$"[Required: {Required.Count}] " +
$"[Ignored: {Ignored.Count}] " +
$"[Preferred: {Preferred.Count}]";
}
}

@ -1,6 +1,6 @@
using FluentValidation;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
namespace Recyclarr.TrashLib.Services.ReleaseProfile;
internal class TermDataValidator : AbstractValidator<TermData>
{

@ -1,32 +1,35 @@
using Recyclarr.Common.Extensions;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api;
using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects;
using Recyclarr.TrashLib.Services.ReleaseProfile.Filters;
using Recyclarr.TrashLib.Services.ReleaseProfile.Guide;
using Recyclarr.TrashLib.Services.Sonarr.Api;
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
using Recyclarr.TrashLib.Services.Sonarr.Config;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
using Spectre.Console;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
namespace Recyclarr.TrashLib.Services.ReleaseProfile;
public class ReleaseProfileUpdater : IReleaseProfileUpdater
{
private readonly IReleaseProfileApiService _releaseProfileApi;
private readonly IReleaseProfileFilterPipeline _pipeline;
private readonly IAnsiConsole _console;
private readonly SonarrGuideService _guide;
private readonly ISonarrApi _api;
private readonly IReleaseProfileGuideService _guide;
private readonly ISonarrTagApiService _tagApiService;
private readonly ILogger _log;
public ReleaseProfileUpdater(
ILogger logger,
SonarrGuideService guide,
ISonarrApi api,
IReleaseProfileGuideService guide,
ISonarrTagApiService tagApiService,
IReleaseProfileApiService releaseProfileApi,
IReleaseProfileFilterPipeline pipeline,
IAnsiConsole console)
{
_log = logger;
_guide = guide;
_api = api;
_tagApiService = tagApiService;
_releaseProfileApi = releaseProfileApi;
_pipeline = pipeline;
_console = console;
@ -195,7 +198,7 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater
return Array.Empty<int>();
}
var sonarrTags = await _api.GetTags();
var sonarrTags = await _tagApiService.GetTags();
await CreateMissingTags(sonarrTags, tags);
return sonarrTags
.Where(t => tags.Any(ct => ct.EqualsIgnoreCase(t.Label)))
@ -209,7 +212,7 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater
foreach (var tag in missingTags)
{
_log.Debug("Creating Tag: {Tag}", tag);
var newTag = await _api.CreateTag(tag);
var newTag = await _tagApiService.CreateTag(tag);
sonarrTags.Add(newTag);
}
}

@ -1,4 +1,4 @@
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
namespace Recyclarr.TrashLib.Services.ReleaseProfile;
public class ScopedState<T>
{

@ -1,7 +1,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
namespace Recyclarr.TrashLib.Services.ReleaseProfile;
internal class TermDataConverter : JsonConverter
{

@ -1,13 +0,0 @@
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
namespace Recyclarr.TrashLib.Services.Sonarr.Api;
public interface ISonarrApi
{
Task<IList<SonarrTag>> GetTags();
Task<SonarrTag> CreateTag(string tag);
Task<IReadOnlyCollection<SonarrQualityDefinitionItem>> GetQualityDefinition();
Task<IList<SonarrQualityDefinitionItem>> UpdateQualityDefinition(
IReadOnlyCollection<SonarrQualityDefinitionItem> newQuality);
}

@ -0,0 +1,9 @@
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
namespace Recyclarr.TrashLib.Services.Sonarr.Api;
public interface ISonarrTagApiService
{
Task<IList<SonarrTag>> GetTags();
Task<SonarrTag> CreateTag(string tag);
}

@ -1,23 +0,0 @@
using JetBrains.Annotations;
namespace Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class SonarrQualityItem
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Source { get; set; } = "";
public int Resolution { get; set; }
}
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class SonarrQualityDefinitionItem
{
public int Id { get; set; }
public SonarrQualityItem? Quality { get; set; }
public string Title { get; set; } = "";
public int Weight { get; set; }
public decimal MinSize { get; set; }
public decimal? MaxSize { get; set; }
}

@ -1,42 +0,0 @@
using Flurl.Http;
using Recyclarr.TrashLib.Http;
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
namespace Recyclarr.TrashLib.Services.Sonarr.Api;
public class SonarrApi : ISonarrApi
{
private readonly IServiceRequestBuilder _service;
public SonarrApi(IServiceRequestBuilder service)
{
_service = service;
}
public async Task<IList<SonarrTag>> GetTags()
{
return await _service.Request("tag")
.GetJsonAsync<List<SonarrTag>>();
}
public async Task<SonarrTag> CreateTag(string tag)
{
return await _service.Request("tag")
.PostJsonAsync(new {label = tag})
.ReceiveJson<SonarrTag>();
}
public async Task<IReadOnlyCollection<SonarrQualityDefinitionItem>> GetQualityDefinition()
{
return await _service.Request("qualitydefinition")
.GetJsonAsync<List<SonarrQualityDefinitionItem>>();
}
public async Task<IList<SonarrQualityDefinitionItem>> UpdateQualityDefinition(
IReadOnlyCollection<SonarrQualityDefinitionItem> newQuality)
{
return await _service.Request("qualityDefinition", "update")
.PutJsonAsync(newQuality)
.ReceiveJson<List<SonarrQualityDefinitionItem>>();
}
}

@ -0,0 +1,28 @@
using Flurl.Http;
using Recyclarr.TrashLib.Http;
using Recyclarr.TrashLib.Services.Sonarr.Api.Objects;
namespace Recyclarr.TrashLib.Services.Sonarr.Api;
public class SonarrTagApiService : ISonarrTagApiService
{
private readonly IServiceRequestBuilder _service;
public SonarrTagApiService(IServiceRequestBuilder service)
{
_service = service;
}
public async Task<IList<SonarrTag>> GetTags()
{
return await _service.Request("tag")
.GetJsonAsync<List<SonarrTag>>();
}
public async Task<SonarrTag> CreateTag(string tag)
{
return await _service.Request("tag")
.PostJsonAsync(new {label = tag})
.ReceiveJson<SonarrTag>();
}
}

@ -1,9 +0,0 @@
namespace Recyclarr.TrashLib.Services.Sonarr;
public interface ISonarrGuideDataLister
{
void ListReleaseProfiles();
void ListTerms(string releaseProfileId);
void ListQualities();
void ListCustomFormats();
}

@ -1,100 +0,0 @@
using System.IO.Abstractions;
using MoreLinq;
using Newtonsoft.Json;
using Recyclarr.Common;
using Recyclarr.Common.Extensions;
using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Services.CustomFormat.Guide;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.QualitySize;
using Recyclarr.TrashLib.Services.QualitySize.Guide;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Services.Sonarr;
public class LocalRepoSonarrGuideService : SonarrGuideService
{
private readonly IRepoPathsFactory _pathsFactory;
private readonly ILogger _log;
private readonly ICustomFormatLoader _cfLoader;
private readonly Lazy<IEnumerable<ReleaseProfileData>> _data;
private readonly QualitySizeGuideParser<QualitySizeData> _parser;
public LocalRepoSonarrGuideService(
IRepoPathsFactory pathsFactory,
ILogger log,
ICustomFormatLoader cfLoader)
{
_pathsFactory = pathsFactory;
_log = log;
_cfLoader = cfLoader;
_data = new Lazy<IEnumerable<ReleaseProfileData>>(GetReleaseProfileDataImpl);
_parser = new QualitySizeGuideParser<QualitySizeData>(log);
}
public override ICollection<QualitySizeData> GetQualities()
{
return _parser.GetQualities(_pathsFactory.Create().SonarrQualityPaths);
}
public override ICollection<CustomFormatData> GetCustomFormatData()
{
var paths = _pathsFactory.Create();
return _cfLoader.LoadAllCustomFormatsAtPaths(
paths.SonarrCustomFormatPaths,
paths.SonarrCollectionOfCustomFormats);
}
private IEnumerable<ReleaseProfileData> GetReleaseProfileDataImpl()
{
var converter = new TermDataConverter();
var paths = _pathsFactory.Create();
var tasks = JsonUtils.GetJsonFilesInDirectories(paths.SonarrReleaseProfilePaths, _log)
.Select(x => LoadAndParseFile(x, converter));
var data = Task.WhenAll(tasks).Result
// Make non-nullable type and filter out null values
.Choose(x => x is not null ? (true, x) : default);
var validator = new ReleaseProfileDataValidationFilterer(_log);
return validator.FilterProfiles(data);
}
private async Task<ReleaseProfileData?> LoadAndParseFile(IFileInfo file, params JsonConverter[] converters)
{
try
{
using var stream = file.OpenText();
var json = await stream.ReadToEndAsync();
return JsonConvert.DeserializeObject<ReleaseProfileData>(json, converters);
}
catch (JsonException e)
{
HandleJsonException(e, file);
}
catch (AggregateException ae) when (ae.InnerException is JsonException e)
{
HandleJsonException(e, file);
}
return null;
}
private void HandleJsonException(JsonException exception, IFileInfo file)
{
_log.Warning(exception,
"Failed to parse Sonarr JSON file (This likely indicates a bug that should be " +
"reported in the TRaSH repo): {File}", file.Name);
}
public override ReleaseProfileData? GetUnfilteredProfileById(string trashId)
{
return _data.Value.FirstOrDefault(x => x.TrashId.EqualsIgnoreCase(trashId));
}
public override IReadOnlyCollection<ReleaseProfileData> GetReleaseProfileData()
{
return _data.Value.ToList();
}
}

@ -1,11 +1,6 @@
using Autofac;
using Autofac.Extras.Ordering;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.Sonarr.Api;
using Recyclarr.TrashLib.Services.Sonarr.Capabilities;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile.Filters;
namespace Recyclarr.TrashLib.Services.Sonarr;
@ -13,29 +8,9 @@ public class SonarrAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<SonarrApi>().As<ISonarrApi>();
builder.RegisterType<ReleaseProfileApiService>().As<IReleaseProfileApiService>();
builder.RegisterType<SonarrTagApiService>().As<ISonarrTagApiService>();
builder.RegisterType<SonarrCapabilityEnforcer>();
builder.RegisterType<SonarrCapabilityChecker>().As<ISonarrCapabilityChecker>()
.InstancePerLifetimeScope();
builder.RegisterType<SonarrGuideDataLister>().As<ISonarrGuideDataLister>();
builder.RegisterType<LocalRepoSonarrGuideService>()
.As<SonarrGuideService>()
.Keyed<IGuideService>(SupportedServices.Sonarr);
// Release Profile Support
builder.RegisterType<ReleaseProfileUpdater>().As<IReleaseProfileUpdater>();
builder.RegisterType<SonarrReleaseProfileCompatibilityHandler>()
.As<ISonarrReleaseProfileCompatibilityHandler>();
builder.RegisterType<ReleaseProfileFilterPipeline>().As<IReleaseProfileFilterPipeline>();
// Release Profile Filters (ORDER MATTERS!)
builder.RegisterTypes(
typeof(IncludeExcludeFilter),
typeof(StrictNegativeScoresFilter))
.As<IReleaseProfileFilter>()
.OrderByRegistration();
}
}

@ -1,14 +0,0 @@
using Recyclarr.TrashLib.Services.Common;
using Recyclarr.TrashLib.Services.CustomFormat.Models;
using Recyclarr.TrashLib.Services.QualitySize;
using Recyclarr.TrashLib.Services.Sonarr.ReleaseProfile;
namespace Recyclarr.TrashLib.Services.Sonarr;
public abstract class SonarrGuideService : IGuideService
{
public abstract IReadOnlyCollection<ReleaseProfileData> GetReleaseProfileData();
public abstract ReleaseProfileData? GetUnfilteredProfileById(string trashId);
public abstract ICollection<CustomFormatData> GetCustomFormatData();
public abstract ICollection<QualitySizeData> GetQualities();
}
Loading…
Cancel
Save