livia-test/Livia/Models/Data/ServerHandler.cs
2025-03-28 14:31:53 +08:00

855 lines
29 KiB
C#

using System.ComponentModel;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Livia.Utility;
using Microsoft.Extensions.Logging;
namespace Livia.Models.Data;
public interface IServerHandler : INotifyPropertyChanged, INotifyPropertyChanging
{
bool Processing { get; set; }
bool IsLoggedIn { get; }
string Quota { get; }
Task<(bool success, string messageIndex)> ProcessData(DataBlock dataBlock, CancellationToken token);
Task<(bool success, string messageIndex)> DownloadData(DataBlock dataBlock);
Task<(bool success, string messageIndex)> DownloadReport(string key, int id, string savePath, CancellationToken token);
Task<(bool success, string messageIndex)> EditPassword(string oldPassword, string newPassword);
Task<(List<ServerJobData> list, int count)> CheckServerJobList(int limit, int offset);
Task<(List<ServerJobData> list, int count)> SearchData(SearchQueryJson json);
Task<List<ReportItem>> GetReportList(string key);
Task<string> Login(string username, string password);
Task Cancel(string key);
Task DeleteReport(string key, int id);
Task<(bool success, string messageIndex)> DownloadArchive(string? key, string savePath, CancellationToken token);
Task<(bool success, string messageIndex)> DownloadSorted(string? key, string savePath, CancellationToken none);
Task<(bool success, string messageIndex)> DeleteData(int? id, CancellationToken cancellationToken);
Task<(bool success, string messageIndex)> DeleteHistory(string? key, CancellationToken cancellationToken);
Task UpdateDongleInfo();
Task<ServerNotice?> GetNotice();
Task<List<SeriesGroup>> GetSeriesGroupList(string key);
Task<(bool success, string messageIndex)> SelectSeries(string key, List<string> series);
Task<(bool success, string messageIndex)> SelectReportModule(string fileName, SelectReportModuleJson json, CancellationToken token);
Task<(bool success, string messageIndex)> PushToPacs(string key, int id);
Task<DicomNodeInfo?> GetDicomNodeInfo();
}
public class ServerHandler : ObservableRecipient, IServerHandler, IRecipient<LogoutMessage>, IRecipient<IdleTimerTickMessage>
{
public bool Processing { get => _processing; set => SetProperty(ref _processing, value); }
public bool IsLoggedIn { get => _isLoggedIn; private set => SetProperty(ref _isLoggedIn, value); }
public string Quota { get => _quota; private set => SetProperty(ref _quota, value); }
private bool _processing;
private bool _isLoggedIn;
private string _quota = string.Empty;
private readonly ILogger _logger;
private bool _updatingPatientList;
private readonly LiviaHttpClientFactory _liviaHttpClientFactory;
private const int CheckResultRetryCount = 10;
private const int CheckResultRetryTime = 5000;
public ServerHandler(ILogger logger, LiviaHttpClientFactory liviaHttpClientFactory)
{
_logger = logger;
_liviaHttpClientFactory = liviaHttpClientFactory;
WeakReferenceMessenger.Default.RegisterAll(this);
}
public async Task Cancel(string key)
{
if (string.IsNullOrEmpty(key))
{
_logger.LogWarning("Key is invalid, cannot cancel job");
return;
}
try
{
ServerResponseJson serverResponseJson = await _liviaHttpClientFactory.Create().Cancel(key).ConfigureAwait(false);
_logger.LogInformation($"Cancel request response is {serverResponseJson}");
}
catch (Exception e)
{
_logger.LogError(e, "Error canceling data");
}
}
public async Task DeleteReport(string key, int id)
{
try
{
ServerResponseJson serverResponseJson = await _liviaHttpClientFactory.Create().DeleteReport(key, id).ConfigureAwait(false);
_logger.LogInformation($"DeleteReport response is {serverResponseJson}");
}
catch (Exception e)
{
_logger.LogError(e, "Error DeleteReport {id}", id);
}
}
public async Task<(bool success, string messageIndex)> DownloadData(DataBlock dataBlock)
{
Processing = true;
string messageIndex = string.Empty;
try
{
if (!string.IsNullOrEmpty(dataBlock.ResultPath))
{
// it is finished
return (true, messageIndex);
}
string saveName = $"{dataBlock.Key}.zip";
string savePath = Path.Combine(ServiceConfigurations.TempFolder, saveName);
await _liviaHttpClientFactory.Create().CheckResult(dataBlock.Key, savePath, CancellationToken.None).ConfigureAwait(false);
string path = Path.Combine(ServiceConfigurations.TempFolder, dataBlock.Key);
//remove if somehow it is there
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}
ZipFile.ExtractToDirectory(savePath, path);
dataBlock.ResultPath = path;
return (true, messageIndex);
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ProcessingError";
}
_logger.LogError(e, "Error processing data");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error processing data");
messageIndex = "ProcessingError";
return (false, messageIndex);
}
finally
{
Processing = false;
}
}
//TODO::create a return object instead maybe so that fileName can also be returned
public virtual async Task<(bool success, string messageIndex)> ProcessData(DataBlock dataBlock, CancellationToken token)
{
Processing = true;
string messageIndex = string.Empty;
try
{
await dataBlock.Pack(token).ConfigureAwait(false);
ProcessDataResponseJson serverResponseJson = await _liviaHttpClientFactory.Create().ProcessData(dataBlock.ZipPath, token).ConfigureAwait(false);
if (serverResponseJson.Code != 0)
{
return (false, GetErrorMessage(serverResponseJson.ErrorNumber));
}
//check for response
dataBlock.Key = serverResponseJson.Key;
int retry = 0;
while (retry < CheckResultRetryCount)
{
try
{
string saveName = $"{dataBlock.Key}.zip";
string savePath = Path.Combine(ServiceConfigurations.TempFolder, saveName);
await _liviaHttpClientFactory.Create().CheckResult(dataBlock.Key, savePath, token).ConfigureAwait(false);
await UpdateDongleInfo();
dataBlock.ResultPath = Path.Combine(ServiceConfigurations.TempFolder, dataBlock.Key);
ZipFile.ExtractToDirectory(savePath, dataBlock.ResultPath);
return (true, messageIndex);
}
catch (ResultNotReadyException e)
{
if (e.ServerResponseJson.Code != 0)
{
return (false, GetErrorMessage(e.ServerResponseJson.ErrorNumber));
}
await Task.Delay(CheckResultRetryTime, token);
}
catch (Exception e)
{
retry++;
_logger.LogWarning(e, "Error checking result, try again in 5 seconds");
await Task.Delay(CheckResultRetryTime, token);
}
}
throw new TimeoutException();
}
catch (OperationCanceledException)
{
await Cancel(dataBlock.Key);
return (false, "ProcessingError");
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ProcessingError";
}
_logger.LogError(e, "Error processing data");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error processing data");
messageIndex = "ProcessingError";
return (false, messageIndex);
}
finally
{
Processing = false;
}
}
public async Task<(bool success, string messageIndex)> DownloadReport(string key, int id, string savePath, CancellationToken token)
{
Processing = true;
string messageIndex;
try
{
await _liviaHttpClientFactory.Create().DownloadReport(key, id, savePath, token).ConfigureAwait(false);
return (true, "DownloadReportSuccess");
}
catch (OperationCanceledException)
{
return (false, "ExportFailed");
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ExportFailed";
}
_logger.LogError(e, "DownloadReport");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "DownloadReport");
messageIndex = "ExportFailed";
return (false, messageIndex);
}
finally
{
Processing = false;
}
}
public async Task<(bool success, string messageIndex)> DownloadArchive(string? key, string savePath, CancellationToken token)
{
if (string.IsNullOrEmpty(key))
return (false, "ProcessingError");
Processing = true;
string messageIndex = string.Empty;
try
{
await _liviaHttpClientFactory.Create().DownloadArchive(key, savePath, token).ConfigureAwait(false);
return (true, messageIndex);
}
catch (OperationCanceledException)
{
await Cancel(key);
try
{
File.Delete(savePath);
}
catch (Exception e)
{
_logger.LogError(e, "Cannot delete {path}", savePath);
}
return (false, "ProcessingError");
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ProcessingError";
}
_logger.LogError(e, "Error DownloadArchive");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error DownloadArchive");
messageIndex = "ProcessingError";
return (false, messageIndex);
}
finally
{
Processing = false;
}
}
public async Task<(bool success, string messageIndex)> DownloadSorted(string? key, string savePath, CancellationToken token)
{
if (string.IsNullOrEmpty(key))
return (false, "ProcessingError");
Processing = true;
string messageIndex = string.Empty;
try
{
await _liviaHttpClientFactory.Create().DownloadSorted(key, savePath, token).ConfigureAwait(false);
return (true, messageIndex);
}
catch (OperationCanceledException)
{
await Cancel(key);
try
{
File.Delete(savePath);
}
catch (Exception e)
{
_logger.LogError(e, "Cannot delete {path}", savePath);
}
return (false, "ProcessingError");
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ProcessingError";
}
_logger.LogError(e, "Error DownloadSorted");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error DownloadSorted");
messageIndex = "ProcessingError";
return (false, messageIndex);
}
finally
{
Processing = false;
}
}
public async Task<(bool success, string messageIndex)> DeleteData(int? id, CancellationToken token)
{
if (id == null)
return (false, "ProcessingError");
Processing = true;
string messageIndex;
try
{
ServerResponseJson serverResponseJson = await _liviaHttpClientFactory.Create().DeleteData(id.ToString(), token).ConfigureAwait(false);
return serverResponseJson.Code != 0 ? (false, GetErrorMessage(serverResponseJson.ErrorNumber)) : (true, "DeleteSuccess");
}
catch (OperationCanceledException)
{
return (false, "ProcessingError");
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ProcessingError";
}
_logger.LogError(e, "Error DeleteData");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error DeleteData");
messageIndex = "ProcessingError";
return (false, messageIndex);
}
finally
{
Processing = false;
}
}
public async Task<(bool success, string messageIndex)> DeleteHistory(string? key, CancellationToken token)
{
if (string.IsNullOrEmpty(key))
return (false, "ProcessingError");
Processing = true;
string messageIndex;
try
{
ServerResponseJson serverResponseJson = await _liviaHttpClientFactory.Create().DeleteHistory(key, token).ConfigureAwait(false);
return serverResponseJson.Code != 0 ? (false, GetErrorMessage(serverResponseJson.ErrorNumber)) : (true, "DeleteSuccess");
}
catch (OperationCanceledException)
{
return (false, "ProcessingError");
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ProcessingError";
}
_logger.LogError(e, "Error DeleteData");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error DeleteData");
messageIndex = "ProcessingError";
return (false, messageIndex);
}
finally
{
Processing = false;
}
}
public async Task<(bool success, string messageIndex)> EditPassword(string oldPassword, string newPassword)
{
Processing = true;
string messageIndex;
try
{
ServerResponseJson serverResponseJson = await _liviaHttpClientFactory.Create().EditPassword(oldPassword, newPassword).ConfigureAwait(false);
return serverResponseJson.Code != 0 ? (false, GetErrorMessage(serverResponseJson.ErrorNumber)) : (true, "EditPasswordSuccess");
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ProcessingError";
}
_logger.LogError(e, "Error processing data");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error processing data");
messageIndex = "ProcessingError";
return (false, messageIndex);
}
finally
{
Processing = false;
}
}
public async Task<(List<ServerJobData> list, int count)> CheckServerJobList(int limit, int offset)
{
//TODO::should return fail instead, so it is now used
List<ServerJobData> resultList = [];
int count = 0;
if (_updatingPatientList)
return (resultList, count);
if (!IsLoggedIn)
return (resultList, count);
_updatingPatientList = true;
try
{
ServerJobListJsonResponse serverResponseJson = await _liviaHttpClientFactory.Create().CheckServerJobList(limit, offset).ConfigureAwait(false);
if (serverResponseJson.Code != 0)
throw new Exception(GetErrorMessage(serverResponseJson.Code));
resultList.AddRange(serverResponseJson.DataList);
count = serverResponseJson.Count;
}
//TODO::we may get 401 here
catch (Exception e)
{
_logger.LogError(e, "Error updating patient list");
}
finally
{
_updatingPatientList = false;
}
return (resultList, count);
}
public async Task<(List<ServerJobData> list, int count)> SearchData(SearchQueryJson json)
{
//TODO::should return fail instead, so it is now used
List<ServerJobData> resultList = [];
int count = 0;
if (_updatingPatientList)
return (resultList, count);
if (!IsLoggedIn)
return (resultList, count);
_updatingPatientList = true;
try
{
ServerJobListJsonResponse serverResponseJson = await _liviaHttpClientFactory.Create().SearchData(json).ConfigureAwait(false);
if (serverResponseJson.Code != 0)
throw new Exception(GetErrorMessage(serverResponseJson.Code));
resultList.AddRange(serverResponseJson.DataList);
count = serverResponseJson.Count;
}
//TODO::we may get 401 here
catch (Exception e)
{
_logger.LogError(e, "Error SearchData");
}
finally
{
_updatingPatientList = false;
}
return (resultList, count);
}
public async Task<List<ReportItem>> GetReportList(string key)
{
try
{
return await _liviaHttpClientFactory.Create().GetReportList(key).ConfigureAwait(false);
}
catch (Exception e)
{
_logger.LogError(e, "Error GetReportList");
}
return [];
}
private string GetErrorMessage(int code)
{
_logger.LogError($"Server error, errno = {code}");
string index = $"ServerError{code}";
return Application.Current.TryFindResource(index) != null ? index : "ServerErrorUnknown";
}
public async Task UpdateDongleInfo()
{
if (!IsLoggedIn)
return;
try
{
DongleInfoResponseJson serverResponseJson = await _liviaHttpClientFactory.Create().UpdateDongleInfo().ConfigureAwait(false);
if (serverResponseJson.Code != 0)
throw new Exception(serverResponseJson.Msg);
if (serverResponseJson.Quota == uint.MaxValue)
{
if (Application.Current.TryFindResource("UnlimitedQuota") is string str)
{
Quota = str;
}
else
{
Quota = "";
}
}
else
{
Quota = serverResponseJson.Quota.ToString();
}
}
//TODO::we may get 401 here
catch (Exception e)
{
_logger.LogError(e, "Error GetDongleInfo");
}
}
public async Task<ServerNotice?> GetNotice()
{
try
{
ServerNoticeJsonResponse serverResponseJson = await _liviaHttpClientFactory.Create().GetNotice().ConfigureAwait(false);
//no notice
if (serverResponseJson.Code == -1)
return null;
if (serverResponseJson.Code != 0)
throw new Exception(GetErrorMessage(serverResponseJson.Code));
return serverResponseJson.Notice;
}
//TODO::we may get 401 here
catch (Exception e)
{
_logger.LogError(e, "Error updating ServerNotice");
}
return null;
}
public async Task<List<SeriesGroup>> GetSeriesGroupList(string key)
{
try
{
SeriesGroupListJson serverResponseJson = await _liviaHttpClientFactory.Create().GetSeriesGroupList(key).ConfigureAwait(false);
//no notice
if (serverResponseJson.Code == -1)
return [];
if (serverResponseJson.Code != 0)
throw new Exception(GetErrorMessage(serverResponseJson.Code));
return serverResponseJson.SeriesGroupList;
}
//TODO::we may get 401 here
catch (Exception e)
{
_logger.LogError(e, "Error GetSeriesGroupList");
}
return [];
}
public async Task<(bool success, string messageIndex)> SelectSeries(string key, List<string> series)
{
string messageIndex;
try
{
ServerResponseJson serverResponseJson = await _liviaHttpClientFactory.Create().SelectSeries(key, series).ConfigureAwait(false);
if (serverResponseJson.Code != 0)
return (false, GetErrorMessage(serverResponseJson.ErrorNumber));
serverResponseJson = await _liviaHttpClientFactory.Create().Start(key).ConfigureAwait(false);
return serverResponseJson.Code != 0 ? (false, GetErrorMessage(serverResponseJson.ErrorNumber)) : (true, "SelectSeriesSuccess");
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ProcessingError";
}
_logger.LogError(e, "Error SelectSeries");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error SelectSeries");
messageIndex = "ProcessingError";
return (false, messageIndex);
}
}
public virtual async Task<(bool success, string messageIndex)> SelectReportModule(string fileName, SelectReportModuleJson json, CancellationToken token)
{
Processing = true;
string messageIndex;
try
{
ReportIdJson reportIdJson = await _liviaHttpClientFactory.Create().SelectReportModule(json).ConfigureAwait(false);
await _liviaHttpClientFactory.Create().DownloadReport(json.Key, reportIdJson.ReportId, fileName, token).ConfigureAwait(false);
return (true, "DownloadReportSuccess");
}
catch (SelectReportModuleFailedException e)
{
return (false, GetErrorMessage(e.ErrorNumber));
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ExportFailed";
}
_logger.LogError(e, "Error ReportModule");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error ReportModule");
messageIndex = "ExportFailed";
return (false, messageIndex);
}
finally { Processing = false; }
}
public async Task<(bool success, string messageIndex)> PushToPacs(string key, int id)
{
string messageIndex;
try
{
ServerResponseJson serverResponseJson = await _liviaHttpClientFactory.Create().PushToPacs(key, id).ConfigureAwait(false);
return serverResponseJson.Code != 0 ? (false, GetErrorMessage(serverResponseJson.ErrorNumber)) : (true, "PushToPacsSuccess");
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
messageIndex = "ServerUnauthorizedError";
WeakReferenceMessenger.Default.Send(new LogoutMessage());
}
else
{
messageIndex = "ProcessingError";
}
_logger.LogError(e, "Error PushToPacs");
return (false, messageIndex);
}
catch (Exception e)
{
_logger.LogError(e, "Error PushToPacs");
messageIndex = "ProcessingError";
return (false, messageIndex);
}
}
public async Task<DicomNodeInfo?> GetDicomNodeInfo()
{
try
{
DicomNodeInfoJsonResponse serverResponseJson = await _liviaHttpClientFactory.Create().GetDicomNodeInfo().ConfigureAwait(false);
//no notice
if (serverResponseJson.Code == -1)
return null;
if (serverResponseJson.Code != 0)
throw new Exception(GetErrorMessage(serverResponseJson.Code));
return serverResponseJson.Dicom;
}
//TODO::we may get 401 here
catch (Exception e)
{
_logger.LogError(e, "Error updating ServerNotice");
}
return null;
}
public async Task<string> Login(string username, string password)
{
//TODO::maybe only return index instead
if (Processing)
{
return (string)Application.Current.TryFindResource("LoginError");
}
Processing = true;
string message = string.Empty;
try
{
_logger.LogInformation("Sending post request");
await _liviaHttpClientFactory.Create().Login(username, password).ConfigureAwait(false);
IsLoggedIn = true;
await UpdateDongleInfo();
}
catch (LoginErrorException e)
{
message = (string)(Application.Current.TryFindResource($"ServerError{e.ServerResponseJson.ErrorNumber}") ?? Application.Current.TryFindResource("LoginError"));
//Account is locked
if (e.ServerResponseJson.ErrorNumber == 10021)
{
message = string.Format(message, e.ServerResponseJson.Count, e.ServerResponseJson.Seconds);
}
}
catch (HttpRequestException e)
{
_logger.LogError(e, "HttpRequestException while logging in");
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.LogError(e, "Incorrect password");
message = (string)Application.Current.TryFindResource("IncorrectPasswordError");
}
else if (e.InnerException is SocketException)
{
_logger.LogError(e, "SocketException");
message = (string)Application.Current.TryFindResource("SocketExceptionError");
}
else
{
message = (string)Application.Current.TryFindResource("LoginError");
}
}
catch (Exception e)
{
_logger.LogError(e, "Error logging in");
message = (string)Application.Current.TryFindResource("LoginError");
}
finally
{
Processing = false;
}
return message;
}
public void Receive(LogoutMessage message)
{
IsLoggedIn = false;
LiviaHttpClient.ClearToken();
}
public void Receive(IdleTimerTickMessage message)
{
if (Processing)
message.IdleTimer.ResetTimer();
}
}