feat: massive rework

1) GSS2.Core:
- strip prefixes in GSS2.Core.Analysis.AmineContent namespace class names
- add IEnumerable<double>.Variance extension
- remake analysis + update database so not needed to store all every file, calibration data also stored in database, also currently capturing images stored in system temporary directory and rewriting every time
2) GSS.UI.Core: AsyncImageRecordViewModel make zoom and pans public
3) GSS:
- remove image-storage-clear command
- remove ResultsView and ResultsViewModel, results presented in analysis
- rework calibration views
- add analysis views
- add text and number fields editors view
- add confirmation view on danger buttons
This commit is contained in:
2026-05-13 12:56:32 +03:00
parent ec95eef375
commit 3cc4d21947
63 changed files with 4359 additions and 3961 deletions
-160
View File
@@ -1,160 +0,0 @@
using System.CommandLine;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using GSS2.Core;
using GSS2.Core.Hardware;
using GSS2.Core.Analysis.AmineContent.Database.Calibration;
using GSS2.Core.Analysis.AmineContent.Database.Results;
namespace GSS2.Commands;
public static class ImageStorageClear
{
private class ImageStorageClearWorker : BackgroundService
{
private readonly ILogger<ImageStorageClearWorker> _logger;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly AmineContentCalibrationContext _context;
private readonly ImageStorageService _imageStorage;
public ImageStorageClearWorker(
ILogger<ImageStorageClearWorker> logger,
IHostApplicationLifetime applicationLifetime,
AmineContentCalibrationContext context,
ImageStorageService imageStorage
)
{
_applicationLifetime = applicationLifetime;
_logger = logger;
_context = context;
_imageStorage = imageStorage;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Логгируем информацию
_logger.LogInformation("Очистка хранилища от неиспользуемых изображений...");
// Получаем все изображения из базы данных
var recordsFiles = _context.ImageRecords.Select(r => r.ImagePath);
// Получаем все изображения в хранилище
var storedFiles = _imageStorage.ListFiles()
.Select(Path.GetFileName)
.OfType<string>();
// Находим неиспользуемые изображения
var unusedFiles = storedFiles.Except(recordsFiles)
.ToList()
.Order();
// Если неиспользуемых изображений нет, то закрываемся
if (unusedFiles is null || unusedFiles.Count() == 0)
{
_logger.LogInformation("Неиспользуемые изображения не найдены");
_applicationLifetime.StopApplication();
return;
}
_logger.LogInformation("Найдено неиспользуемых изображений: {}\n\t{}", unusedFiles.Count(), string.Join("\n\t", unusedFiles));
// Пока пользователь не выдал правильное действие или не закрыл приложение
while (!stoppingToken.IsCancellationRequested)
{
// Задаём вопрос
_logger.LogInformation("Удалить эти файлы? (Да/Нет) ");
// Читаем консоль
var input = Console.ReadLine()?.Trim().ToLower();
// Если нет, то выходим
if (input == "n" || input == "no" || input == "н" || input == "нет")
break;
// Если да, то удаляем файлы и выходим
if (input == "y" || input == "yes" || input == "д" || input == "да")
{
foreach (var file in unusedFiles)
_imageStorage.Delete(file);
break;
}
}
_applicationLifetime.StopApplication();
}
}
public static readonly RootCommand RootCommand;
private static readonly Command Command;
static ImageStorageClear()
{
// Создаём команды, опции, аргументы и пр.
RootCommand = new("Очистить хранилище от неиспользуемых изображений");
Command = new("image-storage-clear")
{
Description = "Очистить хранилище от неиспользуемых изображений"
};
// Изменяем описания стандартных опций
Helper.TranslateDefaultOptionDescriptions(RootCommand);
// Наполняем команды
RootCommand.SetAction(CommandAction);
Command.SetAction(CommandAction);
}
/// <summary>
/// Получить команду как корневую
/// </summary>
public static RootCommand GetCommand() => RootCommand;
/// <summary>
/// Зарегистрировать команду как подкоманду
/// </summary>
/// <param name="command">Команда в которой необходимо зарегистрировать подкоманду</param>
public static void RegisterCommand(Command command) => command.Add(Command);
private static void CommandAction(ParseResult result)
{
// Создаём хост
var builder = Host.CreateApplicationBuilder();
// Добавляем конфигурацию из appsettings.json
builder.Configuration.AddAppSettingsJson();
// Конфигурируем логгер
builder.Logging.ConfigureLogging();
// Добавляем сервисы баз данных
builder.Services.AddAmineContentCalibrationContext(builder.Configuration);
builder.Services.AddAmineContentResultsContext(builder.Configuration);
// Добавляем сервисы аппаратной части
builder.Services.AddImageStorageService(new DirectoryInfo(builder.Configuration.GetValue<string>("ImageStorage") ?? "images"));
// Добавляем рабочий сервис
builder.Services.AddHostedService<ImageStorageClearWorker>();
// Собираем хост
var host = builder.Build();
// Применяем миграции баз данных если их схема данных изменялась
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AmineContentCalibrationContext>();
context.Database.Migrate();
}
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AmineContentResultsContext>();
context.Database.Migrate();
}
// Запускаем хост
host.Run();
}
}
+4 -2
View File
@@ -12,6 +12,7 @@ using GSS2.Core;
using GSS2.Core.Logging;
using GSS2.Core.Analysis.AmineContent.Database.Calibration;
using GSS2.Core.Analysis.AmineContent.Database.Results;
using GSS2.Core.Hardware;
namespace GSS2.Commands;
@@ -160,6 +161,7 @@ public static class UI
builder.Services.AddComputeResourcesService();
builder.Services.AddImageStorageService(new DirectoryInfo(builder.Configuration.GetValue<string>("ImageStorage") ?? "images"));
builder.Services.AddAmineContentImageCapturerService();
builder.Services.AddAmineContentAnalyzerService();
// Добавляем элементы интерфейса как сервисы
builder.Services.AddViewsAndModels();
@@ -174,12 +176,12 @@ public static class UI
// Применяем миграции баз данных если их схема данных изменялась
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AmineContentCalibrationContext>();
var context = scope.ServiceProvider.GetRequiredService<CalibrationContext>();
context.Database.Migrate();
}
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AmineContentResultsContext>();
var context = scope.ServiceProvider.GetRequiredService<ResultsContext>();
context.Database.Migrate();
}