diff --git a/MainWindow.xaml b/MainWindow.xaml
index 2dab736..a7a3cf0 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -53,9 +53,6 @@
VerticalAlignment="Center"
KeyDown="SearchTextBox_KeyDown"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
-
-
-
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index b06073e..c1cc73e 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -29,11 +29,6 @@ public partial class MainWindow : Window
await ExecuteUiAsync(_viewModel.InitializeAsync);
}
- private async void RefreshButton_Click(object sender, RoutedEventArgs e)
- {
- await ExecuteUiAsync(() => _viewModel.RefreshAsync());
- }
-
private async void SyncButton_Click(object sender, RoutedEventArgs e)
{
await ExecuteUiAsync(async () =>
diff --git a/Services/PdfStorageService.cs b/Services/PdfStorageService.cs
index df558cd..2a49c36 100644
--- a/Services/PdfStorageService.cs
+++ b/Services/PdfStorageService.cs
@@ -15,7 +15,15 @@ internal sealed class PdfStorageService
_client = client;
var options = configuration.GetSection("Crawler").Get()
?? throw new InvalidOperationException("Раздел Crawler не найден в appsettings.json.");
- _rootPath = Environment.ExpandEnvironmentVariables(options.PdfStoragePath);
+ var configuredPath = Environment.ExpandEnvironmentVariables(options.PdfStoragePath ?? string.Empty).Trim();
+ if (string.IsNullOrWhiteSpace(configuredPath))
+ {
+ configuredPath = "PdfStore";
+ }
+
+ _rootPath = Path.IsPathRooted(configuredPath)
+ ? configuredPath
+ : Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, configuredPath));
Directory.CreateDirectory(_rootPath);
}
diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs
index 41dcf5d..5acef8a 100644
--- a/ViewModels/MainWindowViewModel.cs
+++ b/ViewModels/MainWindowViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
@@ -11,6 +12,8 @@ namespace CRAWLER.ViewModels;
internal sealed class MainWindowViewModel : ObservableObject
{
+ private const int SearchRefreshDelayMilliseconds = 350;
+
private readonly InstrumentCatalogService _catalogService;
private readonly IPdfOpener _pdfOpener;
private InstrumentSummary _selectedSummary;
@@ -19,6 +22,8 @@ internal sealed class MainWindowViewModel : ObservableObject
private int _pagesToScan;
private string _statusText;
private bool _isBusy;
+ private bool _isInitialized;
+ private CancellationTokenSource _searchRefreshCancellationTokenSource;
private CancellationTokenSource _selectionCancellationTokenSource;
public MainWindowViewModel(InstrumentCatalogService catalogService, IPdfOpener pdfOpener)
@@ -53,7 +58,13 @@ internal sealed class MainWindowViewModel : ObservableObject
public string SearchText
{
get { return _searchText; }
- set { SetProperty(ref _searchText, value); }
+ set
+ {
+ if (SetProperty(ref _searchText, value) && _isInitialized)
+ {
+ ScheduleSearchRefresh();
+ }
+ }
}
public int PagesToScan
@@ -80,40 +91,14 @@ internal sealed class MainWindowViewModel : ObservableObject
{
StatusText = "Подготовка базы данных...";
await _catalogService.InitializeAsync(CancellationToken.None);
- await RefreshAsync();
+ await RefreshCoreAsync();
+ _isInitialized = true;
});
}
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}.";
- });
+ await RunBusyAsync(() => RefreshCoreAsync(selectId));
}
public async Task SyncAsync()
@@ -124,7 +109,7 @@ internal sealed class MainWindowViewModel : ObservableObject
{
var progress = new Progress(message => StatusText = message);
result = await _catalogService.SyncFromSiteAsync(PagesToScan, progress, CancellationToken.None);
- await RefreshAsync(SelectedSummary?.Id);
+ await RefreshCoreAsync(SelectedSummary?.Id);
});
return result;
@@ -143,7 +128,7 @@ internal sealed class MainWindowViewModel : ObservableObject
return SelectedInstrument?.Clone();
}
- public async Task SaveAsync(InstrumentRecord draft, System.Collections.Generic.IEnumerable pendingPdfPaths)
+ public async Task SaveAsync(InstrumentRecord draft, IEnumerable pendingPdfPaths)
{
long id = 0;
@@ -151,7 +136,7 @@ internal sealed class MainWindowViewModel : ObservableObject
{
StatusText = "Сохранение записи...";
id = await _catalogService.SaveInstrumentAsync(draft, pendingPdfPaths, CancellationToken.None);
- await RefreshAsync(id);
+ await RefreshCoreAsync(id);
StatusText = "Изменения сохранены.";
});
@@ -171,12 +156,12 @@ internal sealed class MainWindowViewModel : ObservableObject
{
StatusText = "Удаление записи...";
await _catalogService.DeleteInstrumentAsync(SelectedInstrument, CancellationToken.None);
- await RefreshAsync();
+ await RefreshCoreAsync();
StatusText = $"Запись {deletedId} удалена.";
});
}
- public async Task AddAttachmentsToSelectedAsync(System.Collections.Generic.IEnumerable paths)
+ public async Task AddAttachmentsToSelectedAsync(IEnumerable paths)
{
if (SelectedInstrument == null)
{
@@ -221,9 +206,66 @@ internal sealed class MainWindowViewModel : ObservableObject
}
}
+ 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;
diff --git a/appsettings.json b/appsettings.json
index 7485248..81bcb19 100644
--- a/appsettings.json
+++ b/appsettings.json
@@ -20,7 +20,7 @@
"CatalogPathFormat": "/poverka/gosreestr_sredstv_izmereniy?page={0}",
"RequestDelayMilliseconds": 350,
"DefaultPagesToScan": 1,
- "PdfStoragePath": "%LOCALAPPDATA%\\CRAWLER\\PdfStore",
+ "PdfStoragePath": "PdfStore",
"TimeoutSeconds": 30,
"UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) CRAWLER/1.0"
}