refactor(radarr): local repo guide service

For accessing custom format JSON data using a local Git clone instead of
using the Github API. The Github API has limitations on the number of
times you can use it, which causes intermittent failures.
recyclarr
Robert Dailey 4 years ago
parent bd6bd0b225
commit a6dfb1e220

@ -1,32 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using TrashLib.Radarr.CustomFormat.Guide;
namespace TrashLib.Tests.Radarr.CustomFormat.Guide
{
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class GithubCustomFormatJsonRequesterTest
{
[Test]
public async Task Requesting_json_from_github_works()
{
var requester = new GithubCustomFormatJsonRequester();
var jsonList = (await requester.GetCustomFormatJson()).ToList();
Action act = () => JObject.Parse(jsonList.First());
// As of the time this test was written, there are around 58 custom format JSON files.
// This number can fluctuate over time, but I'm only interested in verifying we get a handful
// of files in the response.
jsonList.Should().HaveCountGreaterOrEqualTo(5);
act.Should().NotThrow();
}
}
}

@ -2,8 +2,8 @@
using System.Diagnostics.CodeAnalysis;
using Common;
using FluentAssertions;
using Flurl.Http.Testing;
using Newtonsoft.Json.Linq;
using NSubstitute;
using NUnit.Framework;
using Serilog;
using TestLibrary.FluentAssertions;
@ -55,25 +55,18 @@ namespace TrashLib.Tests.Radarr.CustomFormat.Processors
public void Guide_processor_behaves_as_expected_with_normal_guide_data()
{
var ctx = new Context();
var guideProcessor =
new GuideProcessor(ctx.Logger, new GithubCustomFormatJsonRequester(),
() => new TestGuideProcessorSteps());
var guideService = Substitute.For<IRadarrGuideService>();
var guideProcessor = new GuideProcessor(ctx.Logger, guideService, () => new TestGuideProcessorSteps());
// simulate guide data
using var testHttp = new HttpTest();
testHttp.RespondWithJson(new[]
{
new {name = "ImportableCustomFormat1.json", type = "file", download_url = "http://not_real/file.json"},
new {name = "ImportableCustomFormat2.json", type = "file", download_url = "http://not_real/file.json"},
new {name = "NoScore.json", type = "file", download_url = "http://not_real/file.json"},
new {name = "WontBeInConfig.json", type = "file", download_url = "http://not_real/file.json"}
// Simulate custom format JSON data from local Git repository
guideService.GetCustomFormatJson().Returns(new[]
{
ctx.Data.ReadData("ImportableCustomFormat1.json"),
ctx.Data.ReadData("ImportableCustomFormat2.json"),
ctx.Data.ReadData("NoScore.json"),
ctx.Data.ReadData("WontBeInConfig.json")
});
testHttp.RespondWithJson(ctx.ReadJson("ImportableCustomFormat1.json"));
testHttp.RespondWithJson(ctx.ReadJson("ImportableCustomFormat2.json"));
testHttp.RespondWithJson(ctx.ReadJson("NoScore.json"));
testHttp.RespondWithJson(ctx.ReadJson("WontBeInConfig.json"));
// Simulate user config in YAML
var config = new List<CustomFormatConfig>
{

@ -0,0 +1,7 @@
namespace TrashLib.Config
{
public interface IResourcePaths
{
string RepoPath { get; }
}
}

@ -2,7 +2,7 @@
{
public abstract class ServiceConfiguration : IServiceConfiguration
{
public string BaseUrl { get; init; } = "";
public string ApiKey { get; init; } = "";
public string BaseUrl { get; set; } = "";
public string ApiKey { get; set; } = "";
}
}

@ -1,57 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Flurl.Http;
using Flurl.Http.Configuration;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace TrashLib.Radarr.CustomFormat.Guide
{
internal class GithubCustomFormatJsonRequester : IRadarrGuideService
{
private readonly ISerializer _flurlSerializer;
public GithubCustomFormatJsonRequester()
{
// In addition to setting the naming strategy, this also serves as a mechanism to avoid inheriting the
// global Flurl serializer setting: MissingMemberHandling. We do not want missing members to error out
// since we're only deserializing a subset of the github response object.
_flurlSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
}
});
}
public async Task<IEnumerable<string>> GetCustomFormatJson()
{
var response = await "https://api.github.com/repos/TRaSH-/Guides/contents/docs/json/radarr"
.WithHeader("User-Agent", "Trash Updater")
.ConfigureRequest(settings => settings.JsonSerializer = _flurlSerializer)
.GetJsonAsync<List<RepoContentEntry>>();
var tasks = response
.Where(o => o.Type == "file" && o.Name.EndsWith(".json"))
.Select(o => DownloadJsonContents(o.DownloadUrl));
return await Task.WhenAll(tasks);
}
private async Task<string> DownloadJsonContents(string jsonUrl)
{
return await jsonUrl.GetStringAsync();
}
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
private record RepoContentEntry
{
public string Name { get; init; } = default!;
public string Type { get; init; } = default!;
public string DownloadUrl { get; init; } = default!;
}
}
}

@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using LibGit2Sharp;
using TrashLib.Config;
namespace TrashLib.Radarr.CustomFormat.Guide
{
internal class LocalRepoCustomFormatJsonParser : IRadarrGuideService
{
private readonly IFileSystem _fileSystem;
private readonly string _repoPath;
public LocalRepoCustomFormatJsonParser(IFileSystem fileSystem, IResourcePaths paths)
{
_fileSystem = fileSystem;
_repoPath = paths.RepoPath;
}
public async Task<IEnumerable<string>> GetCustomFormatJson()
{
await Task.Run(() =>
{
if (!Repository.IsValid(_repoPath))
{
_fileSystem.Directory.Delete(_repoPath, true);
Repository.Clone("https://github.com/TRaSH-/Guides.git", _repoPath, new CloneOptions
{
RecurseSubmodules = false
});
}
using var repo = new Repository(_repoPath);
Commands.Checkout(repo, "master", new CheckoutOptions
{
CheckoutModifiers = CheckoutModifiers.Force
});
var origin = repo.Network.Remotes["origin"];
Commands.Fetch(repo, origin.Name, origin.FetchRefSpecs.Select(s => s.Specification), null, "");
repo.Reset(ResetMode.Hard, repo.Branches["origin/master"].Tip);
});
var jsonDir = Path.Combine(_repoPath, "docs/json/radarr");
var tasks = _fileSystem.Directory.GetFiles(jsonDir, "*.json")
.Select(async f => await _fileSystem.File.ReadAllTextAsync(f));
return await Task.WhenAll(tasks);
}
}
}

@ -37,13 +37,12 @@ namespace TrashLib.Radarr
// Custom Format Support
builder.RegisterType<CustomFormatUpdater>().As<ICustomFormatUpdater>();
builder.RegisterType<GithubCustomFormatJsonRequester>().As<IRadarrGuideService>();
builder.RegisterType<LocalRepoCustomFormatJsonParser>().As<IRadarrGuideService>();
builder.RegisterType<CachePersister>().As<ICachePersister>();
// Guide Processor
builder.RegisterType<GuideProcessor>()
.As<
IGuideProcessor>(); // todo: register as singleton to avoid parsing guide multiple times when using 2 or more instances in config
// todo: register as singleton to avoid parsing guide multiple times when using 2 or more instances in config
builder.RegisterType<GuideProcessor>().As<IGuideProcessor>();
builder.RegisterAggregateService<IGuideProcessorSteps>();
builder.RegisterType<CustomFormatStep>().As<ICustomFormatStep>();
builder.RegisterType<ConfigStep>().As<IConfigStep>();

@ -9,6 +9,7 @@
<PackageReference Include="System.Data.HashFunction.FNV" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="morelinq" />
<PackageReference Include="LibGit2Sharp" />
</ItemGroup>
<ItemGroup>

Loading…
Cancel
Save