using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using CRAWLER.Infrastructure; using CRAWLER.Models; using CRAWLER.Services; namespace CRAWLER.ViewModels; internal sealed class MainWindowViewModel : ObservableObject { private readonly InstrumentCatalogService _catalogService; private readonly IPdfOpener _pdfOpener; private InstrumentSummary _selectedSummary; private InstrumentRecord _selectedInstrument; private string _searchText; private int _pagesToScan; private string _statusText; private bool _isBusy; private CancellationTokenSource _selectionCancellationTokenSource; public MainWindowViewModel(InstrumentCatalogService catalogService, IPdfOpener pdfOpener) { _catalogService = catalogService; _pdfOpener = pdfOpener; _pagesToScan = _catalogService.DefaultPagesToScan; _statusText = "Готово."; Instruments = new ObservableCollection(); } public ObservableCollection Instruments { get; } public InstrumentSummary SelectedSummary { get { return _selectedSummary; } set { if (SetProperty(ref _selectedSummary, value)) { _ = LoadSelectedInstrumentAsync(value?.Id); } } } public InstrumentRecord SelectedInstrument { get { return _selectedInstrument; } private set { SetProperty(ref _selectedInstrument, value); } } public string SearchText { get { return _searchText; } set { SetProperty(ref _searchText, value); } } public int PagesToScan { get { return _pagesToScan; } set { SetProperty(ref _pagesToScan, value < 1 ? 1 : value); } } public string StatusText { get { return _statusText; } private set { SetProperty(ref _statusText, value); } } public bool IsBusy { get { return _isBusy; } private set { SetProperty(ref _isBusy, value); } } public async Task InitializeAsync() { await RunBusyAsync(async () => { StatusText = "Подготовка базы данных..."; await _catalogService.InitializeAsync(CancellationToken.None); await RefreshAsync(); }); } public async Task RefreshAsync(long? selectId = null) { await RunBusyAsync(async () => { StatusText = "Загрузка списка записей..."; var items = await _catalogService.SearchAsync(SearchText, CancellationToken.None); Instruments.Clear(); foreach (var item in items) { Instruments.Add(item); } if (Instruments.Count == 0) { SelectedInstrument = null; SelectedSummary = null; StatusText = "Записи не найдены."; return; } var summary = selectId.HasValue ? Instruments.FirstOrDefault(item => item.Id == selectId.Value) : SelectedSummary == null ? Instruments.FirstOrDefault() : Instruments.FirstOrDefault(item => item.Id == SelectedSummary.Id) ?? Instruments.FirstOrDefault(); SelectedSummary = summary; StatusText = $"Загружено записей: {Instruments.Count}."; }); } public async Task SyncAsync() { SyncResult result = null; await RunBusyAsync(async () => { var progress = new Progress(message => StatusText = message); result = await _catalogService.SyncFromSiteAsync(PagesToScan, progress, CancellationToken.None); await RefreshAsync(SelectedSummary?.Id); }); return result; } public InstrumentRecord CreateNewDraft() { return new InstrumentRecord { SourceSystem = "Manual" }; } public InstrumentRecord CreateDraftFromSelected() { return SelectedInstrument?.Clone(); } public async Task SaveAsync(InstrumentRecord draft, System.Collections.Generic.IEnumerable pendingPdfPaths) { long id = 0; await RunBusyAsync(async () => { StatusText = "Сохранение записи..."; id = await _catalogService.SaveInstrumentAsync(draft, pendingPdfPaths, CancellationToken.None); await RefreshAsync(id); StatusText = "Изменения сохранены."; }); return id; } public async Task DeleteSelectedAsync() { if (SelectedInstrument == null) { return; } var deletedId = SelectedInstrument.Id; await RunBusyAsync(async () => { StatusText = "Удаление записи..."; await _catalogService.DeleteInstrumentAsync(SelectedInstrument, CancellationToken.None); await RefreshAsync(); StatusText = $"Запись {deletedId} удалена."; }); } public async Task AddAttachmentsToSelectedAsync(System.Collections.Generic.IEnumerable paths) { if (SelectedInstrument == null) { return; } await RunBusyAsync(async () => { StatusText = "Копирование PDF-файлов..."; await _catalogService.AddManualAttachmentsAsync(SelectedInstrument.Id, SelectedInstrument.RegistryNumber, paths, CancellationToken.None); await LoadSelectedInstrumentAsync(SelectedInstrument.Id); StatusText = "PDF-файлы добавлены."; }); } public async Task RemoveAttachmentAsync(PdfAttachment attachment) { if (attachment == null || SelectedInstrument == null) { return; } await RunBusyAsync(async () => { StatusText = "Удаление PDF-файла..."; await _catalogService.RemoveAttachmentAsync(attachment, CancellationToken.None); await LoadSelectedInstrumentAsync(SelectedInstrument.Id); StatusText = "PDF-файл удалён."; }); } public void OpenAttachment(PdfAttachment attachment) { _pdfOpener.OpenAttachment(attachment); } public void OpenSourceUrl() { if (SelectedInstrument != null && !string.IsNullOrWhiteSpace(SelectedInstrument.DetailUrl)) { _pdfOpener.OpenUri(SelectedInstrument.DetailUrl); } } private async Task LoadSelectedInstrumentAsync(long? id) { _selectionCancellationTokenSource?.Cancel(); _selectionCancellationTokenSource = new CancellationTokenSource(); var token = _selectionCancellationTokenSource.Token; if (!id.HasValue) { SelectedInstrument = null; return; } try { var instrument = await _catalogService.GetByIdAsync(id.Value, token); if (!token.IsCancellationRequested) { SelectedInstrument = instrument; } } catch (OperationCanceledException) { } } private async Task RunBusyAsync(Func action) { if (IsBusy) { return; } try { IsBusy = true; await action(); } finally { IsBusy = false; } } }