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

Merged
kozhilya merged 33 commits from dev into master 2025-06-01 22:28:15 +04:00
56 changed files with 1017 additions and 829 deletions
Showing only changes of commit 5d2982330d - Show all commits
-62
View File
@@ -1,62 +0,0 @@
namespace Pf2eDataScrapper.App;
/// <summary>
/// Общий аттрибут обработчика.
/// </summary>
/// <param name="name">Название обработчика.</param>
/// <param name="dependencies">Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.</param>
[AttributeUsage(AttributeTargets.Class)]
public class ProcessAttribute(string name, ICollection<string> dependencies) : Attribute
{
/// <summary>
/// Название обработчика.
/// </summary>
public string Name { get; init; } = name;
/// <summary>
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
/// </summary>
public ICollection<string> Dependencies { get; init; } = dependencies;
public ProcessAttribute(string name)
: this(name, []) { }
}
/// <summary>
/// Аттрибут обработчика для уточнения группы.
/// </summary>
/// <param group="group">Группа обработчиков, к которому относится обработчик.</param>
[AttributeUsage(AttributeTargets.Class)]
internal class ProcessorGroupAttribute(ProcessorGroupEnum group) : Attribute
{
/// <summary>
/// Название обработчика, к которому относится обработчик.
/// </summary>
public ProcessorGroupEnum Group { get; init; } = group;
}
/// <summary>
/// Аттрибут обработчика для уточнения списка зависимостей.
/// </summary>
/// <param name="dependencies">Список названий зависимостей.</param>
[AttributeUsage(AttributeTargets.Class)]
internal class ProcessorDependenciesAttribute(ICollection<string> dependencies) : Attribute
{
/// <summary>
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
/// </summary>
public ICollection<string> Dependencies { get; init; } = dependencies;
}
/// <summary>
/// Аттрибут обработчика для внутреннего типа элемента.
/// </summary>
/// <param name="innerType">Внутренний типа элемента.</param>
[AttributeUsage(AttributeTargets.Class)]
internal class ProcessorInnerTypeAttribute(string innerType) : Attribute
{
/// <summary>
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
/// </summary>
public string InnerType { get; init; } = innerType;
}
@@ -0,0 +1,42 @@
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 string? InnerType { get; init; } = null;
}
/// <summary>
/// Аттрибут зависимостей обработчика.
/// </summary>
/// <param name="dependencies">Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.</param>
[AttributeUsage(AttributeTargets.Class)]
public class ProcessorDependenciesAttribute(string[] dependencies) : Attribute
{
/// <summary>
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
/// </summary>
public ICollection<string> Dependencies { get; } = dependencies;
}
+8 -14
View File
@@ -29,7 +29,7 @@ internal class ProcessorCollection
foreach (Type type in assembly.GetTypes()) foreach (Type type in assembly.GetTypes())
{ {
if (type.GetCustomAttributes(typeof(ProcessAttribute), true).Length <= 0) if (type.GetCustomAttributes(typeof(ProcessorAttribute), true).Length <= 0)
{ {
continue; continue;
} }
@@ -51,21 +51,15 @@ internal class ProcessorCollection
if (!type.IsSubclassOf(typeof(IProcessor))) if (!type.IsSubclassOf(typeof(IProcessor)))
{ {
throw new( throw new(
$"'ProcessAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not." $"'ProcessorAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not."
); );
} }
ProcessAttribute processAttribute = type.GetCustomAttribute<ProcessAttribute>()!; ProcessorAttribute processorAttribute = type.GetCustomAttribute<ProcessorAttribute>()!;
DescriptionAttribute? descriptionAttribute =
type.GetCustomAttribute<DescriptionAttribute>();
ProcessorGroupAttribute? groupAttribute =
type.GetCustomAttribute<ProcessorGroupAttribute>();
ProcessorDependenciesAttribute? dependenciesAttribute = ProcessorDependenciesAttribute? dependenciesAttribute =
type.GetCustomAttribute<ProcessorDependenciesAttribute>(); type.GetCustomAttribute<ProcessorDependenciesAttribute>();
ProcessorInnerTypeAttribute? innerTypeAttribute =
type.GetCustomAttribute<ProcessorInnerTypeAttribute>();
bool isCollection = innerTypeAttribute is not null; bool isCollection = processorAttribute.InnerType is not null;
ConstructorInfo? constructor = isCollection ConstructorInfo? constructor = isCollection
? type.GetConstructor([]) ? type.GetConstructor([])
@@ -78,13 +72,13 @@ internal class ProcessorCollection
return new() return new()
{ {
Name = processAttribute.Name, Name = processorAttribute.Name,
Type = type, Type = type,
Description = descriptionAttribute?.Description ?? processAttribute.Name, Description = processorAttribute.Description,
Group = groupAttribute?.Group ?? ProcessorGroupEnum.General, Group = processorAttribute.Group,
Dependencies = dependenciesAttribute?.Dependencies ?? [], Dependencies = dependenciesAttribute?.Dependencies ?? [],
IsCollectionProcessor = isCollection, IsCollectionProcessor = isCollection,
InnerType = innerTypeAttribute?.InnerType, InnerType = processorAttribute.InnerType,
Constructor = constructor, Constructor = constructor,
}; };
} }
+33 -32
View File
@@ -31,11 +31,11 @@ partial class StartForm
MainSplitContainer = new SplitContainer(); MainSplitContainer = new SplitContainer();
PropertiesGroupBox = new GroupBox(); PropertiesGroupBox = new GroupBox();
SettingsPropertyGrid = new PropertyGrid(); SettingsPropertyGrid = new PropertyGrid();
StartButton = new Button();
ProcessorsGroupBox = new GroupBox(); ProcessorsGroupBox = new GroupBox();
DeselectAllButton = new Button();
SelectAllButton = new Button();
ProcessorsListView = new ListView(); ProcessorsListView = new ListView();
SelectAllButton = new Button();
DeselectAllButton = new Button();
StartButton = new Button();
((System.ComponentModel.ISupportInitialize)MainSplitContainer).BeginInit(); ((System.ComponentModel.ISupportInitialize)MainSplitContainer).BeginInit();
MainSplitContainer.Panel1.SuspendLayout(); MainSplitContainer.Panel1.SuspendLayout();
MainSplitContainer.Panel2.SuspendLayout(); MainSplitContainer.Panel2.SuspendLayout();
@@ -84,17 +84,6 @@ partial class StartForm
SettingsPropertyGrid.Size = new Size(288, 649); SettingsPropertyGrid.Size = new Size(288, 649);
SettingsPropertyGrid.TabIndex = 0; SettingsPropertyGrid.TabIndex = 0;
// //
// StartButton
//
StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
StartButton.Font = new Font("Segoe UI", 18F, FontStyle.Bold);
StartButton.Location = new Point(3, 611);
StartButton.Name = "StartButton";
StartButton.Size = new Size(621, 63);
StartButton.TabIndex = 0;
StartButton.Text = "Запуск";
StartButton.UseVisualStyleBackColor = true;
//
// ProcessorsGroupBox // ProcessorsGroupBox
// //
ProcessorsGroupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; ProcessorsGroupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
@@ -108,24 +97,6 @@ partial class StartForm
ProcessorsGroupBox.TabStop = false; ProcessorsGroupBox.TabStop = false;
ProcessorsGroupBox.Text = "Обработчики"; ProcessorsGroupBox.Text = "Обработчики";
// //
// DeselectAllButton
//
DeselectAllButton.Location = new Point(6, 19);
DeselectAllButton.Name = "DeselectAllButton";
DeselectAllButton.Size = new Size(137, 23);
DeselectAllButton.TabIndex = 0;
DeselectAllButton.Text = "Снять выделение всех";
DeselectAllButton.UseVisualStyleBackColor = true;
//
// SelectAllButton
//
SelectAllButton.Location = new Point(149, 19);
SelectAllButton.Name = "SelectAllButton";
SelectAllButton.Size = new Size(137, 23);
SelectAllButton.TabIndex = 1;
SelectAllButton.Text = "Выделить все";
SelectAllButton.UseVisualStyleBackColor = true;
//
// ProcessorsListView // ProcessorsListView
// //
ProcessorsListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; ProcessorsListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
@@ -136,6 +107,35 @@ partial class StartForm
ProcessorsListView.TabIndex = 2; ProcessorsListView.TabIndex = 2;
ProcessorsListView.UseCompatibleStateImageBehavior = false; ProcessorsListView.UseCompatibleStateImageBehavior = false;
// //
// SelectAllButton
//
SelectAllButton.Location = new Point(149, 19);
SelectAllButton.Name = "SelectAllButton";
SelectAllButton.Size = new Size(137, 23);
SelectAllButton.TabIndex = 1;
SelectAllButton.Text = "Выделить все";
SelectAllButton.UseVisualStyleBackColor = true;
//
// DeselectAllButton
//
DeselectAllButton.Location = new Point(6, 19);
DeselectAllButton.Name = "DeselectAllButton";
DeselectAllButton.Size = new Size(137, 23);
DeselectAllButton.TabIndex = 0;
DeselectAllButton.Text = "Снять выделение всех";
DeselectAllButton.UseVisualStyleBackColor = true;
//
// StartButton
//
StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
StartButton.Font = new Font("Segoe UI", 18F, FontStyle.Bold);
StartButton.Location = new Point(3, 611);
StartButton.Name = "StartButton";
StartButton.Size = new Size(621, 63);
StartButton.TabIndex = 0;
StartButton.Text = "Запуск";
StartButton.UseVisualStyleBackColor = true;
//
// StartForm // StartForm
// //
AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleDimensions = new SizeF(7F, 15F);
@@ -144,6 +144,7 @@ partial class StartForm
Controls.Add(MainSplitContainer); Controls.Add(MainSplitContainer);
Name = "StartForm"; Name = "StartForm";
Text = "Настройки импорта данных"; Text = "Настройки импорта данных";
Load += StartForm_Load;
MainSplitContainer.Panel1.ResumeLayout(false); MainSplitContainer.Panel1.ResumeLayout(false);
MainSplitContainer.Panel2.ResumeLayout(false); MainSplitContainer.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)MainSplitContainer).EndInit(); ((System.ComponentModel.ISupportInitialize)MainSplitContainer).EndInit();
+58 -3
View File
@@ -1,9 +1,64 @@
namespace Pf2eDataScrapper.Forms; using System.ComponentModel;
using System.Reflection;
using Pf2eDataScrapper.App;
public partial class StartForm : Form namespace Pf2eDataScrapper.Forms;
internal partial class StartForm : Form
{ {
public StartForm() private readonly ProcessorCollection _processorCollection;
public StartForm(ProcessorCollection processorCollection)
{ {
InitializeComponent(); InitializeComponent();
_processorCollection = processorCollection;
}
private void StartForm_Load(object sender, EventArgs e)
{
PopulateListView();
}
private void PopulateListView()
{
Dictionary<ProcessorGroupEnum, List<ProcessorCollectionItem>> items = [];
foreach (ProcessorCollectionItem item in _processorCollection.AllItems)
{
items.TryAdd(item.Group, []);
items[item.Group].Add(item);
}
ProcessorsListView.Groups.Clear();
foreach (FieldInfo field in typeof(ProcessorGroupEnum).GetFields())
{
ProcessorGroupEnum groupEnum = (ProcessorGroupEnum)field.GetValue(null)!;
if (!items.TryGetValue(groupEnum, out List<ProcessorCollectionItem>? list))
{
continue;
}
DescriptionAttribute? attribute = (DescriptionAttribute?)
Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
string name = attribute?.Description ?? field.Name;
ListViewGroup listViewGroup = new(name);
ProcessorsListView.Groups.Add(listViewGroup);
foreach (ProcessorCollectionItem item in list)
{
ListViewItem listViewItem = new()
{
Group = listViewGroup,
Text = item.Description,
Selected = true,
};
ProcessorsListView.Items.Add(listViewItem);
}
}
} }
} }
@@ -1,7 +1,7 @@
using Pf2eModel; using Pf2eModel;
using Slugify; using Slugify;
namespace Pf2eDataScrapper.Importers; namespace Pf2eDataScrapper.Helpers;
/// <summary> /// <summary>
/// Класс предоставляет методы для работы с базой данных. /// Класс предоставляет методы для работы с базой данных.
+73 -5
View File
@@ -1,6 +1,10 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.App; using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Forms; using Pf2eDataScrapper.Forms;
using Pf2eDataScrapper.Importers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd;
using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry;
using Pf2eDataScrapper.Processors;
using Pf2eModel; using Pf2eModel;
namespace Pf2eDataScrapper; namespace Pf2eDataScrapper;
@@ -15,23 +19,27 @@ internal sealed class ImportApplication : ApplicationContext
#region "Внедрение зависимостей (DI)" #region "Внедрение зависимостей (DI)"
public IServiceCollection Services = new ServiceCollection(); public readonly IServiceCollection Services = new ServiceCollection();
public IServiceProvider ServiceProvider { get; private set; } = null!;
private void InitializeServices() private void InitializeServices()
{ {
Services.AddSingleton(this); Services.AddSingleton(this);
Services.AddSingleton(Settings); Services.AddSingleton(Settings);
Services.AddSingleton(Processors);
Services.AddSingleton<ProcessorCollection>();
Services.AddDbContext<Pf2eDbContext>(); Services.AddDbContext<Pf2eDbContext>();
ServiceProvider = Services.BuildServiceProvider();
} }
#endregion #endregion
#region "Настройки" #region "Настройки"
public ApplicationSettings Settings = new(); public readonly ApplicationSettings Settings = new();
public readonly ProcessorCollection Processors = new();
#endregion #endregion
@@ -45,13 +53,73 @@ internal sealed class ImportApplication : ApplicationContext
private void CreateStartForm() private void CreateStartForm()
{ {
_startForm = new() { StartPosition = FormStartPosition.CenterParent }; _startForm = ActivatorUtilities.CreateInstance<StartForm>(ServiceProvider);
_startForm.StartPosition = FormStartPosition.CenterParent;
_startForm.Show(); _startForm.Show();
} }
private void StartProcess() private void StartProcess()
{ {
_startForm!.Close(); _startForm!.Close();
Processors.SortItems();
ProcessProgress progress = new ProcessProgress();
foreach (ProcessorCollectionItem item in Processors.Items)
{
if (item.IsCollectionProcessor)
{
RunCollection(item, progress);
}
else
{
RunSingle(item, progress);
}
}
}
private void RunSingle(ProcessorCollectionItem item, ProcessProgress progress)
{
progress.Local.Reset(1);
progress.Local.Start("");
IProcessor processor = (IProcessor)
ActivatorUtilities.CreateInstance(ServiceProvider, item.Type);
processor.Process();
processor.Save();
progress.Local.Complete();
}
private void RunCollection(ProcessorCollectionItem item, ProcessProgress progress)
{
AbstractImporter importer = item.Group switch
{
ProcessorGroupEnum.AonprdPreparation
or ProcessorGroupEnum.AonprdEntities
or ProcessorGroupEnum.AonprdSubclasses => new AonprdImporter(),
ProcessorGroupEnum.FoundryEnPreparation => new Pf2EFoundryImporter(),
_ => throw new("Can't recognize importer"),
};
progress.Local.Reset(importer.CountInputs(item.InnerType!));
foreach (ProcessorInput input in importer.ResolveInputs(item.InnerType!))
{
progress.Local.Start(input.Name);
IProcessor processor = (IProcessor)
ActivatorUtilities.CreateInstance(ServiceProvider, item.Type);
processor.Process();
processor.Save();
progress.Local.End();
}
progress.Local.Complete();
} }
#endregion #endregion
@@ -1,4 +1,5 @@
using Pf2eDataScrapper.Importers.Repositories; using Pf2eDataScrapper.Importers.Repositories;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers; namespace Pf2eDataScrapper.Importers;
@@ -36,15 +37,32 @@ internal abstract class AbstractImporter
/// <summary> /// <summary>
/// Метод подготовки данных для импорта. /// Метод подготовки данных для импорта.
/// </summary> /// </summary>
[Obsolete]
public abstract void Prepare(); public abstract void Prepare();
/// <summary> /// <summary>
/// Импорт данных /// Импорт данных
/// </summary> /// </summary>
[Obsolete]
public abstract void Import(); public abstract void Import();
/// <summary> /// <summary>
/// Построение схем данных. /// Построение схем данных.
/// </summary> /// </summary>
[Obsolete]
public abstract void BuildSchemas(); public abstract void BuildSchemas();
/// <summary>
/// Получение количества записей, принаделажащих типу <paramref name="type"/>.
/// </summary>
/// <param name="type">Тип записей.</param>
/// <returns>Количество записей.</returns>
public abstract int CountInputs(string type);
/// <summary>
/// Итератор ввода для записей, принаделажащих типу <paramref name="type"/>.
/// </summary>
/// <param name="type">Тип записей.</param>
/// <returns>Итератор.</returns>
public abstract IEnumerable<ProcessorInput> ResolveInputs(string type);
} }
@@ -1,6 +1,8 @@
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.SchemaBuilder; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
using Pf2eDataScrapper.Importers.Repositories; using Pf2eDataScrapper.Importers.Repositories;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd;
@@ -9,6 +11,11 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd;
/// </summary> /// </summary>
internal class AonprdImporter : AbstractImporter internal class AonprdImporter : AbstractImporter
{ {
/// <summary>
/// Название внешней ссылки.
/// </summary>
public const string TargetName = "Archives of Nethys Pathfinder 2e";
/// <inheritdoc/> /// <inheritdoc/>
public override string Name => TargetName; public override string Name => TargetName;
@@ -16,8 +23,7 @@ internal class AonprdImporter : AbstractImporter
public override string Slug => "aonprd"; public override string Slug => "aonprd";
/// <inheritdoc/> /// <inheritdoc/>
public override AbstractImporterRepository Repository => public override AbstractImporterRepository Repository { get; }
new AonprdRepository(this, "https://elasticsearch.aonprd.com");
/// <summary> /// <summary>
/// Инструмент для работы с данными сайта Архивов Нефеса. /// Инструмент для работы с данными сайта Архивов Нефеса.
@@ -29,37 +35,47 @@ internal class AonprdImporter : AbstractImporter
/// </summary> /// </summary>
public AonprdImporter() public AonprdImporter()
{ {
Repository = new AonprdRepository(this, "https://elasticsearch.aonprd.com");
Loader = new(this); Loader = new(this);
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Prepare() public override void Prepare() { }
/// <inheritdoc/>
public override void BuildSchemas() { }
/// <inheritdoc/>
public override void Import() { }
/// <inheritdoc/>
public override int CountInputs(string type)
{ {
Repository.Update(); return Loader.Compendium.Count(type);
Loader.Prepare();
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void BuildSchemas() public override IEnumerable<ProcessorInput> ResolveInputs(string type)
{ {
AonprdSchemaBuilder builder = new(Loader); foreach (string file in Loader.Compendium.Files(type))
{
using StreamReader fileReader = new(Path.Combine(Dir, $"{file}.json"));
JArray array = JArray.Parse(fileReader.ReadToEnd());
builder.BuildSchemas(); foreach (JToken item in array)
builder.Save(EnvSettings.OutputDir); {
string? id = item.SelectToken("id")?.ToString();
if (id is null)
{
continue;
} }
/// <inheritdoc/> if (AonprdReference.ResolveType(id) == type)
public override void Import()
{ {
AonprdDataBuilder builder = new(Loader); yield return new AonprdProcessorInput(id, (JObject)item);
builder.Processors.UseOnlyNames(ProcessorNames); }
}
builder.TruncateTables(); }
builder.Build();
} }
/// <summary>
/// Название внешней ссылки.
/// </summary>
public const string TargetName = "Archives of Nethys Pathfinder 2e";
} }
@@ -1,14 +1,14 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd;
/// <summary> /// <summary>
/// Класс для работы с JSON-объектом при обработки данных с Архивов Нефиса. /// Класс для работы с JSON-объектом при обработки данных с Архивов Нефиса.
/// </summary> /// </summary>
/// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param> /// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param>
internal class AonprdProcessorInput(JObject input) : ProcessorInput(input) internal class AonprdProcessorInput(string name, JObject input) : ProcessorInput(name, input)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override T Enum<T>(string value) public override T Enum<T>(string value)
@@ -1,6 +1,7 @@
using Konsole; using Konsole;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Importers.Repositories; using Pf2eDataScrapper.Importers.Repositories;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd;
@@ -38,18 +39,21 @@ internal class AonprdRepository(AbstractImporter importer, string searchUrl)
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Update() public override void Update(ProcessProgress progress)
{ {
if (CheckRevision()) if (CheckRevision())
{ {
Console.WriteLine($"Данные Aonprd актуальны, текущая ревизия {Revision}."); progress.Local.Complete();
progress.Message($"Данные Aonprd актуальны, текущая ревизия {Revision}.");
return; return;
} }
Console.WriteLine( progress.Message(
$"Данные Aonprd устарели, обновляем {RepositoryDirPath} до ревизии {Revision}..." $"Данные Aonprd устарели, обновляем {RepositoryDirPath} до ревизии {Revision}..."
); );
progress.Local.Reset(1);
if (Directory.Exists(RepositoryDirPath)) if (Directory.Exists(RepositoryDirPath))
{ {
Directory.Delete(RepositoryDirPath, true); Directory.Delete(RepositoryDirPath, true);
@@ -58,27 +62,28 @@ internal class AonprdRepository(AbstractImporter importer, string searchUrl)
WriteFile("revision.txt", Revision.ToString()); WriteFile("revision.txt", Revision.ToString());
Console.WriteLine("Загрузка индекса..."); progress.Local.Start("Загрузка индекса...");
string indexContent = Load($"/json-data/aon{Revision}-index.json"); string indexContent = Load($"/json-data/aon{Revision}-index.json");
WriteFile("index.json", indexContent); WriteFile("index.json", indexContent);
JObject indexJson = JObject.Parse(indexContent); JObject indexJson = JObject.Parse(indexContent);
Console.WriteLine("Загрузка индексных файлов..."); progress.Local.Reset(indexJson.Count + 1);
progress.Local.End();
ProgressBar bar = new(indexJson.Count);
int i = 0;
foreach (KeyValuePair<string, JToken?> kvp in indexJson) foreach (KeyValuePair<string, JToken?> kvp in indexJson)
{ {
string fileName = kvp.Key; string fileName = kvp.Key;
bar.Refresh(i++, fileName); progress.Local.Start(fileName);
string fileContent = Load($"/json-data/{fileName}.json"); string fileContent = Load($"/json-data/{fileName}.json");
WriteFile($"{fileName}.json", fileContent); WriteFile($"{fileName}.json", fileContent);
progress.Local.End();
} }
bar.Refresh(indexJson.Count, "Готово!"); progress.Local.Complete();
} }
/// <summary> /// <summary>
@@ -35,18 +35,32 @@ internal class AonprdCompendium
/// <returns>Количество элементов.</returns> /// <returns>Количество элементов.</returns>
public int Count() => Types.Values.Sum(t => t.Count); public int Count() => Types.Values.Sum(t => t.Count);
/// <summary>
/// Возвращает количество элементов для указанного типа данных.
/// </summary>
/// <param name="type">Тип данных.</param>
/// <returns>Количество элементов.</returns>
public int Count(string type) => Types.Values.First(t => t.Type == type).Count;
/// <summary> /// <summary>
/// Возвращает количество элементов для указанных типов данных. /// Возвращает количество элементов для указанных типов данных.
/// </summary> /// </summary>
/// <param name="types">Список типов.</param> /// <param name="types">Список типов данных.</param>
/// <returns>Количество элементов.</returns> /// <returns>Количество элементов.</returns>
public int Count(IEnumerable<string> types) => public int Count(IEnumerable<string> types) =>
Types.Values.Where(t => types.Contains(t.Type)).Sum(t => t.Count); Types.Values.Where(t => types.Contains(t.Type)).Sum(t => t.Count);
/// <summary>
/// Возвращает список файлов для выбора сущностей указанного типа данных.
/// </summary>
/// <param name="type">Тип данных.</param>
/// <returns>Список искомых файлов.</returns>
public IEnumerable<string> Files(string type) => Types.Values.First(t => t.Type == type).Files;
/// <summary> /// <summary>
/// Возвращает список файлов для выбора сущностей указанных типов данных. /// Возвращает список файлов для выбора сущностей указанных типов данных.
/// </summary> /// </summary>
/// <param name="types">Список типов.</param> /// <param name="types">Список типов данных.</param>
/// <returns>Список искомых файлов.</returns> /// <returns>Список искомых файлов.</returns>
public IEnumerable<string> Files(IEnumerable<string> types) => public IEnumerable<string> Files(IEnumerable<string> types) =>
Types.Values.Where(t => types.Contains(t.Type)).SelectMany(t => t.Files).Distinct(); Types.Values.Where(t => types.Contains(t.Type)).SelectMany(t => t.Files).Distinct();
@@ -25,7 +25,7 @@ internal class AonprdReference(string id, string file)
/// <summary> /// <summary>
/// Определение типа сущности как префикса идентификатора. /// Определение типа сущности как префикса идентификатора.
/// </summary> /// </summary>
private static string ResolveType(string id) public static string ResolveType(string id)
{ {
string type = string.Join('-', id.Split('-')[..^1]); string type = string.Join('-', id.Split('-')[..^1]);
@@ -1,8 +1,7 @@
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
/// <summary> /// <summary>
/// Класс AonprdType представляет собой уникальный тип данных, связанный с Aon (Advanced Opportunity Network). /// Класс типа элементов с Архива Нефиса.
/// Это основной класс, который хранит информацию о различных элементах системы.
/// </summary> /// </summary>
internal class AonprdType(string type) internal class AonprdType(string type)
{ {
@@ -1,148 +0,0 @@
using Konsole;
using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
using Pf2eDataScrapper.Processors;
using Pf2eModel;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
/// <summary>
/// Класс для создание схемы данных Архива Нефиса.
/// </summary>
/// <param name="loader"></param>
internal class AonprdDataBuilder(AonprdLoader loader)
{
/// <summary>
/// Основной класс для работы с данными Архива Нефиса.
/// </summary>
public readonly AonprdLoader Loader = loader;
/// <summary>
/// Коллекция папок компендиума.
/// </summary>
public AonprdCompendium Compendium => Loader.Compendium;
/// <summary>
/// Коллекция обработчиков данных.
/// </summary>
public readonly AonprdProcessorCollection Processors = new();
/// <summary>
/// Очистить таблицы в базе данных.
/// </summary>
public void TruncateTables()
{
Console.WriteLine("Truncating all old data...");
using Pf2eDbContext context = new();
string cmd = context.Entities.Truncate() + "\n" + context.TruncateAll();
Console.WriteLine(cmd);
Console.WriteLine("Truncation completed!");
}
/// <summary>
/// Конфигурация интерфейса вывода результатов.
/// </summary>
protected ConcurrentWriter Window = null!;
/// <summary>
/// Основное окно для вывода информации.
/// </summary>
protected Window Output = null!;
/// <summary>
/// Полоса прогресса для основного процесса обработки данных.
/// </summary>
protected ProgressBar ProcessProgressBar = null!;
/// <summary>
/// Полоса прогресса для под-процессов обработки данных.
/// </summary>
protected ProgressBar SubProcessProgressBar = null!;
/// <summary>
/// Метод для настройки интерфейса.
/// </summary>
protected void ConfigureWindow()
{
Window = new Window(0, 0, Console.WindowWidth, Console.WindowHeight).Concurrent();
IConsole bars = Window.OpenBox(
"Progress",
Console.WindowWidth,
4,
new() { Body = Colors.WhiteOnBlack }
);
Output = new(0, 4, Console.WindowWidth, Console.WindowHeight - 4, Window);
ProcessProgressBar = new(bars, Processors.Items.Count, 15);
SubProcessProgressBar = new(bars, 1, 15);
}
/// <summary>
/// Обработка отдельного процессора данных.
/// </summary>
protected void RunProcessor(AonprdProcessorCollection.ProcessorItem item, JObject json)
{
AonprdProcessorInput input = new(json);
AbstractProcessor processor = (AbstractProcessor)item.Constructor.Invoke([input]);
try
{
processor.Process();
processor.Save();
}
catch (LegacyItemException ex)
{
Output.WriteLine(ex.Message);
}
catch (SkipItemException ex)
{
Output.WriteLine(ex.Message);
}
}
/// <summary>
/// Полная обработка и сохранение данных.
/// </summary>
public void Build()
{
ConfigureWindow();
ProcessProgressBar.Refresh(-1, "");
foreach (AonprdProcessorCollection.ProcessorItem item in Processors.Iterate())
{
ProcessProgressBar.Next(item.Attribute.Name);
SubProcessProgressBar.Refresh(-1, "Starting...");
if (item.Attribute.Type is null)
{
RunProcessor(item, []);
continue;
}
SubProcessProgressBar.Max = Compendium.Count([item.Attribute.Type]);
foreach (AonprdLoader.LoaderEntry entry in Loader.Entries(item.Attribute.Type))
{
SubProcessProgressBar.Next($"{entry.Id}");
RunProcessor(item, entry.Json);
}
SubProcessProgressBar.Next("Ready");
DatabaseHelper.ResetContext();
}
ProcessProgressBar.Next("Done!");
Window.Clear();
}
}
@@ -1,62 +0,0 @@
using Pf2eDataScrapper.App.Attributes;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
/// <summary>
/// Атрибут обработчика данных Архивов Нефиса.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class AonprdProcessorAttribute : Attributes
{
/// <summary>
/// Важные общие зависимости для обработчка.
/// </summary>
private static readonly string[] DefaultDependencies = ["aonprd:trait", "aonprd:source"];
/// <summary>
/// Тип данных, которые обработчик обрабатывает.
/// </summary>
public string Type { get; init; }
/// <summary>
/// Конструктор класса <see cref="AonprdProcessorAttribute"/> со стандартными общими зависимости для обработчка.
/// </summary>
/// <param name="type">Тип данных, которые обработчик обрабатывает. Также будет использоваться в качестве названия.</param>
public AonprdProcessorAttribute(string type)
: this(type, type, DefaultDependencies) { }
/// <summary>
/// Конструктор класса <see cref="AonprdProcessorAttribute"/>.
/// </summary>
/// <param name="type">Тип данных, которые обработчик обрабатывает. Также будет использоваться в качестве названия.</param>
/// <param name="dependencies">Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.</param>
/// <param name="includeDefaults">Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка.</param>
public AonprdProcessorAttribute(string type, string[] dependencies, bool includeDefaults = true)
: this(type, type, dependencies, includeDefaults) { }
/// <summary>
/// Конструктор класса <see cref="AonprdProcessorAttribute"/> со стандартными общими зависимости для обработчка.
/// </summary>
/// <param name="name">Название обработчика.</param>
/// <param name="type">Тип данных, которые обработчик обрабатывает.</param>
public AonprdProcessorAttribute(string name, string type)
: this(name, type, DefaultDependencies) { }
/// <summary>
/// Основной конструктор класса <see cref="AonprdProcessorAttribute"/>.
/// </summary>
/// <param name="name">Название обработчика.</param>
/// <param name="type">Тип данных, которые обработчик обрабатывает.</param>
/// <param name="dependencies">Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.</param>
/// <param name="includeDefaults">Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка.</param>
public AonprdProcessorAttribute(
string name,
string? type,
ICollection<string> dependencies,
bool includeDefaults = true
)
: base($"aonprd:{name}", [.. includeDefaults ? DefaultDependencies : [], .. dependencies])
{
Type = type;
}
}
@@ -1,187 +0,0 @@
using System.Reflection;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
/// <summary>
/// Класс отвечает за управление обработчиками, отмеченных атрибутом <see cref="AonprdProcessorAttribute"/>.
/// </summary>
internal class AonprdProcessorCollection
{
/// <summary>
/// Элемент коллекции обработчика.
/// </summary>
/// <param name="attribute">Атрибут, связанный с обаботчиком.</param>
/// <param name="constructor">Конструктор обработчика.</param>
public class ProcessorItem(AonprdProcessorAttribute attribute, ConstructorInfo constructor)
{
/// <summary>
/// Атрибут, связанный с обаботчиком.
/// </summary>
public readonly AonprdProcessorAttribute Attribute = attribute;
/// <summary>
/// Конструктор обработчика.
/// </summary>
public readonly ConstructorInfo Constructor = constructor;
/// <summary>
/// Приоритет выполнения обработчика.
/// </summary>
public int Priority = -1;
}
/// <summary>
/// Список всех объявленных обработчиков.
/// </summary>
private readonly List<ProcessorItem> _allItems;
/// <summary>
/// Список используемых обработчиков.
/// </summary>
public List<ProcessorItem> Items { get; private set; } = [];
/// <summary>
/// Конструктор класса <see cref="AonprdProcessorCollection"/>, загружающий все обработчики.
/// </summary>
public AonprdProcessorCollection()
{
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
{
if (type.GetCustomAttributes(typeof(AonprdProcessorAttribute), true).Length <= 0)
{
continue;
}
if (!type.IsSubclassOf(typeof(AbstractProcessor)))
{
throw new(
$"'AonprdProcessor' attribute should only be applied to children of 'AbstractProcessor' class. '{type.FullName}' is not."
);
}
AonprdProcessorAttribute attribute =
type.GetCustomAttribute<AonprdProcessorAttribute>()!;
ConstructorInfo constructor = type.GetConstructor([typeof(AonprdProcessorInput)])!;
ProcessorItem? collision = Items.FirstOrDefault(i =>
i.Attribute.Name == attribute.Name
);
if (collision is not null)
{
throw new(
$"Multiply attributes 'AonprdProcessor' have same name '{attribute.Name}'."
+ $" They are added to classes: '{type.FullName}' and '{collision.Constructor.DeclaringType!.FullName}'"
);
}
Items.Add(new(attribute, constructor));
}
_allItems = Items.ToList();
}
/// <summary>
/// Проверка, что все зависимости всех обработчиков могут быть удовлетворены.
/// </summary>
private void CheckDependencies()
{
foreach (ProcessorItem item in Items)
{
foreach (string dependency in item.Attribute.Dependencies)
{
if (Items.All(i => i.Attribute.Name != dependency))
{
throw new(
$"Dependency '{dependency}' for processor '{item.Attribute.Name}' not found."
);
}
}
}
}
/// <summary>
/// Сортировать обработчики в соответсвии со всеми зависимостями.
/// </summary>
private void SortItems()
{
CheckDependencies();
Dictionary<string, int> priorities = [];
while (priorities.Count < Items.Count)
{
foreach (ProcessorItem item in Items)
{
if (item.Priority > 0)
{
continue;
}
IEnumerable<int> dependencyPriorities = item.Attribute.Dependencies.Select(d =>
priorities.GetValueOrDefault(d, -1)
);
if (dependencyPriorities.Any(p => p == -1))
{
continue;
}
item.Priority = dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1;
priorities.Add(item.Attribute.Name, item.Priority);
}
}
Items.Sort((a, b) => a.Priority - b.Priority);
}
/// <summary>
/// Итерация через все используемые обработчики.
/// </summary>
/// <returns>Итератор.</returns>
public IEnumerable<ProcessorItem> Iterate()
{
SortItems();
foreach (ProcessorItem item in Items)
{
yield return item;
}
}
/// <summary>
/// Ограничить список используемых обработчиков по названию.
/// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики.
/// </summary>
/// <param name="processorNames">Коллекция названиий требуемых обработчиков.</param>
public void UseOnlyNames(ICollection<string> processorNames)
{
if (processorNames.Count == 0)
{
Items = _allItems;
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()
)
{
ProcessorItem item = _allItems.First(item => item.Attribute.Name == pair.Key);
foreach (string dependency in item.Attribute.Dependencies)
{
nameFlags.TryAdd(dependency, false);
}
nameFlags[pair.Key] = true;
}
}
Items = _allItems.Where(item => nameFlags.ContainsKey(item.Attribute.Name)).ToList();
}
}
@@ -1,27 +0,0 @@
using Pf2eDataScrapper.Processors;
using Pf2eModel.Model.Base.Targets;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common;
[AonprdProcessor("external", null, [], false)]
internal class ExternalProcessor(AonprdProcessorInput input) : AbstractProcessor(input)
{
public ExternalTarget AonprdTarget = null!;
public override void Process()
{
AonprdTarget = new()
{
Name = AonprdImporter.TargetName,
Icon = "http://www.google.com/s2/favicons?domain=2e.aonprd.com",
Prefix = "https://2e.aonprd.com",
};
}
public override void Save()
{
Context.ExternalTargets.Add(AonprdTarget);
Globals.AonprdTarget = AonprdTarget;
}
}
@@ -1,29 +0,0 @@
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("archetype")]
internal class ArchetypeProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
/// <inheritdoc/>
public override EntityType Type => EntityType.Archetype;
/// <inheritdoc/>
public override Archetype Object { get; } = new();
/// <inheritdoc/>
public override void Add() => Context.Archetypes.Add(Object);
/// <inheritdoc/>
public override void Process()
{
base.Process();
Object.Level = Input.GetOptional<int>("level");
}
/// <inheritdoc/>
protected override string[] AddedDescriptionPaths() =>
["prerequisite_markdown", "requirement_markdown", "summary_markdown"];
}
@@ -1,17 +0,0 @@
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("language")]
internal class LanguageProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
/// <inheritdoc/>
public override EntityType Type => EntityType.Language;
/// <inheritdoc/>
public override Language Object { get; } = new();
/// <inheritdoc/>
public override void Add() => Context.Languages.Add(Object);
}
@@ -1,37 +0,0 @@
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types;
[AonprdProcessor("trait", ["external"], false)]
internal class TraitProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input)
{
/// <inheritdoc/>
public override EntityType Type => EntityType.Trait;
/// <inheritdoc/>
public override Trait Object { get; } = new();
/// <inheritdoc/>
public override void Add() => Context.Traits.Add(Object);
/// <inheritdoc/>
public override void Process()
{
base.Process();
Object.Type = TraitType.Regular;
Context.Traits.Add(Object);
}
/// <inheritdoc/>
protected override void AddTraits()
{
// Необходимо пропускать
}
/// <inheritdoc/>
protected override string GetDescription() =>
base.GetDescription() + AddedDescription(["summary_markdown", "search_markdown"]);
}
@@ -1,6 +1,6 @@
using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Base.Targets;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
/// <summary> /// <summary>
/// Класс-помощник, хранящий необходимые глобальные данные. /// Класс-помощник, хранящий необходимые глобальные данные.
@@ -1,6 +1,6 @@
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
/// <summary> /// <summary>
/// Класс, помогающий разрешить строковые значения с Архивов Нефиса в перечисления. /// Класс, помогающий разрешить строковые значения с Архивов Нефиса в перечисления.
@@ -1,9 +1,10 @@
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors;
using Pf2eModel.Model.Base; using Pf2eModel.Model.Base;
using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Source;
using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Common.I18n;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
/// <summary> /// <summary>
/// Обработчик сущностей, помеченных как устаревшие. /// Обработчик сущностей, помеченных как устаревшие.
@@ -1,6 +1,6 @@
using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Source;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
/// <summary> /// <summary>
/// Класс коллекции книг, содержащих правила. /// Класс коллекции книг, содержащих правила.
@@ -3,6 +3,10 @@ using System.Text;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.Processors;
using Pf2eModel; using Pf2eModel;
using Pf2eModel.Model; using Pf2eModel.Model;
@@ -13,18 +17,28 @@ using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors;
/// <summary> /// <summary>
/// Абстрактный класс-обработчик сущностей, полученных из Архивов Нефиса. /// Абстрактный класс-обработчик сущностей, полученных из Архивов Нефиса.
/// Класс предназначен для обработки данных с помощью различных обработчиков, /// Класс предназначен для обработки данных с помощью различных обработчиков,
/// для которых указан атрибут <see cref="AonprdProcessorAttribute"/>. /// для которых указан атрибут <see cref="ProcessorAttribute"/>.
/// </summary> /// </summary>
/// <param name="input">Объект обработки вводимого JSON.</param> /// <param name="input">Объект обработки вводимого JSON.</param>
/// <seealso cref="AonprdProcessorAttribute"/> /// <seealso cref="ProcessorAttribute"/>
internal abstract class AbstractEntityProcessor(AonprdProcessorInput input) internal abstract class AbstractEntityProcessor(
: AbstractProcessor(input) AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractInputProcessor(input)
{ {
public Pf2eDbContext Context { get; } = context;
public StringHelper StringHelper { get; } = stringHelper;
public DatabaseHelper Helper { get; } = helper;
public AonprdBuilderGlobals Globals { get; } = globals;
/// <summary> /// <summary>
/// Корневая сущность. /// Корневая сущность.
/// </summary> /// </summary>
@@ -1,20 +1,27 @@
using Pf2eModel; using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Base; using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors;
/// <summary> /// <summary>
/// Абстрактный класс-обработчик сущностей подклассов, полученных из Архивов Нефиса. /// Абстрактный класс-обработчик сущностей подклассов, полученных из Архивов Нефиса.
/// Класс предназначен для обработки данных с помощью различных обработчиков, /// Класс предназначен для обработки данных с помощью различных обработчиков,
/// для которых указан атрибут <see cref="AonprdProcessorAttribute"/>. /// для которых указан атрибут <see cref="ProcessorAttribute"/>.
/// </summary> /// </summary>
/// <param name="input">Объект обработки вводимого JSON.</param> internal abstract class AbstractSubclassProcessor(
/// <seealso cref="AonprdProcessorAttribute"/> AonprdProcessorInput input,
internal abstract class AbstractSubclassProcessor(AonprdProcessorInput input) Pf2eDbContext context,
: AbstractEntityProcessor(input) StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override EntityType Type => EntityType.Subclass; public override EntityType Type => EntityType.Subclass;
@@ -0,0 +1,26 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
[Processor(
"aonprd:repository",
Description = "Обновление репозитория",
Group = ProcessorGroupEnum.AonprdPreparation
)]
internal class AonprdRepositoryUpdateProcessor(AonprdImporter importer, ProcessProgress progress)
: IProcessor
{
private AonprdImporter Importer { get; } = importer;
public ProcessProgress Progress { get; } = progress;
public void Process()
{
Importer.Repository.Update(Progress);
}
public void Save()
{
Importer.Loader.Prepare();
}
}
@@ -1,24 +1,29 @@
using Konsole; using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.Processors;
using Pf2eDataScrapper.SchemaBuilder; using Pf2eDataScrapper.SchemaBuilder;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.SchemaBuilder; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
/// <summary> [Processor(
/// Класс создания схемы данных с сайта Архивов Нефеса. "aonprd:schema",
/// </summary> Description = "Построение схемы данных",
/// <param name="loader">Загузчик данных с сайта Архивов Нефеса.</param> Group = ProcessorGroupEnum.AonprdPreparation
internal class AonprdSchemaBuilder(AonprdLoader loader) )]
[ProcessorDependencies(["aonprd:repository"])]
internal class AonprdSchemaBuildProcessor(
AonprdImporter importer,
ProcessProgress progress,
ApplicationSettings settings
) : IProcessor
{ {
public AonprdImporter Importer { get; } = importer;
public ProcessProgress Progress { get; } = progress;
public ApplicationSettings Settings { get; } = settings;
/// <summary> /// <summary>
/// Загузчик данных с сайта Архивов Нефеса. /// Загузчик данных с сайта Архивов Нефеса.
/// </summary> /// </summary>
public readonly AonprdLoader Loader = loader; public AonprdLoader Loader => Importer.Loader;
/// <inheritdoc cref="AonprdLoader.Compendium"/>
public readonly AonprdCompendium Compendium = loader.Compendium;
// public readonly AonprdSchemaTypeResolver Resolver = new();
/// <summary> /// <summary>
/// Дополнительные схемы данных. /// Дополнительные схемы данных.
@@ -28,6 +33,44 @@ internal class AonprdSchemaBuilder(AonprdLoader loader)
["_subclass"] = new() { Path = "_subclass" }, ["_subclass"] = new() { Path = "_subclass" },
}; };
/// <summary>
/// Построение схемы данных.
/// </summary>
public void Process()
{
Progress.Local.Reset(Loader.ItemCount);
foreach (AonprdLoader.LoaderEntry entry in Loader.Entries())
{
Progress.Local.Start(entry.Type);
GetSchema(entry).AddValue(entry.Json);
if (_subclassSchemaTypes.Contains(entry.Type))
{
Schemas["_subclass"].AddValue(entry.Json);
}
Progress.Local.End();
}
Progress.Local.Complete();
}
/// <summary>
/// Сохранение постренных схем данных.
/// </summary>
public void Save()
{
string targetDir = Settings.OutputDir;
targetDir = Path.Combine(targetDir, Loader.Importer.Slug);
foreach (KeyValuePair<string, SchemaNode> kvp in Schemas)
{
SaveSchema(targetDir, kvp.Key, kvp.Value);
}
}
/// <summary> /// <summary>
/// Список типов, являющихся подклассами. /// Список типов, являющихся подклассами.
/// </summary> /// </summary>
@@ -87,50 +130,6 @@ internal class AonprdSchemaBuilder(AonprdLoader loader)
return value; return value;
} }
/// <summary>
/// Построение схемы данных.
/// </summary>
public void BuildSchemas()
{
Console.WriteLine("Building schemas...");
ProgressBar bar = new(Loader.ItemCount);
int i = 0;
foreach (AonprdLoader.LoaderEntry entry in Loader.Entries())
{
bar.Refresh(i++, entry.Type);
GetSchema(entry).AddValue(entry.Json);
if (_subclassSchemaTypes.Contains(entry.Type))
{
Schemas["_subclass"].AddValue(entry.Json);
}
}
bar.Refresh(Loader.ItemCount, "Done!");
}
/// <summary>
/// Сохранение постренных схем данных.
/// </summary>
/// <param name="targetDir">Целевой каталог.</param>
public void Save(string targetDir)
{
Console.WriteLine("Dumping schemas...");
targetDir = Path.Combine(targetDir, Loader.Importer.Slug);
ProgressBar bar = new(Schemas.Count);
int i = 0;
foreach (KeyValuePair<string, SchemaNode> kvp in Schemas)
{
bar.Refresh(i++, kvp.Key);
SaveSchema(targetDir, kvp.Key, kvp.Value);
}
}
/// <summary> /// <summary>
/// Сохранение отдельной схемы данных. /// Сохранение отдельной схемы данных.
/// </summary> /// </summary>
@@ -0,0 +1,37 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors;
using Pf2eModel;
using Pf2eModel.Model.Base.Targets;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
[Processor(
"aonprd:external",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdPreparation
)]
[ProcessorDependencies(["aonprd:repository"])]
internal class ExternalProcessor(Pf2eDbContext context) : AbstractProcessor
{
public Pf2eDbContext Context { get; } = context;
public ExternalTarget AonprdTarget = null!;
public override void Process()
{
AonprdTarget = new()
{
Name = AonprdImporter.TargetName,
Icon = "http://www.google.com/s2/favicons?domain=2e.aonprd.com",
Prefix = "https://2e.aonprd.com",
};
}
public override void Save()
{
Context.ExternalTargets.Add(AonprdTarget);
// TODO
// Globals.AonprdTarget = AonprdTarget;
}
}
@@ -1,13 +1,23 @@
using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors;
using Pf2eModel;
using Pf2eModel.Model.Common; using Pf2eModel.Model.Common;
using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
[AonprdProcessor("skill", [], false)] [Processor(
internal class SkillProcessor(AonprdProcessorInput input) : AbstractProcessor(input) "aonprd:skill",
InnerType = "skill",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdPreparation
)]
internal class SkillProcessor(AonprdProcessorInput input, Pf2eDbContext context)
: AbstractInputProcessor(input)
{ {
public Pf2eDbContext Context { get; } = context;
/// <summary> /// <summary>
/// Обучаемый объект. /// Обучаемый объект.
/// </summary> /// </summary>
@@ -1,12 +1,23 @@
using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Processors;
using Pf2eModel;
using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Source;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
[AonprdProcessor("source", [], false)] [Processor(
internal class SourceProcessor(AonprdProcessorInput input) : AbstractProcessor(input) "aonprd:source",
InnerType = "source",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdPreparation
)]
[ProcessorDependencies(["aonprd:repository"])]
internal class SourceProcessor(AonprdProcessorInput input, Pf2eDbContext context)
: AbstractInputProcessor(input)
{ {
public Pf2eDbContext Context { get; } = context;
/// <summary> /// <summary>
/// Источник сущности. /// Источник сущности.
/// </summary> /// </summary>
@@ -1,14 +1,32 @@
using Pf2eModel; using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Base; using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common; using Pf2eModel.Model.Common;
using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Subclasses; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
[AonprdProcessor("apparition", ["class", "spell", "skill"])] [Processor(
internal class ApparitionProcessor(AonprdProcessorInput input) : AbstractSubclassProcessor(input) "aonprd:apparition",
InnerType = "apparition",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdSubclasses
)]
[ProcessorDependencies(
["aonprd:trait", "aonprd:source", "aonprd:class", "aonprd:spell", "aonprd:skill"]
)]
internal class ApparitionProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractSubclassProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override Subclass Object { get; } = new(); public override Subclass Object { get; } = new();
@@ -1,9 +1,26 @@
using Pf2eModel.Model.Enum; using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[AonprdProcessor("action")] [Processor(
internal class ActionProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) "aonprd:action",
InnerType = "action",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
internal class ActionProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override EntityType Type => EntityType.Action; public override EntityType Type => EntityType.Action;
@@ -1,11 +1,28 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[AonprdProcessor("ancestry", ["trait", "source", "language"])] [Processor(
internal class AncestryProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) "aonprd:ancestry",
InnerType = "ancestry",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:language"])]
internal class AncestryProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override EntityType Type => EntityType.Ancestry; public override EntityType Type => EntityType.Ancestry;
@@ -0,0 +1,46 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[Processor(
"aonprd:archetype",
InnerType = "archetype",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
internal class ArchetypeProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{
/// <inheritdoc/>
public override EntityType Type => EntityType.Archetype;
/// <inheritdoc/>
public override Archetype Object { get; } = new();
/// <inheritdoc/>
public override void Add() => Context.Archetypes.Add(Object);
/// <inheritdoc/>
public override void Process()
{
base.Process();
Object.Level = Input.GetOptional<int>("level");
}
/// <inheritdoc/>
protected override string[] AddedDescriptionPaths() =>
["prerequisite_markdown", "requirement_markdown", "summary_markdown"];
}
@@ -1,12 +1,29 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure;
using Pf2eModel.Model.Structure.BackgroundRelation; using Pf2eModel.Model.Structure.BackgroundRelation;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[AonprdProcessor("background")] [Processor(
internal class BackgroundProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) "aonprd:background",
InnerType = "background",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
internal class BackgroundProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override EntityType Type => EntityType.Background; public override EntityType Type => EntityType.Background;
@@ -1,5 +1,9 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel; using Pf2eModel;
using Pf2eModel.Model.Common; using Pf2eModel.Model.Common;
using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Common.I18n;
@@ -8,10 +12,22 @@ using Pf2eModel.Model.Structure;
using Pf2eModel.Model.Structure.ClassRelation; using Pf2eModel.Model.Structure.ClassRelation;
using static System.Text.RegularExpressions.Regex; using static System.Text.RegularExpressions.Regex;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[AonprdProcessor("class", ["trait", "source", "skill"])] [Processor(
internal partial class ClassProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) "aonprd:class",
InnerType = "class",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])]
internal partial class ClassProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override EntityType Type => EntityType.Class; public override EntityType Type => EntityType.Class;
@@ -1,12 +1,28 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel; using Pf2eModel;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[AonprdProcessor("feat", ["trait", "source", "archetype"])] [Processor(
internal class FeatProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) "aonprd:feat",
InnerType = "feat",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:archetype"])]
internal class FeatProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override EntityType Type => EntityType.Feat; public override EntityType Type => EntityType.Feat;
@@ -0,0 +1,34 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[Processor(
"aonprd:language",
InnerType = "language",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
internal class LanguageProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{
/// <inheritdoc/>
public override EntityType Type => EntityType.Language;
/// <inheritdoc/>
public override Language Object { get; } = new();
/// <inheritdoc/>
public override void Add() => Context.Languages.Add(Object);
}
@@ -1,16 +1,31 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers; using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel; using Pf2eModel;
using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure;
using Pf2eModel.Model.Structure.RitualRelation; using Pf2eModel.Model.Structure.RitualRelation;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[AonprdProcessor("ritual", ["trait", "source", "skill"])] [Processor(
internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) "aonprd:ritual",
InnerType = "ritual",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:skill"])]
internal partial class RitualProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override EntityType Type => EntityType.Ritual; public override EntityType Type => EntityType.Ritual;
@@ -1,14 +1,31 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Source;
using Pf2eModel.Model.Base.Targets; using Pf2eModel.Model.Base.Targets;
using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
using Rule = Pf2eModel.Model.Structure.Rule; using Rule = Pf2eModel.Model.Structure.Rule;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules;
[AonprdProcessor("rules", ["rules-toc"])] [Processor(
internal class RuleProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) "aonprd:rules",
InnerType = "rules",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:rules-toc"])]
internal class RuleProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override EntityType Type => EntityType.Rule; public override EntityType Type => EntityType.Rule;
@@ -6,6 +6,7 @@
// Ну а пока просто расставляем родительские и дочерние элементы. // Ну а пока просто расставляем родительские и дочерние элементы.
using System.Data; using System.Data;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.Processors;
using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure;
using Rule = Pf2eModel.Model.Structure.Rule; using Rule = Pf2eModel.Model.Structure.Rule;
@@ -2,6 +2,7 @@
using System.Web; using System.Web;
using AngleSharp; using AngleSharp;
using AngleSharp.Dom; using AngleSharp.Dom;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.Processors;
using Pf2eModel.Model.Base.Source; using Pf2eModel.Model.Base.Source;
@@ -1,13 +1,30 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Common.I18n; using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum; using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure; using Pf2eModel.Model.Structure;
using Pf2eModel.Model.Structure.SpellRelation; using Pf2eModel.Model.Structure.SpellRelation;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types; namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[AonprdProcessor("spell")] [Processor(
internal class SpellProcessor(AonprdProcessorInput input) : AbstractEntityProcessor(input) "aonprd:spell",
InnerType = "spell",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:trait", "aonprd:source"])]
internal class SpellProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override EntityType Type => EntityType.Spell; public override EntityType Type => EntityType.Spell;
@@ -0,0 +1,54 @@
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
using Pf2eModel;
using Pf2eModel.Model.Enum;
using Pf2eModel.Model.Structure;
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
[Processor(
"aonprd:trait",
InnerType = "trait",
Description = "Создание цели внешних ссылок",
Group = ProcessorGroupEnum.AonprdEntities
)]
[ProcessorDependencies(["aonprd:external"])]
internal class TraitProcessor(
AonprdProcessorInput input,
Pf2eDbContext context,
StringHelper stringHelper,
DatabaseHelper helper,
AonprdBuilderGlobals globals
) : AbstractEntityProcessor(input, context, stringHelper, helper, globals)
{
/// <inheritdoc/>
public override EntityType Type => EntityType.Trait;
/// <inheritdoc/>
public override Trait Object { get; } = new();
/// <inheritdoc/>
public override void Add() => Context.Traits.Add(Object);
/// <inheritdoc/>
public override void Process()
{
base.Process();
Object.Type = TraitType.Regular;
Context.Traits.Add(Object);
}
/// <inheritdoc/>
protected override void AddTraits()
{
// Необходимо пропускать
}
/// <inheritdoc/>
protected override string GetDescription() =>
base.GetDescription() + AddedDescription(["summary_markdown", "search_markdown"]);
}
@@ -2,6 +2,7 @@
using System.Text; using System.Text;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors; using Pf2eDataScrapper.Processors;
using Pf2eModel; using Pf2eModel;
using Pf2eModel.Model; using Pf2eModel.Model;
@@ -1,4 +1,6 @@
namespace Pf2eDataScrapper.Importers.Repositories; using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers.Repositories;
/// <summary> /// <summary>
/// Класс абстрактного репозитория. /// Класс абстрактного репозитория.
@@ -22,5 +24,5 @@ internal abstract class AbstractImporterRepository(AbstractImporter importer)
/// <summary> /// <summary>
/// Обновление данных в репозитории. /// Обновление данных в репозитории.
/// </summary> /// </summary>
public abstract void Update(); public abstract void Update(ProcessProgress progress);
} }
@@ -1,4 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Importers.Repositories; namespace Pf2eDataScrapper.Importers.Repositories;
@@ -16,11 +17,14 @@ internal class GitRepository(AbstractImporter importer, string url)
public string Url { get; } = url; public string Url { get; } = url;
/// <inheritdoc/> /// <inheritdoc/>
public override void Update() public override void Update(ProcessProgress progress)
{ {
progress.Local.Reset(1);
if (Directory.Exists(RepositoryDirPath)) if (Directory.Exists(RepositoryDirPath))
{ {
// git pull progress.Local.Start("git pull");
Process process = new() Process process = new()
{ {
StartInfo = new() StartInfo = new()
@@ -34,7 +38,8 @@ internal class GitRepository(AbstractImporter importer, string url)
} }
else else
{ {
// git clone progress.Local.Start("git clone");
Process process = new() Process process = new()
{ {
StartInfo = new() StartInfo = new()
@@ -46,5 +51,7 @@ internal class GitRepository(AbstractImporter importer, string url)
}; };
process.Start(); process.Start();
} }
progress.Local.Complete();
} }
} }
@@ -1,34 +1,18 @@
using Microsoft.Extensions.DependencyInjection; using Pf2eDataScrapper.App;
using Pf2eDataScrapper.App;
using Pf2eModel;
namespace Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.Processors;
/// <summary> /// <summary>
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса. /// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
/// Класс предназначен для обработки данных с помощью различных обработчиков, /// Класс предназначен для обработки данных с помощью различных обработчиков,
/// для которых указан атрибут <see cref="ProcessAttribute"/>. /// для которых указан атрибут <see cref="ProcessorAttribute"/>.
/// </summary> /// </summary>
/// <param name="serviceProvider">Провайдер сервисов.</param> /// <param name="input">Входные данные обработчика.</param>
/// <param name="input">Провайдер сервисов.</param> /// <seealso cref="ProcessorAttribute"/>
/// <seealso cref="ProcessAttribute"/> internal abstract class AbstractInputProcessor(ProcessorInput input) : IProcessor
internal abstract class AbstractInputProcessor(
IServiceProvider serviceProvider,
ProcessorInput input
) : IProcessor
{ {
/// <summary> /// <summary>
/// Провайдер сервисов. /// Входные данные обработчика.
/// </summary>
public IServiceProvider ServiceProvider { get; } = serviceProvider;
/// <summary>
/// Контекст базы данных.
/// </summary>
public Pf2eDbContext Context { get; } = serviceProvider.GetService<Pf2eDbContext>()!;
/// <summary>
/// Ввод.
/// </summary> /// </summary>
public ProcessorInput Input { get; } = input; public ProcessorInput Input { get; } = input;
@@ -7,22 +7,11 @@ namespace Pf2eDataScrapper.Processors;
/// <summary> /// <summary>
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса. /// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
/// Класс предназначен для обработки данных с помощью различных обработчиков, /// Класс предназначен для обработки данных с помощью различных обработчиков,
/// для которых указан атрибут <see cref="ProcessAttribute"/>. /// для которых указан атрибут <see cref="ProcessorAttribute"/>.
/// </summary> /// </summary>
/// <param name="serviceProvider">Провайдер сервисов.</param> /// <seealso cref="ProcessorAttribute"/>
/// <seealso cref="ProcessAttribute"/> internal abstract class AbstractProcessor : IProcessor
internal abstract class AbstractProcessor(IServiceProvider serviceProvider) : IProcessor
{ {
/// <summary>
/// Провайдер сервисов.
/// </summary>
public IServiceProvider ServiceProvider { get; } = serviceProvider;
/// <summary>
/// Контекст базы данных.
/// </summary>
public Pf2eDbContext Context { get; } = serviceProvider.GetService<Pf2eDbContext>()!;
/// <summary> /// <summary>
/// Основной метод обработки данных. /// Основной метод обработки данных.
/// </summary> /// </summary>
@@ -0,0 +1,16 @@
using Pf2eDataScrapper.App;
using Pf2eModel;
namespace Pf2eDataScrapper.Processors;
[Processor("truncate", Description = "Отчистка таблиц БД", Group = ProcessorGroupEnum.General)]
internal class DatabaseTruncateProcessor : IProcessor
{
public void Process()
{
using Pf2eDbContext context = new();
string _ = context.Entities.Truncate() + "\n" + context.TruncateAll();
}
public void Save() { }
}
+1 -8
View File
@@ -1,14 +1,7 @@
using Microsoft.Extensions.DependencyInjection; namespace Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.Processors;
public interface IProcessor public interface IProcessor
{ {
/// <summary>
/// Провайдер сервисов.
/// </summary>
IServiceProvider ServiceProvider { get; }
/// <summary> /// <summary>
/// Основной метод обработки данных. /// Основной метод обработки данных.
/// </summary> /// </summary>
@@ -0,0 +1,156 @@
namespace Pf2eDataScrapper.Processors;
internal class ProcessProgress
{
/// <summary>
/// Прогресс обработки обработчиков <see cref="IProcessor"/>.
/// </summary>
public ProgressItem Processors { get; } = new();
/// <summary>
/// Прогресс работы конкретного обработчика <see cref="IProcessor"/>.
/// </summary>
public ProgressItem Local { get; } = new();
/// <summary>
/// Конструктор класса <see cref="ProcessProgress"/>.
/// </summary>
public ProcessProgress()
{
Processors.Update += Update;
Local.Update += Update;
}
/// <summary>
/// Событие обновления
/// </summary>
private void Update()
{
ProgressUpdate?.Invoke(this);
}
public void Message(string message)
{
ProgressMessage?.Invoke(this, message);
}
public delegate void ProgressUpdateDelegate(ProcessProgress sender);
public event ProgressUpdateDelegate? ProgressUpdate;
public delegate void ProgressMessageDelegate(ProcessProgress sender, string message);
public event ProgressMessageDelegate? ProgressMessage;
/// <summary>
/// Класс прогресса.
/// </summary>
public class ProgressItem
{
/// <summary>
/// Количество обрабатываемых процессов.
/// </summary>
public int Count
{
get => _count;
set
{
_count = value;
Update?.Invoke();
}
}
private int _count = 0;
/// <summary>
/// Индекс текущего обрабатываемого процесса.
/// </summary>
public int Current
{
get => _current;
set
{
_current = value;
Update?.Invoke();
}
}
private int _current = 0;
/// <summary>
/// Название текущего обрабатываемого процесса.
/// </summary>
public string Text
{
get => _text;
set
{
_text = value;
Update?.Invoke();
}
}
private string _text = "";
/// <summary>
/// Значение прогресса.
/// </summary>
public double Percent => Count > 0 ? 1.0 * Current / Count : 1.0;
/// <summary>
/// Сброс прогресса до нуля и установка нового максимального значения.
/// </summary>
/// <param name="max"></param>
public void Reset(int max = 0)
{
_count = max;
_current = 0;
_text = "";
Update?.Invoke();
}
/// <summary>
/// Отмечает начало работы над процессом, описываемым строкой <paramref name="text"/>.
/// </summary>
/// <param name="text">Строка, описывающая процесс.</param>
public void Start(string text)
{
_text = text;
Update?.Invoke();
}
/// <summary>
/// Отмечает конец работы над процессом и увеличивает значение прогресса.
/// </summary>
public void End()
{
_text = "";
_current++;
Update?.Invoke();
}
/// <summary>
/// Отмечает прогресс завершённым.
/// </summary>
public void Complete()
{
_current = _count;
_text = "Готово!";
Update?.Invoke();
}
/// <summary>
/// Получение строкового представления прогресса.
/// </summary>
/// <returns>Строковое представление прогресса.</returns>
public override string ToString()
{
return $"{Text} [{Current} / {Count}; {Percent:P}]";
}
/// <summary>
/// Делегат события обновления.
/// </summary>
public delegate void UpdateDelegate();
/// <summary>
/// Событие обновление.
/// </summary>
public event UpdateDelegate? Update;
}
}
@@ -7,9 +7,15 @@ namespace Pf2eDataScrapper.Processors;
/// <summary> /// <summary>
/// Класс для работы с JSON-объектом. /// Класс для работы с JSON-объектом.
/// </summary> /// </summary>
/// <param name="name">Название объекта, ассоциированного с вводом.</param>
/// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param> /// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param>
internal class ProcessorInput(JObject input) internal class ProcessorInput(string name, JObject input)
{ {
/// <summary>
/// Название объекта, ассоциированного с вводом.
/// </summary>
public string Name { get; } = name;
/// <summary> /// <summary>
/// Входящий JSON-объект, который содержит данные для обработки. /// Входящий JSON-объект, который содержит данные для обработки.
/// </summary> /// </summary>