Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3d710d7d7 | ||
|
|
a9f9ae4b93 |
@@ -0,0 +1,62 @@
|
||||
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,229 @@
|
||||
using System.ComponentModel;
|
||||
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>
|
||||
/// Конструктор класса <see cref="ProcessorCollection"/>.
|
||||
/// </summary>
|
||||
public ProcessorCollection()
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
foreach (Type type in assembly.GetTypes())
|
||||
{
|
||||
if (type.GetCustomAttributes(typeof(ProcessAttribute), 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.IsSubclassOf(typeof(IProcessor)))
|
||||
{
|
||||
throw new(
|
||||
$"'ProcessAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not."
|
||||
);
|
||||
}
|
||||
|
||||
ProcessAttribute processAttribute = type.GetCustomAttribute<ProcessAttribute>()!;
|
||||
DescriptionAttribute? descriptionAttribute =
|
||||
type.GetCustomAttribute<DescriptionAttribute>();
|
||||
ProcessorGroupAttribute? groupAttribute =
|
||||
type.GetCustomAttribute<ProcessorGroupAttribute>();
|
||||
ProcessorDependenciesAttribute? dependenciesAttribute =
|
||||
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
|
||||
ProcessorInnerTypeAttribute? innerTypeAttribute =
|
||||
type.GetCustomAttribute<ProcessorInnerTypeAttribute>();
|
||||
|
||||
bool isCollection = innerTypeAttribute is not null;
|
||||
|
||||
ConstructorInfo? constructor = isCollection
|
||||
? type.GetConstructor([])
|
||||
: type.GetConstructor([typeof(ProcessorInput)])!;
|
||||
|
||||
if (constructor is null)
|
||||
{
|
||||
throw new($"Не удаётся определить конструктор класса {type}");
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
Name = processAttribute.Name,
|
||||
Type = type,
|
||||
Description = descriptionAttribute?.Description ?? processAttribute.Name,
|
||||
Group = groupAttribute?.Group ?? ProcessorGroupEnum.General,
|
||||
Dependencies = dependenciesAttribute?.Dependencies ?? [],
|
||||
IsCollectionProcessor = isCollection,
|
||||
InnerType = innerTypeAttribute?.InnerType,
|
||||
Constructor = constructor,
|
||||
};
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
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 = dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1;
|
||||
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>
|
||||
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 int Priority { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов.
|
||||
/// Внутренний тип элементов указывается в <see cref="InnerType"/>.
|
||||
/// </summary>
|
||||
public required bool IsCollectionProcessor { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов.
|
||||
/// </summary>
|
||||
public string? InnerType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор обработчика.
|
||||
/// </summary>
|
||||
public required ConstructorInfo Constructor { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Pf2eDataScrapper.App;
|
||||
|
||||
public enum ProcessorGroupEnum
|
||||
{
|
||||
[Description("Общие процессоры")]
|
||||
General,
|
||||
|
||||
[Description("Архивы Нефиса: Подготовка")]
|
||||
AonprdPreparation,
|
||||
|
||||
[Description("Архивы Нефиса: Основные сущности")]
|
||||
AonprdEntities,
|
||||
|
||||
[Description("Архивы Нефиса: Подклассы")]
|
||||
AonprdSubclasses,
|
||||
|
||||
[Description("Foundry EN: Подготовка")]
|
||||
FoundryEnPreparation,
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.ComponentModel;
|
||||
using dotenv.net;
|
||||
using dotenv.net.Utilities;
|
||||
|
||||
namespace Pf2eDataScrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Настройки программы.
|
||||
/// </summary>
|
||||
internal class ApplicationSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="ApplicationSettings"/>.
|
||||
/// </summary>
|
||||
public ApplicationSettings()
|
||||
{
|
||||
DotEnv.Load();
|
||||
}
|
||||
|
||||
#region Настройки из .env
|
||||
|
||||
/// <summary>
|
||||
/// Путь к папке с входными данными.
|
||||
/// </summary>
|
||||
[Browsable(true)]
|
||||
[ReadOnly(true)]
|
||||
[DisplayName("Путь к папке с входными данными.")]
|
||||
[Description("Редактирование через .env-файл.")]
|
||||
public string DataDirPath { get; } = EnvReader.GetStringValue("DATA_DIR");
|
||||
|
||||
/// <summary>
|
||||
/// Папка в входными данными.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public DirectoryInfo DataDir => new(DataDirPath);
|
||||
|
||||
/// <summary>
|
||||
/// Путь к папке для сохранения результатов.
|
||||
/// </summary>
|
||||
[Browsable(true)]
|
||||
[ReadOnly(true)]
|
||||
[DisplayName("Путь к папке для сохранения результатов.")]
|
||||
[Description("Редактирование через .env-файл.")]
|
||||
public string OutputDirPath { get; } = EnvReader.GetStringValue("OUTPUT_DIR");
|
||||
|
||||
/// <summary>
|
||||
/// Папка для сохранения результатов.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public string OutputDir => new(OutputDirPath);
|
||||
|
||||
/// <summary>
|
||||
/// Строка подключения к базе данных.
|
||||
/// </summary>
|
||||
[Browsable(true)]
|
||||
[ReadOnly(true)]
|
||||
[DisplayName("Строка подключения к базе данных.")]
|
||||
[Description("Редактирование через .env-файл.")]
|
||||
public string DatabaseConnection { get; } = EnvReader.GetStringValue("DATABASE_CONNECTION");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Основные настройки
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что все старые записи необходимо удалить.
|
||||
/// </summary>
|
||||
[Browsable(true)]
|
||||
[DisplayName("Удаление всех записей перед запуском.")]
|
||||
[Description("Редактирование через .env-файл.")]
|
||||
public bool TruncateAllTables { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Список обработчиков, которые должны быть выполнены.
|
||||
/// Пустой список означает, что должны быть выполнены все обработчики.
|
||||
/// </summary>
|
||||
public ICollection<string> ProcessorNames { get; } = new List<string>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
namespace Pf2eDataScrapper.Forms;
|
||||
|
||||
partial class StartForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
MainSplitContainer = new SplitContainer();
|
||||
PropertiesGroupBox = new GroupBox();
|
||||
SettingsPropertyGrid = new PropertyGrid();
|
||||
StartButton = new Button();
|
||||
ProcessorsGroupBox = new GroupBox();
|
||||
DeselectAllButton = new Button();
|
||||
SelectAllButton = new Button();
|
||||
ProcessorsListView = new ListView();
|
||||
((System.ComponentModel.ISupportInitialize)MainSplitContainer).BeginInit();
|
||||
MainSplitContainer.Panel1.SuspendLayout();
|
||||
MainSplitContainer.Panel2.SuspendLayout();
|
||||
MainSplitContainer.SuspendLayout();
|
||||
PropertiesGroupBox.SuspendLayout();
|
||||
ProcessorsGroupBox.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// MainSplitContainer
|
||||
//
|
||||
MainSplitContainer.Dock = DockStyle.Fill;
|
||||
MainSplitContainer.Location = new Point(0, 0);
|
||||
MainSplitContainer.Name = "MainSplitContainer";
|
||||
//
|
||||
// MainSplitContainer.Panel1
|
||||
//
|
||||
MainSplitContainer.Panel1.Controls.Add(PropertiesGroupBox);
|
||||
MainSplitContainer.Panel1MinSize = 300;
|
||||
//
|
||||
// MainSplitContainer.Panel2
|
||||
//
|
||||
MainSplitContainer.Panel2.Controls.Add(ProcessorsGroupBox);
|
||||
MainSplitContainer.Panel2.Controls.Add(StartButton);
|
||||
MainSplitContainer.Panel2MinSize = 300;
|
||||
MainSplitContainer.Size = new Size(931, 677);
|
||||
MainSplitContainer.SplitterDistance = 300;
|
||||
MainSplitContainer.TabIndex = 0;
|
||||
//
|
||||
// PropertiesGroupBox
|
||||
//
|
||||
PropertiesGroupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
PropertiesGroupBox.Controls.Add(SettingsPropertyGrid);
|
||||
PropertiesGroupBox.Location = new Point(3, 3);
|
||||
PropertiesGroupBox.Name = "PropertiesGroupBox";
|
||||
PropertiesGroupBox.Size = new Size(294, 671);
|
||||
PropertiesGroupBox.TabIndex = 0;
|
||||
PropertiesGroupBox.TabStop = false;
|
||||
PropertiesGroupBox.Text = "Свойства";
|
||||
//
|
||||
// SettingsPropertyGrid
|
||||
//
|
||||
SettingsPropertyGrid.BackColor = SystemColors.Control;
|
||||
SettingsPropertyGrid.Dock = DockStyle.Fill;
|
||||
SettingsPropertyGrid.Location = new Point(3, 19);
|
||||
SettingsPropertyGrid.Name = "SettingsPropertyGrid";
|
||||
SettingsPropertyGrid.Size = new Size(288, 649);
|
||||
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.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
ProcessorsGroupBox.Controls.Add(ProcessorsListView);
|
||||
ProcessorsGroupBox.Controls.Add(SelectAllButton);
|
||||
ProcessorsGroupBox.Controls.Add(DeselectAllButton);
|
||||
ProcessorsGroupBox.Location = new Point(3, 3);
|
||||
ProcessorsGroupBox.Name = "ProcessorsGroupBox";
|
||||
ProcessorsGroupBox.Size = new Size(621, 602);
|
||||
ProcessorsGroupBox.TabIndex = 1;
|
||||
ProcessorsGroupBox.TabStop = false;
|
||||
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.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
ProcessorsListView.CheckBoxes = true;
|
||||
ProcessorsListView.Location = new Point(6, 48);
|
||||
ProcessorsListView.Name = "ProcessorsListView";
|
||||
ProcessorsListView.Size = new Size(606, 548);
|
||||
ProcessorsListView.TabIndex = 2;
|
||||
ProcessorsListView.UseCompatibleStateImageBehavior = false;
|
||||
//
|
||||
// StartForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(931, 677);
|
||||
Controls.Add(MainSplitContainer);
|
||||
Name = "StartForm";
|
||||
Text = "Настройки импорта данных";
|
||||
MainSplitContainer.Panel1.ResumeLayout(false);
|
||||
MainSplitContainer.Panel2.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)MainSplitContainer).EndInit();
|
||||
MainSplitContainer.ResumeLayout(false);
|
||||
PropertiesGroupBox.ResumeLayout(false);
|
||||
ProcessorsGroupBox.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private SplitContainer MainSplitContainer;
|
||||
private GroupBox PropertiesGroupBox;
|
||||
private PropertyGrid SettingsPropertyGrid;
|
||||
private Button StartButton;
|
||||
private GroupBox ProcessorsGroupBox;
|
||||
private Button SelectAllButton;
|
||||
private Button DeselectAllButton;
|
||||
private ListView ProcessorsListView;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Pf2eDataScrapper.Forms;
|
||||
|
||||
public partial class StartForm : Form
|
||||
{
|
||||
public StartForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -0,0 +1,58 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2eDataScrapper.App;
|
||||
using Pf2eDataScrapper.Forms;
|
||||
using Pf2eModel;
|
||||
|
||||
namespace Pf2eDataScrapper;
|
||||
|
||||
internal sealed class ImportApplication : ApplicationContext
|
||||
{
|
||||
public ImportApplication()
|
||||
{
|
||||
InitializeServices();
|
||||
CreateStartForm();
|
||||
}
|
||||
|
||||
#region "Внедрение зависимостей (DI)"
|
||||
|
||||
public IServiceCollection Services = new ServiceCollection();
|
||||
|
||||
private void InitializeServices()
|
||||
{
|
||||
Services.AddSingleton(this);
|
||||
Services.AddSingleton(Settings);
|
||||
|
||||
Services.AddSingleton<ProcessorCollection>();
|
||||
|
||||
Services.AddDbContext<Pf2eDbContext>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Настройки"
|
||||
|
||||
public ApplicationSettings Settings = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Формы"
|
||||
|
||||
private StartForm? _startForm;
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Жизненный цикл"
|
||||
|
||||
private void CreateStartForm()
|
||||
{
|
||||
_startForm = new() { StartPosition = FormStartPosition.CenterParent };
|
||||
_startForm.Show();
|
||||
}
|
||||
|
||||
private void StartProcess()
|
||||
{
|
||||
_startForm!.Close();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
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;
|
||||
|
||||
@@ -11,11 +13,6 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
/// <param name="loader"></param>
|
||||
internal class AonprdDataBuilder(AonprdLoader loader)
|
||||
{
|
||||
/// <summary>
|
||||
/// Класс работы с базой данных.
|
||||
/// </summary>
|
||||
public readonly Pf2eDatabase Database = Pf2eDatabase.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Основной класс для работы с данными Архива Нефиса.
|
||||
/// </summary>
|
||||
@@ -38,7 +35,10 @@ internal class AonprdDataBuilder(AonprdLoader loader)
|
||||
{
|
||||
Console.WriteLine("Truncating all old data...");
|
||||
|
||||
Database.ExecuteCommand("SELECT f_truncate_tables();");
|
||||
using Pf2eDbContext context = new();
|
||||
string cmd = context.Entities.Truncate() + "\n" + context.TruncateAll();
|
||||
|
||||
Console.WriteLine(cmd);
|
||||
|
||||
Console.WriteLine("Truncation completed!");
|
||||
}
|
||||
|
||||
+24
-27
@@ -1,43 +1,22 @@
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
using Pf2eDataScrapper.App.Attributes;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// Атрибут обработчика данных Архивов Нефиса.
|
||||
/// </summary>
|
||||
/// <param name="name">Название обработчика.</param>
|
||||
/// <param name="type">Тип данных, которые обработчик обрабатывает.</param>
|
||||
/// <param name="dependencies">Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.</param>
|
||||
/// <param name="includeDefaults">Флаг, указывающий, следует ли добавить важные общие зависимости для обработчка.</param>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class AonprdProcessorAttribute(
|
||||
string name,
|
||||
string? type,
|
||||
string[] dependencies,
|
||||
bool includeDefaults = true
|
||||
) : Attribute
|
||||
public class AonprdProcessorAttribute : Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Важные общие зависимости для обработчка.
|
||||
/// </summary>
|
||||
private static readonly string[] DefaultDependencies = ["trait", "source"];
|
||||
|
||||
/// <summary>
|
||||
/// Название обработчика.
|
||||
/// </summary>
|
||||
public readonly string Name = name;
|
||||
private static readonly string[] DefaultDependencies = ["aonprd:trait", "aonprd:source"];
|
||||
|
||||
/// <summary>
|
||||
/// Тип данных, которые обработчик обрабатывает.
|
||||
/// </summary>
|
||||
public readonly string? Type = type;
|
||||
|
||||
/// <summary>
|
||||
/// Названия обработчиков, которые должны быть выполнены до запуска этого обработчика.
|
||||
/// </summary>
|
||||
public readonly IEnumerable<string> Dependencies =
|
||||
[
|
||||
.. includeDefaults ? DefaultDependencies : [],
|
||||
.. dependencies,
|
||||
];
|
||||
public string Type { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdProcessorAttribute"/> со стандартными общими зависимости для обработчка.
|
||||
@@ -62,4 +41,22 @@ public class AonprdProcessorAttribute(
|
||||
/// <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
-1
@@ -1,5 +1,5 @@
|
||||
using System.Reflection;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder;
|
||||
|
||||
|
||||
+1
@@ -3,6 +3,7 @@ using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model;
|
||||
using Pf2eModel.Model.Base;
|
||||
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
using Pf2eModel;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
|
||||
/// Класс предназначен для обработки данных с помощью различных обработчиков,
|
||||
/// для которых указан атрибут <see cref="AonprdProcessorAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="input">Объект обработки вводимого JSON.</param>
|
||||
/// <seealso cref="AonprdProcessorAttribute"/>
|
||||
internal abstract class AbstractProcessor(AonprdProcessorInput input)
|
||||
{
|
||||
/// <summary>
|
||||
/// Вспомогательный класс для работы с базой данных.
|
||||
/// </summary>
|
||||
protected readonly DatabaseHelper Helper = DatabaseHelper.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Вспомогательный класс для работы со строками и форматированием.
|
||||
/// </summary>
|
||||
protected readonly StringHelper StringHelper = StringHelper.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Вспомогательный класс для работы с глобальными настройками.
|
||||
/// </summary>
|
||||
protected readonly AonprdBuilderGlobals Globals = AonprdBuilderGlobals.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Контекст базы данных для работы с данными.
|
||||
/// </summary>
|
||||
public readonly Pf2eDbContext Context = DatabaseHelper.DbContext;
|
||||
|
||||
/// <summary>
|
||||
/// Объект обработки вводимого JSON.
|
||||
/// </summary>
|
||||
public readonly AonprdProcessorInput Input = input;
|
||||
|
||||
/// <summary>
|
||||
/// Основной метод обработки данных.
|
||||
/// </summary>
|
||||
public abstract void Process();
|
||||
|
||||
/// <summary>
|
||||
/// Метод для сохранения изменений в базе данных.
|
||||
/// </summary>
|
||||
public abstract void Save();
|
||||
}
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common;
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
using Pf2eModel.Model.Common;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel.Model.Common;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
using Pf2eModel.Model.Enum;
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Common;
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ internal class FeatProcessor(AonprdProcessorInput input) : AbstractEntityProcess
|
||||
Archetype[] archetypes = Context
|
||||
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
|
||||
.Archetypes.Where(a => Pf2eDbContext.LocaleSearchArray(a.Entity.Name, archetypeStrings))
|
||||
// .Where(a => archetypeStrings.Contains(a.Entity.Name.ToString()))
|
||||
// .Where(a => archetypeStrings.Contains(a.Entity.Group.ToString()))
|
||||
.ToArray();
|
||||
|
||||
if (archetypes.Length != 0)
|
||||
|
||||
+1
@@ -6,6 +6,7 @@
|
||||
// Ну а пока просто расставляем родительские и дочерние элементы.
|
||||
|
||||
using System.Data;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel.Model.Structure;
|
||||
using Rule = Pf2eModel.Model.Structure.Rule;
|
||||
|
||||
|
||||
+1
@@ -2,6 +2,7 @@
|
||||
using System.Web;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Processors.Types.Rules;
|
||||
|
||||
+1
-1
@@ -83,6 +83,6 @@ internal class CompendiumFolder
|
||||
/// <returns>Строковое представление данных о папки компендиума.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"CompendiumFolder([Id={Id}][Type={Type}][Name={Name}][Path={Path}][Parent={Parent?.Name ?? "null"}])";
|
||||
return $"CompendiumFolder([Id={Id}][Type={Type}][Group={Name}][Path={Path}][Parent={Parent?.Name ?? "null"}])";
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model;
|
||||
using Pf2eModel.Model.Base;
|
||||
@@ -51,7 +52,7 @@ internal abstract class AbstractProcessor(ProcessorInput input)
|
||||
EntityAlias alias = new()
|
||||
{
|
||||
Type = LocaleType.English,
|
||||
Alias = Entity.Name,
|
||||
Alias = Entity.Group,
|
||||
EntityId = Entity.Id,
|
||||
Entity = Entity
|
||||
};
|
||||
@@ -147,13 +148,13 @@ internal abstract class AbstractProcessor(ProcessorInput input)
|
||||
/*
|
||||
foreach (string name in names)
|
||||
{
|
||||
if (result.Any(t => t.Entity.Name.Contains(name)))
|
||||
if (result.Any(t => t.Entity.Group.Contains(name)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Trait trait = new()
|
||||
{
|
||||
Name = name,
|
||||
Group = name,
|
||||
Slug = name,
|
||||
Type = TraitType.Regular,
|
||||
Description = "",
|
||||
|
||||
+6
-3
@@ -2,13 +2,13 @@
|
||||
using Konsole;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder;
|
||||
|
||||
internal class PF2eFoundryDataBuilder
|
||||
{
|
||||
public Pf2eDatabase Database = Pf2eDatabase.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Загрузчик данных.
|
||||
/// </summary>
|
||||
@@ -51,7 +51,10 @@ internal class PF2eFoundryDataBuilder
|
||||
{
|
||||
Console.WriteLine("Truncating all old data...");
|
||||
|
||||
Database.ExecuteCommand("SELECT f_truncate_tables();");
|
||||
using Pf2eDbContext context = new();
|
||||
string cmd = context.Entities.Truncate() + "\n" + context.TruncateAll();
|
||||
|
||||
Console.WriteLine(cmd);
|
||||
|
||||
Console.WriteLine("Truncation completed!");
|
||||
}
|
||||
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder.Processors;
|
||||
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel.Model.Enum;
|
||||
using Pf2eModel.Model.Structure;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<LangVersion>13.0</LangVersion>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Npgsql;
|
||||
|
||||
namespace Pf2eDataScrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Main class for database connection and operations.
|
||||
/// </summary>
|
||||
public partial class Pf2eDatabase
|
||||
{
|
||||
/// <summary>
|
||||
/// Подключение к базе данных.
|
||||
/// </summary>
|
||||
public readonly NpgsqlConnection Connection;
|
||||
|
||||
/// <summary>
|
||||
/// Имя пользователя для подключения к базе данных.
|
||||
/// </summary>
|
||||
public readonly string Username;
|
||||
|
||||
/// <summary>
|
||||
/// Название базы данных.
|
||||
/// </summary>
|
||||
public readonly string DatabaseName;
|
||||
|
||||
private Pf2eDatabase()
|
||||
{
|
||||
Username = UsernamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value;
|
||||
DatabaseName = DatabaseNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value;
|
||||
|
||||
Connection = new(EnvSettings.DatabaseConnection);
|
||||
Connection.Open();
|
||||
}
|
||||
|
||||
~Pf2eDatabase()
|
||||
{
|
||||
Connection.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singleton instance of the database connection manager.
|
||||
/// </summary>
|
||||
public static Pf2eDatabase Instance { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Current transaction associated with this database connection.
|
||||
/// </summary>
|
||||
public NpgsqlTransaction? Transaction { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Begin a new transaction on the database connection.
|
||||
/// </summary>
|
||||
public void StartTransaction()
|
||||
{
|
||||
if (Transaction != null)
|
||||
{
|
||||
throw new InvalidOperationException("Transaction already started.");
|
||||
}
|
||||
|
||||
Transaction = Connection.BeginTransaction();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commit the current transaction to the database.
|
||||
/// </summary>
|
||||
public void Commit()
|
||||
{
|
||||
if (Transaction == null)
|
||||
{
|
||||
throw new InvalidOperationException("No transaction started.");
|
||||
}
|
||||
|
||||
Transaction.Commit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a SQL command against the database.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL command to execute</param>
|
||||
public void ExecuteCommand(string sql)
|
||||
{
|
||||
NpgsqlCommand command = GetCommand(sql);
|
||||
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new SQL command for the database connection.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL command string</param>
|
||||
/// <returns>A new NpgsqlCommand object</returns>
|
||||
public NpgsqlCommand GetCommand(string sql)
|
||||
{
|
||||
return new(sql, Connection);
|
||||
}
|
||||
|
||||
/*
|
||||
public void RebuildDatabase()
|
||||
{
|
||||
// Закрываем текущее соединение
|
||||
Connection.Close();
|
||||
|
||||
// Получение имён
|
||||
var databaseNamePattern = DatabaseNamePattern();
|
||||
var userConnection = databaseNamePattern.Replace(EnvSettings.DatabaseConnection, ";");
|
||||
var databaseName = databaseNamePattern.Match(EnvSettings.DatabaseConnection).Groups[1].Value;
|
||||
var username = UserNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value;
|
||||
|
||||
// Временное подключение к базе данных
|
||||
NpgsqlConnection tmpConnection = new(userConnection);
|
||||
tmpConnection.Open();
|
||||
|
||||
// Удаляем базу данных
|
||||
NpgsqlCommand dropCommand = new($"DROP DATABASE \"{databaseName}\";", tmpConnection);
|
||||
dropCommand.ExecuteNonQuery();
|
||||
|
||||
// Создаём новую базу данных
|
||||
NpgsqlCommand createCommand = new($@"
|
||||
CREATE DATABASE {databaseName}
|
||||
WITH OWNER = {username}
|
||||
ENCODING = 'UTF8'
|
||||
CONNECTION LIMIT = -1;
|
||||
", tmpConnection);
|
||||
createCommand.ExecuteNonQuery();
|
||||
|
||||
string[] permissionCommands = [
|
||||
$"GRANT TEMPORARY, CONNECT ON DATABASE {databaseName} TO PUBLIC;",
|
||||
$"GRANT ALL ON DATABASE {databaseName} TO {username} WITH GRANT OPTION;",
|
||||
$"GRANT ALL ON DATABASE {databaseName} TO postgres;"
|
||||
];
|
||||
|
||||
foreach (var command in permissionCommands)
|
||||
{
|
||||
NpgsqlCommand permissionCommand = new(command, tmpConnection);
|
||||
permissionCommand.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Закрываем временное соединение
|
||||
tmpConnection.Close();
|
||||
|
||||
// Заново создаём соединение
|
||||
Connection = new NpgsqlConnection(EnvSettings.DatabaseConnection);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
[GeneratedRegex(@"(?:^|;)Database=(.*?)(?:$|;)")]
|
||||
private static partial Regex DatabaseNamePattern();
|
||||
|
||||
[GeneratedRegex(@"(?:^|;)User=(.*?)(?:$|;)")]
|
||||
private static partial Regex UsernamePattern();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2eDataScrapper.App;
|
||||
using Pf2eModel;
|
||||
|
||||
namespace Pf2eDataScrapper.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
|
||||
/// Класс предназначен для обработки данных с помощью различных обработчиков,
|
||||
/// для которых указан атрибут <see cref="ProcessAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">Провайдер сервисов.</param>
|
||||
/// <param name="input">Провайдер сервисов.</param>
|
||||
/// <seealso cref="ProcessAttribute"/>
|
||||
internal abstract class AbstractInputProcessor(
|
||||
IServiceProvider serviceProvider,
|
||||
ProcessorInput input
|
||||
) : IProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Провайдер сервисов.
|
||||
/// </summary>
|
||||
public IServiceProvider ServiceProvider { get; } = serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Контекст базы данных.
|
||||
/// </summary>
|
||||
public Pf2eDbContext Context { get; } = serviceProvider.GetService<Pf2eDbContext>()!;
|
||||
|
||||
/// <summary>
|
||||
/// Ввод.
|
||||
/// </summary>
|
||||
public ProcessorInput Input { get; } = input;
|
||||
|
||||
/// <summary>
|
||||
/// Основной метод обработки данных.
|
||||
/// </summary>
|
||||
public abstract void Process();
|
||||
|
||||
/// <summary>
|
||||
/// Метод для сохранения изменений в базе данных.
|
||||
/// </summary>
|
||||
public abstract void Save();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2eDataScrapper.App;
|
||||
using Pf2eModel;
|
||||
|
||||
namespace Pf2eDataScrapper.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс-обработчик данных, полученных из Архивов Нефиса.
|
||||
/// Класс предназначен для обработки данных с помощью различных обработчиков,
|
||||
/// для которых указан атрибут <see cref="ProcessAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">Провайдер сервисов.</param>
|
||||
/// <seealso cref="ProcessAttribute"/>
|
||||
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>
|
||||
public abstract void Process();
|
||||
|
||||
/// <summary>
|
||||
/// Метод для сохранения изменений в базе данных.
|
||||
/// </summary>
|
||||
public abstract void Save();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Pf2eDataScrapper.Processors;
|
||||
|
||||
public interface IProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Провайдер сервисов.
|
||||
/// </summary>
|
||||
IServiceProvider ServiceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Основной метод обработки данных.
|
||||
/// </summary>
|
||||
void Process();
|
||||
|
||||
/// <summary>
|
||||
/// Метод для сохранения изменений в базе данных.
|
||||
/// </summary>
|
||||
void Save();
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
using NpgsqlTypes;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers;
|
||||
namespace Pf2eDataScrapper.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// Класс для работы с JSON-объектом.
|
||||
+19
-14
@@ -1,18 +1,23 @@
|
||||
using Pf2eDataScrapper;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd;
|
||||
namespace Pf2eDataScrapper;
|
||||
|
||||
// Console.SetWindowSize(Console.LargestWindowWidth - 20, Console.LargestWindowHeight - 10);
|
||||
internal static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
// To customize application configuration such as set high DPI settings or default font,
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
|
||||
EnvSettings.Load();
|
||||
Application.SetHighDpiMode(HighDpiMode.SystemAware);
|
||||
|
||||
/*
|
||||
Pf2EFoundryImporter importer = new();
|
||||
*/
|
||||
#pragma warning disable WFO5001
|
||||
Application.SetColorMode(SystemColorMode.System);
|
||||
#pragma warning restore WFO5001
|
||||
|
||||
AonprdImporter importer = new();
|
||||
|
||||
// importer.ProcessorNames = ["rules", "rules-order"];
|
||||
|
||||
importer.Prepare();
|
||||
importer.Import();
|
||||
// importer.BuildSchemas();
|
||||
Application.Run(new ImportApplication());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
||||
namespace Pf2eModel;
|
||||
|
||||
/// <summary>
|
||||
/// Класс, обеспецивающий общий способ для отчистки данных таблиц.
|
||||
///
|
||||
/// Источник: https://www.codeproject.com/Articles/5339402/Delete-All-Rows-in-Entity-Framework-Core-6
|
||||
/// </summary>
|
||||
public static class EfCleanHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Определение полного названия таблицы по служебному классу.
|
||||
/// </summary>
|
||||
/// <param name="entityType">Служебный класс, определяющий таблицу базы данных.</param>
|
||||
/// <param name="defaultSchemaName">Схема таблицы по умолчанию (<c>"public"</c> для PostgresSQL)</param>
|
||||
/// <returns></returns>
|
||||
private static string GetName(IEntityType entityType, string defaultSchemaName = "public")
|
||||
{
|
||||
object? schema = entityType.FindAnnotation("Relational:Schema")?.Value;
|
||||
|
||||
string tableName =
|
||||
entityType.GetAnnotation("Relational:TableName")?.Value?.ToString()
|
||||
?? throw new($"Не могу определить название таблицы для типа {entityType}");
|
||||
|
||||
string schemaName = schema?.ToString() ?? defaultSchemaName;
|
||||
|
||||
string name = $"\"{schemaName}\".\"{tableName}\"";
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Определение полного названия таблицы по типу сущности.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип искомой сущности.</typeparam>
|
||||
/// <param name="dbContext">Контекст, в котором производится поиск таблицы.</param>
|
||||
/// <returns>Строковое представление названия таблицы.</returns>
|
||||
public static string TableName<T>(DbContext dbContext)
|
||||
where T : class
|
||||
{
|
||||
IEntityType entityType =
|
||||
dbContext.Model.FindEntityType(typeof(T))
|
||||
?? throw new($"Не могу определить название таблицы для класса {typeof(T)}");
|
||||
|
||||
return GetName(entityType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Определение полного названия таблицы по типу сущности.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип искомой сущности.</typeparam>
|
||||
/// <param name="dbSet">CLR-представление таблицы, название которой требуется определить.</param>
|
||||
/// <returns>Строковое представление названия таблицы.</returns>
|
||||
public static string TableName<T>(DbSet<T> dbSet)
|
||||
where T : class
|
||||
{
|
||||
IEntityType entityType = dbSet.EntityType;
|
||||
return GetName(entityType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отчистка содержимого таблицы.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип сущности, таблицу которой необходимо отчистить.</typeparam>
|
||||
/// <param name="dbSet">CLR-представление таблицы, которую требуется отчистить.</param>
|
||||
/// <returns>Строковое представление выполненной SQL-команды.</returns>
|
||||
public static string Truncate<T>(this DbSet<T> dbSet)
|
||||
where T : class
|
||||
{
|
||||
string cmd = $"TRUNCATE TABLE {TableName(dbSet)} CASCADE;";
|
||||
DbContext context = dbSet.GetService<ICurrentDbContext>().Context;
|
||||
context.Database.ExecuteSqlRaw(cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение SQL-команды отчистки содержимого всех таблиц базы данных.
|
||||
/// </summary>
|
||||
/// <param name="context">Контекст базы данных, в которой необходимо отчистить все строки.</param>
|
||||
/// <returns>Строковое представление выполненных SQL-команды.</returns>
|
||||
public static string TruncateAll(this DbContext context)
|
||||
{
|
||||
IEnumerable<IEntityType> tables = context.Model.GetEntityTypes();
|
||||
List<string> cmdList = [];
|
||||
|
||||
foreach (IEntityType entityType in tables)
|
||||
{
|
||||
string cmd = $"TRUNCATE TABLE {GetName(entityType)} CASCADE;";
|
||||
context.Database.ExecuteSqlRaw(cmd);
|
||||
cmdList.Add(cmd);
|
||||
}
|
||||
|
||||
return string.Join('\n', cmdList);
|
||||
}
|
||||
}
|
||||
@@ -12,25 +12,6 @@ public partial class Function : Migration
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
CREATE OR REPLACE FUNCTION f_truncate_tables()
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql AS
|
||||
$func$
|
||||
BEGIN
|
||||
EXECUTE
|
||||
(SELECT 'TRUNCATE TABLE '
|
||||
|| string_agg(format('%I.%I', schemaname, tablename), ', ')
|
||||
|| ' CASCADE;'
|
||||
FROM pg_tables
|
||||
WHERE schemaname = 'public'
|
||||
AND tablename != '__EFMigrationsHistory'
|
||||
);
|
||||
END
|
||||
$func$;
|
||||
"""
|
||||
);
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
CREATE OR REPLACE FUNCTION f_drop_tables()
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql AS
|
||||
|
||||
@@ -52,7 +52,12 @@ public partial class Pf2eDbContext
|
||||
private void BindEntity<T>(EntityTypeBuilder<T> entity)
|
||||
where T : class, IEntity
|
||||
{
|
||||
entity.HasOne(e => e.Entity).WithOne().HasForeignKey<T>(e => e.Id).IsRequired();
|
||||
entity
|
||||
.HasOne(e => e.Entity)
|
||||
.WithOne()
|
||||
.HasForeignKey<T>(e => e.Id)
|
||||
.IsRequired()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -26,7 +26,4 @@
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user