livia-test/Livia/ViewModels/MainWindowViewModel.cs
2025-03-28 14:31:53 +08:00

348 lines
13 KiB
C#

using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Livia.Models;
using Livia.Models.Data;
using Livia.Properties;
using Livia.Utility;
using Livia.Utility.DependencyInjection;
using Livia.Views.Utility;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Livia.ViewModels;
public class MainWindowViewModel : ObservableRecipient, IDisposable, IRecipient<LogoutMessage>
{
public int TransitionerSelectedIndex
{
get
{
if (_versionUpdateManager.Updating)
return 2;
if (_underMaintenance)
return 3;
return ServerHandler.IsLoggedIn switch
{
true => 1,
_ => 0
};
}
}
public bool DialogOpen { get => _dialogOpen; set => SetProperty(ref _dialogOpen, value); }
public string? MaintenanceMessage { get => _maintenanceMessage; private set => SetProperty(ref _maintenanceMessage, value); }
public static bool ConfirmExiting { get; set; } = true;
public static bool ShowHistoryButton { get; set; } = true;
public static bool ShowRemainingQuota { get; set; } = true;
public static bool ShowLogoutButton { get; set; } = true;
public static bool ShowLoadDataButton { get; set; } = true;
public static bool ShowExportReportButton { get; set; } = true;
public static bool ShowCheckReportsButton { get; set; } = true;
public static bool ShowSearchButton { get; set; } = true;
public static bool HideTitleBar { get; set; }
public static bool ClearFilesOnExit { get; set; } = true;
public IServerHandler ServerHandler { get; }
public IPatientInfoManager PatientInfoManager { get; }
public IDataBlockLoader DataBlockLoader { get; }
public DataBlockTableViewModel JobListTableViewModel { get; }
public DataBlockTableViewModel SearchResultTableViewModel { get; }
public SearchQueryInputControlViewModel SearchQueryViewModel { get; }
private DataBlock? _currentProcessingDataBlock;
private bool _dialogOpen;
private bool _underMaintenance;
private string? _maintenanceMessage;
private readonly ILogger _logger;
private readonly IWarningSystem _warningSystem;
private readonly IVersionUpdateManager _versionUpdateManager;
public MainWindowViewModel(ILogger logger, IWarningSystem warningSystem, IServerHandler serverHandler, IVersionUpdateManager versionUpdateManager, IPatientInfoManager patientInfoManager, IDataBlockLoader dataBlockLoader)
{
_warningSystem = warningSystem;
ServerHandler = serverHandler;
_versionUpdateManager = versionUpdateManager;
PatientInfoManager = patientInfoManager;
DataBlockLoader = dataBlockLoader;
_logger = logger;
JobListTableViewModel = ActivatorUtilities.GetServiceOrCreateInstance<DataBlockTableViewModel>(ServiceProviderFactory.ServiceProvider);
SearchResultTableViewModel = ActivatorUtilities.GetServiceOrCreateInstance<DataBlockTableViewModel>(ServiceProviderFactory.ServiceProvider);
SearchResultTableViewModel.SetSearchResult();
SearchQueryViewModel = ActivatorUtilities.GetServiceOrCreateInstance<SearchQueryInputControlViewModel>(ServiceProviderFactory.ServiceProvider);
if (!Settings.Default.ApplicationQuitNormally)
{
_warningSystem.ShowDialog(WarningWindowKind.Warning, false, "ApplicationDidNotQuitNormallyWarning");
}
Settings.Default.ApplicationQuitNormally = false;
Settings.Default.Save();
_warningSystem = warningSystem;
_versionUpdateManager.CheckForUpdate(false).ContinueWith(_ => InstallUpdate());
//update TransitionerSelectedIndex
_versionUpdateManager.PropertyChanged += (_, args) =>
{
if (args.PropertyName != nameof(IVersionUpdateManager.Updating)) return;
OnPropertyChanged(nameof(TransitionerSelectedIndex));
};
ServerHandler.PropertyChanged += (_, args) =>
{
if (args.PropertyName != nameof(IServerHandler.IsLoggedIn)) return;
OnPropertyChanged(nameof(TransitionerSelectedIndex));
};
//create temp folder
Directory.CreateDirectory(ServiceConfigurations.TempFolder);
WeakReferenceMessenger.Default.RegisterAll(this);
//get server notice
Task.Run(UpdateNotice);
}
private async Task UpdateNotice()
{
ServerNotice? notice = await ServerHandler.GetNotice().ConfigureAwait(false);
if (notice == null)
return;
_underMaintenance = notice.Value.ServerStatus == 0;
if (_underMaintenance)
{
MaintenanceMessage = notice.Value.Content;
OnPropertyChanged(nameof(TransitionerSelectedIndex));
}
else if (Settings.Default.LastReadNoticeId != notice.Value.Id)
{
_warningSystem.ShowDialogString(WarningWindowKind.Info, false, notice.Value.Content);
Settings.Default.LastReadNoticeId = notice.Value.Id;
}
}
private void InstallUpdate()
{
if (!_versionUpdateManager.UpdateAvailable)
return;
bool updateNow = _warningSystem.ShowDialog(WarningWindowKind.Info, true, "NewVersionAvailableInfoMessage") ?? false;
if (updateNow)
{
Task.Run(_versionUpdateManager.UpdateApp);
}
}
public async Task ProcessData(string path, CancellationToken token)
{
_currentProcessingDataBlock = new DataBlock(path);
_logger.LogInformation("Creating data block from {path}", path);
(bool success, string messageIndex) = await ServerHandler.ProcessData(_currentProcessingDataBlock, token).ConfigureAwait(false);
if (success)
{
//TODO::this is bad
await Application.Current.Dispatcher.Invoke(() => DataBlockLoader.LoadData(_currentProcessingDataBlock));
}
else
{
if (token.IsCancellationRequested)
{
_logger.LogInformation("Data processing canceled");
}
else
{
_warningSystem.ShowDialog(WarningWindowKind.Error, true, messageIndex);
}
}
}
//from https://stackoverflow.com/questions/3625658/how-do-you-create-the-hash-of-a-folder-in-c
private static string CreateMd5ForFolder(string path)
{
// assuming you want to include nested folders
List<string> files = Directory.GetFiles(path, "*", SearchOption.AllDirectories).OrderBy(p => p).ToList();
MD5 md5 = MD5.Create();
for (int i = 0; i < files.Count; i++)
{
string file = files[i];
// hash path
string relativePath = file[(path.Length + 1)..];
byte[] pathBytes = Encoding.UTF8.GetBytes(relativePath.ToLower());
md5.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0);
// hash contents
byte[] contentBytes = File.ReadAllBytes(file);
if (i == files.Count - 1)
md5.TransformFinalBlock(contentBytes, 0, contentBytes.Length);
else
md5.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0);
}
return md5.Hash == null ? string.Empty : BitConverter.ToString(md5.Hash).Replace("-", "").ToLower();
}
public async Task ProcessDataCommandLine(Dictionary<string, string> argsDict, CancellationToken token)
{
string path = argsDict["input_dir"];
string hash;
try
{
hash = CreateMd5ForFolder(path);
Directory.CreateDirectory(argsDict["output_dir"]);
Directory.CreateDirectory(argsDict["log_dir"]);
}
catch (Exception e)
{
_logger.LogError(e, "Error creating MD5 for {dir}", path);
_warningSystem.ShowDialog(WarningWindowKind.Error, true, "PathNotFoundError");
return;
}
_logger.LogInformation("Folder hash = {hash}", hash);
if (hash == string.Empty)
{
_warningSystem.ShowDialog(WarningWindowKind.Error, true, "ServerErrorUnknown");
Application.Current.Dispatcher.Invoke(Application.Current.Shutdown);
return;
}
_currentProcessingDataBlock = new DataBlock(path)
{
Key = hash,
ArgsDict = argsDict
};
string resultPath = Path.Combine(ServiceConfigurations.TempFolder, _currentProcessingDataBlock.Key);
if (File.Exists(Path.Join(resultPath, "complete")))
{
//cache exists, load them
_currentProcessingDataBlock.ResultPath = Path.Join(resultPath, "result");
//TODO::this is bad
await Application.Current.Dispatcher.Invoke(() => DataBlockLoader.LoadData(_currentProcessingDataBlock)).ConfigureAwait(false);
return;
}
_logger.LogInformation("Creating data block from {path}", path);
(bool success, string messageIndex) = await ServerHandler.ProcessData(_currentProcessingDataBlock, token).ConfigureAwait(false);
if (success)
{
//TODO::this is bad
await Application.Current.Dispatcher.Invoke(() => DataBlockLoader.LoadData(_currentProcessingDataBlock)).ConfigureAwait(false);
}
else
{
if (token.IsCancellationRequested)
{
_logger.LogInformation("Data processing canceled");
}
else
{
_warningSystem.ShowDialog(WarningWindowKind.Warning, true, messageIndex);
}
Application.Current.Dispatcher.Invoke(Application.Current.Shutdown);
}
}
public void Dispose()
{
if (ServerHandler.Processing && _currentProcessingDataBlock != null)
{
ServerHandler.Cancel(_currentProcessingDataBlock.Key);
}
//remove everything from temp folder
if (ClearFilesOnExit && Directory.Exists(ServiceConfigurations.TempFolder))
{
foreach (string file in Directory.GetFiles(ServiceConfigurations.TempFolder))
{
try
{
File.Delete(file);
}
catch (Exception e)
{
_logger.LogError(e, "Cannot delete file: {file}", file);
}
}
foreach (string dir in Directory.GetDirectories(ServiceConfigurations.TempFolder))
{
try
{
Directory.Delete(dir, true);
}
catch (Exception e)
{
_logger.LogError(e, "Cannot delete directory: {file}", dir);
}
}
}
else
{
_logger.LogInformation("TempFolder does not exist, skipping");
}
//clear old logs
if (ClearFilesOnExit && Directory.Exists(ServiceConfigurations.TempFolder))
{
TimeSpan threshold = TimeSpan.FromDays(30);
string[] files = Directory.GetFiles(ServiceProviderFactory.LogFolder);
foreach (string file in files)
{
try
{
string fileName = Path.GetFileNameWithoutExtension(file);
string timeString = Regex.Match(fileName, @"\d+").Value;
DateTime logDate = DateTime.ParseExact(timeString, "yyyyMMdd", CultureInfo.InvariantCulture);
if (DateTime.Now - logDate < threshold)
continue;
File.Delete(file);
}
catch (Exception e)
{
_logger.LogError(e, "Cannot process file: {file}", file);
}
}
}
else
{
_logger.LogInformation("LogFolder does not exist, skipping");
}
Settings.Default.ApplicationQuitNormally = true;
Settings.Default.Save();
}
public bool ConfirmClosing()
{
if (!ConfirmExiting)
return true;
return _warningSystem.ShowDialog(WarningWindowKind.Confirmation, true, "ConfirmExitWarning") ?? true;
}
public bool ConfirmLogout()
{
return _warningSystem.ShowDialog(WarningWindowKind.Confirmation, true, "ConfirmLogoutWarning") ?? false;
}
public void Receive(LogoutMessage message)
{
DialogOpen = false;
_logger.LogInformation("Logging out");
}
}