include cache update
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Quartz;
|
||||
|
||||
namespace Solicitors.CacheBuild;
|
||||
|
||||
@@ -10,6 +12,43 @@ public static class DIExtensions
|
||||
{
|
||||
return services
|
||||
.AddScoped<ISolicitorParser, SolicitorsDotCom.SolicitorParser>()
|
||||
.AddScoped<ISolicitorImporter, SolicitorImporter>();
|
||||
.AddScoped<ISolicitorImporter, SolicitorImporter>()
|
||||
.AddQuartz()
|
||||
.AddQuartzHostedService(opt => opt.WaitForJobsToComplete = true);
|
||||
}
|
||||
|
||||
public static async Task UseCacheBuildAsync(this IServiceScope scope, CancellationToken cancellationToken)
|
||||
{
|
||||
var factory = scope.ServiceProvider.GetRequiredService<ISchedulerFactory>();
|
||||
var options = scope.ServiceProvider.GetRequiredService<IOptions<ImportConfiguration>>().Value;
|
||||
var scheduler = await factory.GetScheduler(cancellationToken);
|
||||
|
||||
var importJob = JobBuilder.Create<ImportRunner>()
|
||||
.WithIdentity("ImportJob", "ImportGroup")
|
||||
.Build();
|
||||
|
||||
var importTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("ImportRunner", "ImportGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(builder => builder
|
||||
.WithIntervalInMinutes(options.ImportMinutes)
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(importJob, importTrigger, cancellationToken);
|
||||
|
||||
var staleJob = JobBuilder.Create<StaleDataRemover>()
|
||||
.WithIdentity("StaleDataRemover", "RemoveGroup")
|
||||
.Build();
|
||||
|
||||
var staleTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity("StaleDataTrigger", "RemoveGroup")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(builder => builder
|
||||
.WithIntervalInMinutes(options.RemoveMinutes)
|
||||
.RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(staleJob, staleTrigger, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Solicitors.CacheBuild;
|
||||
|
||||
public class ImportConfiguration
|
||||
{
|
||||
public int StaleMinutes { get; set; }
|
||||
public int RemoveMinutes { get; set; }
|
||||
public int ImportMinutes { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Quartz;
|
||||
using Solicitors.Core.Data;
|
||||
|
||||
namespace Solicitors.CacheBuild;
|
||||
|
||||
internal class ImportRunner(
|
||||
ISolicitorImporter importer,
|
||||
ISolicitorRepository repository) : IJob
|
||||
{
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
await repository.EnsureCreatedAsync(context.CancellationToken);
|
||||
await importer.RunFullImport(context.CancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -3,27 +3,18 @@ using Solicitors.Core.Models.Imports;
|
||||
|
||||
namespace Solicitors.CacheBuild;
|
||||
|
||||
internal class SolicitorImporter : ISolicitorImporter
|
||||
internal class SolicitorImporter(
|
||||
ISolicitorParser parser,
|
||||
ISolicitorRepository repository) : ISolicitorImporter
|
||||
{
|
||||
private readonly ISolicitorParser _parser;
|
||||
private readonly ISolicitorRepository _repository;
|
||||
|
||||
public SolicitorImporter(
|
||||
ISolicitorParser parser,
|
||||
ISolicitorRepository repository)
|
||||
{
|
||||
_parser = parser;
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
public async Task RunFullImport(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var solicitor in await _parser.GetSolicitorsAsync(cancellationToken))
|
||||
foreach (var solicitor in await parser.GetSolicitorsAsync(cancellationToken))
|
||||
{
|
||||
await RunImport(solicitor, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private Task RunImport(SolicitorData solicitor, CancellationToken cancellationToken)
|
||||
=> _repository.AddOrUpdateSolicitorAsync(solicitor, cancellationToken);
|
||||
=> repository.AddOrUpdateSolicitorAsync(solicitor, cancellationToken);
|
||||
}
|
||||
@@ -13,6 +13,15 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
|
||||
<PackageReference Include="Quartz" Version="3.18.1" />
|
||||
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.18.1" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.18.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Extensions.Hosting.Abstractions">
|
||||
<HintPath>..\..\..\..\..\..\..\.dotnet\shared\Microsoft.AspNetCore.App\10.0.7\Microsoft.Extensions.Hosting.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Http.Headers;
|
||||
using Solicitors.Core.Misc;
|
||||
using Solicitors.Core.Models.Imports;
|
||||
using Solicitors.HtmlParsing;
|
||||
@@ -154,7 +153,7 @@ internal class SolicitorParser : ISolicitorParser
|
||||
ParseSidebar(sidebarChildren, out phone, out email, out website, out ratings);
|
||||
}
|
||||
|
||||
return new SolicitorData()
|
||||
return new SolicitorData
|
||||
{
|
||||
Name = builder.Name,
|
||||
UrlPath = builder.Path,
|
||||
@@ -168,14 +167,14 @@ internal class SolicitorParser : ISolicitorParser
|
||||
};
|
||||
}
|
||||
|
||||
Location[] ParseOffices(IEnumerable<IHtmlNode> officeNodes)
|
||||
private Location[] ParseOffices(IEnumerable<IHtmlNode> officeNodes)
|
||||
=> officeNodes
|
||||
.Select(ParseOffice)
|
||||
.Where(x => x is not null)
|
||||
.Cast<Location>()
|
||||
.ToArray();
|
||||
|
||||
Location? ParseOffice(IHtmlNode officeNode)
|
||||
private Location? ParseOffice(IHtmlNode officeNode)
|
||||
{
|
||||
if (!officeNode.TryGetChildren(out var officeChildren)
|
||||
|| !officeNode.TryGetByTagName("address", out var addressNode)
|
||||
@@ -183,7 +182,7 @@ internal class SolicitorParser : ISolicitorParser
|
||||
return null;
|
||||
|
||||
Location? location = null;
|
||||
string? address = "";
|
||||
var address = "";
|
||||
foreach (var line in addressLines)
|
||||
{
|
||||
if (line.TryGetText(out var lineText))
|
||||
@@ -214,7 +213,7 @@ internal class SolicitorParser : ISolicitorParser
|
||||
return location;
|
||||
}
|
||||
|
||||
void ParseSidebar(
|
||||
private void ParseSidebar(
|
||||
IEnumerable<IHtmlNode> sidebarChildren,
|
||||
out string? phone,
|
||||
out string? email,
|
||||
@@ -253,7 +252,7 @@ internal class SolicitorParser : ISolicitorParser
|
||||
foreach (var child in childrenArray.Where(x => x.TryGetByClass("rev-box", out _, true)))
|
||||
{
|
||||
if (child.TryGetAttributeValue("title", out var ratingString)
|
||||
&& TryParseRatingString(ratingString, out decimal rating, out decimal maxRating)
|
||||
&& TryParseRatingString(ratingString, out var rating, out var maxRating)
|
||||
&& child.TryGetByTagNameAndClass("img", "rev-logo", out var imgNode)
|
||||
&& imgNode.TryGetAttributeValue("src", out var imgSrc)
|
||||
&& TryParseRatingSrc(imgSrc, out var ratingProvider))
|
||||
@@ -290,15 +289,15 @@ internal class SolicitorParser : ISolicitorParser
|
||||
return decimal.TryParse(parts[0], out rating) && decimal.TryParse(parts[1], out maxRating);
|
||||
}
|
||||
|
||||
bool TryParseRatingSrc(string imgSrc, out string provider)
|
||||
private bool TryParseRatingSrc(string imgSrc, out string provider)
|
||||
{
|
||||
provider = "";
|
||||
imgSrc = imgSrc.Trim('"');
|
||||
var prefix = "/images/logo-";
|
||||
const string prefix = "/images/logo-";
|
||||
if (!imgSrc.StartsWith(prefix))
|
||||
return false;
|
||||
|
||||
var providerAndExtension = imgSrc.Substring(prefix.Length);
|
||||
var providerAndExtension = imgSrc[prefix.Length..];
|
||||
provider = providerAndExtension.Split('.').First();
|
||||
return true;
|
||||
}
|
||||
@@ -306,11 +305,11 @@ internal class SolicitorParser : ISolicitorParser
|
||||
bool TryParseOwnRatingString(string ratingString, out decimal rating)
|
||||
{
|
||||
rating = 0;
|
||||
var prefix = "Average review score : ";
|
||||
const string prefix = "Average review score : ";
|
||||
if (!ratingString.StartsWith(prefix))
|
||||
return false;
|
||||
|
||||
var ratingPart = ratingString.Substring(prefix.Length);
|
||||
var ratingPart = ratingString[prefix.Length..];
|
||||
return decimal.TryParse(ratingPart, out rating);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Quartz;
|
||||
using Solicitors.Core.Data;
|
||||
|
||||
namespace Solicitors.CacheBuild;
|
||||
|
||||
internal class StaleDataRemover(
|
||||
IOptions<ImportConfiguration> config,
|
||||
ISolicitorRepository repository) : IJob
|
||||
{
|
||||
private readonly ImportConfiguration _config = config.Value;
|
||||
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
return repository.RemoveStaleEntriesAsync(
|
||||
TimeSpan.FromMinutes(_config.StaleMinutes),
|
||||
context.CancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user