1046 lines
44 KiB
C#
1046 lines
44 KiB
C#
|
using ServiceShared;
|
|||
|
using ServiceShared.Crypto;
|
|||
|
using ServiceShared.Database;
|
|||
|
using ServiceShared.Models.HL7;
|
|||
|
using ServiceShared.Models.Response;
|
|||
|
|
|||
|
namespace ServiceInside.Service
|
|||
|
{
|
|||
|
public class BackgroundWorker
|
|||
|
{
|
|||
|
private enum AckTypes { SUBSCRIBE, UNSUBSCRIBE, DELETE_DEVICE, PICKEDUP, ERROR }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Directory path of private files(hl7). This directory must not be accessible by outside service
|
|||
|
/// </summary>
|
|||
|
private static string _PrivateDirectory;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Ack directory inside of PrivateDirectory(it will be created automatically)
|
|||
|
/// </summary>
|
|||
|
private static string _PrivateDirectoryAck;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Ack done directory inside of PrivateDirectoryAck(it will be created automatically)
|
|||
|
/// </summary>
|
|||
|
private static string _PrivateDirectoryAckDone;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Directory path of public files(encrypted pdf). This directory must contain only encrypted files and can be accessed by outside with a valid/authorized request
|
|||
|
/// No access over filesystem for outside service
|
|||
|
/// </summary>
|
|||
|
private static string _PublicDirectory;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Queue of Workers
|
|||
|
/// </summary>
|
|||
|
private static List<string> WorkerThreadIds = null;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Locks operations on the worker threads
|
|||
|
/// </summary>
|
|||
|
private static bool _WorkerThreadsLock = false;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Number of max worker threads at same time
|
|||
|
/// </summary>
|
|||
|
private static int MaxWorkerThreads = 10;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Number of tries to check not found results
|
|||
|
/// </summary>
|
|||
|
private static int MaxTryNotFoundResults = 2;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Checking interval for not found results
|
|||
|
/// </summary>
|
|||
|
private static int CheckNotFoundResultsIntervalMinutes = 24;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// BackgroundWorker Thread
|
|||
|
/// </summary>
|
|||
|
private static Thread _workerThread;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// BackgroundWroker for missing actions (next try of not found results, not notified completed results)
|
|||
|
/// </summary>
|
|||
|
private static Thread _workerThreadMissingActions;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Flag if BackgroundWorker is running
|
|||
|
/// </summary>
|
|||
|
private static bool _Running = false;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Results database controller
|
|||
|
/// </summary>
|
|||
|
private static ServiceShared.Database.Controllers.Results dbResults;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets parameter for BackgroundWorker
|
|||
|
/// </summary>
|
|||
|
/// <param name="privateDirectory">Directory path of private files(hl7). This directory must be private and outside service must not have access on this path</param>
|
|||
|
/// <param name="publicDirectory">Directory path of public files(encrypted pdf). This directory contains encrypted files and can be accessed by outside with a valid/authorized request</param>
|
|||
|
/// <param name="maxWorkerThreads">Number of allowed worker threads at the same time</param>
|
|||
|
/// <param name="maxTryNotFoundResults">Number of posible tries to check not found results, else sends not found notification to patient</param>
|
|||
|
/// <param name="checkNotFoundResultsIntervalMinutes">Timeinterval for checking not found results in minutes</param>
|
|||
|
public static void SetParameters(string privateDirectory, string publicDirectory, DbContext dbContext, int maxWorkerThreads = 10, int maxTryNotFoundResults = 2, int checkNotFoundResultsIntervalMinutes = 24)
|
|||
|
{
|
|||
|
_PrivateDirectory = privateDirectory;
|
|||
|
_PublicDirectory = publicDirectory;
|
|||
|
MaxWorkerThreads = maxWorkerThreads;
|
|||
|
MaxTryNotFoundResults = maxTryNotFoundResults;
|
|||
|
CheckNotFoundResultsIntervalMinutes = checkNotFoundResultsIntervalMinutes;
|
|||
|
|
|||
|
if(CheckNotFoundResultsIntervalMinutes < 5)
|
|||
|
{
|
|||
|
CheckNotFoundResultsIntervalMinutes = 5;
|
|||
|
}
|
|||
|
|
|||
|
WorkerThreadIds = new List<string>();
|
|||
|
dbResults = new ServiceShared.Database.Controllers.Results(dbContext);
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
if(!Directory.Exists(_PrivateDirectory))
|
|||
|
{
|
|||
|
Directory.CreateDirectory(_PrivateDirectory);
|
|||
|
}
|
|||
|
|
|||
|
if (!Directory.Exists(_PublicDirectory))
|
|||
|
{
|
|||
|
Directory.CreateDirectory(_PublicDirectory);
|
|||
|
}
|
|||
|
|
|||
|
_PrivateDirectoryAck = Path.Combine(_PrivateDirectory, "ack");
|
|||
|
|
|||
|
if (!Directory.Exists(_PrivateDirectoryAck))
|
|||
|
{
|
|||
|
Directory.CreateDirectory(_PrivateDirectoryAck);
|
|||
|
}
|
|||
|
|
|||
|
_PrivateDirectoryAckDone = Path.Combine(_PrivateDirectoryAck, "done");
|
|||
|
|
|||
|
if (!Directory.Exists(_PrivateDirectoryAckDone))
|
|||
|
{
|
|||
|
Directory.CreateDirectory(_PrivateDirectoryAckDone);
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Error(ex, "ServiceInside.Service.BackgroundWorker", "SetParameters");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Starts the background working
|
|||
|
/// </summary>
|
|||
|
public static void Start()
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if(_Running)
|
|||
|
{
|
|||
|
Stop();
|
|||
|
}
|
|||
|
|
|||
|
_workerThread = new Thread(WorkAsync);
|
|||
|
_workerThread.Start();
|
|||
|
|
|||
|
_workerThreadMissingActions = new Thread(CheckMissingActionsAsync);
|
|||
|
_workerThreadMissingActions.Start();
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "Start");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Stops the background working
|
|||
|
/// </summary>
|
|||
|
public static void Stop()
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
_Running = false;
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "Stop");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates subscribe ack log file for the primary system
|
|||
|
/// </summary>
|
|||
|
/// <param name="pgs">pgs that was currently created</param>
|
|||
|
/// <param name="udid">device id where the pgs was currently created</param>
|
|||
|
public static void Subscribe(string pgs, string udid)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(pgs) && !string.IsNullOrEmpty(udid))
|
|||
|
{
|
|||
|
ServiceShared.Models.Database.Results results = dbResults.GetResults(pgs, udid);
|
|||
|
|
|||
|
if (results != null && CreateAck(results.UDID, results.PGS, results.PGS_HASH, AckTypes.SUBSCRIBE))
|
|||
|
{
|
|||
|
Log.Trace(AckTypes.SUBSCRIBE.ToString(), results.PGS, results.UDID);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "Subscribe(string, string)");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates unsubscribe ack log file for the primary system and removes results if already exists from the storages
|
|||
|
/// </summary>
|
|||
|
/// <param name="pgs">pgs that should be unsubscripted</param>
|
|||
|
/// <param name="pgs_hash">pgs aes encrypted value</param>
|
|||
|
/// <param name="udid">device id where the pgs was created</param>
|
|||
|
public static void Unsubscribe(string pgs, string pgs_hash, string udid)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(pgs) && !string.IsNullOrEmpty(udid))
|
|||
|
{
|
|||
|
DeleteFilesFor(pgs, udid);
|
|||
|
|
|||
|
if (CreateAck(udid, pgs, pgs_hash, AckTypes.UNSUBSCRIBE))
|
|||
|
{
|
|||
|
Log.Trace(AckTypes.UNSUBSCRIBE.ToString(), pgs, udid);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "Unsubscribe(string, string, string)");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates delete device ack log file for the primary system
|
|||
|
/// </summary>
|
|||
|
/// <param name="udid">udid hash(partial pk)</param>
|
|||
|
/// <param name="verificator_hash">verificator_hash, that validate the client permission</param>
|
|||
|
/// <returns>returns true if all data of udid have been deleted from the app services</returns>
|
|||
|
public static bool DeleteDevice(string udid, string verificator_hash)
|
|||
|
{
|
|||
|
bool result = false;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(udid) && !string.IsNullOrEmpty(verificator_hash))
|
|||
|
{
|
|||
|
ServiceShared.Models.Database.Device device = dbResults.GetDevice(udid);
|
|||
|
|
|||
|
if(device != null)
|
|||
|
{
|
|||
|
List<ServiceShared.Models.Database.Results> results = dbResults.GetDeviceResults(udid, verificator_hash);
|
|||
|
|
|||
|
if (results != null && results.Count > 0)
|
|||
|
{
|
|||
|
foreach (ServiceShared.Models.Database.Results res in results)
|
|||
|
{
|
|||
|
DeleteFilesFor(res.PGS, res.UDID);
|
|||
|
Log.Trace("device deleted", res.PGS, res.UDID);
|
|||
|
}
|
|||
|
|
|||
|
if (dbResults.DeleteDevice(udid, verificator_hash))
|
|||
|
{
|
|||
|
results = dbResults.GetDeviceResults(udid, verificator_hash);
|
|||
|
result = (results == null || results.Count <= 0);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
result = true;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
result = true;
|
|||
|
}
|
|||
|
|
|||
|
if (result && dbResults.DeleteDevice(udid, verificator_hash))
|
|||
|
{
|
|||
|
CreateAck(udid, null, null, AckTypes.DELETE_DEVICE, (device != null ? device.PatHash : null));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "DeleteDevice(string, string)");
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns encrypted results as download object if it´s already available and has COMPLETED status
|
|||
|
/// </summary>
|
|||
|
/// <param name="pgs">pgs of results(PK part)</param>
|
|||
|
/// <param name="udid">udid of results(PK part)</param>
|
|||
|
/// <returns>Returns download object, that contains encrypted results</returns>
|
|||
|
public static Download GetDownload(string pgs, string udid)
|
|||
|
{
|
|||
|
Download result = null;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(pgs) && !string.IsNullOrEmpty(udid))
|
|||
|
{
|
|||
|
ServiceShared.Models.Database.Results results = dbResults.GetResults(pgs, udid);
|
|||
|
|
|||
|
if (results.Available &&
|
|||
|
results.Status == ServiceShared.Models.Database.Results.ResultsStatus.COMPLETED &&
|
|||
|
!string.IsNullOrEmpty(results.ServerPublicKey))
|
|||
|
{
|
|||
|
string file = Path.Combine(_PublicDirectory, GetFileName(results.PGS, results.UDID));
|
|||
|
|
|||
|
if (File.Exists(file))
|
|||
|
{
|
|||
|
result = new Download();
|
|||
|
result.server_public_key = results.ServerPublicKey;
|
|||
|
result.encrypted_content = File.ReadAllText(file);
|
|||
|
result.pgs = results.PGS;
|
|||
|
result.udid = results.UDID;
|
|||
|
Log.Trace("get_download", results.PGS, results.UDID);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "GetDownload(string, string)");
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Moves results file to the done or error directory in the public area, depended on successfully pickedup checksum
|
|||
|
/// If pickedup was successfully it removes the original results from the private storage
|
|||
|
/// </summary>
|
|||
|
/// <param name="pgs">pgs of results(PK part)</param>
|
|||
|
/// <param name="udid">udid of results(PK part)</param>
|
|||
|
/// <param name="file_checksum">checksum of content(base64) after decryption, that was pickedup by patient</param>
|
|||
|
public static void SetPickedUpResultsAsync(string pgs, string udid, string file_checksum)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(pgs) && !string.IsNullOrEmpty(udid))
|
|||
|
{
|
|||
|
Thread thread = new Thread(() =>
|
|||
|
{
|
|||
|
ServiceShared.Models.Database.Results results = dbResults.GetResults(pgs, udid);
|
|||
|
|
|||
|
if (results != null &&
|
|||
|
results.PickedUp &&
|
|||
|
!string.IsNullOrEmpty(results.FileChecksum) &&
|
|||
|
results.FileChecksum == file_checksum)
|
|||
|
{
|
|||
|
string file = Path.Combine(_PublicDirectory, GetFileName(results.PGS, results.UDID));
|
|||
|
|
|||
|
if (File.Exists(file))
|
|||
|
{
|
|||
|
File.Delete(file);
|
|||
|
}
|
|||
|
|
|||
|
CreateAck(results.UDID, results.PGS, results.PGS_HASH, AckTypes.PICKEDUP);
|
|||
|
|
|||
|
DeleteFilesFor(results.PGS, results.UDID);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
thread.Start();
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "SetPickedUpResultsAsync(string, string, string)");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Checks missing or failed actions like notification on completed results or not found results
|
|||
|
/// </summary>
|
|||
|
private static void CheckMissingActionsAsync()
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
_Running = true;
|
|||
|
|
|||
|
while (_Running)
|
|||
|
{
|
|||
|
Log.Debug("[BW] check missing actions");
|
|||
|
|
|||
|
Log.Debug("[BW] check not found results (Interval: " + CheckNotFoundResultsIntervalMinutes + ", MaxTry: " + MaxTryNotFoundResults + ")");
|
|||
|
|
|||
|
/** check not found results after x Minutes (it should be minimum 5 minutes) **/
|
|||
|
List<ServiceShared.Models.Database.Results> notFoundResults = dbResults.GetNotFoundResults(CheckNotFoundResultsIntervalMinutes, MaxTryNotFoundResults);
|
|||
|
|
|||
|
if(notFoundResults != null && notFoundResults.Count > 0)
|
|||
|
{
|
|||
|
Log.Debug("[BW] found not found results (" + notFoundResults.Count + ")");
|
|||
|
|
|||
|
foreach(ServiceShared.Models.Database.Results results in notFoundResults)
|
|||
|
{
|
|||
|
CreateAck(results.UDID, results.PGS, results.PGS_HASH, AckTypes.SUBSCRIBE);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/** check not notified completed results **/
|
|||
|
List<ServiceShared.Models.Database.Results> complitedNotNotfiedResults = dbResults.GetNotNotifiedResults();
|
|||
|
|
|||
|
if(complitedNotNotfiedResults != null && complitedNotNotfiedResults.Count > 0)
|
|||
|
{
|
|||
|
Log.Debug("[BW] found not notified completed results (" + complitedNotNotfiedResults.Count + ")");
|
|||
|
|
|||
|
foreach (ServiceShared.Models.Database.Results results in complitedNotNotfiedResults)
|
|||
|
{
|
|||
|
Notification notification = new Notification();
|
|||
|
notification.pgs = results.PGS;
|
|||
|
notification.udid = results.UDID;
|
|||
|
notification.created = DateTime.Now;
|
|||
|
notification.status = results.Status;
|
|||
|
notification.available = results.Available;
|
|||
|
notification.available_ts = results.AvailableTS;
|
|||
|
|
|||
|
Log.Debug("[BW] try next notification PGS(" + results.PGS + ")");
|
|||
|
|
|||
|
ServiceOutside.Notify(notification);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/** check not found & not notificated results **/
|
|||
|
List<ServiceShared.Models.Database.Results> notFoundNotNotfiedResults = dbResults.GetNotFoundNotNotifiedResults(MaxTryNotFoundResults);
|
|||
|
|
|||
|
if (notFoundNotNotfiedResults != null && notFoundNotNotfiedResults.Count > 0)
|
|||
|
{
|
|||
|
Log.Debug("[BW] found not notified not found results (" + notFoundNotNotfiedResults.Count + ")");
|
|||
|
|
|||
|
foreach (ServiceShared.Models.Database.Results results in notFoundNotNotfiedResults)
|
|||
|
{
|
|||
|
Notification notification = new Notification();
|
|||
|
notification.pgs = results.PGS;
|
|||
|
notification.udid = results.UDID;
|
|||
|
notification.created = DateTime.Now;
|
|||
|
notification.status = results.Status;
|
|||
|
notification.available = false;
|
|||
|
notification.available_ts = null;
|
|||
|
|
|||
|
Log.Debug("[BW] try next notification PGS(" + results.PGS + ") for not found");
|
|||
|
|
|||
|
ServiceOutside.Notify(notification);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Sleep 5 minutes
|
|||
|
Thread.Sleep(((1000 * 60) * 5));
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "CheckMissingActionsAsync");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Works in background
|
|||
|
/// </summary>
|
|||
|
private static void WorkAsync()
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
_Running = true;
|
|||
|
|
|||
|
while(_Running)
|
|||
|
{
|
|||
|
string[] files = Directory.GetFiles(_PrivateDirectory, "*.hl7");
|
|||
|
|
|||
|
if (files != null && files.Length > 0)
|
|||
|
{
|
|||
|
int i = 0;
|
|||
|
|
|||
|
foreach(string file in files)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if(FileCanBeRead(file))
|
|||
|
{
|
|||
|
if (i > MaxWorkerThreads)
|
|||
|
{
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
string newThreadId = SHA512.Encrypt(file);
|
|||
|
|
|||
|
if (WorkerThreadIds.Count < MaxWorkerThreads && !WorkerThreadIds.Contains(newThreadId) && !_WorkerThreadsLock)
|
|||
|
{
|
|||
|
WorkerThreadIds.Add(newThreadId);
|
|||
|
ParameterizedThreadStart workerStart = new ParameterizedThreadStart(DoWorkAsync);
|
|||
|
Thread workerThread = new Thread(workerStart);
|
|||
|
workerThread.Start(new object[] { workerThread, file, newThreadId });
|
|||
|
}
|
|||
|
|
|||
|
i++;
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "WorkAsync(Step)");
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
Thread.Sleep(200);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Thread.Sleep(200);
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "WorkAsync");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Single work with hl7 file from working directory
|
|||
|
/// </summary>
|
|||
|
/// <param name="args">array of arguments [0] = workerThread, [1] = hl7 file</param>
|
|||
|
private static void DoWorkAsync(object args)
|
|||
|
{
|
|||
|
string file = null;
|
|||
|
Thread workerThread = null;
|
|||
|
string workerThreadId = null;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
if(args != null)
|
|||
|
{
|
|||
|
object[] param = (object[])args;
|
|||
|
|
|||
|
if(param != null && param.Length == 3)
|
|||
|
{
|
|||
|
workerThread = (Thread)param[0];
|
|||
|
file = (string)param[1];
|
|||
|
workerThreadId = (string)param[2];
|
|||
|
|
|||
|
Log.Debug("[BW] File: " + file);
|
|||
|
Log.Debug("[BW] ThreadId: " + workerThreadId);
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(file) && File.Exists(file) && FileCanBeRead(file))
|
|||
|
{
|
|||
|
MDM mdm = ServiceShared.HL7.Parser.GetMDM(file);
|
|||
|
|
|||
|
if (mdm != null)
|
|||
|
{
|
|||
|
ServiceShared.Models.Database.Results results = null;
|
|||
|
Notification notification = null;
|
|||
|
string pgs = "";
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(mdm.PGSInitial) && !string.IsNullOrEmpty(mdm.UDID))
|
|||
|
{
|
|||
|
pgs = mdm.PGSInitial;
|
|||
|
results = dbResults.GetResults(mdm.PGSInitial, mdm.UDID);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
pgs = mdm.PGS();
|
|||
|
results = dbResults.GetResults(mdm.PGS(), mdm.UDID);
|
|||
|
}
|
|||
|
|
|||
|
Log.Debug("[BW] PGS: " + pgs);
|
|||
|
Log.Debug("[BW] UDID: " + mdm.UDID);
|
|||
|
Log.Debug("[BW] ZIP: " + mdm.Zip);
|
|||
|
Log.Debug("[BW] Birthday: " + mdm.Birthday);
|
|||
|
Log.Debug("[BW] SampleId: " + mdm.SampleId);
|
|||
|
Log.Debug("[BW] Status: " + mdm.ResultsStatus);
|
|||
|
|
|||
|
if (results != null)
|
|||
|
{
|
|||
|
Log.Debug("[BW] in db exists: ");
|
|||
|
|
|||
|
notification = new Notification();
|
|||
|
notification.pgs = results.PGS;
|
|||
|
notification.udid = results.UDID;
|
|||
|
notification.created = DateTime.Now;
|
|||
|
notification.status = mdm.ResultsStatus;
|
|||
|
|
|||
|
if (mdm.ResultsStatus == ServiceShared.Models.Database.Results.ResultsStatus.COMPLETED)
|
|||
|
{
|
|||
|
notification.available = true;
|
|||
|
notification.available_ts = mdm.TimeStamp;
|
|||
|
|
|||
|
KeyPair keyPair = Curve25519.GenerateKeyPair();
|
|||
|
byte[] deriveKey = keyPair.GetSharedKey(results.ClientPublicKey!);
|
|||
|
|
|||
|
if (deriveKey == null || deriveKey.Length <= 0 ||
|
|||
|
!WriteEncryptedToPublic(mdm.PGS(), mdm.UDID, mdm.Base64Content, deriveKey) ||
|
|||
|
|
|||
|
!dbResults.UpdateStatus(results.PGS,
|
|||
|
results.UDID,
|
|||
|
true,
|
|||
|
keyPair.PublicKey,
|
|||
|
SHA512.Encrypt(mdm.Base64Content),
|
|||
|
mdm.TimeStamp,
|
|||
|
mdm.ResultsStatus,
|
|||
|
mdm.PAT_HASH()))
|
|||
|
{
|
|||
|
results = null;
|
|||
|
notification = null;
|
|||
|
|
|||
|
Log.Critical(new Exception("Could not move to the encrypted public storage" + file + ")"), "ServiceInside.Service.BackgroundWorker", "DoWorkAsync(object)");
|
|||
|
}
|
|||
|
}
|
|||
|
else if(mdm.ResultsStatus == ServiceShared.Models.Database.Results.ResultsStatus.REJECTED)
|
|||
|
{
|
|||
|
Log.Debug("[BW] Status - Rejexted: " + file);
|
|||
|
notification.available = false;
|
|||
|
notification.available_ts = null;
|
|||
|
dbResults.SetReject(results.PGS, results.UDID);
|
|||
|
DeleteFilesFor(results.PGS, results.UDID);
|
|||
|
}
|
|||
|
else if (mdm.ResultsStatus == ServiceShared.Models.Database.Results.ResultsStatus.NOT_FOUND)
|
|||
|
{
|
|||
|
Log.Debug("[BW] Status - NOT_FOUND: " + file);
|
|||
|
|
|||
|
if((results.NotFoundCounter + 1) >= MaxTryNotFoundResults)
|
|||
|
{
|
|||
|
notification.available = false;
|
|||
|
notification.available_ts = null;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
notification = null;
|
|||
|
}
|
|||
|
|
|||
|
dbResults.SetNotFound(results.PGS, results.UDID);
|
|||
|
DeleteFilesFor(results.PGS, results.UDID);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
CreateAck(results.PGS, results.PGS_HASH, AckTypes.ERROR, results.UDID, file, "MDM has no valid ResultsStatus file");
|
|||
|
Log.Critical(new Exception("MDM has no valid ResultsStatus file: " + file), "ServiceInside.Service.BackgroundWorker", "DoWorkAsync");
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Get device by udid to get public key for encryption
|
|||
|
ServiceShared.Models.Database.Device device = dbResults.GetDevice(mdm.UDID);
|
|||
|
|
|||
|
if (device != null && !string.IsNullOrEmpty(device.ClientPublicKey))
|
|||
|
{
|
|||
|
Log.Debug("[BW] device(" + mdm.UDID + ") in db found:");
|
|||
|
|
|||
|
KeyPair keyPair = Curve25519.GenerateKeyPair();
|
|||
|
byte[] deriveKey = keyPair.GetSharedKey(device.ClientPublicKey!);
|
|||
|
|
|||
|
if (deriveKey != null && deriveKey.Length > 0 &&
|
|||
|
WriteEncryptedToPublic(mdm.PGS(), mdm.UDID, mdm.Base64Content, deriveKey))
|
|||
|
{
|
|||
|
ServiceShared.Models.Database.Results newResults = new ServiceShared.Models.Database.Results();
|
|||
|
|
|||
|
newResults.PGS = mdm.PGS();
|
|||
|
newResults.PGS_HASH = mdm.PGS_HASH(mdm.UDID);
|
|||
|
newResults.Available = true;
|
|||
|
newResults.AvailableTS = mdm.TimeStamp;
|
|||
|
newResults.Status = mdm.ResultsStatus;
|
|||
|
newResults.DeviceToken = device.DeviceToken;
|
|||
|
newResults.DeviceType = device.DeviceType;
|
|||
|
newResults.ClientPublicKey = device.ClientPublicKey;
|
|||
|
newResults.UDID = device.UDID;
|
|||
|
newResults.VerificationHash = device.VerificationHash;
|
|||
|
newResults.ServerPublicKey = keyPair.PublicKey;
|
|||
|
newResults.FileChecksum = SHA512.Encrypt(mdm.Base64Content);
|
|||
|
|
|||
|
if (dbResults.Create(newResults))
|
|||
|
{
|
|||
|
notification = new Notification();
|
|||
|
notification.pgs = mdm.PGS();
|
|||
|
notification.udid = mdm.UDID;
|
|||
|
notification.available = true;
|
|||
|
notification.available_ts = mdm.TimeStamp;
|
|||
|
notification.status = mdm.ResultsStatus;
|
|||
|
notification.created = DateTime.Now;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Log.Critical(new Exception("Could not create results in database MDM(" + file + ")"), "ServiceInside.Service.BackgroundWorker", "DoWorkAsync(object)");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Log.Debug("[BW] device " + mdm.UDID + " not found in db");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (notification != null)
|
|||
|
{
|
|||
|
Log.Debug("[BW] notify");
|
|||
|
ServiceOutside.Notify(notification);
|
|||
|
}
|
|||
|
|
|||
|
Log.Trace("mdm(" + mdm.ResultsStatus + ")", pgs, mdm.UDID);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Log.Critical(new Exception("Not valid MDM(" + file + ")"), "ServiceInside.Service.BackgroundWorker", "DoWorkAsync(object)");
|
|||
|
}
|
|||
|
|
|||
|
Log.Debug("[BW] delete file " + file);
|
|||
|
|
|||
|
// Delte original hl7 file
|
|||
|
File.Delete(file);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Log.Debug("[BW] File: " + file + " not exists");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
if(ex != null &&
|
|||
|
!string.IsNullOrEmpty(ex.Message) &&
|
|||
|
!ex.Message.Contains("process cannot access the file"))
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "DoWorkAsync(object)");
|
|||
|
}
|
|||
|
|
|||
|
if(!string.IsNullOrEmpty(file) && File.Exists(file))
|
|||
|
{
|
|||
|
File.Delete(file);
|
|||
|
}
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
if(!string.IsNullOrEmpty(file))
|
|||
|
{
|
|||
|
FreeWorker(workerThreadId);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Deletes files for pgs and udid
|
|||
|
/// </summary>
|
|||
|
/// <param name="pgs">pgs that should be used in the new filename</param>
|
|||
|
/// <param name="udid">udid that should be used in the new filename</param>
|
|||
|
private static void DeleteFilesFor(string pgs, string udid)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if(!string.IsNullOrEmpty(pgs) && !string.IsNullOrEmpty(udid))
|
|||
|
{
|
|||
|
// Delete from public storage
|
|||
|
string file = Path.Combine(_PublicDirectory, GetFileName(pgs, udid));
|
|||
|
if(!string.IsNullOrEmpty(file) && File.Exists(file))
|
|||
|
{
|
|||
|
File.Delete(file);
|
|||
|
}
|
|||
|
|
|||
|
// Delte from private storage
|
|||
|
file = Path.Combine(_PrivateDirectory, GetFileName(pgs, udid, "hl7"));
|
|||
|
if (!string.IsNullOrEmpty(file) && File.Exists(file))
|
|||
|
{
|
|||
|
File.Delete(file);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "DeleteFilesFor(string, string)");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Encrypts and writes a content to the public directory
|
|||
|
/// </summary>
|
|||
|
/// <param name="pgs">pgs of results that will used as filename</param>
|
|||
|
/// <param name="udid">udid of results that will used as filename</param>
|
|||
|
/// <param name="content">content that should be encrypted</param>
|
|||
|
/// <param name="deriveKey">shared derive key that should be used for the encrypton</param>
|
|||
|
/// <returns></returns>
|
|||
|
private static bool WriteEncryptedToPublic(string pgs, string udid, string content, byte[] deriveKey)
|
|||
|
{
|
|||
|
bool result = false;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
if(!string.IsNullOrEmpty(pgs) && !string.IsNullOrEmpty(udid) && !string.IsNullOrEmpty(content) && deriveKey != null && deriveKey.Length > 0)
|
|||
|
{
|
|||
|
string encryptedFile = Path.Combine(_PublicDirectory, GetFileName(pgs, udid));
|
|||
|
string encrypted = AES.Encrypt(content, deriveKey);
|
|||
|
File.WriteAllText(encryptedFile, encrypted);
|
|||
|
result = File.Exists(encryptedFile);
|
|||
|
|
|||
|
Log.Trace("encrypted move to (" + encryptedFile + ")", pgs, udid);
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "WriteToPublic(string, string, string, byte[])");
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Removes worher Thread from the queue list
|
|||
|
/// </summary>
|
|||
|
/// <param name="threadId">Worker thread id</param>
|
|||
|
private static void FreeWorker(string threadId)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if(threadId != null)
|
|||
|
{
|
|||
|
if (WorkerThreadIds.Contains(threadId))
|
|||
|
{
|
|||
|
WorkerThreadIds.Remove(threadId);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Error(ex, "ServiceInside.Service.BackgroundWorker", "FreeWorker");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns filename by pgs and udid SHA512 hash
|
|||
|
/// </summary>
|
|||
|
/// <param name="pgs">pgs hash that should be used in filename</param>
|
|||
|
/// <param name="udid">udid that should be used in filename</param>
|
|||
|
/// <param name="extension">file extension if should be set</param>
|
|||
|
/// <param name="addTimestamp">flag if filename should be sufixed by timestamp</param>
|
|||
|
/// <returns>returns SHA512 hash by (pgs + udid).extension</returns>
|
|||
|
private static string GetFileName(string pgs, string udid, string extension = null, bool addTimestamp = false)
|
|||
|
{
|
|||
|
string result = null;
|
|||
|
|
|||
|
if(!string.IsNullOrEmpty(udid))
|
|||
|
{
|
|||
|
result = SHA512.Encrypt(udid + (!string.IsNullOrEmpty(pgs) ? pgs : "")) + (addTimestamp ? "_" + DateTime.Now.ToString("yyyyMMddHHmmss") : "") + (!string.IsNullOrEmpty(extension) ? "." + extension.ToLower() : "");
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a ack log file for primary system
|
|||
|
/// </summary>
|
|||
|
/// <param name="udid">udid of mobile client</param>
|
|||
|
/// <param name="pgs">pgs SHA512</param>
|
|||
|
/// <param name="pgs_hash">pgs_hash AES Encrypted</param>
|
|||
|
/// <param name="type">type of ack log file</param>
|
|||
|
/// <param name="pat_hash">pat_hash AES Encrypted</param>
|
|||
|
/// <returns></returns>
|
|||
|
private static bool CreateAck(string udid, string pgs, string pgs_hash, AckTypes type, string pat_hash = null)
|
|||
|
{
|
|||
|
bool result = false;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
string ack = "TYPE:" + type.ToString() + "\r\n";
|
|||
|
ack += "UDID:" + udid + "\r\n";
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(pgs))
|
|||
|
{
|
|||
|
ack += "PGS:" + pgs + "\r\n";
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(pgs_hash))
|
|||
|
{
|
|||
|
string decrypted_values = AES.Decrypt(pgs_hash, AES.GetKey(udid + AES.PGS_ENCRYPT_PARTIAL_KEY));
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(decrypted_values))
|
|||
|
{
|
|||
|
string[] values = decrypted_values.Split('|');
|
|||
|
|
|||
|
if (values.Length == 3)
|
|||
|
{
|
|||
|
ack += "ZIP:" + values[0] + "\r\n";
|
|||
|
ack += "BIRTHDATE:" + values[1] + "\r\n";
|
|||
|
ack += "ORDER_ID:" + values[2] + "\r\n";
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(!string.IsNullOrEmpty(pat_hash))
|
|||
|
{
|
|||
|
string decrypted_pat_id = AES.Decrypt(pat_hash);
|
|||
|
|
|||
|
if(!string.IsNullOrEmpty(decrypted_pat_id))
|
|||
|
{
|
|||
|
ack += "PAT_ID:" + decrypted_pat_id + "\r\n";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ack += "CREATED:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|||
|
|
|||
|
string ack_file = Path.Combine(_PrivateDirectoryAck, GetFileName(pgs, udid, "log", true));
|
|||
|
|
|||
|
if (File.Exists(ack_file))
|
|||
|
{
|
|||
|
File.Delete(ack_file);
|
|||
|
}
|
|||
|
|
|||
|
File.WriteAllText(ack_file, ack);
|
|||
|
|
|||
|
result = File.Exists(ack_file);
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "CreateAck(string, string, AckTypes");
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a ack log file for primary system
|
|||
|
/// </summary>
|
|||
|
/// <param name="pgs">pgs for the error log file</param>
|
|||
|
/// <param name="pgs_hash">pgs_hash that can be decrypted for plz, geb.datum, sampleid</param>
|
|||
|
/// <param name="type">type of ack log file</param>
|
|||
|
/// <param name="udid">udid for the error log file</param>
|
|||
|
/// <param name="filename">filename, that contains error</param>
|
|||
|
/// <param name="reason">reason of error</param>
|
|||
|
private static bool CreateAck(string pgs, string pgs_hash, AckTypes type, string udid = null, string filename = null, string reason = null)
|
|||
|
{
|
|||
|
bool result = false;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
string ack = "TYPE:" + type.ToString() + "\r\n";
|
|||
|
ack += "UDID:" + udid + "\r\n";
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(pgs))
|
|||
|
{
|
|||
|
ack += "PGS:" + pgs + "\r\n";
|
|||
|
|
|||
|
if(!string.IsNullOrEmpty(pgs_hash))
|
|||
|
{
|
|||
|
string decrypted_values = AES.Decrypt(pgs_hash, AES.GetKey(udid + AES.PGS_ENCRYPT_PARTIAL_KEY));
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(decrypted_values))
|
|||
|
{
|
|||
|
string[] values = decrypted_values.Split('|');
|
|||
|
|
|||
|
if (values.Length == 3)
|
|||
|
{
|
|||
|
ack += "ZIP:" + values[0] + "\r\n";
|
|||
|
ack += "BIRTHDATE:" + values[1] + "\r\n";
|
|||
|
ack += "ORDER_ID:" + values[2] + "\r\n";
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(filename))
|
|||
|
{
|
|||
|
ack += "FILENAME:" + filename + "\r\n";
|
|||
|
}
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(reason))
|
|||
|
{
|
|||
|
ack += "REASON:" + reason + "\r\n";
|
|||
|
}
|
|||
|
|
|||
|
ack += "CREATED:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|||
|
|
|||
|
string ack_file = Path.Combine(_PrivateDirectoryAck, GetFileName(pgs, udid, "log", true));
|
|||
|
|
|||
|
if (File.Exists(ack_file))
|
|||
|
{
|
|||
|
File.Delete(ack_file);
|
|||
|
}
|
|||
|
|
|||
|
File.WriteAllText(ack_file, ack);
|
|||
|
|
|||
|
result = File.Exists(ack_file);
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "CreateErrorLog(string, AckTypes, string, string, string)");
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Checks if file exists and it can be read
|
|||
|
/// </summary>
|
|||
|
/// <param name="file"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private static bool FileCanBeRead(string file)
|
|||
|
{
|
|||
|
bool result = false;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(file) && File.Exists(file))
|
|||
|
{
|
|||
|
using (var fs = new FileStream(file, FileMode.Open))
|
|||
|
{
|
|||
|
result = fs.CanRead;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
catch
|
|||
|
{
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns private directory path
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public static string GetPrivateDirectory()
|
|||
|
{
|
|||
|
return _PrivateDirectory;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns private directory ack path
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public static string GetPrivateDirectoryAck()
|
|||
|
{
|
|||
|
return _PrivateDirectoryAck;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns private directory ack done path
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public static string GetPrivateDirectoryAckDone()
|
|||
|
{
|
|||
|
return _PrivateDirectoryAckDone;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns public directory path
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public static string GetPublicDirectory()
|
|||
|
{
|
|||
|
return _PublicDirectory;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|