refactor: Make release profile sync pipeline generic

pull/231/head
Robert Dailey 11 months ago
parent 5c27c6bf56
commit 647e0280ec

@ -84,7 +84,7 @@ public static class CompositionRoot
typeof(CustomFormatSyncPipeline), typeof(CustomFormatSyncPipeline),
typeof(QualityProfileSyncPipeline), typeof(QualityProfileSyncPipeline),
typeof(QualitySizeSyncPipeline), typeof(QualitySizeSyncPipeline),
typeof(ReleaseProfileSyncPipeline), typeof(GenericSyncPipeline<ReleaseProfilePipelineContext>),
typeof(GenericSyncPipeline<MediaNamingPipelineContext>)) typeof(GenericSyncPipeline<MediaNamingPipelineContext>))
.As<ISyncPipeline>() .As<ISyncPipeline>()
.OrderByRegistration(); .OrderByRegistration();

@ -1,12 +1,14 @@
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Config.Models; using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.ReleaseProfile; using Recyclarr.ServarrApi.ReleaseProfile;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
public class ReleaseProfileApiFetchPhase(IReleaseProfileApiService rpService) public class ReleaseProfileApiFetchPhase(IReleaseProfileApiService rpService)
: IApiFetchPipelinePhase<ReleaseProfilePipelineContext>
{ {
public async Task<IList<SonarrReleaseProfile>> Execute(IServiceConfiguration config) public async Task Execute(ReleaseProfilePipelineContext context, IServiceConfiguration config)
{ {
return await rpService.GetReleaseProfiles(config); context.ApiFetchOutput = await rpService.GetReleaseProfiles(config);
} }
} }

@ -1,28 +1,28 @@
using Recyclarr.Cli.Pipelines.ReleaseProfile.Models; using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Config.Models; using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.ReleaseProfile; using Recyclarr.ServarrApi.ReleaseProfile;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
public class ReleaseProfileApiPersistencePhase(ILogger log, IReleaseProfileApiService api) public class ReleaseProfileApiPersistencePhase(IReleaseProfileApiService api)
: IApiPersistencePipelinePhase<ReleaseProfilePipelineContext>
{ {
public async Task Execute(IServiceConfiguration config, ReleaseProfileTransactionData transactions) public async Task Execute(ReleaseProfilePipelineContext context, IServiceConfiguration config)
{ {
var transactions = context.TransactionOutput;
foreach (var profile in transactions.UpdatedProfiles) foreach (var profile in transactions.UpdatedProfiles)
{ {
log.Information("Update existing profile: {ProfileName}", profile.Name);
await api.UpdateReleaseProfile(config, profile); await api.UpdateReleaseProfile(config, profile);
} }
foreach (var profile in transactions.CreatedProfiles) foreach (var profile in transactions.CreatedProfiles)
{ {
log.Information("Create new profile: {ProfileName}", profile.Name);
await api.CreateReleaseProfile(config, profile); await api.CreateReleaseProfile(config, profile);
} }
foreach (var profile in transactions.DeletedProfiles) foreach (var profile in transactions.DeletedProfiles)
{ {
log.Information("Deleting old release profile: {ProfileName}", profile.Name);
await api.DeleteReleaseProfile(config, profile.Id); await api.DeleteReleaseProfile(config, profile.Id);
} }
} }

@ -1,3 +1,4 @@
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; using Recyclarr.Cli.Pipelines.ReleaseProfile.Filters;
using Recyclarr.Common.Extensions; using Recyclarr.Common.Extensions;
using Recyclarr.Config.Models; using Recyclarr.Config.Models;
@ -14,19 +15,21 @@ public class ReleaseProfileConfigPhase(
ILogger log, ILogger log,
IReleaseProfileGuideService guide, IReleaseProfileGuideService guide,
IReleaseProfileFilterPipeline filters) IReleaseProfileFilterPipeline filters)
: IConfigPipelinePhase<ReleaseProfilePipelineContext>
{ {
public IReadOnlyList<ProcessedReleaseProfileData>? Execute(SonarrConfiguration config) public Task Execute(ReleaseProfilePipelineContext context, IServiceConfiguration config)
{ {
if (config.ReleaseProfiles.IsEmpty()) var releaseProfiles = ((SonarrConfiguration) config).ReleaseProfiles;
if (!releaseProfiles.Any())
{ {
log.Debug("{Instance} has no release profiles", config.InstanceName); log.Debug("{Instance} has no release profiles", config.InstanceName);
return null; return Task.CompletedTask;
} }
var profilesFromGuide = guide.GetReleaseProfileData(); var profilesFromGuide = guide.GetReleaseProfileData();
var filteredProfiles = new List<ProcessedReleaseProfileData>(); var filteredProfiles = new List<ProcessedReleaseProfileData>();
var configProfiles = config.ReleaseProfiles.SelectMany(x => x.TrashIds.Select(y => (TrashId: y, Config: x))); var configProfiles = releaseProfiles.SelectMany(x => x.TrashIds.Select(y => (TrashId: y, Config: x)));
foreach (var (trashId, configProfile) in configProfiles) foreach (var (trashId, configProfile) in configProfiles)
{ {
// For each release profile specified in our YAML config, find the matching profile in the guide. // For each release profile specified in our YAML config, find the matching profile in the guide.
@ -44,6 +47,7 @@ public class ReleaseProfileConfigPhase(
filteredProfiles.Add(new ProcessedReleaseProfileData(selectedProfile, configProfile.Tags)); filteredProfiles.Add(new ProcessedReleaseProfileData(selectedProfile, configProfile.Tags));
} }
return filteredProfiles; context.ConfigOutput = filteredProfiles;
return Task.CompletedTask;
} }
} }

@ -0,0 +1,46 @@
using Recyclarr.Cli.Pipelines.Generic;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
public class ReleaseProfileLogPhase(ILogger log) : ILogPipelinePhase<ReleaseProfilePipelineContext>
{
public bool LogConfigPhaseAndExitIfNeeded(ReleaseProfilePipelineContext context)
{
if (context.ConfigOutput.Any())
{
return false;
}
log.Debug("No Release Profiles to process");
return true;
}
public void LogPersistenceResults(ReleaseProfilePipelineContext context)
{
var transactions = context.TransactionOutput;
var somethingChanged = false;
if (transactions.UpdatedProfiles.Count != 0)
{
log.Information("Update existing profiles: {ProfileNames}", transactions.UpdatedProfiles);
somethingChanged = true;
}
if (transactions.CreatedProfiles.Count != 0)
{
log.Information("Create new profiles: {ProfileNames}", transactions.CreatedProfiles);
somethingChanged = true;
}
if (transactions.DeletedProfiles.Count != 0)
{
log.Information("Deleting old release profiles: {ProfileNames}", transactions.DeletedProfiles);
somethingChanged = true;
}
if (!somethingChanged)
{
log.Information("All Release Profiles are up to date!");
}
}
}

@ -1,13 +1,15 @@
using Recyclarr.Cli.Pipelines.ReleaseProfile.Models; using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.ServarrApi.ReleaseProfile; using Recyclarr.ServarrApi.ReleaseProfile;
using Spectre.Console; using Spectre.Console;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
public class ReleaseProfilePreviewPhase(IAnsiConsole console) public class ReleaseProfilePreviewPhase(IAnsiConsole console) : IPreviewPipelinePhase<ReleaseProfilePipelineContext>
{ {
public void Execute(ReleaseProfileTransactionData profiles) public void Execute(ReleaseProfilePipelineContext context)
{ {
var profiles = context.TransactionOutput;
var tree = new Tree("Release Profiles [red](Preview)[/]"); var tree = new Tree("Release Profiles [red](Preview)[/]");
PrintCategoryOfChanges("Created Profiles", tree, profiles.CreatedProfiles); PrintCategoryOfChanges("Created Profiles", tree, profiles.CreatedProfiles);

@ -1,3 +1,4 @@
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Models; using Recyclarr.Cli.Pipelines.ReleaseProfile.Models;
using Recyclarr.Cli.Pipelines.Tags; using Recyclarr.Cli.Pipelines.Tags;
using Recyclarr.Common.Extensions; using Recyclarr.Common.Extensions;
@ -6,18 +7,17 @@ using Recyclarr.ServarrApi.ReleaseProfile;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
public class ReleaseProfileTransactionPhase(ServiceTagCache tagCache) public class ReleaseProfileTransactionPhase(ServiceTagCache tagCache)
: ITransactionPipelinePhase<ReleaseProfilePipelineContext>
{ {
public ReleaseProfileTransactionData Execute( public void Execute(ReleaseProfilePipelineContext context)
IReadOnlyList<ProcessedReleaseProfileData> configProfiles,
IList<SonarrReleaseProfile> serviceData)
{ {
var created = new List<SonarrReleaseProfile>(); var created = new List<SonarrReleaseProfile>();
var updated = new List<SonarrReleaseProfile>(); var updated = new List<SonarrReleaseProfile>();
foreach (var configProfile in configProfiles) foreach (var configProfile in context.ConfigOutput)
{ {
var title = $"[Trash] {configProfile.Profile.Name}"; var title = $"[Trash] {configProfile.Profile.Name}";
var matchingServiceProfile = serviceData.FirstOrDefault(x => x.Name.EqualsIgnoreCase(title)); var matchingServiceProfile = context.ApiFetchOutput.FirstOrDefault(x => x.Name.EqualsIgnoreCase(title));
if (matchingServiceProfile is not null) if (matchingServiceProfile is not null)
{ {
SetupProfileRequestObject(matchingServiceProfile, configProfile); SetupProfileRequestObject(matchingServiceProfile, configProfile);
@ -31,9 +31,8 @@ public class ReleaseProfileTransactionPhase(ServiceTagCache tagCache)
} }
} }
var deleted = DeleteOldManagedProfiles(serviceData, configProfiles); var deleted = DeleteOldManagedProfiles(context.ApiFetchOutput, context.ConfigOutput.AsReadOnly());
context.TransactionOutput = new ReleaseProfileTransactionData(updated, created, deleted);
return new ReleaseProfileTransactionData(updated, created, deleted);
} }
private static List<SonarrReleaseProfile> DeleteOldManagedProfiles( private static List<SonarrReleaseProfile> DeleteOldManagedProfiles(

@ -1,5 +1,4 @@
using Autofac; using Autofac;
using Autofac.Extras.AggregateService;
using Autofac.Extras.Ordering; using Autofac.Extras.Ordering;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; using Recyclarr.Cli.Pipelines.ReleaseProfile.Filters;
using Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; using Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
@ -17,18 +16,20 @@ public class ReleaseProfileAutofacModule : Module
builder.RegisterType<ReleaseProfileFilterPipeline>().As<IReleaseProfileFilterPipeline>(); builder.RegisterType<ReleaseProfileFilterPipeline>().As<IReleaseProfileFilterPipeline>();
builder.RegisterType<ReleaseProfileDataLister>(); builder.RegisterType<ReleaseProfileDataLister>();
builder.RegisterAggregateService<IReleaseProfilePipelinePhases>();
builder.RegisterType<ReleaseProfileConfigPhase>();
builder.RegisterType<ReleaseProfileApiFetchPhase>();
builder.RegisterType<ReleaseProfileTransactionPhase>();
builder.RegisterType<ReleaseProfilePreviewPhase>();
builder.RegisterType<ReleaseProfileApiPersistencePhase>();
// Release Profile Filters (ORDER MATTERS!) // Release Profile Filters (ORDER MATTERS!)
builder.RegisterTypes( builder.RegisterTypes(
typeof(IncludeExcludeFilter), typeof(IncludeExcludeFilter),
typeof(StrictNegativeScoresFilter)) typeof(StrictNegativeScoresFilter))
.As<IReleaseProfileFilter>() .As<IReleaseProfileFilter>()
.OrderByRegistration(); .OrderByRegistration();
builder.RegisterTypes(
typeof(ReleaseProfileConfigPhase),
typeof(ReleaseProfilePreviewPhase),
typeof(ReleaseProfileApiFetchPhase),
typeof(ReleaseProfileTransactionPhase),
typeof(ReleaseProfileApiPersistencePhase),
typeof(ReleaseProfileLogPhase))
.AsImplementedInterfaces();
} }
} }

@ -0,0 +1,23 @@
using System.Diagnostics.CodeAnalysis;
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Models;
using Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
using Recyclarr.Common;
using Recyclarr.ServarrApi.ReleaseProfile;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile;
[SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification =
"Context objects are similar to DTOs; for usability we want to assign not append")]
public class ReleaseProfilePipelineContext : IPipelineContext
{
public string PipelineDescription => "Release Profile Pipeline";
public IReadOnlyCollection<SupportedServices> SupportedServiceTypes { get; } = new[]
{
SupportedServices.Sonarr
};
public IList<ProcessedReleaseProfileData> ConfigOutput { get; set; } = default!;
public IList<SonarrReleaseProfile> ApiFetchOutput { get; set; } = default!;
public ReleaseProfileTransactionData TransactionOutput { get; set; } = default!;
}

@ -1,45 +0,0 @@
using Recyclarr.Cli.Console.Settings;
using Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
using Recyclarr.Config.Models;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile;
public interface IReleaseProfilePipelinePhases
{
ReleaseProfileConfigPhase ConfigPhase { get; }
ReleaseProfileApiFetchPhase ApiFetchPhase { get; }
ReleaseProfileTransactionPhase TransactionPhase { get; }
Lazy<ReleaseProfilePreviewPhase> PreviewPhase { get; }
ReleaseProfileApiPersistencePhase ApiPersistencePhase { get; }
}
public class ReleaseProfileSyncPipeline(ILogger log, IReleaseProfilePipelinePhases phases) : ISyncPipeline
{
public async Task Execute(ISyncSettings settings, IServiceConfiguration config)
{
if (config is not SonarrConfiguration sonarrConfig)
{
log.Debug("Skipping release profile pipeline because {Instance} is not a Sonarr config",
config.InstanceName);
return;
}
var profiles = phases.ConfigPhase.Execute(sonarrConfig);
if (profiles is null)
{
log.Debug("No release profiles to process");
return;
}
var serviceData = await phases.ApiFetchPhase.Execute(config);
var transactions = phases.TransactionPhase.Execute(profiles, serviceData);
if (settings.Preview)
{
phases.PreviewPhase.Value.Execute(transactions);
return;
}
await phases.ApiPersistencePhase.Execute(config, transactions);
}
}
Loading…
Cancel
Save