180 lines
5.7 KiB
C#
180 lines
5.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
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. For example, this could be year and
|
|
/// month archive pages, tag or category pages. Support is given for
|
|
/// merging existing pages so a description could be written from a file
|
|
/// and then the index logic is automatically added.
|
|
/// </summary>
|
|
public class CreateIndexEntities<TIndexKey> : OperationBase
|
|
where TIndexKey : notnull
|
|
{
|
|
// TODO: This does not use [WithProperties] because the source generator hasn't been taught how to do generics.
|
|
|
|
private readonly ILogger logger;
|
|
|
|
private readonly IValidator<CreateIndexEntities<TIndexKey>> validator;
|
|
|
|
public CreateIndexEntities(ILogger logger)
|
|
{
|
|
// TODO: Figure out why Autofac won't let us register IValidator of generic classes.
|
|
this.validator = new CreateIndexEntitiesValidator<TIndexKey>();
|
|
this.logger = logger.ForContext(typeof(CreateIndexEntities<>));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an index for a given key. This will not be called for any
|
|
/// index that has been already created.
|
|
/// </summary>
|
|
public Func<TIndexKey, IList<Entity>, Entity>? CreateIndexEntity
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <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, TIndexKey?>? GetIndexEntityKey { get; set; }
|
|
|
|
/// <summary>
|
|
/// A method that gets the keys for a given entity. If this returns an
|
|
/// empty list, then the entity will not added to an index.
|
|
/// </summary>
|
|
public Func<Entity, IEnumerable<TIndexKey>>? GetIndexKeys { get; set; }
|
|
|
|
/// <summary>
|
|
/// Updates an existing index entity to include new information.
|
|
/// </summary>
|
|
public Func<Entity, TIndexKey, IList<Entity>, Entity>? UpdateIndexEntity
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
|
{
|
|
// Make sure we have sane data.
|
|
this.validator.ValidateAndThrow(this);
|
|
|
|
// We need to process two lists out of the output, so we need to put
|
|
// it into a list so we can enumerate through it twice. This will
|
|
// also cause the output to be reordered.
|
|
Dictionary<TIndexKey, Entity> indexes = new();
|
|
Dictionary<TIndexKey, List<Entity>> indexed = new();
|
|
List<Entity> results = new();
|
|
|
|
foreach (Entity? entity in input)
|
|
{
|
|
// See if we are an index page first.
|
|
if (this.GetIndexEntityKey != null)
|
|
{
|
|
TIndexKey? indexKey = this.GetIndexEntityKey(entity);
|
|
|
|
if (indexKey != null)
|
|
{
|
|
indexes[indexKey] = entity;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// We aren't an index, so check to see if this entity is
|
|
// something to be indexed.
|
|
foreach (TIndexKey indexedKey in this.GetIndexKeys!(entity))
|
|
{
|
|
if (!indexed.TryGetValue(indexedKey, out List<Entity>? list))
|
|
{
|
|
indexed[indexedKey] = list = new List<Entity>();
|
|
}
|
|
|
|
list.Add(entity);
|
|
}
|
|
|
|
// Add to the non-index page list.
|
|
results.Add(entity);
|
|
}
|
|
|
|
// Go through all the index pages and update them. We get a list of
|
|
// all the pages in the index and pass them into the function to
|
|
// update the existing index. Then we update the entity and add it
|
|
// to the bottom of the results list.
|
|
foreach ((TIndexKey key, Entity? oldIndex) in indexes)
|
|
{
|
|
if (!indexed.TryGetValue(key, out List<Entity>? list))
|
|
{
|
|
list = new List<Entity>();
|
|
}
|
|
|
|
Entity newEntity = this.UpdateIndexEntity!(oldIndex, key, list);
|
|
|
|
results.Add(newEntity);
|
|
}
|
|
|
|
// Go through all the known index keys and create the missing pages.
|
|
int created = 0;
|
|
|
|
foreach ((TIndexKey key, List<Entity>? list) in indexed)
|
|
{
|
|
// See if we already have a page, if we do, then we've already
|
|
// processed that page and don't have to do anything.
|
|
if (indexes.ContainsKey(key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We don't have that page and need to add it to the list.
|
|
Entity entity = this.CreateIndexEntity!(key, list);
|
|
|
|
created++;
|
|
results.Add(entity);
|
|
}
|
|
|
|
// Return the combined together version.
|
|
this.logger.Debug(
|
|
"Found {Old:N0} and created {New:N0} index pages for {Keys:N0} keys",
|
|
indexes.Count,
|
|
created,
|
|
indexed.Count);
|
|
|
|
return results;
|
|
}
|
|
|
|
public CreateIndexEntities<TIndexKey> WithCreateIndexEntity(Func<TIndexKey, IList<Entity>, Entity>? callback)
|
|
{
|
|
this.CreateIndexEntity = callback;
|
|
return this;
|
|
}
|
|
|
|
public CreateIndexEntities<TIndexKey> WithGetIndexEntityKey(Func<Entity, TIndexKey?>? callback)
|
|
{
|
|
this.GetIndexEntityKey = callback;
|
|
return this;
|
|
}
|
|
|
|
public CreateIndexEntities<TIndexKey> WithGetIndexKeys(Func<Entity, IEnumerable<TIndexKey>>? callback)
|
|
{
|
|
this.GetIndexKeys = callback;
|
|
return this;
|
|
}
|
|
|
|
public CreateIndexEntities<TIndexKey> WithUpdateIndexEntity(
|
|
Func<Entity, TIndexKey, IList<Entity>, Entity>? callback)
|
|
{
|
|
this.UpdateIndexEntity = callback;
|
|
return this;
|
|
}
|
|
}
|