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;
///
/// Класс, выполняющий обработку коллекции обработчиков.
///
/// [DI]
/// [DI]
/// [DI]
internal sealed class ProcessorExecutor(
ProcessorCollection collection,
ProcessProgress progress,
ApplicationSettings settings
)
{
///
public ProcessorCollection Collection { get; } = collection;
///
public ProcessProgress Progress { get; } = progress;
///
public ApplicationSettings Settings { get; } = settings;
///
/// Запускает обработку коллекции обработчиков.
///
public void Start()
{
Collection.SortItems();
Progress.Processors.Reset(Collection.Items.Count);
StartItem();
}
///
/// Текущий индекс обрабатываемого элемента в коллекции.
///
private int _currentIndex;
///
/// Текущая фабрика процессоров, которая используется для обработки.
///
private AbstractProcessorFactory CurrentFactory { get; set; } = null!;
///
/// Инициирует обработку текущего элемента в коллекции.
///
/// Этот метод извлекает текущий элемент из коллекции, подготавливает связанную
/// фабрику и запускает фоновый обработчик для обработки задач элемента. Обработка
/// выполняется параллельно, если конфигурация элемента это позволяет, со степенью
/// параллелизма, определяемой настройками приложения. После завершения обработки метод
/// переходит к следующему шагу рабочего процесса.
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();
}
///
/// Запускает указанный процессор для выполнения операций обработки и сохранения.
///
/// Если процессор выдает , сообщение об исключении
/// регистрируется как информационный прогресс. Если выдается ,
/// оно молча игнорируется.
/// Экземпляр процессора, выполняющий операции обработки и сохранения.
/// Экземпляр , который можно использовать для управления выполнением
/// параллельного цикла.
/// Длинное значение, представляющее текущий индекс итерации или связанные данные для процессора.
private void RunProcessor(IProcessor processor, ParallelLoopState state, long arg3)
{
try
{
processor.Process();
processor.Save();
}
catch (SkipItemException exception)
{
Progress.Information(exception.Message);
}
catch (SilentSkipItemException)
{
//
}
}
///
/// Переходит к обработке следующего элемента в коллекции или завершает процесс, если все элементы были обработаны.
///
/// Этот метод увеличивает текущий индекс и определяет, начинать ли обработку следующего элемента или завершить
/// процесс. Если текущий индекс превышает количество элементов в коллекции, процесс помечается как завершенный.
private void Continue()
{
Progress.Processors.End();
_currentIndex++;
if (_currentIndex < Collection.Items.Count)
{
StartItem();
}
else
{
Progress.Processors.Complete();
}
}
///
/// Выполняет итерацию по всем процессорам, предоставленным текущей фабрикой.
///
/// Этот метод выдает каждый процессор последовательно, позволяя вызывающему
/// обрабатывать их по одному за раз. Отслеживание прогресса обновляется для каждого
/// процессора во время итерации.
/// Перечисляемая коллекция экземпляров ,
/// представляющая процессоры, доступные в текущей фабрике.
private IEnumerable 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();
}
}
///
/// Исключение, показывающее, что обработка сущности пропускается как устаревшая.
///
/// Название сущности.
public class LegacyItemException(string name) : SkipItemException(name, "Устаревший элемент") { }
///
/// Исключение, показывающее, что обработка сущности пропускается.
///
/// Название сущности.
/// Причина пропуска.
public class SkipItemException(string name, string reason)
: Exception($"Пропускаю элемент '{name}', указана причина: {reason}.") { }
///
/// Исключение, показывающее, что обработка сущности пропускается без вывода сообщения.
///
public class SilentSkipItemException : Exception { }