fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Add initial Blazor Server project

recyclarr
Robert Dailey 3 years ago
parent 461ff68730
commit 8b45379cc8

@ -1,4 +1,4 @@
# noinspection EditorConfigKeyCorrectness
[*]
charset = utf-8
end_of_line = crlf
@ -919,7 +919,7 @@ resharper_event_handler_pattern_short = On$event$
resharper_expression_braces = inside
resharper_expression_pars = inside
resharper_extra_spaces = remove_all
resharper_force_attribute_style = separate
resharper_force_attribute_style = join
resharper_force_chop_compound_do_expression = false
resharper_force_chop_compound_if_expression = false
resharper_force_chop_compound_while_expression = false

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
@ -33,9 +32,7 @@ namespace Recyclarr.Code.Radarr
});
}
public void Remove(TrashIdMapping cfId)
{
_context.CustomFormatCache.Remove(cfId);
}
public void Remove(TrashIdMapping cfId) => _context.CustomFormatCache.Remove(cfId);
public void Save() => _context.SaveChanges();
}
}

@ -79,7 +79,7 @@ namespace Recyclarr.Code.Radarr
// Match CFs between the guide & Radarr and merge the data. The goal is to retain as much of the
// original data from Radarr as possible. There are many properties in the response JSON that we don't
// directly care about. We keep those and just update the ones we do care about.
_jsonTransactionStep.Process(CustomFormats, radarrCfs);
_jsonTransactionStep.Process(CustomFormats, radarrCfs, config);
// Step 1.1: Optionally record deletions of custom formats in cache but not in the guide
if (config.DeleteOldCustomFormats)

@ -9,8 +9,8 @@ namespace TrashLib.Radarr.Config
public class RadarrConfig : ServiceConfiguration
{
public QualityDefinitionConfig? QualityDefinition { get; init; }
public ICollection<CustomFormatConfig> CustomFormats { get; set; } // = new();
public ICollection<QualityProfileConfig> QualityProfiles { get; init; } // = new();
public List<CustomFormatConfig> CustomFormats { get; init; } = new();
public List<QualityProfileConfig> QualityProfiles { get; init; } = new();
public bool DeleteOldCustomFormats { get; init; }
}
@ -22,7 +22,8 @@ namespace TrashLib.Radarr.Config
public class QualityProfileConfig
{
public ICollection<ScoreEntryConfig> Scores { get; init; } // = new();
public string ProfileName { get; init; } = "";
public List<ScoreEntryConfig> Scores { get; init; } = new();
public bool ResetUnmatchedScores { get; init; }
}

@ -1,24 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TrashLib.Radarr.CustomFormat.Api.Models;
using TrashLib.Radarr.CustomFormat.Models;
namespace TrashLib.Radarr.CustomFormat.Api
{
public class RadarrCustomFormatData
{
public int Id { get; set; }
public string Name { get; set; }
[JsonExtensionData, UsedImplicitly]
private JObject? _extraJson;
}
public interface ICustomFormatService
{
Task<List<RadarrCustomFormatData>> GetCustomFormats();
Task<List<CustomFormatData>> GetCustomFormats();
Task<int> CreateCustomFormat(ProcessedCustomFormatData cf);
Task UpdateCustomFormat(int formatId, ProcessedCustomFormatData cf);
Task DeleteCustomFormat(int formatId);

@ -0,0 +1,25 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace TrashLib.Radarr.CustomFormat.Api.Models
{
public class CustomFormatData
{
public int Id { get; set; }
public string Name { get; set; }
public List<SpecificationData> Specifications { get; set; } = new();
[JsonExtensionData, UsedImplicitly]
public JObject? ExtraJson { get; init; }
}
public class SpecificationData
{
public string Name { get; set; }
[JsonExtensionData, UsedImplicitly]
public JObject? ExtraJson { get; init; }
}
}

@ -9,5 +9,6 @@ namespace TrashLib.Radarr.CustomFormat.Cache
IEnumerable<TrashIdMapping> Mappings { get; }
void Add(int formatId, ProcessedCustomFormatData format);
void Remove(TrashIdMapping cfId);
void Save();
}
}

@ -1,28 +1,39 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json.Linq;
using TrashLib.Radarr.CustomFormat.Models.Cache;
using Newtonsoft.Json;
using TrashLib.Radarr.CustomFormat.Api.Models;
namespace TrashLib.Radarr.CustomFormat.Models
{
public class ProcessedCustomFormatData
{
public ProcessedCustomFormatData(string name, string trashId, JObject json)
public ProcessedCustomFormatData(string trashId, CustomFormatData data)
{
Name = name;
TrashId = trashId;
Json = json;
Data = data;
}
public string Name { get; }
public string Name => Data.Name;
public string TrashId { get; }
public int? Score { get; init; }
public JObject Json { get; set; }
// public TrashIdMapping? CacheEntry { get; set; }
// [SuppressMessage("Microsoft.Design", "CA1024", Justification = "Method throws an exception")]
// public int GetCustomFormatId()
// => CacheEntry?.CustomFormatId ??
// throw new InvalidOperationException("CacheEntry must exist to obtain custom format ID");
public CustomFormatData Data { get; }
public static ProcessedCustomFormatData CreateFromJson(string guideData)
{
var cfData = JsonConvert.DeserializeObject<CustomFormatData>(guideData);
var trashId = (string) cfData.ExtraJson["trash_id"];
int? finalScore = null;
if (cfData.ExtraJson.TryGetValue("trash_score", out var score))
{
finalScore = (int) score;
cfData.ExtraJson.Property("trash_score").Remove();
}
// Remove trash_id, it's metadata that is not meant for Radarr itself
// Radarr supposedly drops this anyway, but I prefer it to be removed by TrashUpdater
cfData.ExtraJson.Property("trash_id").Remove();
return new ProcessedCustomFormatData(trashId, cfData) {Score = finalScore};
}
}
}

@ -1,31 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using TrashLib.Config;
using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Cache;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps
{
internal class CustomFormatProcessor : ICustomFormatProcessor
{
private readonly ICustomFormatCache _cache;
public CustomFormatProcessor(ICustomFormatCache cache)
{
_cache = cache;
}
public List<ProcessedCustomFormatData> CustomFormats { get; } = new();
public List<TrashIdMapping> DeletedCustomFormatsInCache { get; } = new();
public void Process(IEnumerable<string> customFormatGuideData, RadarrConfig config)
{
var processedCfs = customFormatGuideData
.Select(jsonData => ProcessCustomFormatData(jsonData, config))
.Select(ProcessedCustomFormatData.CreateFromJson)
.ToList();
// For each ID listed under the `trash_ids` YML property, match it to an existing CF
@ -37,45 +25,6 @@ namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps
cf => cf.TrashId,
(_, cf) => cf,
StringComparer.InvariantCultureIgnoreCase));
// Orphaned entries in cache represent custom formats we need to delete.
ProcessDeletedCustomFormats(config);
}
private ProcessedCustomFormatData ProcessCustomFormatData(
string guideData,
IServiceConfiguration config)
{
JObject obj = JObject.Parse(guideData);
var name = (string) obj["name"];
var trashId = (string) obj["trash_id"];
int? finalScore = null;
if (obj.TryGetValue("trash_score", out var score))
{
finalScore = (int) score;
obj.Property("trash_score").Remove();
}
// Remove trash_id, it's metadata that is not meant for Radarr itself
// Radarr supposedly drops this anyway, but I prefer it to be removed by TrashUpdater
obj.Property("trash_id").Remove();
return new ProcessedCustomFormatData(name, trashId, obj)
{
Score = finalScore,
CacheEntry = _cache.Load(config).FirstOrDefault(c => c.TrashId == trashId)
};
}
private void ProcessDeletedCustomFormats(RadarrConfig config)
{
static bool MatchCfInCache(ProcessedCustomFormatData cf, TrashIdMapping c)
=> cf.CacheEntry != null && cf.CacheEntry.TrashId == c.TrashId;
// Delete if CF is in cache and not in the guide or config
DeletedCustomFormatsInCache.AddRange(_cache.Load(config)
.Where(c => !CustomFormats.Any(cf => MatchCfInCache(cf, c))));
}
}
}

@ -1,15 +1,12 @@
using System.Collections.Generic;
using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps
{
public interface ICustomFormatProcessor
{
List<ProcessedCustomFormatData> CustomFormats { get; }
List<TrashIdMapping> DeletedCustomFormatsInCache { get; }
void Process(IEnumerable<string> customFormatGuideData, RadarrConfig config);
}
}

@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps
@ -8,22 +10,22 @@ namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps
public Dictionary<string, QualityProfileCustomFormatScoreMapping> ProfileScores { get; } = new();
public List<(string name, string trashId, string profileName)> CustomFormatsWithoutScore { get; } = new();
public void Process(IEnumerable<ProcessedConfigData> configData)
public void Process(RadarrConfig config, IReadOnlyCollection<ProcessedCustomFormatData> customFormats)
{
foreach (var config in configData)
// foreach (var config in configData)
foreach (var profile in config.QualityProfiles)
foreach (var cf in config.CustomFormats)
foreach (var cfScore in profile.Scores)
{
// Check if there is a score we can use. Priority is:
// 1. Score from the YAML config is used. If user did not provide,
// 2. Score from the guide is used. If the guide did not have one,
// 3. Warn the user and
var scoreToUse = profile.Score;
var scoreToUse = profile.Scores.FirstOrDefault(s => s.TrashId == cf.TrashId);
if (scoreToUse == null)
{
if (cf.Score == null)
{
CustomFormatsWithoutScore.Add((cf.Name, cf.TrashId, profile.Name));
CustomFormatsWithoutScore.Add((cf.Name, cf.TrashId, profile.));
}
else
{

@ -1,81 +1,81 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Api;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
using TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps;
namespace TrashLib.Radarr.CustomFormat.Processors
{
public interface IPersistenceProcessorSteps
{
public IJsonTransactionStep JsonTransactionStep { get; }
public ICustomFormatApiPersistenceStep CustomFormatCustomFormatApiPersister { get; }
public IQualityProfileApiPersistenceStep ProfileQualityProfileApiPersister { get; }
}
internal class PersistenceProcessor : IPersistenceProcessor
{
private readonly Func<string, ICustomFormatService> _customFormatServiceFactory;
private readonly Func<string, IQualityProfileService> _qualityProfileServiceFactory;
private readonly Func<IPersistenceProcessorSteps> _stepsFactory;
private IPersistenceProcessorSteps _steps;
public PersistenceProcessor(
Func<string, ICustomFormatService> customFormatServiceFactory,
Func<string, IQualityProfileService> qualityProfileServiceFactory,
Func<IPersistenceProcessorSteps> stepsFactory)
{
_qualityProfileServiceFactory = qualityProfileServiceFactory;
_stepsFactory = stepsFactory;
_customFormatServiceFactory = customFormatServiceFactory;
_steps = _stepsFactory();
}
public CustomFormatTransactionData Transactions
=> _steps.JsonTransactionStep.Transactions;
public IDictionary<string, List<UpdatedFormatScore>> UpdatedScores
=> _steps.ProfileQualityProfileApiPersister.UpdatedScores;
public IReadOnlyCollection<string> InvalidProfileNames
=> _steps.ProfileQualityProfileApiPersister.InvalidProfileNames;
public void Reset()
{
_steps = _stepsFactory();
}
public async Task PersistCustomFormats(
RadarrConfig config,
IEnumerable<ProcessedCustomFormatData> guideCfs,
IEnumerable<TrashIdMapping> deletedCfsInCache,
IDictionary<string, QualityProfileCustomFormatScoreMapping> profileScores)
{
var customFormatService = _customFormatServiceFactory(config.BuildUrl());
var radarrCfs = await customFormatService.GetCustomFormats();
// Step 1: Match CFs between the guide & Radarr and merge the data. The goal is to retain as much of the
// original data from Radarr as possible. There are many properties in the response JSON that we don't
// directly care about. We keep those and just update the ones we do care about.
_steps.JsonTransactionStep.Process(guideCfs, radarrCfs);
// Step 1.1: Optionally record deletions of custom formats in cache but not in the guide
if (config.DeleteOldCustomFormats)
{
_steps.JsonTransactionStep.RecordDeletions(deletedCfsInCache, radarrCfs);
}
// Step 2: For each merged CF, persist it to Radarr via its API. This will involve a combination of updates
// to existing CFs and creation of brand new ones, depending on what's already available in Radarr.
await _steps.CustomFormatCustomFormatApiPersister.Process(
customFormatService, _steps.JsonTransactionStep.Transactions);
// Step 3: Update all quality profiles with the scores from the guide for the uploaded custom formats
await _steps.ProfileQualityProfileApiPersister.Process(
_qualityProfileServiceFactory(config.BuildUrl()), profileScores);
}
}
}
// using System;
// using System.Collections.Generic;
// using System.Threading.Tasks;
// using TrashLib.Radarr.Config;
// using TrashLib.Radarr.CustomFormat.Api;
// using TrashLib.Radarr.CustomFormat.Models;
// using TrashLib.Radarr.CustomFormat.Models.Cache;
// using TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps;
//
// namespace TrashLib.Radarr.CustomFormat.Processors
// {
// public interface IPersistenceProcessorSteps
// {
// public IJsonTransactionStep JsonTransactionStep { get; }
// public ICustomFormatApiPersistenceStep CustomFormatCustomFormatApiPersister { get; }
// public IQualityProfileApiPersistenceStep ProfileQualityProfileApiPersister { get; }
// }
//
// internal class PersistenceProcessor : IPersistenceProcessor
// {
// private readonly Func<string, ICustomFormatService> _customFormatServiceFactory;
// private readonly Func<string, IQualityProfileService> _qualityProfileServiceFactory;
// private readonly Func<IPersistenceProcessorSteps> _stepsFactory;
// private IPersistenceProcessorSteps _steps;
//
// public PersistenceProcessor(
// Func<string, ICustomFormatService> customFormatServiceFactory,
// Func<string, IQualityProfileService> qualityProfileServiceFactory,
// Func<IPersistenceProcessorSteps> stepsFactory)
// {
// _qualityProfileServiceFactory = qualityProfileServiceFactory;
// _stepsFactory = stepsFactory;
// _customFormatServiceFactory = customFormatServiceFactory;
// _steps = _stepsFactory();
// }
//
// public CustomFormatTransactionData Transactions
// => _steps.JsonTransactionStep.Transactions;
//
// public IDictionary<string, List<UpdatedFormatScore>> UpdatedScores
// => _steps.ProfileQualityProfileApiPersister.UpdatedScores;
//
// public IReadOnlyCollection<string> InvalidProfileNames
// => _steps.ProfileQualityProfileApiPersister.InvalidProfileNames;
//
// public void Reset()
// {
// _steps = _stepsFactory();
// }
//
// public async Task PersistCustomFormats(
// RadarrConfig config,
// IEnumerable<ProcessedCustomFormatData> guideCfs,
// IEnumerable<TrashIdMapping> deletedCfsInCache,
// IDictionary<string, QualityProfileCustomFormatScoreMapping> profileScores)
// {
// var customFormatService = _customFormatServiceFactory(config.BuildUrl());
// var radarrCfs = await customFormatService.GetCustomFormats();
//
// // Step 1: Match CFs between the guide & Radarr and merge the data. The goal is to retain as much of the
// // original data from Radarr as possible. There are many properties in the response JSON that we don't
// // directly care about. We keep those and just update the ones we do care about.
// _steps.JsonTransactionStep.Process(guideCfs, radarrCfs);
//
// // Step 1.1: Optionally record deletions of custom formats in cache but not in the guide
// if (config.DeleteOldCustomFormats)
// {
// _steps.JsonTransactionStep.RecordDeletions(deletedCfsInCache, radarrCfs);
// }
//
// // Step 2: For each merged CF, persist it to Radarr via its API. This will involve a combination of updates
// // to existing CFs and creation of brand new ones, depending on what's already available in Radarr.
// await _steps.CustomFormatCustomFormatApiPersister.Process(
// customFormatService, _steps.JsonTransactionStep.Transactions);
//
// // Step 3: Update all quality profiles with the scores from the guide for the uploaded custom formats
// await _steps.ProfileQualityProfileApiPersister.Process(
// _qualityProfileServiceFactory(config.BuildUrl()), profileScores);
// }
// }
// }

@ -1,5 +1,6 @@
using System.Linq;
using System;
using System.Threading.Tasks;
using TrashLib.Config;
using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Api;
using TrashLib.Radarr.CustomFormat.Cache;
@ -8,31 +9,36 @@ namespace TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps
{
internal class CustomFormatApiPersistenceStep : ICustomFormatApiPersistenceStep
{
private readonly ICustomFormatCache _cache;
private readonly Func<IServiceConfiguration, ICustomFormatCache> _cacheFactory;
public CustomFormatApiPersistenceStep(ICustomFormatCache cache)
public CustomFormatApiPersistenceStep(Func<IServiceConfiguration, ICustomFormatCache> cacheFactory)
{
_cache = cache;
_cacheFactory = cacheFactory;
}
public async Task Process(RadarrConfig config, ICustomFormatService api, CustomFormatTransactionData transactions)
public async Task Process(RadarrConfig config, ICustomFormatService api,
CustomFormatTransactionData transactions)
{
var cache = _cacheFactory(config);
foreach (var cf in transactions.NewCustomFormats)
{
var id = await api.CreateCustomFormat(cf);
_cache.Add(id, cf);
cache.Add(id, cf);
}
foreach (var cf in transactions.UpdatedCustomFormats)
foreach (var (customFormat, id) in transactions.UpdatedCustomFormats)
{
await api.UpdateCustomFormat(cf.Id, cf.CustomFormat);
await api.UpdateCustomFormat(id, customFormat);
}
foreach (var cfId in transactions.DeletedCustomFormatIds)
{
await api.DeleteCustomFormat(cfId.CustomFormatId);
_cache.Remove(cfId);
cache.Remove(cfId);
}
cache.Save();
}
}
}

@ -5,6 +5,5 @@ namespace TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps
{
public interface ICustomFormatApiPersistenceStep
{
Task Process(ICustomFormatService api, CustomFormatTransactionData transactions);
}
}

@ -8,13 +8,5 @@ namespace TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps
{
public interface IJsonTransactionStep
{
CustomFormatTransactionData Transactions { get; }
void Process(
IEnumerable<ProcessedCustomFormatData> guideCfs,
IReadOnlyCollection<JObject> radarrCfs,
RadarrConfig config);
void RecordDeletions(IEnumerable<TrashIdMapping> deletedCfsInCache, List<JObject> radarrCfs);
}
}

@ -5,7 +5,7 @@ using Common.Extensions;
using Newtonsoft.Json.Linq;
using TrashLib.Config;
using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Api;
using TrashLib.Radarr.CustomFormat.Api.Models;
using TrashLib.Radarr.CustomFormat.Cache;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
@ -35,7 +35,7 @@ namespace TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps
public void Process(
IEnumerable<ProcessedCustomFormatData> guideCfs,
IReadOnlyCollection<JObject> radarrCfs,
IReadOnlyCollection<CustomFormatData> radarrCfs,
RadarrConfig config)
{
var cache = _cacheFactory(config);
@ -44,24 +44,22 @@ namespace TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps
{
var mapping = cache.Mappings.FirstOrDefault(m => m.TrashId == guideCf.TrashId);
var radarrCf = FindRadarrCf(radarrCfs, mapping?.CustomFormatId, guideCf.Name);
var guideCfJson = BuildNewRadarrCf(guideCf.Json);
FixupRadarrCf(guideCf.Data);
// no match; we add this CF as brand new
if (radarrCf == null)
{
guideCf.Json = guideCfJson;
Transactions.NewCustomFormats.Add(guideCf);
}
// found match in radarr CFs; update the existing CF
else
{
guideCf.Json = (JObject) radarrCf.DeepClone();
UpdateRadarrCf(guideCf.Json, guideCfJson);
var originalRadarrJson = JObject.FromObject(radarrCf);
UpdateRadarrCf(radarrCf, guideCf.Data);
if (!JToken.DeepEquals(radarrCf, guideCf.Json))
if (!JToken.DeepEquals(JObject.FromObject(radarrCf), originalRadarrJson))
{
Transactions.UpdatedCustomFormats.Add(
new CachedCustomFormat(guideCf, (int) guideCf.Json["id"]));
Transactions.UpdatedCustomFormats.Add(new CachedCustomFormat(guideCf, guideCf.Data.Id));
}
else
{
@ -72,7 +70,7 @@ namespace TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps
}
public void RecordDeletions(IEnumerable<ProcessedCustomFormatData> guideCfs,
List<RadarrCustomFormatData> radarrCfs, RadarrConfig config)
List<CustomFormatData> radarrCfs, RadarrConfig config)
{
var cache = _cacheFactory(config);
@ -88,60 +86,64 @@ namespace TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps
}
}
private static JObject? FindRadarrCf(IReadOnlyCollection<JObject> radarrCfs, int? cfId, string? cfName)
private static CustomFormatData? FindRadarrCf(IReadOnlyCollection<CustomFormatData> radarrCfs,
int? cfId, string? cfName)
{
JObject? match = null;
CustomFormatData? match = null;
// Try to find match in cache first
if (cfId != null)
{
match = radarrCfs.FirstOrDefault(rcf => cfId == rcf["id"].Value<int>());
match = radarrCfs.FirstOrDefault(rcf => cfId == rcf.Id);
}
// If we don't find by ID, search by name (if a name was given)
if (match == null && cfName != null)
{
match = radarrCfs.FirstOrDefault(rcf => cfName.EqualsIgnoreCase(rcf["name"].Value<string>()));
match = radarrCfs.FirstOrDefault(rcf => cfName.EqualsIgnoreCase(rcf.Name));
}
return match;
}
private static void UpdateRadarrCf(JObject cfToModify, JObject cfToMergeFrom)
private static void UpdateRadarrCf(CustomFormatData cfToModify, CustomFormatData cfToMergeFrom)
{
MergeProperties(cfToModify, cfToMergeFrom, JTokenType.Array);
MergeProperties(cfToModify.ExtraJson, cfToMergeFrom.ExtraJson);
var radarrSpecs = cfToModify["specifications"].Children<JObject>();
var guideSpecs = cfToMergeFrom["specifications"].Children<JObject>();
var radarrSpecs = cfToModify.Specifications;
var guideSpecs = cfToMergeFrom.Specifications;
var matchedGuideSpecs = guideSpecs
.GroupBy(gs => radarrSpecs.FirstOrDefault(gss => KeyMatch(gss, gs, "name")))
.GroupBy(gs => radarrSpecs.FirstOrDefault(gss => gss.Name == gs.Name))
.SelectMany(kvp => kvp.Select(gs => new {GuideSpec = gs, RadarrSpec = kvp.Key}));
var newRadarrSpecs = new JArray();
cfToModify.Specifications.Clear();
foreach (var match in matchedGuideSpecs)
{
if (match.RadarrSpec != null)
{
MergeProperties(match.RadarrSpec, match.GuideSpec);
newRadarrSpecs.Add(match.RadarrSpec);
MergeProperties(match.RadarrSpec.ExtraJson, match.GuideSpec.ExtraJson);
cfToModify.Specifications.Add(match.RadarrSpec);
}
else
{
newRadarrSpecs.Add(match.GuideSpec);
cfToModify.Specifications.Add(match.GuideSpec);
}
}
cfToModify["specifications"] = newRadarrSpecs;
}
private static bool KeyMatch(JObject left, JObject right, string keyName)
=> left[keyName].Value<string>() == right[keyName].Value<string>();
// private static bool KeyMatch(CustomFormatData left, CustomFormatData right, string keyName)
// => left[keyName].Value<string>() == right[keyName].Value<string>();
private static void MergeProperties(JObject radarrCf, JObject guideCfJson,
private static void MergeProperties(JObject? radarrCf, JObject? guideCfJson,
JTokenType exceptType = JTokenType.None)
{
if (radarrCf == null || guideCfJson == null)
{
return;
}
foreach (var guideProp in guideCfJson.Properties().Where(p => p.Value.Type != exceptType))
{
if (guideProp.Value.Type == JTokenType.Array &&
@ -159,7 +161,7 @@ namespace TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps
}
}
private static JObject BuildNewRadarrCf(JObject jsonPayload)
private static void FixupRadarrCf(CustomFormatData cfData)
{
// Information on required fields from nitsua
/*
@ -169,17 +171,20 @@ namespace TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps
everything else radarr can handle with backend logic
*/
foreach (var child in jsonPayload["specifications"])
foreach (var spec in cfData.Specifications)
{
if (spec.ExtraJson is null)
{
continue;
}
// convert from `"fields": {}` to `"fields": [{}]` (object to array of object)
// Weirdly the exported version of a custom format is not in array form, but the API requires the array
// even if there's only one element.
var field = child["fields"];
var field = spec.ExtraJson["fields"];
field["name"] = "value";
child["fields"] = new JArray {field};
spec.ExtraJson["fields"] = new JArray {field};
}
return jsonPayload;
}
}
}

Loading…
Cancel
Save