Files

179 lines
8.0 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Pf2e.Scrapper.Helpers;
using Pf2e.Scrapper.Processors.Collection;
using Pf2e.Scrapper.Processors.Definitions;
using Pf2e.Scrapper.Processors.Factory;
namespace Pf2e.Scrapper.Processors;
/// <summary>
/// Класс, выполняющий обработку коллекции обработчиков.
/// </summary>
/// <param name="collection">[DI] <inheritdoc cref="ProcessorCollection"/></param>
/// <param name="progress">[DI] <inheritdoc cref="ProcessProgress"/></param>
/// <param name="settings">[DI] <inheritdoc cref="ApplicationSettings"/></param>
public sealed class ProcessorExecutor(
ProcessorCollection collection,
ProcessProgress progress,
ApplicationSettings settings
)
{
/// <inheritdoc cref="ProcessorCollection"/>
public ProcessorCollection Collection { get; } = collection;
/// <inheritdoc cref="ProcessProgress"/>
public ProcessProgress Progress { get; } = progress;
/// <inheritdoc cref="ApplicationSettings"/>
public ApplicationSettings Settings { get; } = settings;
/// <summary>
/// Запускает обработку коллекции обработчиков.
/// </summary>
public void Start()
{
Collection.SortItems();
Progress.Processors.Reset(Collection.Items.Count);
StartItem();
}
/// <summary>
/// Текущий индекс обрабатываемого элемента в коллекции.
/// </summary>
private int _currentIndex;
/// <summary>
/// Текущая фабрика процессоров, которая используется для обработки.
/// </summary>
private AbstractProcessorFactory CurrentFactory { get; set; } = null!;
/// <summary>
/// Инициирует обработку текущего элемента в коллекции.
/// </summary>
/// <remarks>Этот метод извлекает текущий элемент из коллекции, подготавливает связанную
/// фабрику и запускает фоновый обработчик для обработки задач элемента. Обработка
/// выполняется параллельно, если конфигурация элемента это позволяет, со степенью
/// параллелизма, определяемой настройками приложения. После завершения обработки метод
/// переходит к следующему шагу рабочего процесса.</remarks>
private void StartItem()
{
ProcessorRecord item = Collection.Items[_currentIndex];
Progress.Processors.Start(item.Description);
Progress.Progress(Progress.Processors.ToString());
BackgroundWorker worker = new();
CurrentFactory = item.Factory;
CurrentFactory.Prepare();
worker.DoWork += (_, _) =>
{
Parallel.ForEach(
IterateProcessors(),
new() { MaxDegreeOfParallelism = item.Parallel ? Settings.MaxThreads : 1 },
RunProcessor
);
};
worker.RunWorkerCompleted += (_, _) => Continue();
worker.RunWorkerAsync();
}
/// <summary>
/// Запускает указанный процессор для выполнения операций обработки и сохранения.
/// </summary>
/// <remarks>Если процессор выдает <see cref="SkipItemException"/>, сообщение об исключении
/// регистрируется как информационный прогресс. Если выдается <see cref="SilentSkipItemException"/>,
/// оно молча игнорируется.</remarks>
/// <param name="processor">Экземпляр процессора, выполняющий операции обработки и сохранения.</param>
/// <param name="state">Экземпляр <see cref="ParallelLoopState"/>, который можно использовать для управления выполнением
/// параллельного цикла.</param>
/// <param name="arg3">Длинное значение, представляющее текущий индекс итерации или связанные данные для процессора.</param>
private void RunProcessor(IProcessor processor, ParallelLoopState state, long arg3)
{
try
{
processor.Process();
processor.Save();
}
catch (SkipItemException exception)
{
Progress.Information(exception.Message);
}
catch (SilentSkipItemException)
{
//
}
}
/// <summary>
/// Переходит к обработке следующего элемента в коллекции или завершает процесс, если все элементы были обработаны.
/// </summary>
/// <remarks>Этот метод увеличивает текущий индекс и определяет, начинать ли обработку следующего элемента или завершить
/// процесс. Если текущий индекс превышает количество элементов в коллекции, процесс помечается как завершенный.</remarks>
private void Continue()
{
Progress.Processors.End();
_currentIndex++;
if (_currentIndex < Collection.Items.Count)
{
StartItem();
}
else
{
Progress.Processors.Complete();
}
}
/// <summary>
/// Выполняет итерацию по всем процессорам, предоставленным текущей фабрикой.
/// </summary>
/// <remarks>Этот метод выдает каждый процессор последовательно, позволяя вызывающему
/// обрабатывать их по одному за раз. Отслеживание прогресса обновляется для каждого
/// процессора во время итерации.</remarks>
/// <returns>Перечисляемая коллекция экземпляров <see cref="IProcessor" />,
/// представляющая процессоры, доступные в текущей фабрике.</returns>
private IEnumerable<IProcessor> IterateProcessors()
{
Progress.Local.Reset(CurrentFactory.Count());
foreach ((string title, IProcessor processor) in CurrentFactory.GetEnumerator())
{
Progress.Local.Start(title);
yield return processor;
Progress.Local.End();
}
Progress.Local.Complete();
}
}
/// <summary>
/// Исключение, показывающее, что обработка сущности пропускается как устаревшая.
/// </summary>
/// <param name="name">Название сущности.</param>
public class LegacyItemException(string name) : SkipItemException(name, "Устаревший элемент") { }
/// <summary>
/// Исключение, показывающее, что обработка сущности пропускается.
/// </summary>
/// <param name="name">Название сущности.</param>
/// <param name="reason">Причина пропуска.</param>
public class SkipItemException(string name, string reason)
: Exception($"Пропускаю элемент '{name}', указана причина: {reason}.") { }
/// <summary>
/// Исключение, показывающее, что обработка сущности пропускается без вывода сообщения.
/// </summary>
public class SilentSkipItemException : Exception { }