using System.Collections.ObjectModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Avalonia.Threading; using GSS2.Core.Hardware; using GSS2.Core.Analysis.AmineContent; using GSS2.Core.Analysis.AmineContent.Database; using GSS2.UI.Core.ViewModels; using GSS2.UI.Core.Services; namespace GSS2.ViewModels.AmineContent; public partial class ExportViewModel : ViewModelBase { public partial class UsbDiskViewModel : ViewModelBase { [ObservableProperty] public partial string Id { get; set; } [ObservableProperty] public partial string Name { get; set; } [ObservableProperty] public partial bool Mounted { get; set; } [ObservableProperty] public partial string? MountPath { get; set; } public UsbDiskViewModel(string id, string name, bool mounted, string mountPath) { Id = id; Name = name; Mounted = mounted; MountPath = mountPath; } } public partial class RecordListViewModel : ViewModelBase { [ObservableProperty] public partial int Id { get; set; } [ObservableProperty] public partial string SampleName { get; set; } public RecordListViewModel(int id, string sampleName) { Id = id; SampleName = sampleName; } } /// /// Логгер /// private readonly ILogger _logger; /// /// Контекст базы данных /// private readonly Context _context; /// /// Сервис дисков /// private readonly UsbService _usb; /// /// Диск смонтирован /// [ObservableProperty] public partial bool DiskMounted { get; set; } = false; /// /// Список дисков /// [ObservableProperty] public partial ObservableCollection Disks { get; set; } /// /// Индекс выбранного диска /// [ObservableProperty] public partial int DisksSelectedIndex { get; set; } = -1; /// /// Записи /// [ObservableProperty] public partial ObservableCollection Records { get; set; } /// /// Выбранные для экспорта записи /// [ObservableProperty] public partial ObservableCollection SelectedRecords { get; set; } = new(); /// /// Путь точки монтирования /// [ObservableProperty] public partial string? DiskMountPath { get; set; } = null; /// /// Оповещение отображено /// [ObservableProperty] public partial bool IsNotificationVisible { get; set; } = false; /// /// Текст оповещения /// [ObservableProperty] public partial string NotificationMessage { get; set; } = string.Empty; /// /// Оповещение об удаче /// [ObservableProperty] public partial bool IsSuccessNotification { get; set; } = false; public ExportViewModel( ILogger logger, Context context, UsbService usb ) { // Устанавливаем readonly поля _logger = logger; _context = context; _usb = usb; _logger.LogInformation("Инициализация"); // Получаем список дисков Disks = new(); RefreshDisks(); // Получаем список результатов Records = new ObservableCollection(_context.ResultRecords.Select(r => new RecordListViewModel(r.Id, r.SampleName))); // Подписываемся на изменения базы данных для отслеживания изменений списка результатов _context.ChangeTracker.StateChanged += (_, ea) => { // Обновляем данные в интерфейсе // Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке, // а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан _ = Dispatcher.UIThread.InvokeAsync(() => { // Если запись является ResultRecord if (ea.Entry.Entity is ResultRecord resultRecord) { // Если запись добавлена if (ea.OldState is EntityState.Added && ea.NewState is EntityState.Unchanged) { // Добавляем запись Records.Add(new RecordListViewModel(resultRecord.Id, resultRecord.SampleName)); } // Если запись удалена else if (ea.NewState is EntityState.Deleted) { // Ищем удалённую запись в списке var deletedItem = Records.FirstOrDefault(r => r.Id == resultRecord.Id); // Если запись не найдена в списке, то логгируем как ошибку if (deletedItem is null) { _logger.LogError("Не удалось найти элемент с Id({}) в списке {}", resultRecord.Id, nameof(Records)); return; } // Если удаляемый элемент выбран, то удаляем его из выбранных if (SelectedRecords.Contains(deletedItem)) SelectedRecords.Remove(deletedItem); // Удаляем элемент из списка Records.Remove(deletedItem); } // Если запись изменилась else if (ea.NewState is EntityState.Modified) { // Ищем изменённую запись в списке var editedItem = Records.FirstOrDefault(r => r.Id == resultRecord.Id); // Если запись не найдена в списке, то логгируем как ошибку if (editedItem is null) { _logger.LogError("Не удалось найти элемент с Id({}) в списке {}", resultRecord.Id, nameof(Records)); return; } editedItem.SampleName = resultRecord.SampleName; } } }); }; _logger.LogInformation("Инициализировано"); } /// /// Обновить список дисков /// [RelayCommand] private void RefreshDisks() { Disks.Clear(); var disks = _usb.GetDevices(); disks.ForEach(d => Disks.Add(new UsbDiskViewModel(d.id, d.name, d.mounted, d.mountPath))); if (disks.Count() == 0 || DisksSelectedIndex >= disks.Count() ) DisksSelectedIndex = -1; if (DisksSelectedIndex == -1 && disks.Count() != 0 ) DisksSelectedIndex = 0; } /// /// Смонтировать диск /// [RelayCommand] private void MountDisk() { if (DiskMounted) { _logger.LogError("Диск уже смонтирован"); RefreshDisks(); return; } if (DisksSelectedIndex < 0 || DisksSelectedIndex >= Disks.Count()) { _logger.LogError("Выбранный диск вне дисапзона"); RefreshDisks(); return; } var disk = Disks[DisksSelectedIndex]; if (disk.Mounted) { _logger.LogWarning("Выбранный диск уже смонтирован"); DiskMountPath = disk.MountPath; DiskMounted = DiskMountPath is not null; disk.Mounted = DiskMountPath is not null; RefreshDisks(); } else { DiskMountPath = _usb.MountDevice(disk.Id); DiskMounted = DiskMountPath is not null; disk.Mounted = DiskMountPath is not null; RefreshDisks(); } } /// /// Размонтировать диск /// [RelayCommand] private void UnmountDisk() { if (!DiskMounted) { _logger.LogError("Диск не смонтирован"); return; } if (DisksSelectedIndex < 0 || DisksSelectedIndex >= Disks.Count()) { _logger.LogError("Выбранный диск вне дисапзона"); return; } var disk = Disks[DisksSelectedIndex]; if (!disk.Mounted) { _logger.LogError("Выбранный диск не смонтирован"); return; } var result = _usb.UnmountDevice(disk.Id); if (result) { DiskMountPath = null; DiskMounted = false; disk.Mounted = false; } } /// /// Экспортировать выбранные результаты в csv /// [RelayCommand] private void ExportCsv() { if (!DiskMounted || DiskMountPath is null) { _logger.LogError("Диск не смонтирован"); return; } try { var fileName = $"{DateTime.Now:yyyyMMdd_HHmmss}.csv"; var exportPath = Path.Join(DiskMountPath, fileName); var exportingRecords = SelectedRecords.Select(r => _context.ResultRecords.Find(r.Id)); if (exportingRecords.Count() == 0) { _logger.LogWarning("Не выбраны записи для экспорта"); ShowNotification("Не выбраны записи для экспорта", false); return; } _logger.LogInformation("Количество записей для экспорта: {}", exportingRecords.Count()); ResultExporter.ExportCsv(exportPath, exportingRecords); _logger.LogInformation("Результаты экспортированны в файл: {}", exportPath); ShowNotification($"Файл успешно экспортирован:\n{fileName}", true); } catch (Exception ex) { _logger.LogError(ex, "Исключение во время экспорта результатов"); ShowNotification("Ошибка при экспорте файла", false); UnmountDisk(); } } /// /// Запкрыть оповещение /// [RelayCommand] private void DismissNotification() { IsNotificationVisible = false; } /// /// Экспортировать выбранные результаты в pdf /// // [RelayCommand] // private void ExportPdf() // { // if (!DiskMounted || MountPath is null) // { // _logger.LogError("Диск не смонтирован"); // return; // } // try // { // var exportPath = Path.Join(MountPath, $"{DateTime.Now:ddMMyyyy_hhmmss}.csv"); // var exportingRecords = SelectedRecords.Select(r => _context.Find(r.Id)); // GSS2.Core.RecordsExporter.ExportPdf(exportPath, exportingRecords); // } // catch (Exception ex) // { // _logger.LogError(ex, "Исключение во время экспорта результатов"); // } // } private void ShowNotification(string message, bool isSuccess) { NotificationMessage = message; IsSuccessNotification = isSuccess; IsNotificationVisible = true; Task.Delay(5000).ContinueWith(_ => IsNotificationVisible = false); } }