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 } /// /// Directory path of private files(hl7). This directory must not be accessible by outside service /// private static string _PrivateDirectory; /// /// Ack directory inside of PrivateDirectory(it will be created automatically) /// private static string _PrivateDirectoryAck; /// /// Ack done directory inside of PrivateDirectoryAck(it will be created automatically) /// private static string _PrivateDirectoryAckDone; /// /// 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 /// private static string _PublicDirectory; /// /// Queue of Workers /// private static List WorkerThreadIds = null; /// /// Locks operations on the worker threads /// private static bool _WorkerThreadsLock = false; /// /// Number of max worker threads at same time /// private static int MaxWorkerThreads = 10; /// /// Number of tries to check not found results /// private static int MaxTryNotFoundResults = 2; /// /// Checking interval for not found results /// private static int CheckNotFoundResultsIntervalMinutes = 24; /// /// BackgroundWorker Thread /// private static Thread _workerThread; /// /// BackgroundWroker for missing actions (next try of not found results, not notified completed results) /// private static Thread _workerThreadMissingActions; /// /// Flag if BackgroundWorker is running /// private static bool _Running = false; /// /// Results database controller /// private static ServiceShared.Database.Controllers.Results dbResults; /// /// Sets parameter for BackgroundWorker /// /// Directory path of private files(hl7). This directory must be private and outside service must not have access on this path /// Directory path of public files(encrypted pdf). This directory contains encrypted files and can be accessed by outside with a valid/authorized request /// Number of allowed worker threads at the same time /// Number of posible tries to check not found results, else sends not found notification to patient /// Timeinterval for checking not found results in minutes 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(); 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"); } } /// /// Starts the background working /// 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"); } } /// /// Stops the background working /// public static void Stop() { try { _Running = false; } catch (Exception ex) { Log.Critical(ex, "ServiceInside.Service.BackgroundWorker", "Stop"); } } /// /// Creates subscribe ack log file for the primary system /// /// pgs that was currently created /// device id where the pgs was currently created 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)"); } } /// /// Creates unsubscribe ack log file for the primary system and removes results if already exists from the storages /// /// pgs that should be unsubscripted /// pgs aes encrypted value /// device id where the pgs was created 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)"); } } /// /// Creates delete device ack log file for the primary system /// /// udid hash(partial pk) /// verificator_hash, that validate the client permission /// returns true if all data of udid have been deleted from the app services 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 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; } /// /// Returns encrypted results as download object if it´s already available and has COMPLETED status /// /// pgs of results(PK part) /// udid of results(PK part) /// Returns download object, that contains encrypted results 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; } /// /// 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 /// /// pgs of results(PK part) /// udid of results(PK part) /// checksum of content(base64) after decryption, that was pickedup by patient 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)"); } } /// /// Checks missing or failed actions like notification on completed results or not found results /// 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 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 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 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"); } } /// /// Works in background /// 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"); } } /// /// Single work with hl7 file from working directory /// /// array of arguments [0] = workerThread, [1] = hl7 file 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); } } } /// /// Deletes files for pgs and udid /// /// pgs that should be used in the new filename /// udid that should be used in the new filename 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)"); } } /// /// Encrypts and writes a content to the public directory /// /// pgs of results that will used as filename /// udid of results that will used as filename /// content that should be encrypted /// shared derive key that should be used for the encrypton /// 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; } /// /// Removes worher Thread from the queue list /// /// Worker thread id 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"); } } /// /// Returns filename by pgs and udid SHA512 hash /// /// pgs hash that should be used in filename /// udid that should be used in filename /// file extension if should be set /// flag if filename should be sufixed by timestamp /// returns SHA512 hash by (pgs + udid).extension 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; } /// /// Creates a ack log file for primary system /// /// udid of mobile client /// pgs SHA512 /// pgs_hash AES Encrypted /// type of ack log file /// pat_hash AES Encrypted /// 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; } /// /// Creates a ack log file for primary system /// /// pgs for the error log file /// pgs_hash that can be decrypted for plz, geb.datum, sampleid /// type of ack log file /// udid for the error log file /// filename, that contains error /// reason of error 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; } /// /// Checks if file exists and it can be read /// /// /// 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; } /// /// Returns private directory path /// /// public static string GetPrivateDirectory() { return _PrivateDirectory; } /// /// Returns private directory ack path /// /// public static string GetPrivateDirectoryAck() { return _PrivateDirectoryAck; } /// /// Returns private directory ack done path /// /// public static string GetPrivateDirectoryAckDone() { return _PrivateDirectoryAckDone; } /// /// Returns public directory path /// /// public static string GetPublicDirectory() { return _PublicDirectory; } } }