Глобальное обновление #1

Merged
kozhilya merged 33 commits from dev into master 2025-06-01 22:28:15 +04:00
64 changed files with 921 additions and 601 deletions
Showing only changes of commit 34be3a5fc4 - Show all commits
@@ -1,79 +0,0 @@
using System;
using System.Collections.Generic;
namespace Pf2eDataScrapper.App;
/// <summary>
/// Общий аттрибут обработчика.
/// </summary>
/// <param name="name">Название обработчика.</param>
[AttributeUsage(AttributeTargets.Class)]
public class ProcessorAttribute(string name) : Attribute
{
/// <summary>
/// Название обработчика.
/// </summary>
public string Name { get; init; } = name;
/// <summary>
/// Описание обработчика.
/// </summary>
public string Description { get; init; } = name;
/// <summary>
/// Группа обработчиков, к которому относится обработчик.
/// </summary>
public ProcessorGroupEnum Group { get; init; } = ProcessorGroupEnum.General;
/// <summary>
/// Процессор включён по умолчанию.
/// </summary>
public bool Active { get; init; } = true;
/// <summary>
/// Разрешить выполнять процессов в параллельном режиме
/// </summary>
public bool Parallel { get; init; } = false;
/// <summary>
/// Обработчик набора элементов
/// </summary>
public virtual bool Collection { get; } = false;
}
/// <summary>
/// Аттрибут обработчика набор элементов.
/// </summary>
/// <param name="name">Название обработчика.</param>
[AttributeUsage(AttributeTargets.Class)]
public class ProcessorCollectionAttribute(string name) : ProcessorAttribute(name)
{
/// <summary>
/// Название типов, который обрабатывает этот оброботчик.
/// <c>null</c> - обработать все типы.
/// </summary>
public required string? InnerType { get; init; }
/// <summary>
/// Тип используемого компендиума.
/// </summary>
public required ProcessorCompendiumEnum CompendiumType { get; init; }
/// <summary>
/// Обработчик набора элементов.
/// </summary>
public override bool Collection { get; } = true;
}
/// <summary>
/// Аттрибут зависимостей обработчика.
/// </summary>
/// <param name="dependencies">Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.</param>
[AttributeUsage(AttributeTargets.Class)]
public class ProcessorDependenciesAttribute(string[] dependencies) : Attribute
{
/// <summary>
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
/// </summary>
public ICollection<string> Dependencies { get; } = dependencies;
}
-261
View File
@@ -1,261 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.App;
/// <summary>
/// Класс с коллекцией обработчиков.
/// </summary>
internal class ProcessorCollection
{
/// <summary>
/// Коллекция всех доступных обработчиков.
/// </summary>
public ICollection<ProcessorCollectionItem> AllItems { get; } = [];
/// <summary>
/// Коллекция обработчиков, которые будут обрабатываться.
/// Для изменения необходимо использовать <see cref="FilterProcessors"/>.
/// </summary>
public List<ProcessorCollectionItem> Items { get; private set; } = [];
/// <summary>
/// Обеспечение доступа к элементам обработчиков.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public ProcessorCollectionItem this[string name] => AllItems.First(item => item.Name == name);
/// <summary>
/// Конструктор класса <see cref="ProcessorCollection"/>.
/// </summary>
public ProcessorCollection()
{
Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes())
{
if (type.GetCustomAttributes(typeof(ProcessorAttribute), true).Length <= 0)
{
continue;
}
AllItems.Add(CreateItem(type));
}
CheckDependencies();
FilterProcessors([]);
}
/// <summary>
/// Создание элемента коллекции обработчиков.
/// </summary>
/// <param name="type">Тип обработчика.</param>
/// <returns></returns>
private ProcessorCollectionItem CreateItem(Type type)
{
if (!type.IsAssignableTo(typeof(IProcessor)))
{
throw new(
$"'ProcessorAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not."
);
}
ProcessorAttribute processorAttribute = type.GetCustomAttribute<ProcessorAttribute>()!;
ProcessorDependenciesAttribute? dependenciesAttribute =
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
bool isCollection = processorAttribute.Collection;
string? innerType = null;
ProcessorCompendiumEnum? compendium = null;
if (processorAttribute.GetType() == typeof(ProcessorCollectionAttribute))
{
ProcessorCollectionAttribute processorCollectionAttribute =
(ProcessorCollectionAttribute)processorAttribute;
innerType = processorCollectionAttribute.InnerType;
compendium = processorCollectionAttribute.CompendiumType;
}
return new()
{
Name = processorAttribute.Name,
Type = type,
Description = processorAttribute.Description,
Group = processorAttribute.Group,
Dependencies = dependenciesAttribute?.Dependencies ?? [],
IsCollection = isCollection,
InnerType = innerType,
Compendium = compendium,
Active = processorAttribute.Active,
Parallel = processorAttribute.Parallel,
};
}
/// <summary>
/// Проверка, что все зависимости всех обработчиков могут быть удовлетворены.
/// </summary>
private void CheckDependencies()
{
foreach (ProcessorCollectionItem item in Items)
{
foreach (string dependency in item.Dependencies)
{
if (Items.All(i => i.Name != dependency))
{
throw new($"Dependency '{dependency}' for processor '{item.Name}' not found.");
}
}
}
}
/// <summary>
/// Сортировать обработчики в соответсвии со всеми зависимостями.
/// </summary>
public void SortItems()
{
Items.ForEach(item => item.Priority = -1);
Dictionary<string, int> priorities = [];
while (priorities.Count < Items.Count)
{
foreach (ProcessorCollectionItem item in Items)
{
if (item.Priority >= 0)
{
continue;
}
IEnumerable<int> dependencyPriorities = item.Dependencies.Select(d =>
priorities.GetValueOrDefault(d, -1)
);
if (dependencyPriorities.Any(p => p == -1))
{
continue;
}
item.Priority = Math.Max(
dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1,
(int)item.Group * 100
);
priorities.Add(item.Name, item.Priority);
}
}
Items.Sort((a, b) => a.Priority - b.Priority);
}
/// <summary>
/// Ограничить список используемых обработчиков по названию.
/// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики.
/// </summary>
/// <param name="processorNames">Коллекция названиий требуемых обработчиков.</param>
public void FilterProcessors(ICollection<string> processorNames)
{
if (processorNames.Count == 0)
{
Items = [.. AllItems];
SortItems();
return;
}
Dictionary<string, bool> nameFlags = processorNames.ToDictionary(name => name, _ => false);
while (nameFlags.Values.Any(v => !v))
{
foreach (
KeyValuePair<string, bool> pair in nameFlags.Where(pair => !pair.Value).ToList()
)
{
ProcessorCollectionItem item = AllItems.First(item => item.Name == pair.Key);
foreach (string dependency in item.Dependencies)
{
nameFlags.TryAdd(dependency, false);
}
nameFlags[pair.Key] = true;
}
}
Items = AllItems.Where(item => nameFlags.ContainsKey(item.Name)).ToList();
}
/// <summary>
/// Получить список типов, которые обрабатываются в компендиуме.
/// </summary>
/// <param name="compendium">Тип компендиума.</param>
/// <returns>Набор типов.</returns>
public ICollection<string> GetTypesOfCompendium(ProcessorCompendiumEnum compendium) =>
Items.Select(item => item.InnerType).OfType<string>().ToList();
}
/// <summary>
/// Элемент коллекции обработчиков.
/// </summary>
internal class ProcessorCollectionItem
{
/// <summary>
/// Название обработчика.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Тип класса обработчика.
/// </summary>
public required Type Type { get; init; }
/// <summary>
/// Описание обработчика.
/// </summary>
public string Description { get; init; } = "";
/// <summary>
/// Группа обработчика.
/// </summary>
public required ProcessorGroupEnum Group { get; init; }
/// <summary>
/// Коллекция названий обработчиков, после которых должен выполняться обработчик.
/// </summary>
public ICollection<string> Dependencies { get; init; } = [];
/// <summary>
/// Процессор включён по умолчанию.
/// </summary>
public bool Active { get; init; } = true;
/// <summary>
/// Приоритет выполнения обработчика.
/// </summary>
public int Priority { get; set; } = -1;
/// <summary>
/// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов.
/// Внутренний тип элементов указывается в <see cref="InnerType"/>.
/// </summary>
public required bool IsCollection { get; init; }
/// <summary>
/// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов.
/// </summary>
public string? InnerType { get; init; }
/// <summary>
/// Название типов, который обрабатывает этот оброботчик.
/// <c>null</c> - обработать все типы.
/// </summary>
public ProcessorCompendiumEnum? Compendium { get; init; }
/// <summary>
/// Разрешить выполнять процессов в параллельном режиме
/// </summary>
public bool Parallel { get; init; } = true;
}
+1
View File
@@ -91,6 +91,7 @@ public class EntityView
.Include(c => c.ClassTrainings)
.ThenInclude(t => t.Training)
.Include(c => c.ClassProgressions)
.Include(c => c.SubclassGroups)
),
new Getter<Feat>(
EntityType.Feat,
+1 -1
View File
@@ -1,4 +1,4 @@
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.Helpers;
+2 -3
View File
@@ -2,10 +2,9 @@
using System.ComponentModel;
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.WPF;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eDataScrapper.WPF.EntityViewer;
using Pf2eDataScrapper.WPF.ProgressForm;
using Pf2eDataScrapper.WPF.StartForm;
@@ -102,7 +101,7 @@ internal sealed class ImportApplication : Application
_entityForm = ActivatorUtilities.CreateInstance<EntityForm>(ServiceProvider);
_entityForm.WindowStartupLocation = WindowStartupLocation.CenterScreen;
_entityForm.Closing += Wpf_HandleFormClosing;
//_entityForm.ApplicationStateChange += ApplicationStateChange;
_entityForm.ApplicationStateChange += ApplicationStateChange;
_entityForm.Show();
CurrentWpfForm = _entityForm;
@@ -1,4 +1,7 @@
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Pf2eModel.Model.Base.Targets;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
@@ -16,108 +19,64 @@ internal class AonprdBuilderGlobals
/// <summary>
/// Словарь идентификаторов источников.
/// </summary>
public Dictionary<string, int> SourceIds { get; set; } = [];
public Dictionary<string, int> SourceIds { get; } = [];
/// <summary>
/// Словарь идентификаторов признаков.
/// </summary>
public Dictionary<string, int> TraitIds { get; set; } = [];
/// <summary>
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
/// </summary>
public AonprdBuilderGlobals() { }
/// <summary>
/// Получить суффикс для ссылки.
/// </summary>
/// <param name="id">Идентификатор Aounprd.</param>
/// <returns>Суффикс для ссылки или пустая строка.</returns>
public string GetSlugSuffix(string id)
{
return SlugSuffixes.TryGetValue(id, out var slug) ? $"-{slug}" : string.Empty;
}
public Dictionary<string, int> TraitIds { get; } = [];
/// <summary>
/// Словарей суффиксов для сущностей
/// </summary>
public Dictionary<string, string> SlugSuffixes { get; } =
new()
{
["feat-5997"] = "legacy",
["feat-5730"] = "dragonblood",
["feat-6469"] = "general",
["feat-4344"] = "archetype",
["feat-3549"] = "vampire",
["feat-7380"] = "mythic",
["feat-5737"] = "dragonblood",
["feat-6284"] = "bounty-hunter",
["feat-2664"] = "archetype",
["feat-6883"] = "kitsune",
["feat-6209"] = "investigator",
["feat-6477"] = "undead-slayer",
["feat-7637"] = "dandy",
["feat-7004"] = "yaoguai",
["feat-4380"] = "stonebound",
};
public Dictionary<string, string> SlugSuffixes { get; } = [];
/// <summary>
/// Словарь допольнительных проверок связей.
/// Ключ - устаревшая сущность, значение - обновлённая.
/// </summary>
public Dictionary<string, string> LegacyRemasterExtra { get; } =
new()
{
["archetype-83"] = "archetype-129",
["archetype-86"] = "archetype-198",
["feat-5997"] = "feat-7108",
["feat-4088"] = "feat-5491",
["feat-1177"] = "feat-4553",
["feat-2986"] = "feat-4340",
["feat-1753"] = "feat-6031",
["feat-2729"] = "feat-4077",
["feat-480"] = "feat-4850",
["feat-2989"] = "feat-4343",
["feat-2987"] = "feat-4341",
["feat-2990"] = "feat-4346",
["feat-936"] = "feat-2269",
["feat-2992"] = "feat-4350",
["feat-2991"] = "feat-4349",
["feat-2742"] = "feat-4078",
["feat-1169"] = "feat-5055",
["feat-2743"] = "feat-4079",
["feat-963"] = "feat-4519",
["feat-1171"] = "feat-4858",
["feat-1167"] = "feat-5880",
["feat-455"] = "feat-6007",
["feat-2993"] = "feat-4352",
["language-91"] = "language-180",
["ritual-31"] = "ritual-191",
["spell-571"] = "spell-1975",
["spell-1052"] = "spell-1309",
["spell-1053"] = "spell-1306",
["spell-843"] = "spell-2376",
["spell-578"] = "spell-1566",
["spell-551"] = "spell-2003",
["spell-553"] = "spell-2012",
["spell-646"] = "spell-1394",
["spell-1054"] = "spell-1305",
["spell-559"] = "spell-2417",
["spell-1055"] = "spell-1311",
["spell-1056"] = "spell-1310",
["spell-841"] = "spell-1107",
["spell-1057"] = "spell-1307",
["spell-560"] = "spell-2418",
["spell-1058"] = "spell-1304",
["spell-641"] = "spell-1413",
["spell-1059"] = "spell-1308",
};
public Dictionary<string, string> LegacyRemasterExtra { get; } = [];
/// <summary>
/// Словарь сущностей, которые нужно слить с другой/другими.
/// Ключ - пропускаемая сущность. Значение - целевая сущность.
/// </summary>
public Dictionary<string, string> MergeSources { get; } = new() { ["spell-654"] = "spell-819" };
public Dictionary<string, string> MergeSources { get; } = [];
/// <summary>
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
/// </summary>
public AonprdBuilderGlobals()
{
using StreamReader fileReader = new("Resources\\ImportAdaptation.json");
JObject? json = JObject
.Parse(fileReader.ReadToEnd())
.SelectToken("Aonprd")
?.ToObject<JObject>();
if (json is not null)
{
SlugSuffixes = JsonToDictionary(json, "SlugSuffixes");
LegacyRemasterExtra = JsonToDictionary(json, "LegacyRemasterExtra");
MergeSources = JsonToDictionary(json, "MergeSources");
}
}
/// <summary>
/// Получить суффикс для ссылки.
/// </summary>
/// <param name="id">Идентификатор Aonprd.</param>
/// <returns>Суффикс для ссылки или пустая строка.</returns>
public string GetSlugSuffix(string id)
{
return SlugSuffixes.TryGetValue(id, out string? slug) ? $"-{slug}" : string.Empty;
}
private Dictionary<string, string> JsonToDictionary(JObject json, string tokenPath)
{
return json.SelectToken(tokenPath)?.ToObject<Dictionary<string, string>>() ?? [];
}
}
/*
@@ -2,10 +2,11 @@
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eModel;
using Pf2eModel.Model;
using Pf2eModel.Model.Base;
@@ -60,6 +61,8 @@ internal abstract class AbstractEntityProcessor(
/// <inheritdoc/>
public override void Process()
{
Check();
AddEntity();
AddSource();
AddExternal();
@@ -73,6 +76,17 @@ internal abstract class AbstractEntityProcessor(
Context.SaveChanges();
}
/// <summary>
/// Проверки необходимости импорта сущности.
/// </summary>
public virtual void Check()
{
if (Globals.MergeSources.ContainsKey(Input.GetRequired<string>("id")))
{
throw new SkipItemException(Input.GetRequired<string>("name"), "помечено на слияние.");
}
}
#region "Генерация описания"
/// <summary>
@@ -1,8 +1,8 @@
using System.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eModel;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common.I18n;
@@ -1,6 +1,7 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eModel;
using Pf2eModel.Model.Base.Targets;
@@ -12,7 +13,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
Group = ProcessorGroupEnum.AonprdPreparation
)]
[ProcessorDependencies(["aonprd:repository"])]
internal class ExternalProcessor(AonprdBuilderGlobals globals) : AbstractProcessor
internal class ExternalProcessor(AonprdBuilderGlobals globals) : AbstractSimpleProcessor
{
public Pf2eDbContext Context { get; } = new();
public AonprdBuilderGlobals Globals { get; } = globals;
@@ -1,5 +1,6 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eModel;
using Pf2eModel.Model.Common;
using Pf2eModel.Model.Common.I18n;
@@ -1,7 +1,8 @@
using System;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eModel;
using Pf2eModel.Model.Base.Source;
using Pf2eModel.Model.Enum;
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eModel;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Base.Targets;
@@ -21,7 +21,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Finalizat
)]
internal class LegacyRemasterProcessors(ProcessorInput input) : AbstractInputProcessor(input)
{
private Pf2eDbContext Context { get; set; } = new();
private Pf2eDbContext Context { get; } = new();
public override void Process()
{
@@ -32,27 +32,20 @@ internal class LegacyRemasterProcessors(ProcessorInput input) : AbstractInputPro
int entityId = entity.EntityId;
Entity legacyEntity =
Context.Entities.FirstOrDefault(e => e.Id == entityId)
?? throw new Exception($"Неизвестная сущность с ID {entityId}");
?? throw new($"Неизвестная сущность с ID {entityId}");
foreach (int remasterId in GetEntityIds("remaster_id"))
{
Context.LegacyRemasters.Add(new() { LegacyId = entityId, RemasterId = remasterId });
Entity remasterEntity =
Context.Entities.AsNoTracking().FirstOrDefault(e => e.Id == remasterId)
?? throw new Exception($"Неизвестная сущность с ID {remasterId}");
?? throw new($"Неизвестная сущность с ID {remasterId}");
if (legacyEntity.Slug == remasterEntity.Slug)
{
legacyEntity.Slug += "-legacy";
}
}
/*
foreach (int legacyId in GetEntityIds("legacy_id"))
{
Context.LegacyRemasters.Add(new() { LegacyId = legacyId, RemasterId = entityId });
}
*/
}
protected List<int> GetEntityIds(string path)
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eDataScrapper.Processors.Factory;
using Pf2eModel;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Base.Targets;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Finalization;
[ProcessorFactory(
"aonprd:fix-legacy-remaster",
ProcessorType = typeof(MissingLegacyRemasterProcessor),
Description = "Дополнение связяй устаревших и обновлённых сущностей",
Group = ProcessorGroupEnum.AonprdFinalization,
Parallel = true
)]
[ProcessorDependencies(["aonprd:legacy-remaster"])]
internal class MissingLegacyRemasterProcessorFactory(
IServiceProvider serviceProvider,
AonprdBuilderGlobals globals
) : AbstractProcessorFactory
{
public IServiceProvider ServiceProvider { get; } = serviceProvider;
public AonprdBuilderGlobals Globals { get; } = globals;
public override int Count() => Globals.LegacyRemasterExtra.Count;
public override IEnumerable<Tuple<string, IProcessor>> GetEnumerator()
{
foreach ((string key, string value) in Globals.LegacyRemasterExtra)
{
yield return new(
$"{key} -> {value}",
ActivatorUtilities.CreateInstance<MissingLegacyRemasterProcessor>(
ServiceProvider,
key,
value
)
);
}
}
}
internal class MissingLegacyRemasterProcessor(string legacyId, string remasterId) : IProcessor
{
public Pf2eDbContext Context { get; } = new();
public string LegacyId { get; } = legacyId;
public string RemasterId { get; } = remasterId;
private Entity GetEntity(string aonprdId)
{
EntityExternal entity =
Context.EntityExternals.AsNoTracking().FirstOrDefault(x => x.ExternalId == aonprdId)
?? throw new SilentSkipItemException();
int entityId = entity.EntityId;
return Context.Entities.FirstOrDefault(e => e.Id == entityId)
?? throw new($"Неизвестная сущность с ID {entityId}");
}
public void Process()
{
Entity legacyEntity = GetEntity(LegacyId);
Entity remasterEntity = GetEntity(RemasterId);
Context.LegacyRemasters.Add(
new() { LegacyId = legacyEntity.Id, RemasterId = remasterEntity.Id }
);
if (legacyEntity.Slug == remasterEntity.Slug)
{
legacyEntity.Slug += "-legacy";
}
}
public void Save()
{
Context.SaveChanges();
}
}
@@ -1,9 +1,9 @@
using System.IO;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation;
@@ -1,6 +1,6 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation;
@@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.SchemaBuilder;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation;
@@ -1,9 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eModel;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common;
@@ -19,8 +20,7 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasse
InnerType = "apparition",
CompendiumType = ProcessorCompendiumEnum.Aonprd,
Description = "Apparitions",
Group = ProcessorGroupEnum.AonprdSubclasses,
Active = false
Group = ProcessorGroupEnum.AonprdSubclasses
)]
[ProcessorDependencies(
["aonprd:trait", "aonprd:source", "aonprd:class", "aonprd:spell", "aonprd:skill"]
@@ -1,7 +1,8 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eModel.Model.Enum;
using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
@@ -42,8 +43,10 @@ internal class ActionProcessor(
Object.Extra = GenerateJsonString("school");
}
public void Check()
public override void Check()
{
base.Check();
if (Input.GetRequired<string>("markdown").StartsWith("**Activate"))
{
throw new SkipItemException(Input.GetRequired<string>("name"), "activating action");
@@ -2,10 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
@@ -1,7 +1,8 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
@@ -1,8 +1,9 @@
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
using Pf2eModel.Model.Structure.BackgroundRelation;
@@ -3,10 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eModel;
using Pf2eModel.Model.Common;
using Pf2eModel.Model.Common.I18n;
@@ -1,9 +1,9 @@
using System.Linq;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eModel;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
@@ -1,7 +1,7 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
using StringHelper = Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers.StringHelper;
@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eModel;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum;
@@ -1,9 +1,9 @@
using System.Linq;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eModel.Model.Base.Source;
using Pf2eModel.Model.Base.Targets;
using Pf2eModel.Model.Common.I18n;
@@ -7,9 +7,9 @@
using System.Collections.Generic;
using System.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eModel;
using Rule = Pf2eModel.Model.Structure.Rule;
@@ -4,9 +4,9 @@ using System.Text.RegularExpressions;
using System.Web;
using AngleSharp;
using AngleSharp.Dom;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eModel;
using Pf2eModel.Model.Base.Source;
@@ -120,7 +120,8 @@ internal partial class RulesTocProcessor(Pf2eDbContext context) : IProcessor
IDocument document = null!;
bool completed = false;
while (!completed){
while (!completed)
{
try
{
document = context.OpenAsync(Url).Result;
@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
@@ -1,7 +1,7 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
@@ -1,7 +1,8 @@
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eModel;
using Pf2eModel.Model.Enum;
@@ -1,7 +1,8 @@
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eModel;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
@@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eModel;
using Pf2eModel.Model;
using Pf2eModel.Model.Base;
@@ -1,8 +1,8 @@
using System.IO;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
@@ -2,10 +2,10 @@
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
@@ -1,6 +1,6 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
@@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.SchemaBuilder;
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Preparation;
+3
View File
@@ -35,6 +35,9 @@
<None Update=".env">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\ImportAdaptation.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="schema.pgsql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@@ -0,0 +1,35 @@
using System;
namespace Pf2eDataScrapper.Processors.Attributes;
/// <summary>
/// Абстрактный аттрибут обработчика.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
internal class AbstractProcessorAttribute(string name) : Attribute
{
/// <summary>
/// Название обработчика.
/// </summary>
public string Name { get; } = name;
/// <summary>
/// Описание обработчика.
/// </summary>
public string Description { get; init; } = name;
/// <summary>
/// Группа обработчиков, к которому относится обработчик.
/// </summary>
public ProcessorGroupEnum Group { get; init; } = ProcessorGroupEnum.General;
/// <summary>
/// Процессор включён по умолчанию.
/// </summary>
public bool Active { get; init; } = true;
/// <summary>
/// Разрешить выполнять процессов в параллельном режиме
/// </summary>
public bool Parallel { get; init; } = false;
}
@@ -0,0 +1,10 @@
using System;
namespace Pf2eDataScrapper.Processors.Attributes;
/// <summary>
/// Общий аттрибут обработчика.
/// </summary>
/// <param name="name">Название обработчика.</param>
[AttributeUsage(AttributeTargets.Class)]
internal class ProcessorAttribute(string name) : AbstractProcessorAttribute(name) { }
@@ -0,0 +1,22 @@
using System;
namespace Pf2eDataScrapper.Processors.Attributes;
/// <summary>
/// Аттрибут обработчика набор элементов.
/// </summary>
/// <param name="name">Название обработчика.</param>
[AttributeUsage(AttributeTargets.Class)]
internal class ProcessorCollectionAttribute(string name) : AbstractProcessorAttribute(name)
{
/// <summary>
/// Название типов, который обрабатывает этот оброботчик.
/// <c>null</c> - обработать все типы.
/// </summary>
public required string? InnerType { get; init; }
/// <summary>
/// Тип используемого компендиума.
/// </summary>
public required ProcessorCompendiumEnum CompendiumType { get; init; }
}
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
namespace Pf2eDataScrapper.Processors.Attributes;
/// <summary>
/// Аттрибут зависимостей обработчика.
/// </summary>
/// <param name="dependencies">Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.</param>
[AttributeUsage(AttributeTargets.Class)]
public class ProcessorDependenciesAttribute(string[] dependencies) : Attribute
{
/// <summary>
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
/// </summary>
public ICollection<string> Dependencies { get; } = dependencies;
}
@@ -0,0 +1,11 @@
using System;
namespace Pf2eDataScrapper.Processors.Attributes;
internal class ProcessorFactoryAttribute(string name) : AbstractProcessorAttribute(name)
{
/// <summary>
/// Тип обрабатывающего процессора
/// </summary>
public required Type ProcessorType { get; init; }
}
@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Reflection;
namespace Pf2eDataScrapper.App;
namespace Pf2eDataScrapper.Processors.Attributes;
public enum ProcessorGroupEnum
{
@@ -33,6 +33,13 @@ public enum ProcessorCompendiumEnum
FoundryEn,
}
public enum ProcessorFactoryTypeEnum
{
Simple,
Compendium,
Iterator,
}
public static class ProcessorGroupEnumExtension
{
public static string GetDescription(this ProcessorGroupEnum group)
@@ -0,0 +1,296 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using LinqToDB;
using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eDataScrapper.Processors.Factory;
namespace Pf2eDataScrapper.Processors.Collection;
/// <summary>
/// Класс с коллекцией обработчиков.
/// </summary>
internal class ProcessorCollection
{
public IServiceProvider ServiceProvider { get; }
/// <summary>
/// Коллекция всех доступных обработчиков.
/// </summary>
public ICollection<ProcessorRecord> AllItems { get; } = [];
/// <summary>
/// Коллекция обработчиков, которые будут обрабатываться.
/// Для изменения необходимо использовать <see cref="FilterProcessors"/>.
/// </summary>
public List<ProcessorRecord> Items { get; private set; } = [];
/// <summary>
/// Обеспечение доступа к элементам обработчиков.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public ProcessorRecord this[string name] => AllItems.First(item => item.Name == name);
/// <summary>
/// Конструктор класса <see cref="ProcessorCollection"/>.
/// </summary>
public ProcessorCollection(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes())
{
ProcessType(type, type.GetCustomAttribute<AbstractProcessorAttribute>());
}
CheckDependencies();
FilterProcessors([]);
}
private void ProcessType(Type type, AbstractProcessorAttribute? attribute)
{
switch (attribute)
{
case null:
break;
case ProcessorAttribute processorAttribute:
ProcessType(type, processorAttribute);
break;
case ProcessorCollectionAttribute processorCollectionAttribute:
ProcessType(type, processorCollectionAttribute);
break;
case ProcessorFactoryAttribute processorFactoryAttribute:
ProcessType(type, processorFactoryAttribute);
break;
default:
throw new(
$"Неизвестная реализация класса {typeof(AbstractProcessorAttribute)} - {attribute.GetType()}"
);
}
}
private void ProcessType(Type type, ProcessorAttribute? attribute)
{
if (attribute is null)
{
return;
}
if (!type.IsAssignableTo(typeof(IProcessor)))
{
throw new(
$"Процессор {type}, которому установлен аттрибут {attribute.GetType()}, не представляет интерфейс {typeof(IProcessor)}"
);
}
ProcessorDependenciesAttribute? dependenciesAttribute =
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
ProcessorRecord item = new()
{
Name = attribute.Name,
Type = type,
Description = attribute.Description,
Group = attribute.Group,
Dependencies = dependenciesAttribute?.Dependencies ?? [],
Active = attribute.Active,
Parallel = attribute.Parallel,
};
item.Factory = ActivatorUtilities.CreateInstance<SimpleProcessorFactory>(
ServiceProvider,
item
);
AllItems.Add(item);
}
private void ProcessType(Type type, ProcessorCollectionAttribute? attribute)
{
if (attribute is null)
{
return;
}
if (!type.IsAssignableTo(typeof(AbstractInputProcessor)))
{
throw new(
$"Процессор {type}, которому установлен аттрибут {attribute.GetType()}, не реализует класс {typeof(AbstractInputProcessor)}."
);
}
ProcessorDependenciesAttribute? dependenciesAttribute =
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
ProcessorRecord item = new()
{
Name = attribute.Name,
Type = type,
Description = attribute.Description,
Group = attribute.Group,
Dependencies = dependenciesAttribute?.Dependencies ?? [],
Active = attribute.Active,
Parallel = attribute.Parallel,
};
CompendiumProcessorFactory factory =
ActivatorUtilities.CreateInstance<CompendiumProcessorFactory>(ServiceProvider, item);
factory.CompendiumType = attribute.CompendiumType;
factory.InnerType = attribute.InnerType;
item.Factory = factory;
AllItems.Add(item);
}
private void ProcessType(Type type, ProcessorFactoryAttribute? attribute)
{
if (attribute is null)
{
return;
}
if (!type.IsAssignableTo(typeof(AbstractProcessorFactory)))
{
throw new(
$"Процессор {type}, которому установлен аттрибут {attribute.GetType()}, не реализует класс {typeof(AbstractProcessorFactory)}."
);
}
ProcessorDependenciesAttribute? dependenciesAttribute =
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
ProcessorRecord item = new()
{
Name = attribute.Name,
Type = attribute.ProcessorType,
Description = attribute.Description,
Group = attribute.Group,
Dependencies = dependenciesAttribute?.Dependencies ?? [],
Active = attribute.Active,
Parallel = attribute.Parallel,
Factory = (AbstractProcessorFactory)
ActivatorUtilities.CreateInstance(ServiceProvider, type),
};
AllItems.Add(item);
}
/// <summary>
/// Проверка, что все зависимости всех обработчиков могут быть удовлетворены.
/// </summary>
private void CheckDependencies()
{
foreach (ProcessorRecord item in Items)
{
foreach (string dependency in item.Dependencies)
{
if (Items.All(i => i.Name != dependency))
{
throw new($"Dependency '{dependency}' for processor '{item.Name}' not found.");
}
}
}
}
/// <summary>
/// Сортировать обработчики в соответсвии со всеми зависимостями.
/// </summary>
public void SortItems()
{
Items.ForEach(item => item.Priority = -1);
Dictionary<string, int> priorities = [];
while (priorities.Count < Items.Count)
{
foreach (ProcessorRecord item in Items)
{
if (item.Priority >= 0)
{
continue;
}
IEnumerable<int> dependencyPriorities = item.Dependencies.Select(d =>
priorities.GetValueOrDefault(d, -1)
);
if (dependencyPriorities.Any(p => p == -1))
{
continue;
}
item.Priority = Math.Max(
dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1,
(int)item.Group * 100
);
priorities.Add(item.Name, item.Priority);
}
}
Items.Sort((a, b) => a.Priority - b.Priority);
}
/// <summary>
/// Ограничить список используемых обработчиков по названию.
/// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики.
/// </summary>
/// <param name="processorNames">Коллекция названиий требуемых обработчиков.</param>
public void FilterProcessors(ICollection<string> processorNames)
{
if (processorNames.Count == 0)
{
Items = [.. AllItems];
SortItems();
return;
}
Dictionary<string, bool> nameFlags = processorNames.ToDictionary(name => name, _ => false);
while (nameFlags.Values.Any(v => !v))
{
foreach (
KeyValuePair<string, bool> pair in nameFlags.Where(pair => !pair.Value).ToList()
)
{
ProcessorRecord item = AllItems.First(item => item.Name == pair.Key);
foreach (string dependency in item.Dependencies)
{
nameFlags.TryAdd(dependency, false);
}
nameFlags[pair.Key] = true;
}
}
Items = AllItems.Where(item => nameFlags.ContainsKey(item.Name)).ToList();
}
/// <summary>
/// Получить список типов, которые обрабатываются в компендиуме.
/// </summary>
/// <param name="compendium">Тип компендиума.</param>
/// <returns>Набор типов.</returns>
public ICollection<string> GetTypesOfCompendium(ProcessorCompendiumEnum compendium) =>
Items
.Where(item =>
item.Factory.GetType().IsAssignableTo(typeof(CompendiumProcessorFactory))
)
.Where(item =>
(item.Factory as CompendiumProcessorFactory)!.CompendiumType == compendium
)
.Select(item => (item.Factory as CompendiumProcessorFactory)!.InnerType)
.OfType<string>()
.ToList();
}
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Factory;
namespace Pf2eDataScrapper.Processors.Collection;
/// <summary>
/// Элемент коллекции обработчиков.
/// </summary>
internal class ProcessorRecord
{
/// <summary>
/// Название обработчика.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Тип класса обработчика.
/// </summary>
public required Type Type { get; init; }
/// <summary>
/// Описание обработчика.
/// </summary>
public string Description { get; init; } = "";
/// <summary>
/// Фабрика обработчиков.
/// </summary>
public AbstractProcessorFactory Factory { get; set; } = null!;
/// <summary>
/// Группа обработчика.
/// </summary>
public required ProcessorGroupEnum Group { get; init; }
/// <summary>
/// Коллекция названий обработчиков, после которых должен выполняться обработчик.
/// </summary>
public ICollection<string> Dependencies { get; init; } = [];
/// <summary>
/// Процессор включён по умолчанию.
/// </summary>
public bool Active { get; init; } = true;
/// <summary>
/// Приоритет выполнения обработчика.
/// </summary>
public int Priority { get; set; } = -1;
/// <summary>
/// Разрешить выполнять процессов в параллельном режиме
/// </summary>
public bool Parallel { get; init; } = true;
}
@@ -1,6 +1,6 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors.Attributes;
namespace Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Processors.Definitions;
/// <summary>
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
@@ -1,6 +1,6 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors.Attributes;
namespace Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Processors.Definitions;
/// <summary>
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
@@ -8,7 +8,7 @@ namespace Pf2eDataScrapper.Processors;
/// для которых указан атрибут <see cref="ProcessorAttribute"/>.
/// </summary>
/// <seealso cref="ProcessorAttribute"/>
internal abstract class AbstractProcessor : IProcessor
internal abstract class AbstractSimpleProcessor : IProcessor
{
/// <summary>
/// Основной метод обработки данных.
@@ -1,4 +1,4 @@
namespace Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Processors.Definitions;
public interface IProcessor
{
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.Processors.Factory;
/// <summary>
/// Абстрактная фабрика создания процессоров.
/// </summary>
internal abstract class AbstractProcessorFactory
{
/// <summary>
/// Подготовить фабрику к работе.
/// </summary>
public virtual void Prepare() { }
/// <summary>
/// Количество элеметов для итерирования.
/// </summary>
/// <returns>Количество элеметов</returns>
public abstract int Count();
/// <summary>
/// Итератор процессоров.
/// </summary>
/// <returns>Пара значений: [0] - описание элемента процессор, [1] - процессор.</returns>
public abstract IEnumerable<Tuple<string, IProcessor>> GetEnumerator();
}
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Importers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.Processors.Factory;
internal class CompendiumProcessorFactory(ProcessorRecord item, IServiceProvider serviceProvider)
: AbstractProcessorFactory
{
public ProcessorRecord Item { get; } = item;
public IServiceProvider ServiceProvider { get; } = serviceProvider;
public ProcessorCompendiumEnum? CompendiumType { get; set; } = null;
public string? InnerType { get; set; } = null;
public AbstractCompendium Compendium { get; private set; } = null!;
public ICollection<string> Types { get; private set; } = [];
public override void Prepare()
{
if (CompendiumType is null)
{
throw new ArgumentNullException(nameof(CompendiumType));
}
base.Prepare();
ProcessorCollection collection =
ServiceProvider.GetService<ProcessorCollection>() ?? throw new();
Compendium =
CompendiumType switch
{
ProcessorCompendiumEnum.Aonprd => ServiceProvider.GetService<AonprdCompendium>(),
ProcessorCompendiumEnum.FoundryEn =>
ServiceProvider.GetService<FoundryEnCompendium>(),
_ => (AbstractCompendium?)null,
} ?? throw new($"Не могу выбрать импортёр для группы {Item.Group}");
Types = InnerType is null
? collection.GetTypesOfCompendium(CompendiumType ?? throw new())
: [InnerType];
}
public override int Count() => Compendium.Count(Types);
public override IEnumerable<Tuple<string, IProcessor>> GetEnumerator()
{
foreach ((AbstractCompendiumItem compendiumItem, JObject json) in Compendium.Iterate(Types))
{
ProcessorInput processorInput = ActivatorUtilities.CreateInstance<ProcessorInput>(
ServiceProvider,
compendiumItem.Id,
json
);
yield return new(
compendiumItem.Id,
(IProcessor)
ActivatorUtilities.CreateInstance(ServiceProvider, Item.Type, processorInput)
);
}
}
}
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.Processors.Factory;
internal class SimpleProcessorFactory(ProcessorRecord item, IServiceProvider serviceProvider)
: AbstractProcessorFactory
{
public ProcessorRecord Item { get; } = item;
public IServiceProvider ServiceProvider { get; } = serviceProvider;
public override int Count() => 1;
public override IEnumerable<Tuple<string, IProcessor>> GetEnumerator()
{
yield return new(
Item.Description,
(IProcessor)ActivatorUtilities.CreateInstance(ServiceProvider, Item.Type)
);
}
}
@@ -1,4 +1,5 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eModel;
namespace Pf2eDataScrapper.Processors.General;
@@ -2,30 +2,21 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
using Pf2eDataScrapper.Processors.Collection;
using Pf2eDataScrapper.Processors.Definitions;
using Pf2eDataScrapper.Processors.Factory;
namespace Pf2eDataScrapper.Processors;
internal sealed class ProcessorExecutor(
IServiceProvider serviceProvider,
ProcessorCollection collection,
ProcessProgress progress,
AonprdCompendium aonprdCompendium,
FoundryEnCompendium foundryEnCompendium,
ApplicationSettings settings
)
{
public IServiceProvider ServiceProvider { get; } = serviceProvider;
public ProcessorCollection Collection { get; } = collection;
public ProcessProgress Progress { get; } = progress;
public AonprdCompendium AonprdCompendium { get; } = aonprdCompendium;
public FoundryEnCompendium FoundryEnCompendium { get; } = foundryEnCompendium;
public ApplicationSettings Settings { get; } = settings;
public void Start()
@@ -39,23 +30,25 @@ internal sealed class ProcessorExecutor(
private int _currentIndex;
private AbstractProcessorFactory CurrentFactory { get; set; } = null!;
private void StartItem()
{
ProcessorCollectionItem item = Collection.Items[_currentIndex];
ProcessorRecord item = Collection.Items[_currentIndex];
Progress.Processors.Start(item.Description);
Progress.Progress(Progress.Processors.ToString());
BackgroundWorker worker = new();
CurrentFactory = item.Factory;
CurrentFactory.Prepare();
worker.DoWork += (_, _) =>
{
Parallel.ForEach(
IterateProcessors(item),
new ParallelOptions()
{
MaxDegreeOfParallelism = item.Parallel ? Settings.MaxThreads : 1,
},
IterateProcessors(),
new() { MaxDegreeOfParallelism = item.Parallel ? Settings.MaxThreads : 1 },
RunProcessor
);
};
@@ -94,53 +87,18 @@ internal sealed class ProcessorExecutor(
else
{
Progress.Processors.Complete();
Progress.Complete();
}
}
private IEnumerable<IProcessor> IterateProcessors(ProcessorCollectionItem item)
private IEnumerable<IProcessor> IterateProcessors()
{
if (!item.IsCollection)
Progress.Local.Reset(CurrentFactory.Count());
foreach ((string title, IProcessor processor) in CurrentFactory.GetEnumerator())
{
Progress.Local.Reset(1);
Progress.Local.Start(item.Description);
yield return (IProcessor)ActivatorUtilities.CreateInstance(ServiceProvider, item.Type);
Progress.Local.Complete();
yield break;
}
Progress.Local.Start(title);
if (item.Group == ProcessorGroupEnum.General)
{
throw new(
$"Обработчик {item.Description} помечен как общий (поле Group == General) и коллекционный (поле InnerType), что запрещено."
);
}
AbstractCompendium compendium = item.Compendium switch
{
ProcessorCompendiumEnum.Aonprd => AonprdCompendium,
ProcessorCompendiumEnum.FoundryEn => FoundryEnCompendium,
_ => throw new($"Не могу выбрать импортёр для группы {item.Group}"),
};
ICollection<string> types = item.InnerType is null
? Collection.GetTypesOfCompendium(item.Compendium ?? throw new("impossible"))
: [item.InnerType];
Progress.Local.Reset(compendium.Count(types));
foreach ((AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate(types))
{
Progress.Local.Start(compendiumItem.Id);
ProcessorInput processorInput = ActivatorUtilities.CreateInstance<ProcessorInput>(
ServiceProvider,
compendiumItem.Id,
json
);
yield return (IProcessor)
ActivatorUtilities.CreateInstance(ServiceProvider, item.Type, processorInput);
yield return processor;
Progress.Local.End();
}
@@ -0,0 +1,82 @@
{
"Aonprd": {
"SlugSuffixes": {
"feat-455": "initial",
"spell-843": "initial",
"feat-2664": "archetype",
"feat-3247": "game-hunter",
"feat-3477": "undead-slayer",
"feat-3549": "vampire",
"feat-4344": "archetype",
"feat-4380": "stonebound",
"feat-5730": "dragonblood",
"feat-5737": "dragonblood",
"feat-5997": "legacy",
"feat-6209": "investigator",
"feat-6469": "general",
"feat-6477": "undead-slayer",
"feat-6883": "kitsune",
"feat-7004": "yaoguai",
"feat-7380": "mythic",
"feat-7637": "dandy"
},
"LegacyRemasterExtra": {
"action-14": "action-2273",
"action-15": "action-2275",
"action-558": "action-2277",
"archetype-83": "archetype-129",
"archetype-86": "archetype-198",
"feat-455": "feat-6007",
"feat-480": "feat-4850",
"feat-936": "feat-2269",
"feat-1177": "feat-4553",
"feat-1753": "feat-6031",
"feat-2117": "feat-7577",
"feat-2729": "feat-4077",
"feat-2742": "feat-4078",
"feat-2743": "feat-4079",
"feat-2986": "feat-4340",
"feat-2987": "feat-4341",
"feat-2989": "feat-4343",
"feat-2990": "feat-4346",
"feat-2991": "feat-4349",
"feat-2992": "feat-4350",
"feat-2993": "feat-4352",
"feat-4088": "feat-5491",
"feat-5997": "feat-7108",
"language-91": "language-180",
"ritual-31": "ritual-191",
"spell-641": "spell-1413",
"spell-646": "spell-1394",
"spell-841": "spell-1107",
"spell-843": "spell-2376",
"spell-1052": "spell-1309",
"spell-1053": "spell-1306",
"spell-1054": "spell-1305",
"spell-1055": "spell-1311",
"spell-1056": "spell-1310",
"spell-1057": "spell-1307",
"spell-1058": "spell-1304",
"spell-1059": "spell-1308"
},
"MergeSources": {
"feat-963": "feat-4519",
"feat-1167": "feat-5880",
"feat-1169": "feat-5055",
"feat-1171": "feat-4858",
"spell-551": "spell-2003",
"spell-553": "spell-2012",
"spell-559": "spell-2417",
"spell-560": "spell-2418",
"spell-571": "spell-1975",
"spell-578": "spell-1566",
"spell-654": "spell-819"
}
}
}
@@ -4,7 +4,7 @@ using System.IO;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers;
using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.Processors.Definitions;
namespace Pf2eDataScrapper.SchemaBuilder;
@@ -1,42 +1,15 @@
using System.Windows;
using Pf2eDataScrapper.WPF.EntityViewer.ViewModel;
namespace Pf2eDataScrapper.WPF.EntityViewer.Component;
namespace Pf2eDataScrapper.WPF.EntityViewer.Component;
/// <summary>
/// Логика взаимодействия для DbEntityListControl.xaml
/// </summary>
internal partial class EntityListControl : System.Windows.Controls.UserControl
{
private EntityListViewModel? _viewModel;
public EntityListControl()
{
InitializeComponent();
}
//https://www.youtube.com/watch?v=54ZmhbpjBmg
private void FirstButton_OnClick(object sender, RoutedEventArgs e)
{
// DataContext?.FirstPage();
}
private void PreviousButton_OnClick(object sender, RoutedEventArgs e)
{
//ViewModel?.PreviousPage();
}
private void NextButton_OnClick(object sender, RoutedEventArgs e)
{
//ViewModel?.NextPage();
}
private void LastButton_OnClick(object sender, RoutedEventArgs e)
{
//ViewModel?.LastPage();
}
private void TextBox_PreviewTextInput(
object sender,
System.Windows.Input.TextCompositionEventArgs e
@@ -29,6 +29,8 @@ internal partial class EntityForm : Window
private void Button_Click(object sender, RoutedEventArgs e)
{
(Application.Current as ImportApplication)!.CurrentStage = ApplicationStage.InitialSettings;
ApplicationStateChange?.Invoke(this, ApplicationStage.InitialSettings);
}
public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange;
}
@@ -124,10 +124,6 @@ internal class StructureRow : INotifyPropertyChanged
Field.PropertyChanged += Field_PropertyChanged;
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
private void Field_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(Field));
@@ -2,6 +2,7 @@
using System.Collections.ObjectModel;
using System.Drawing;
using System.Windows;
using LinqToDB.Tools;
using Pf2eDataScrapper.Helpers;
namespace Pf2eDataScrapper.WPF.ProgressForm;
@@ -26,7 +27,6 @@ internal partial class ProgressForm : Window
Progress.ProgressComplete += Progress_ProgressComplete;
}
private void Progress_ProgressUpdate(ProcessProgress sender)
{
if (!Dispatcher.CheckAccess())
@@ -58,9 +58,10 @@ internal partial class ProgressForm : Window
MessageListView.ScrollIntoView(item);
}
private void Progress_ProgressComplete(ProcessProgress sender)
{
(Application.Current as ImportApplication)!.CurrentStage = ApplicationStage.Viewer;
ApplicationStateChange?.Invoke(this, ApplicationStage.Viewer);
}
public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange;
@@ -3,7 +3,8 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors.Attributes;
using Pf2eDataScrapper.Processors.Collection;
using CheckBox = System.Windows.Controls.CheckBox;
namespace Pf2eDataScrapper.WPF.StartForm.Components;
@@ -61,11 +62,7 @@ internal partial class ProcessorCollectionControl : System.Windows.Controls.User
Style = (Style)Resources["ElementPanelStyle"]!,
};
foreach (
ProcessorCollectionItem item in Collection.AllItems.Where(item =>
item.Group == group
)
)
foreach (ProcessorRecord item in Collection.AllItems.Where(item => item.Group == group))
{
ItemCheckBoxes[item.Name] = new()
{
@@ -96,7 +93,7 @@ internal partial class ProcessorCollectionControl : System.Windows.Controls.User
foreach ((string key, CheckBox? checkBox) in ItemCheckBoxes)
{
ProcessorCollectionItem item = Collection[key];
ProcessorRecord item = Collection[key];
bool isChecked = checkBox.IsChecked ?? false;
if (GroupLastStats.TryGetValue(item.Group, out bool? state))
@@ -1,5 +1,5 @@
using System.Windows;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors.Collection;
namespace Pf2eDataScrapper.WPF.StartForm;
@@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Migrations
{
[DbContext(typeof(Pf2eDbContext))]
[Migration("20250521102908_1")]
[Migration("20250521155437_1")]
partial class _1
{
/// <inheritdoc />