309 lines
8.7 KiB
C#
309 lines
8.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
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 const int SearchRefreshDelayMilliseconds = 350;
|
|
|
|
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 bool _isInitialized;
|
|
private CancellationTokenSource _searchRefreshCancellationTokenSource;
|
|
private CancellationTokenSource _selectionCancellationTokenSource;
|
|
|
|
public MainWindowViewModel(InstrumentCatalogService catalogService, IPdfOpener pdfOpener)
|
|
{
|
|
_catalogService = catalogService;
|
|
_pdfOpener = pdfOpener;
|
|
_pagesToScan = _catalogService.DefaultPagesToScan;
|
|
_statusText = "Готово.";
|
|
Instruments = new ObservableCollection<InstrumentSummary>();
|
|
}
|
|
|
|
public ObservableCollection<InstrumentSummary> 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
|
|
{
|
|
if (SetProperty(ref _searchText, value) && _isInitialized)
|
|
{
|
|
ScheduleSearchRefresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
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 RefreshCoreAsync();
|
|
_isInitialized = true;
|
|
});
|
|
}
|
|
|
|
public async Task RefreshAsync(long? selectId = null)
|
|
{
|
|
await RunBusyAsync(() => RefreshCoreAsync(selectId));
|
|
}
|
|
|
|
public async Task<SyncResult> SyncAsync()
|
|
{
|
|
SyncResult result = null;
|
|
|
|
await RunBusyAsync(async () =>
|
|
{
|
|
var progress = new Progress<string>(message => StatusText = message);
|
|
result = await _catalogService.SyncFromSiteAsync(PagesToScan, progress, CancellationToken.None);
|
|
await RefreshCoreAsync(SelectedSummary?.Id);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
public InstrumentRecord CreateNewDraft()
|
|
{
|
|
return new InstrumentRecord
|
|
{
|
|
SourceSystem = "Manual"
|
|
};
|
|
}
|
|
|
|
public InstrumentRecord CreateDraftFromSelected()
|
|
{
|
|
return SelectedInstrument?.Clone();
|
|
}
|
|
|
|
public async Task<long> SaveAsync(InstrumentRecord draft, IEnumerable<string> pendingPdfPaths)
|
|
{
|
|
long id = 0;
|
|
|
|
await RunBusyAsync(async () =>
|
|
{
|
|
StatusText = "Сохранение записи...";
|
|
id = await _catalogService.SaveInstrumentAsync(draft, pendingPdfPaths, CancellationToken.None);
|
|
await RefreshCoreAsync(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 RefreshCoreAsync();
|
|
StatusText = $"Запись {deletedId} удалена.";
|
|
});
|
|
}
|
|
|
|
public async Task AddAttachmentsToSelectedAsync(IEnumerable<string> 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 RefreshCoreAsync(long? selectId = null)
|
|
{
|
|
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}.";
|
|
}
|
|
|
|
private void ScheduleSearchRefresh()
|
|
{
|
|
_searchRefreshCancellationTokenSource?.Cancel();
|
|
_searchRefreshCancellationTokenSource?.Dispose();
|
|
_searchRefreshCancellationTokenSource = new CancellationTokenSource();
|
|
_ = RunSearchRefreshAsync(_searchRefreshCancellationTokenSource.Token);
|
|
}
|
|
|
|
private async Task RunSearchRefreshAsync(CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(SearchRefreshDelayMilliseconds, cancellationToken);
|
|
|
|
while (IsBusy)
|
|
{
|
|
await Task.Delay(100, cancellationToken);
|
|
}
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
await RefreshAsync();
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
}
|
|
|
|
private async Task LoadSelectedInstrumentAsync(long? id)
|
|
{
|
|
_selectionCancellationTokenSource?.Cancel();
|
|
_selectionCancellationTokenSource?.Dispose();
|
|
_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<Task> action)
|
|
{
|
|
if (IsBusy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
IsBusy = true;
|
|
await action();
|
|
}
|
|
finally
|
|
{
|
|
IsBusy = false;
|
|
}
|
|
}
|
|
}
|