115 lines
3.7 KiB
C#
115 lines
3.7 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
|
||
|
using FluentValidation;
|
||
|
|
||
|
using Gallium;
|
||
|
|
||
|
using Serilog;
|
||
|
|
||
|
namespace Nitride.Entities;
|
||
|
|
||
|
/// <summary>
|
||
|
/// A Nitride operation that creates and merges entities that are intended
|
||
|
/// to be indexes of another entity. Examples of this would be year and month
|
||
|
/// archive pages for a blog or a tag/category pages for associated data. This
|
||
|
/// uses the scanner to determine how many index entities are needed and then
|
||
|
/// merges existing entities with their data or creates new indexes for ones
|
||
|
/// that don't already have an index.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// This makes the assumption that there is one index per page.
|
||
|
/// </remarks>
|
||
|
[WithProperties]
|
||
|
public partial class CreateOrUpdateIndex : OperationBase
|
||
|
{
|
||
|
private readonly ILogger logger;
|
||
|
|
||
|
private readonly IValidator<CreateOrUpdateIndex> validator;
|
||
|
|
||
|
public CreateOrUpdateIndex(ILogger logger, IValidator<CreateOrUpdateIndex> validator)
|
||
|
{
|
||
|
this.validator = validator;
|
||
|
this.logger = logger.ForContext(typeof(CreateOrUpdateIndex));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates an index for a given key. This will not be called for any
|
||
|
/// index that has been already created.
|
||
|
/// </summary>
|
||
|
public Func<string, IList<Entity>, Entity> CreateIndex { get; set; } = null!;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets the function to retrieve the key from an existing
|
||
|
/// index page. If this returns null, then the entity is considered not
|
||
|
/// to be an index page.
|
||
|
/// </summary>
|
||
|
public Func<Entity, string?> GetIndexKey { get; set; } = null!;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets the scanner that provides the keys.
|
||
|
/// </summary>
|
||
|
public EntityScanner Scanner { get; set; } = null!;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Updates an existing index entity to include new information.
|
||
|
/// </summary>
|
||
|
public Func<Entity, string, IEnumerable<Entity>, Entity> UpdateIndex { get; set; } = null!;
|
||
|
|
||
|
/// <inheritdoc />
|
||
|
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||
|
{
|
||
|
// Make sure we have sane data.
|
||
|
this.validator.ValidateAndThrow(this);
|
||
|
|
||
|
// Get the list of all the scanned entities.
|
||
|
var scanned = this.Scanner.GetScannedResults().ToDictionary(x => x.Key, x => x.Value);
|
||
|
|
||
|
// We loop through the results and look for index entities. Any one we
|
||
|
// find, we update with the existing entries. If we get to the end and
|
||
|
// still have any left over, we create those pages.
|
||
|
HashSet<string> existing = new();
|
||
|
|
||
|
foreach (Entity? entity in input)
|
||
|
{
|
||
|
// See if this entity is an index for anything.
|
||
|
string? key = this.GetIndexKey(entity);
|
||
|
|
||
|
if (key == null)
|
||
|
{
|
||
|
// Not an index page, we don't need to pay attention.
|
||
|
yield return entity;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// This is an existing entity page that needs to be updated.
|
||
|
IEnumerable<Entity> entries =
|
||
|
scanned.TryGetValue(key, out List<Entity>? list) ? list : Array.Empty<Entity>();
|
||
|
|
||
|
existing.Add(key);
|
||
|
|
||
|
yield return this.UpdateIndex(entity, key, entries);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Once we're done with the list, we need to create the missing indexes.
|
||
|
foreach (string? key in scanned.Keys)
|
||
|
{
|
||
|
if (existing.Contains(key))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
yield return this.CreateIndex(key, scanned[key]);
|
||
|
}
|
||
|
|
||
|
// Report the results.
|
||
|
this.logger.Debug(
|
||
|
"Found {Old:N0} and created {New:N0} index pages for {Keys:N0} keys",
|
||
|
existing.Count,
|
||
|
scanned.Count - existing.Count,
|
||
|
scanned.Keys.Count());
|
||
|
}
|
||
|
}
|