-
-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #179
- Loading branch information
Showing
39 changed files
with
1,506 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
src/Recyclarr.Cli/Pipelines/MediaNaming/MediaNamingAutofacModule.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using Autofac; | ||
using Autofac.Extras.AggregateService; | ||
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases; | ||
|
||
namespace Recyclarr.Cli.Pipelines.MediaNaming; | ||
|
||
public class MediaNamingAutofacModule : Module | ||
{ | ||
protected override void Load(ContainerBuilder builder) | ||
{ | ||
base.Load(builder); | ||
|
||
builder.RegisterAggregateService<IMediaNamingPipelinePhases>(); | ||
builder.RegisterType<MediaNamingConfigPhase>(); | ||
builder.RegisterType<MediaNamingApiFetchPhase>(); | ||
builder.RegisterType<MediaNamingTransactionPhase>(); | ||
builder.RegisterType<MediaNamingPreviewPhase>(); | ||
builder.RegisterType<MediaNamingApiPersistencePhase>(); | ||
builder.RegisterType<MediaNamingPhaseLogger>(); | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/Recyclarr.Cli/Pipelines/MediaNaming/MediaNamingSyncPipeline.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using Recyclarr.Cli.Console.Settings; | ||
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases; | ||
using Recyclarr.Config.Models; | ||
|
||
namespace Recyclarr.Cli.Pipelines.MediaNaming; | ||
|
||
public interface IMediaNamingPipelinePhases | ||
{ | ||
MediaNamingConfigPhase ConfigPhase { get; } | ||
MediaNamingPhaseLogger Logger { get; } | ||
MediaNamingApiFetchPhase ApiFetchPhase { get; } | ||
MediaNamingTransactionPhase TransactionPhase { get; } | ||
MediaNamingPreviewPhase PreviewPhase { get; } | ||
MediaNamingApiPersistencePhase ApiPersistencePhase { get; } | ||
} | ||
|
||
public class MediaNamingSyncPipeline : ISyncPipeline | ||
{ | ||
private readonly IMediaNamingPipelinePhases _phases; | ||
|
||
public MediaNamingSyncPipeline(IMediaNamingPipelinePhases phases) | ||
{ | ||
_phases = phases; | ||
} | ||
|
||
public async Task Execute(ISyncSettings settings, IServiceConfiguration config) | ||
{ | ||
var processedNaming = await _phases.ConfigPhase.Execute(config); | ||
if (_phases.Logger.LogConfigPhaseAndExitIfNeeded(processedNaming)) | ||
{ | ||
return; | ||
} | ||
|
||
var serviceData = await _phases.ApiFetchPhase.Execute(config); | ||
|
||
var transactions = _phases.TransactionPhase.Execute(serviceData, processedNaming); | ||
|
||
if (settings.Preview) | ||
{ | ||
_phases.PreviewPhase.Execute(transactions); | ||
return; | ||
} | ||
|
||
await _phases.ApiPersistencePhase.Execute(config, transactions); | ||
_phases.Logger.LogPersistenceResults(transactions); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/MediaNamingApiFetchPhase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using Recyclarr.Config.Models; | ||
using Recyclarr.ServarrApi.MediaNaming; | ||
|
||
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases; | ||
|
||
public class MediaNamingApiFetchPhase | ||
{ | ||
private readonly IMediaNamingApiService _api; | ||
|
||
public MediaNamingApiFetchPhase(IMediaNamingApiService api) | ||
{ | ||
_api = api; | ||
} | ||
|
||
public async Task<IMediaNamingDto> Execute(IServiceConfiguration config) | ||
{ | ||
return await _api.GetNaming(config); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/MediaNamingApiPersistencePhase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using Recyclarr.Config.Models; | ||
using Recyclarr.ServarrApi.MediaNaming; | ||
|
||
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases; | ||
|
||
public class MediaNamingApiPersistencePhase | ||
{ | ||
private readonly IMediaNamingApiService _api; | ||
|
||
public MediaNamingApiPersistencePhase(IMediaNamingApiService api) | ||
{ | ||
_api = api; | ||
} | ||
|
||
public async Task Execute(IServiceConfiguration config, IMediaNamingDto serviceDto) | ||
{ | ||
await _api.UpdateNaming(config, serviceDto); | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/MediaNamingConfigPhase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
using Recyclarr.Compatibility.Sonarr; | ||
using Recyclarr.Config.Models; | ||
using Recyclarr.ServarrApi.MediaNaming; | ||
using Recyclarr.TrashGuide.MediaNaming; | ||
|
||
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases; | ||
|
||
public record InvalidNamingConfig(string Type, string ConfigValue); | ||
|
||
public record ProcessedNamingConfig | ||
{ | ||
public required IMediaNamingDto Dto { get; init; } | ||
public IReadOnlyCollection<InvalidNamingConfig> InvalidNaming { get; init; } = new List<InvalidNamingConfig>(); | ||
} | ||
|
||
public class MediaNamingConfigPhase | ||
{ | ||
private readonly IMediaNamingGuideService _guide; | ||
private readonly ISonarrCapabilityFetcher _sonarrCapabilities; | ||
private List<InvalidNamingConfig> _errors = new(); | ||
|
||
public MediaNamingConfigPhase(IMediaNamingGuideService guide, ISonarrCapabilityFetcher sonarrCapabilities) | ||
{ | ||
_guide = guide; | ||
_sonarrCapabilities = sonarrCapabilities; | ||
} | ||
|
||
public async Task<ProcessedNamingConfig> Execute(IServiceConfiguration config) | ||
{ | ||
_errors = new List<InvalidNamingConfig>(); | ||
|
||
var dto = config switch | ||
{ | ||
RadarrConfiguration c => ProcessRadarrNaming(c), | ||
SonarrConfiguration c => await ProcessSonarrNaming(c), | ||
_ => throw new ArgumentException("Configuration type unsupported for naming sync") | ||
}; | ||
|
||
return new ProcessedNamingConfig {Dto = dto, InvalidNaming = _errors}; | ||
} | ||
|
||
private IMediaNamingDto ProcessRadarrNaming(RadarrConfiguration config) | ||
{ | ||
var guideData = _guide.GetRadarrNamingData(); | ||
var configData = config.MediaNaming; | ||
|
||
return new RadarrMediaNamingDto | ||
{ | ||
StandardMovieFormat = ObtainFormat(guideData.File, configData.Movie?.Format, "Movie File Format"), | ||
MovieFolderFormat = ObtainFormat(guideData.Folder, configData.Folder, "Movie Folder Format"), | ||
RenameMovies = configData.Movie?.Rename | ||
}; | ||
} | ||
|
||
private async Task<IMediaNamingDto> ProcessSonarrNaming(SonarrConfiguration config) | ||
{ | ||
var guideData = _guide.GetSonarrNamingData(); | ||
var configData = config.MediaNaming; | ||
var capabilities = await _sonarrCapabilities.GetCapabilities(config); | ||
var keySuffix = capabilities.SupportsCustomFormats ? ":4" : ":3"; | ||
|
||
return new SonarrMediaNamingDto | ||
{ | ||
SeasonFolderFormat = ObtainFormat(guideData.Season, configData.Season, "Season Folder Format"), | ||
SeriesFolderFormat = ObtainFormat(guideData.Series, configData.Series, "Series Folder Format"), | ||
StandardEpisodeFormat = ObtainFormat( | ||
guideData.Episodes.Standard, | ||
configData.Episodes?.Standard, | ||
keySuffix, | ||
"Standard Episode Format"), | ||
DailyEpisodeFormat = ObtainFormat( | ||
guideData.Episodes.Daily, | ||
configData.Episodes?.Daily, | ||
keySuffix, | ||
"Daily Episode Format"), | ||
AnimeEpisodeFormat = ObtainFormat( | ||
guideData.Episodes.Anime, | ||
configData.Episodes?.Anime, | ||
keySuffix, | ||
"Anime Episode Format"), | ||
RenameEpisodes = configData.Episodes?.Rename | ||
}; | ||
} | ||
|
||
private string? ObtainFormat( | ||
IReadOnlyDictionary<string, string> guideFormats, | ||
string? configFormatKey, | ||
string errorDescription) | ||
{ | ||
return ObtainFormat(guideFormats, configFormatKey, null, errorDescription); | ||
} | ||
|
||
private string? ObtainFormat( | ||
IReadOnlyDictionary<string, string> guideFormats, | ||
string? configFormatKey, | ||
string? keySuffix, | ||
string errorDescription) | ||
{ | ||
if (configFormatKey is null) | ||
{ | ||
return null; | ||
} | ||
|
||
// Use lower-case for the config value because System.Text.Json doesn't let us create a case-insensitive | ||
// dictionary. The MediaNamingGuideService converts all parsed guide JSON keys to lower case. | ||
var lowerKey = configFormatKey.ToLowerInvariant(); | ||
|
||
var keys = new List<string> {lowerKey}; | ||
if (keySuffix is not null) | ||
{ | ||
// Put the more specific key first | ||
keys.Insert(0, lowerKey + keySuffix); | ||
} | ||
|
||
foreach (var k in keys) | ||
{ | ||
if (guideFormats.TryGetValue(k, out var format)) | ||
{ | ||
return format; | ||
} | ||
} | ||
|
||
_errors.Add(new InvalidNamingConfig(errorDescription, configFormatKey)); | ||
return null; | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/MediaNamingPhaseLogger.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using Recyclarr.ServarrApi.MediaNaming; | ||
|
||
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases; | ||
|
||
public class MediaNamingPhaseLogger | ||
{ | ||
private readonly ILogger _log; | ||
|
||
public MediaNamingPhaseLogger(ILogger log) | ||
{ | ||
_log = log; | ||
} | ||
|
||
// Returning 'true' means to exit. 'false' means to proceed. | ||
public bool LogConfigPhaseAndExitIfNeeded(ProcessedNamingConfig config) | ||
{ | ||
if (config.InvalidNaming.Any()) | ||
{ | ||
foreach (var (topic, invalidValue) in config.InvalidNaming) | ||
{ | ||
_log.Error("An invalid media naming format is specified for {Topic}: {Value}", topic, invalidValue); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
var hasChanges = config.Dto switch | ||
{ | ||
RadarrMediaNamingDto dto => RadarrHasChanges(dto), | ||
SonarrMediaNamingDto dto => SonarrHasChanges(dto), | ||
_ => throw new ArgumentException("Unsupported configuration type in LogConfigPhase method") | ||
}; | ||
|
||
if (!hasChanges) | ||
{ | ||
_log.Debug("No media naming changes to process"); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private static bool SonarrHasChanges(SonarrMediaNamingDto dto) | ||
{ | ||
return dto != new SonarrMediaNamingDto(); | ||
} | ||
|
||
private static bool RadarrHasChanges(RadarrMediaNamingDto dto) | ||
{ | ||
return dto != new RadarrMediaNamingDto(); | ||
} | ||
|
||
public void LogPersistenceResults(IMediaNamingDto transactions) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/MediaNamingPreviewPhase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
using Recyclarr.ServarrApi.MediaNaming; | ||
using Spectre.Console; | ||
|
||
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases; | ||
|
||
public class MediaNamingPreviewPhase | ||
{ | ||
private readonly IAnsiConsole _console; | ||
private Table? _table; | ||
|
||
public MediaNamingPreviewPhase(IAnsiConsole console) | ||
{ | ||
_console = console; | ||
} | ||
|
||
public void Execute(IMediaNamingDto serviceDto) | ||
{ | ||
_table = new Table {Border = TableBorder.Simple} | ||
.Title("Radarr Media Naming") | ||
.AddColumns("Field", "Format"); | ||
|
||
switch (serviceDto) | ||
{ | ||
case RadarrMediaNamingDto dto: | ||
PreviewRadarr(dto); | ||
break; | ||
|
||
case SonarrMediaNamingDto dto: | ||
PreviewSonarr(dto); | ||
break; | ||
|
||
default: | ||
throw new ArgumentException("Config type not supported in media naming preview"); | ||
} | ||
|
||
_console.Write(_table); | ||
} | ||
|
||
private void AddRow(string field, object? value) | ||
{ | ||
_table?.AddRow(field, value?.ToString() ?? "UNSET"); | ||
} | ||
|
||
private void PreviewRadarr(RadarrMediaNamingDto dto) | ||
{ | ||
AddRow("Enable Movie Renames?", dto.RenameMovies); | ||
AddRow("Movie", dto.StandardMovieFormat); | ||
AddRow("Folder", dto.MovieFolderFormat); | ||
} | ||
|
||
private void PreviewSonarr(SonarrMediaNamingDto dto) | ||
{ | ||
AddRow("Enable Episode Renames?", dto.RenameEpisodes); | ||
AddRow("Series Folder", dto.SeriesFolderFormat); | ||
AddRow("Season Folder", dto.SeasonFolderFormat); | ||
AddRow("Standard Episodes", dto.StandardEpisodeFormat); | ||
AddRow("Daily Episodes", dto.DailyEpisodeFormat); | ||
AddRow("Anime Episodes", dto.AnimeEpisodeFormat); | ||
} | ||
} |
Oops, something went wrong.