commit
a124902f6d
@ -1,4 +1,8 @@
|
||||
* text=auto
|
||||
|
||||
# Files that require LF line endings
|
||||
*.sh eol=lf
|
||||
Dockerfile eol=lf
|
||||
|
||||
# Ignore whitespace in these files
|
||||
*.sln -whitespace
|
||||
|
@ -0,0 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -0,0 +1,32 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Common.TestLibrary;
|
||||
|
||||
public static class CommonMockFileSystemExtensions
|
||||
{
|
||||
public static void AddFileFromResource(this MockFileSystem fs, string resourceFilename)
|
||||
{
|
||||
fs.AddFileFromResource(resourceFilename, resourceFilename, Assembly.GetCallingAssembly());
|
||||
}
|
||||
|
||||
public static void AddFileFromResource(this MockFileSystem fs, IFileInfo file, string resourceFilename,
|
||||
string resourceDir = "Data")
|
||||
{
|
||||
fs.AddFileFromResource(file.FullName, resourceFilename, Assembly.GetCallingAssembly(), resourceDir);
|
||||
}
|
||||
|
||||
public static void AddFileFromResource(this MockFileSystem fs, string file, string resourceFilename,
|
||||
string resourceDir = "Data")
|
||||
{
|
||||
fs.AddFileFromResource(file, resourceFilename, Assembly.GetCallingAssembly(), resourceDir);
|
||||
}
|
||||
|
||||
public static void AddFileFromResource(this MockFileSystem fs, string file, string resourceFilename,
|
||||
Assembly assembly, string resourceDir = "Data")
|
||||
{
|
||||
var resourceReader = new ResourceDataReader(assembly, resourceDir);
|
||||
fs.AddFile(file, new MockFileData(resourceReader.ReadData(resourceFilename)));
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"json_paths": {
|
||||
"radarr": {
|
||||
"custom_formats": ["docs/json/radarr/cf"],
|
||||
"qualities": ["docs/json/radarr/quality-size"]
|
||||
},
|
||||
"sonarr": {
|
||||
"release_profiles": ["docs/json/sonarr/rp"],
|
||||
"custom_formats": ["docs/json/sonarr/cf"],
|
||||
"qualities": ["docs/json/sonarr/quality-size"]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common.TestLibrary\Common.TestLibrary.csproj" />
|
||||
<ProjectReference Include="..\Recyclarr\Recyclarr.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NUnit" PrivateAssets="All" />
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -0,0 +1,55 @@
|
||||
using System.IO.Abstractions.Extensions;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using FluentAssertions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using Recyclarr.TestLibrary;
|
||||
using TestLibrary.FluentAssertions;
|
||||
using TrashLib.Services.CustomFormat.Guide;
|
||||
using TrashLib.TestLibrary;
|
||||
|
||||
namespace TrashLib.Tests.CustomFormat.Guide;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public class CustomFormatLoaderTest : IntegrationFixture
|
||||
{
|
||||
[Test]
|
||||
public void Get_custom_format_json_works()
|
||||
{
|
||||
var sut = Resolve<ICustomFormatLoader>();
|
||||
Fs.AddFile("first.json", new MockFileData("{'name':'first','trash_id':'1'}"));
|
||||
Fs.AddFile("second.json", new MockFileData("{'name':'second','trash_id':'2'}"));
|
||||
|
||||
var results = sut.LoadAllCustomFormatsAtPaths(new[] {Fs.CurrentDirectory()});
|
||||
|
||||
results.Should().BeEquivalentTo(new[]
|
||||
{
|
||||
NewCf.Data("first", "1"),
|
||||
NewCf.Data("second", "2")
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Trash_properties_are_removed()
|
||||
{
|
||||
Fs.AddFile("first.json", new MockFileData(@"
|
||||
{
|
||||
'name':'first',
|
||||
'trash_id':'1',
|
||||
'trash_foo': 'foo',
|
||||
'trash_bar': 'bar',
|
||||
'extra': 'e1'
|
||||
}"));
|
||||
|
||||
var sut = Resolve<ICustomFormatLoader>();
|
||||
|
||||
var results = sut.LoadAllCustomFormatsAtPaths(new[] {Fs.CurrentDirectory()});
|
||||
|
||||
const string expectedExtraJson = @"{'name':'first','extra': 'e1'}";
|
||||
|
||||
results.Should()
|
||||
.ContainSingle().Which.ExtraJson.Should()
|
||||
.BeEquivalentTo(JObject.Parse(expectedExtraJson), op => op.Using(new JsonEquivalencyStep()));
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using TrashLib.Services.Radarr.Config;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Processors.GuideSteps;
|
||||
using TrashLib.Config.Services;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Processors.GuideSteps;
|
||||
using TrashLib.TestLibrary;
|
||||
|
||||
namespace TrashLib.Tests.Radarr.CustomFormat.Processors.GuideSteps;
|
||||
namespace TrashLib.Tests.CustomFormat.Processors.GuideSteps;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
@ -1,12 +1,12 @@
|
||||
using NSubstitute;
|
||||
using NUnit.Framework;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Api;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models.Cache;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Processors.PersistenceSteps;
|
||||
using TrashLib.Services.CustomFormat.Api;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models.Cache;
|
||||
using TrashLib.Services.CustomFormat.Processors.PersistenceSteps;
|
||||
using TrashLib.TestLibrary;
|
||||
|
||||
namespace TrashLib.Tests.Radarr.CustomFormat.Processors.PersistenceSteps;
|
||||
namespace TrashLib.Tests.CustomFormat.Processors.PersistenceSteps;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
@ -1,78 +0,0 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using AutoFixture.NUnit3;
|
||||
using FluentAssertions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NSubstitute;
|
||||
using NUnit.Framework;
|
||||
using TestLibrary.AutoFixture;
|
||||
using TestLibrary.FluentAssertions;
|
||||
using TrashLib.Repo;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Guide;
|
||||
using TrashLib.Startup;
|
||||
using TrashLib.TestLibrary;
|
||||
|
||||
namespace TrashLib.Tests.Radarr.CustomFormat.Guide;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public class LocalRepoRadarrGuideServiceTest
|
||||
{
|
||||
[Test, AutoMockData]
|
||||
public void Get_custom_format_json_works(
|
||||
[Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs,
|
||||
[Frozen] IAppPaths appPaths,
|
||||
[Frozen] IRepoPaths repoPaths,
|
||||
LocalRepoRadarrGuideService sut)
|
||||
{
|
||||
var jsonDir = appPaths.RepoDirectory
|
||||
.SubDirectory("docs")
|
||||
.SubDirectory("json")
|
||||
.SubDirectory("radarr");
|
||||
|
||||
fs.AddFile(jsonDir.File("first.json").FullName, new MockFileData("{'name':'first','trash_id':'1'}"));
|
||||
fs.AddFile(jsonDir.File("second.json").FullName, new MockFileData("{'name':'second','trash_id':'2'}"));
|
||||
|
||||
repoPaths.RadarrCustomFormatPaths.Returns(new[] {jsonDir});
|
||||
|
||||
var results = sut.GetCustomFormatData();
|
||||
|
||||
results.Should().BeEquivalentTo(new[]
|
||||
{
|
||||
NewCf.Data("first", "1"),
|
||||
NewCf.Data("second", "2")
|
||||
});
|
||||
}
|
||||
|
||||
[Test, AutoMockData]
|
||||
public void Trash_properties_are_removed(
|
||||
[Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs,
|
||||
[Frozen] IAppPaths appPaths,
|
||||
[Frozen] IRepoPaths repoPaths,
|
||||
LocalRepoRadarrGuideService sut)
|
||||
{
|
||||
var jsonDir = appPaths.RepoDirectory
|
||||
.SubDirectory("docs")
|
||||
.SubDirectory("json")
|
||||
.SubDirectory("radarr");
|
||||
|
||||
fs.AddFile(jsonDir.File("first.json").FullName, new MockFileData(@"
|
||||
{
|
||||
'name':'first',
|
||||
'trash_id':'1',
|
||||
'trash_foo': 'foo',
|
||||
'trash_bar': 'bar',
|
||||
'extra': 'e1'
|
||||
}"));
|
||||
|
||||
repoPaths.RadarrCustomFormatPaths.Returns(new[] {jsonDir});
|
||||
|
||||
var results = sut.GetCustomFormatData();
|
||||
|
||||
const string expectedExtraJson = @"{'name':'first','extra': 'e1'}";
|
||||
|
||||
results.Should()
|
||||
.ContainSingle().Which.ExtraJson.Should()
|
||||
.BeEquivalentTo(JObject.Parse(expectedExtraJson), op => op.Using(new JsonEquivalencyStep()));
|
||||
}
|
||||
}
|
@ -1,7 +1,27 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace TrashLib.Config.Services;
|
||||
|
||||
public abstract class ServiceConfiguration : IServiceConfiguration
|
||||
{
|
||||
public string BaseUrl { get; init; } = "";
|
||||
public string ApiKey { get; init; } = "";
|
||||
public ICollection<CustomFormatConfig> CustomFormats { get; init; } = new List<CustomFormatConfig>();
|
||||
public bool DeleteOldCustomFormats { get; init; }
|
||||
}
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
|
||||
public class CustomFormatConfig
|
||||
{
|
||||
public ICollection<string> Names { get; init; } = new List<string>();
|
||||
public ICollection<string> TrashIds { get; init; } = new List<string>();
|
||||
public ICollection<QualityProfileConfig> QualityProfiles { get; init; } = new List<QualityProfileConfig>();
|
||||
}
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
|
||||
public class QualityProfileConfig
|
||||
{
|
||||
public string Name { get; init; } = "";
|
||||
public int? Score { get; init; }
|
||||
public bool ResetUnmatchedScores { get; init; }
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
using CliFx.Infrastructure;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.Common;
|
||||
|
||||
public class GuideDataLister : IGuideDataLister
|
||||
{
|
||||
private readonly IConsole _console;
|
||||
|
||||
public GuideDataLister(IConsole console)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
public void ListCustomFormats(IEnumerable<CustomFormatData> customFormats)
|
||||
{
|
||||
_console.Output.WriteLine("\nList of Custom Formats in the TRaSH Guides:\n");
|
||||
|
||||
foreach (var cf in customFormats)
|
||||
{
|
||||
_console.Output.WriteLine($" - {cf.TrashId} # {cf.Name}");
|
||||
}
|
||||
|
||||
_console.Output.WriteLine(
|
||||
"\nThe above Custom Formats are in YAML format and ready to be copied & pasted " +
|
||||
"under the `trash_ids:` property.");
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Autofac;
|
||||
|
||||
namespace TrashLib.Services.Common;
|
||||
|
||||
public class GuideServicesAutofacModule : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
builder.RegisterType<GuideDataLister>().As<IGuideDataLister>();
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.Common;
|
||||
|
||||
public interface IGuideDataLister
|
||||
{
|
||||
void ListCustomFormats(IEnumerable<CustomFormatData> customFormats);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.Common;
|
||||
|
||||
public interface IGuideService
|
||||
{
|
||||
ICollection<CustomFormatData> GetCustomFormatData();
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Api;
|
||||
namespace TrashLib.Services.CustomFormat.Api;
|
||||
|
||||
public interface ICustomFormatService
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Api;
|
||||
namespace TrashLib.Services.CustomFormat.Api;
|
||||
|
||||
public interface IQualityProfileService
|
||||
{
|
@ -1,10 +1,10 @@
|
||||
using Common.Extensions;
|
||||
using Serilog;
|
||||
using TrashLib.Cache;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models.Cache;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models.Cache;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat;
|
||||
namespace TrashLib.Services.CustomFormat;
|
||||
|
||||
internal class CachePersister : ICachePersister
|
||||
{
|
@ -0,0 +1,34 @@
|
||||
using Autofac;
|
||||
using Autofac.Extras.AggregateService;
|
||||
using TrashLib.Services.CustomFormat.Api;
|
||||
using TrashLib.Services.CustomFormat.Guide;
|
||||
using TrashLib.Services.CustomFormat.Processors;
|
||||
using TrashLib.Services.CustomFormat.Processors.GuideSteps;
|
||||
using TrashLib.Services.CustomFormat.Processors.PersistenceSteps;
|
||||
|
||||
namespace TrashLib.Services.CustomFormat;
|
||||
|
||||
public class CustomFormatAutofacModule : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterType<CustomFormatService>().As<ICustomFormatService>();
|
||||
builder.RegisterType<QualityProfileService>().As<IQualityProfileService>();
|
||||
builder.RegisterType<CustomFormatUpdater>().As<ICustomFormatUpdater>();
|
||||
builder.RegisterType<CachePersister>().As<ICachePersister>();
|
||||
builder.RegisterType<GuideProcessor>().As<IGuideProcessor>();
|
||||
builder.RegisterType<CustomFormatLoader>().As<ICustomFormatLoader>();
|
||||
builder.RegisterType<CustomFormatParser>().As<ICustomFormatParser>();
|
||||
|
||||
builder.RegisterAggregateService<IGuideProcessorSteps>();
|
||||
builder.RegisterType<CustomFormatStep>().As<ICustomFormatStep>();
|
||||
builder.RegisterType<ConfigStep>().As<IConfigStep>();
|
||||
builder.RegisterType<QualityProfileStep>().As<IQualityProfileStep>();
|
||||
builder.RegisterType<PersistenceProcessor>().As<IPersistenceProcessor>();
|
||||
|
||||
builder.RegisterAggregateService<IPersistenceProcessorSteps>();
|
||||
builder.RegisterType<JsonTransactionStep>().As<IJsonTransactionStep>();
|
||||
builder.RegisterType<CustomFormatApiPersistenceStep>().As<ICustomFormatApiPersistenceStep>();
|
||||
builder.RegisterType<QualityProfileApiPersistenceStep>().As<IQualityProfileApiPersistenceStep>();
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Threading.Tasks;
|
||||
using Common.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.CustomFormat.Guide;
|
||||
|
||||
public class CustomFormatLoader : ICustomFormatLoader
|
||||
{
|
||||
private readonly ILogger _log;
|
||||
private readonly ICustomFormatParser _parser;
|
||||
|
||||
public CustomFormatLoader(ILogger log, ICustomFormatParser parser)
|
||||
{
|
||||
_log = log;
|
||||
_parser = parser;
|
||||
}
|
||||
|
||||
public ICollection<CustomFormatData> LoadAllCustomFormatsAtPaths(IEnumerable<IDirectoryInfo> jsonPaths)
|
||||
{
|
||||
var jsonFiles = jsonPaths.SelectMany(x => x.GetFiles("*.json"));
|
||||
return jsonFiles.ToObservable()
|
||||
.Select(x => Observable.Defer(() => LoadJsonFromFile(x)))
|
||||
.Merge(8)
|
||||
.NotNull()
|
||||
.ToEnumerable()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private IObservable<CustomFormatData?> LoadJsonFromFile(IFileInfo file)
|
||||
{
|
||||
return Observable.Using(file.OpenText, x => x.ReadToEndAsync().ToObservable())
|
||||
.Do(_ => _log.Debug("Parsing CF Json: {Name}", file.Name))
|
||||
.Select(_parser.ParseCustomFormatData)
|
||||
.Catch((JsonException e) =>
|
||||
{
|
||||
_log.Warning("Failed to parse JSON file: {File} ({Reason})", file.Name, e.Message);
|
||||
return Observable.Empty<CustomFormatData>();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Common.Extensions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.CustomFormat.Guide;
|
||||
|
||||
public class CustomFormatParser : ICustomFormatParser
|
||||
{
|
||||
public CustomFormatData ParseCustomFormatData(string guideData)
|
||||
{
|
||||
var obj = JObject.Parse(guideData);
|
||||
|
||||
var name = obj.ValueOrThrow<string>("name");
|
||||
var trashId = obj.ValueOrThrow<string>("trash_id");
|
||||
int? finalScore = null;
|
||||
|
||||
if (obj.TryGetValue("trash_score", out var score))
|
||||
{
|
||||
finalScore = (int) score;
|
||||
}
|
||||
|
||||
// Remove any properties starting with "trash_". Those are metadata that are not meant for the remote service
|
||||
// itself. The service supposedly drops this anyway, but I prefer it to be removed. ToList() is important here
|
||||
// since removing the property itself modifies the collection, and we don't want the collection to get modified
|
||||
// while still looping over it.
|
||||
foreach (var trashProperty in obj.Properties().Where(x => Regex.IsMatch(x.Name, @"^trash_")).ToList())
|
||||
{
|
||||
trashProperty.Remove();
|
||||
}
|
||||
|
||||
return new CustomFormatData(name, trashId, finalScore, obj);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System.IO.Abstractions;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.CustomFormat.Guide;
|
||||
|
||||
public interface ICustomFormatLoader
|
||||
{
|
||||
ICollection<CustomFormatData> LoadAllCustomFormatsAtPaths(IEnumerable<IDirectoryInfo> jsonPaths);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.CustomFormat.Guide;
|
||||
|
||||
public interface ICustomFormatParser
|
||||
{
|
||||
CustomFormatData ParseCustomFormatData(string guideData);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models.Cache;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models.Cache;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat;
|
||||
namespace TrashLib.Services.CustomFormat;
|
||||
|
||||
public interface ICachePersister
|
||||
{
|
@ -0,0 +1,9 @@
|
||||
using TrashLib.Config.Services;
|
||||
using TrashLib.Services.Common;
|
||||
|
||||
namespace TrashLib.Services.CustomFormat;
|
||||
|
||||
public interface ICustomFormatUpdater
|
||||
{
|
||||
Task Process(bool isPreview, IEnumerable<CustomFormatConfig> config, IGuideService guideService);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using TrashLib.Cache;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Models.Cache;
|
||||
namespace TrashLib.Services.CustomFormat.Models.Cache;
|
||||
|
||||
[CacheObjectName("custom-format-cache")]
|
||||
public class CustomFormatCache
|
@ -1,7 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
namespace TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
public record CustomFormatData(
|
||||
string Name,
|
@ -1,6 +1,6 @@
|
||||
using TrashLib.Services.Radarr.Config;
|
||||
using TrashLib.Config.Services;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
namespace TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
public class ProcessedConfigData
|
||||
{
|
@ -1,8 +1,8 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models.Cache;
|
||||
using TrashLib.Services.CustomFormat.Models.Cache;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
namespace TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
public class ProcessedCustomFormatData
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
namespace TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
public record FormatMappingEntry(ProcessedCustomFormatData CustomFormat, int Score);
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
namespace TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
public enum FormatScoreUpdateReason
|
||||
{
|
@ -1,9 +1,9 @@
|
||||
using Common.Extensions;
|
||||
using Serilog;
|
||||
using TrashLib.Services.Radarr.Config;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Config.Services;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.GuideSteps;
|
||||
namespace TrashLib.Services.CustomFormat.Processors.GuideSteps;
|
||||
|
||||
public class ConfigStep : IConfigStep
|
||||
{
|
@ -1,9 +1,9 @@
|
||||
using Common.Extensions;
|
||||
using TrashLib.Services.Radarr.Config;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models.Cache;
|
||||
using TrashLib.Config.Services;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models.Cache;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.GuideSteps;
|
||||
namespace TrashLib.Services.CustomFormat.Processors.GuideSteps;
|
||||
|
||||
public class CustomFormatStep : ICustomFormatStep
|
||||
{
|
@ -1,7 +1,7 @@
|
||||
using TrashLib.Services.Radarr.Config;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Config.Services;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.GuideSteps;
|
||||
namespace TrashLib.Services.CustomFormat.Processors.GuideSteps;
|
||||
|
||||
public interface IConfigStep
|
||||
{
|
@ -1,8 +1,8 @@
|
||||
using TrashLib.Services.Radarr.Config;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models.Cache;
|
||||
using TrashLib.Config.Services;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models.Cache;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.GuideSteps;
|
||||
namespace TrashLib.Services.CustomFormat.Processors.GuideSteps;
|
||||
|
||||
public interface ICustomFormatStep
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.GuideSteps;
|
||||
namespace TrashLib.Services.CustomFormat.Processors.GuideSteps;
|
||||
|
||||
public interface IQualityProfileStep
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.GuideSteps;
|
||||
namespace TrashLib.Services.CustomFormat.Processors.GuideSteps;
|
||||
|
||||
internal class QualityProfileStep : IQualityProfileStep
|
||||
{
|
@ -1,8 +1,8 @@
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models.Cache;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Processors.PersistenceSteps;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models.Cache;
|
||||
using TrashLib.Services.CustomFormat.Processors.PersistenceSteps;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors;
|
||||
namespace TrashLib.Services.CustomFormat.Processors;
|
||||
|
||||
public interface IPersistenceProcessor
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using TrashLib.Services.Radarr.CustomFormat.Api;
|
||||
using TrashLib.Services.CustomFormat.Api;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.PersistenceSteps;
|
||||
namespace TrashLib.Services.CustomFormat.Processors.PersistenceSteps;
|
||||
|
||||
internal class CustomFormatApiPersistenceStep : ICustomFormatApiPersistenceStep
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using TrashLib.Services.Radarr.CustomFormat.Api;
|
||||
using TrashLib.Services.CustomFormat.Api;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.PersistenceSteps;
|
||||
namespace TrashLib.Services.CustomFormat.Processors.PersistenceSteps;
|
||||
|
||||
public interface ICustomFormatApiPersistenceStep
|
||||
{
|
@ -0,0 +1,15 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Models.Cache;
|
||||
|
||||
namespace TrashLib.Services.CustomFormat.Processors.PersistenceSteps;
|
||||
|
||||
public interface IJsonTransactionStep
|
||||
{
|
||||
CustomFormatTransactionData Transactions { get; }
|
||||
|
||||
void Process(IEnumerable<ProcessedCustomFormatData> guideCfs,
|
||||
IReadOnlyCollection<JObject> serviceCfs);
|
||||
|
||||
void RecordDeletions(IEnumerable<TrashIdMapping> deletedCfsInCache, IEnumerable<JObject> serviceCfs);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using TrashLib.Services.Radarr.CustomFormat.Api;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.CustomFormat.Api;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.PersistenceSteps;
|
||||
namespace TrashLib.Services.CustomFormat.Processors.PersistenceSteps;
|
||||
|
||||
public interface IQualityProfileApiPersistenceStep
|
||||
{
|
@ -1,9 +0,0 @@
|
||||
namespace TrashLib.Services.Radarr.CustomFormat;
|
||||
|
||||
public enum ApiOperationType
|
||||
{
|
||||
Create,
|
||||
Update,
|
||||
NoChange,
|
||||
Delete
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.QualityDefinition;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Guide;
|
||||
|
||||
public interface IRadarrGuideService
|
||||
{
|
||||
ICollection<CustomFormatData> GetCustomFormatData();
|
||||
ICollection<RadarrQualityData> GetQualities();
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
using Common.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
using TrashLib.Repo;
|
||||
using TrashLib.Services.Common.QualityDefinition;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.QualityDefinition;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Guide;
|
||||
|
||||
public class LocalRepoRadarrGuideService : IRadarrGuideService
|
||||
{
|
||||
private readonly IRepoPathsFactory _pathsFactory;
|
||||
private readonly ILogger _log;
|
||||
private readonly QualityGuideParser<RadarrQualityData> _parser;
|
||||
|
||||
public LocalRepoRadarrGuideService(IRepoPathsFactory pathsFactory, ILogger log)
|
||||
{
|
||||
_pathsFactory = pathsFactory;
|
||||
_log = log;
|
||||
_parser = new QualityGuideParser<RadarrQualityData>(log);
|
||||
}
|
||||
|
||||
public ICollection<RadarrQualityData> GetQualities()
|
||||
=> _parser.GetQualities(_pathsFactory.Create().RadarrQualityPaths);
|
||||
|
||||
public ICollection<CustomFormatData> GetCustomFormatData()
|
||||
{
|
||||
var paths = _pathsFactory.Create();
|
||||
var jsonFiles = paths.RadarrCustomFormatPaths
|
||||
.SelectMany(x => x.GetFiles("*.json"));
|
||||
|
||||
return jsonFiles.ToObservable()
|
||||
.Select(x => Observable.Defer(() => LoadJsonFromFile(x)))
|
||||
.Merge(8)
|
||||
.NotNull()
|
||||
.ToEnumerable()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private IObservable<CustomFormatData?> LoadJsonFromFile(IFileInfo file)
|
||||
{
|
||||
return Observable.Using(file.OpenText, x => x.ReadToEndAsync().ToObservable())
|
||||
.Do(_ => _log.Debug("Parsing CF Json: {Name}", file.Name))
|
||||
.Select(ParseCustomFormatData)
|
||||
.Catch((JsonException e) =>
|
||||
{
|
||||
_log.Warning("Failed to parse JSON file: {File} ({Reason})", file.Name, e.Message);
|
||||
return Observable.Empty<CustomFormatData>();
|
||||
});
|
||||
}
|
||||
|
||||
public static CustomFormatData ParseCustomFormatData(string guideData)
|
||||
{
|
||||
var obj = JObject.Parse(guideData);
|
||||
|
||||
var name = obj.ValueOrThrow<string>("name");
|
||||
var trashId = obj.ValueOrThrow<string>("trash_id");
|
||||
int? finalScore = null;
|
||||
|
||||
if (obj.TryGetValue("trash_score", out var score))
|
||||
{
|
||||
finalScore = (int) score;
|
||||
}
|
||||
|
||||
// Remove any properties starting with "trash_". Those are metadata that are not meant for Radarr itself Radarr
|
||||
// supposedly drops this anyway, but I prefer it to be removed. ToList() is important here since removing the
|
||||
// property itself modifies the collection, and we don't want the collection to get modified while still looping
|
||||
// over it.
|
||||
foreach (var trashProperty in obj.Properties().Where(x => Regex.IsMatch(x.Name, @"^trash_")).ToList())
|
||||
{
|
||||
trashProperty.Remove();
|
||||
}
|
||||
|
||||
return new CustomFormatData(name, trashId, finalScore, obj);
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
using TrashLib.Services.Radarr.Config;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat;
|
||||
|
||||
public interface ICustomFormatUpdater
|
||||
{
|
||||
Task Process(bool isPreview, RadarrConfiguration config);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.CustomFormat.Models.Cache;
|
||||
|
||||
namespace TrashLib.Services.Radarr.CustomFormat.Processors.PersistenceSteps;
|
||||
|
||||
public interface IJsonTransactionStep
|
||||
{
|
||||
CustomFormatTransactionData Transactions { get; }
|
||||
|
||||
void Process(IEnumerable<ProcessedCustomFormatData> guideCfs,
|
||||
IReadOnlyCollection<JObject> radarrCfs);
|
||||
|
||||
void RecordDeletions(IEnumerable<TrashIdMapping> deletedCfsInCache, IEnumerable<JObject> radarrCfs);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace TrashLib.Services.Radarr.CustomFormat;
|
||||
namespace TrashLib.Services.Radarr;
|
||||
|
||||
public interface IRadarrGuideDataLister
|
||||
{
|
@ -0,0 +1,9 @@
|
||||
using TrashLib.Services.Common;
|
||||
using TrashLib.Services.Radarr.QualityDefinition;
|
||||
|
||||
namespace TrashLib.Services.Radarr;
|
||||
|
||||
public interface IRadarrGuideService : IGuideService
|
||||
{
|
||||
ICollection<RadarrQualityData> GetQualities();
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using Serilog;
|
||||
using TrashLib.Repo;
|
||||
using TrashLib.Services.Common.QualityDefinition;
|
||||
using TrashLib.Services.CustomFormat.Guide;
|
||||
using TrashLib.Services.CustomFormat.Models;
|
||||
using TrashLib.Services.Radarr.QualityDefinition;
|
||||
|
||||
namespace TrashLib.Services.Radarr;
|
||||
|
||||
public class LocalRepoRadarrGuideService : IRadarrGuideService
|
||||
{
|
||||
private readonly IRepoPathsFactory _pathsFactory;
|
||||
private readonly ICustomFormatLoader _cfLoader;
|
||||
private readonly QualityGuideParser<RadarrQualityData> _parser;
|
||||
|
||||
public LocalRepoRadarrGuideService(IRepoPathsFactory pathsFactory, ILogger log, ICustomFormatLoader cfLoader)
|
||||
{
|
||||
_pathsFactory = pathsFactory;
|
||||
_cfLoader = cfLoader;
|
||||
_parser = new QualityGuideParser<RadarrQualityData>(log);
|
||||
}
|
||||
|
||||
public ICollection<RadarrQualityData> GetQualities()
|
||||
=> _parser.GetQualities(_pathsFactory.Create().RadarrQualityPaths);
|
||||
|
||||
public ICollection<CustomFormatData> GetCustomFormatData()
|
||||
{
|
||||
var paths = _pathsFactory.Create();
|
||||
return _cfLoader.LoadAllCustomFormatsAtPaths(paths.RadarrCustomFormatPaths);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue